xref: /php-src/ext/xsl/php_xsl.c (revision 30885f3b)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Christian Stocker <chregu@php.net>                           |
14   +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "php.h"
22 #include "php_ini.h"
23 #include "ext/standard/info.h"
24 #include "php_xsl.h"
25 #include "php_xsl_arginfo.h"
26 
27 zend_class_entry *xsl_xsltprocessor_class_entry;
28 static zend_object_handlers xsl_object_handlers;
29 
30 static const zend_module_dep xsl_deps[] = {
31 	ZEND_MOD_REQUIRED("libxml")
32 	ZEND_MOD_REQUIRED("dom")
33 	ZEND_MOD_END
34 };
35 
36 /* {{{ xsl_module_entry */
37 zend_module_entry xsl_module_entry = {
38 	STANDARD_MODULE_HEADER_EX, NULL,
39 	xsl_deps,
40 	"xsl",
41 	NULL,
42 	PHP_MINIT(xsl),
43 	PHP_MSHUTDOWN(xsl),
44 	NULL,
45 	NULL,
46 	PHP_MINFO(xsl),
47 	PHP_XSL_VERSION,
48 	STANDARD_MODULE_PROPERTIES
49 };
50 /* }}} */
51 
52 #ifdef COMPILE_DL_XSL
ZEND_GET_MODULE(xsl)53 ZEND_GET_MODULE(xsl)
54 #endif
55 
56 static HashTable *xsl_objects_get_gc(zend_object *object, zval **table, int *n)
57 {
58 	xsl_object *intern = php_xsl_fetch_object(object);
59 	return php_dom_xpath_callbacks_get_gc_for_whole_object(&intern->xpath_callbacks, object, table, n);
60 }
61 
xsl_free_sheet(xsl_object * intern)62 void xsl_free_sheet(xsl_object *intern)
63 {
64 	if (intern->ptr) {
65 		xsltStylesheetPtr sheet = intern->ptr;
66 
67 		/* Free wrapper */
68 		if (sheet->_private != NULL) {
69 			sheet->_private = NULL;
70 		}
71 
72 		xsltFreeStylesheet(sheet);
73 		intern->ptr = NULL;
74 	}
75 }
76 
77 /* {{{ xsl_objects_free_storage */
xsl_objects_free_storage(zend_object * object)78 void xsl_objects_free_storage(zend_object *object)
79 {
80 	xsl_object *intern = php_xsl_fetch_object(object);
81 
82 	zend_object_std_dtor(&intern->std);
83 
84 	if (intern->parameter) {
85 		zend_hash_destroy(intern->parameter);
86 		FREE_HASHTABLE(intern->parameter);
87 	}
88 
89 	php_dom_xpath_callbacks_dtor(&intern->xpath_callbacks);
90 
91 	xsl_free_sheet(intern);
92 
93 	if (intern->doc) {
94 		php_libxml_decrement_doc_ref(intern->doc);
95 		efree(intern->doc);
96 	}
97 
98 	if (intern->sheet_ref_obj) {
99 		php_libxml_decrement_doc_ref_directly(intern->sheet_ref_obj);
100 	}
101 
102 	if (intern->profiling) {
103 		efree(intern->profiling);
104 	}
105 }
106 /* }}} */
107 
108 /* {{{ xsl_objects_new */
xsl_objects_new(zend_class_entry * class_type)109 zend_object *xsl_objects_new(zend_class_entry *class_type)
110 {
111 	xsl_object *intern;
112 
113 	intern = zend_object_alloc(sizeof(xsl_object), class_type);
114 	intern->securityPrefs = XSL_SECPREF_DEFAULT;
115 
116 	zend_object_std_init(&intern->std, class_type);
117 	object_properties_init(&intern->std, class_type);
118 	intern->parameter = zend_new_array(0);
119 	php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
120 
121 	/* Default initialize properties that could not be default initialized at the stub because they depend on library
122 	 * configuration parameters. */
123 	ZVAL_LONG(xsl_prop_max_template_depth(&intern->std), xsltMaxDepth);
124 	ZVAL_LONG(xsl_prop_max_template_vars(&intern->std), xsltMaxVars);
125 
126 	return &intern->std;
127 }
128 /* }}} */
129 
130 #if ZEND_DEBUG
131 # define XSL_DEFINE_PROP_ACCESSOR(c_name, php_name, prop_index) \
132 	zval *xsl_prop_##c_name(zend_object *object) \
133 	{ \
134 		zend_string *prop_name = ZSTR_INIT_LITERAL(php_name, false); \
135 		const zend_property_info *prop_info = zend_get_property_info(xsl_xsltprocessor_class_entry, prop_name, 0); \
136 		zend_string_release_ex(prop_name, false); \
137 		ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == prop_index); \
138 		return OBJ_PROP_NUM(object, prop_index); \
139 	}
140 #else
141 # define XSL_DEFINE_PROP_ACCESSOR(c_name, php_name, prop_index) \
142 	zval *xsl_prop_##c_name(zend_object *object) \
143 	{ \
144 		return OBJ_PROP_NUM(object, prop_index); \
145 	}
146 #endif
147 
148 XSL_DEFINE_PROP_ACCESSOR(max_template_depth, "maxTemplateDepth", 2)
149 XSL_DEFINE_PROP_ACCESSOR(max_template_vars, "maxTemplateVars", 3)
150 
xsl_objects_write_property_with_validation(zend_object * object,zend_string * member,zval * value,void ** cache_slot,zval * property)151 static zval *xsl_objects_write_property_with_validation(zend_object *object, zend_string *member, zval *value, void **cache_slot, zval *property)
152 {
153 	/* Read old value so we can restore it if necessary. The value is not refcounted as its type is IS_LONG. */
154 	ZEND_ASSERT(Z_TYPE_P(property) == IS_LONG);
155 	zend_long old_property_value = Z_LVAL_P(property);
156 
157 	/* Write new property, which will also potentially perform coercions. */
158 	zend_std_write_property(object, member, value, NULL);
159 
160 	/* Validate value *after* coercions have been performed, and restore the old value if necessary. */
161 	if (UNEXPECTED(Z_LVAL_P(property) < 0)) {
162 		Z_LVAL_P(property) = old_property_value;
163 		zend_value_error("%s::$%s must be greater than or equal to 0", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
164 		return &EG(error_zval);
165 	}
166 
167 	return property;
168 }
169 
xsl_objects_write_property(zend_object * object,zend_string * member,zval * value,void ** cache_slot)170 static zval *xsl_objects_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot)
171 {
172 	/* Extra validation for maxTemplateDepth and maxTemplateVars */
173 	if (zend_string_equals_literal(member, "maxTemplateDepth")) {
174 		zval *property = xsl_prop_max_template_depth(object);
175 		return xsl_objects_write_property_with_validation(object, member, value, cache_slot, property);
176 	} else if (zend_string_equals_literal(member, "maxTemplateVars")) {
177 		zval *property = xsl_prop_max_template_vars(object);
178 		return xsl_objects_write_property_with_validation(object, member, value, cache_slot, property);
179 	} else {
180 		return zend_std_write_property(object, member, value, cache_slot);
181 	}
182 }
183 
xsl_is_validated_property(const zend_string * member)184 static bool xsl_is_validated_property(const zend_string *member)
185 {
186 	return zend_string_equals_literal(member, "maxTemplateDepth") || zend_string_equals_literal(member, "maxTemplateVars");
187 }
188 
xsl_objects_get_property_ptr_ptr(zend_object * object,zend_string * member,int type,void ** cache_slot)189 static zval *xsl_objects_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot)
190 {
191 	if (xsl_is_validated_property(member)) {
192 		return NULL;
193 	}
194 
195 	return zend_std_get_property_ptr_ptr(object, member, type, cache_slot);
196 }
197 
xsl_objects_read_property(zend_object * object,zend_string * member,int type,void ** cache_slot,zval * rv)198 static zval *xsl_objects_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv)
199 {
200 	/* read handler is being called as a fallback after get_property_ptr_ptr returned NULL */
201 	if (type != BP_VAR_IS && type != BP_VAR_R && xsl_is_validated_property(member)) {
202 		zend_throw_error(NULL, "Indirect modification of %s::$%s is not allowed", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
203 		return &EG(uninitialized_zval);
204 	}
205 
206 	return zend_std_read_property(object, member, type, cache_slot, rv);
207 }
208 
xsl_objects_unset_property(zend_object * object,zend_string * member,void ** cache_slot)209 static void xsl_objects_unset_property(zend_object *object, zend_string *member, void **cache_slot)
210 {
211 	if (xsl_is_validated_property(member)) {
212 		zend_throw_error(NULL, "Cannot unset %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
213 		return;
214 	}
215 
216 	zend_std_unset_property(object, member, cache_slot);
217 }
218 
219 /* Tries to output an error message where a part was replaced by another string.
220  * Returns true if the search string was found and the error message with replacement was outputted.
221  * Return false otherwise. */
xsl_try_output_replaced_error_message(void * ctx,const char * msg,va_list args,const char * search,size_t search_len,const char * replace)222 static bool xsl_try_output_replaced_error_message(
223 	void *ctx,
224 	const char *msg,
225 	va_list args,
226 	const char *search,
227 	size_t search_len,
228 	const char *replace
229 )
230 {
231 	const char *msg_replace_location = strstr(msg, search);
232 	if (msg_replace_location != NULL) {
233 		php_libxml_ctx_error(ctx, "%.*s%s%s", (int) (msg_replace_location - msg), msg, replace, msg_replace_location + search_len);
234 		return true;
235 	}
236 	return false;
237 }
238 
239 /* Helper macro so the string length doesn't need to be passed separately.
240  * Only allows literal strings for `search` and `replace`. */
241 #define XSL_TRY_OUTPUT_REPLACED_ERROR_MESSAGE(ctx, msg, args, search, replace) \
242 	xsl_try_output_replaced_error_message(ctx, msg, args, "" search, sizeof("" search) - 1, "" replace)
243 
244 /* We want to output PHP-tailored error messages for some libxslt error messages, such that
245  * the errors refer to PHP properties instead of libxslt-specific fields. */
xsl_libxslt_error_handler(void * ctx,const char * msg,...)246 static void xsl_libxslt_error_handler(void *ctx, const char *msg, ...)
247 {
248 	va_list args;
249 	va_start(args, msg);
250 
251 	if (strcmp(msg, "%s") == 0) {
252 		/* Adjust error message to be more descriptive */
253 		const char *msg_arg = va_arg(args, const char *);
254 		bool output = XSL_TRY_OUTPUT_REPLACED_ERROR_MESSAGE(ctx, msg_arg, args, "xsltMaxDepth (--maxdepth)", "$maxTemplateDepth")
255 				   || XSL_TRY_OUTPUT_REPLACED_ERROR_MESSAGE(ctx, msg_arg, args, "maxTemplateVars (--maxvars)", "$maxTemplateVars");
256 
257 		if (!output) {
258 			php_libxml_ctx_error(ctx, "%s", msg_arg);
259 		}
260 	} else {
261 		php_libxml_error_handler_va(PHP_LIBXML_ERROR, ctx, msg, args);
262 	}
263 
264 	va_end(args);
265 }
266 
267 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(xsl)268 PHP_MINIT_FUNCTION(xsl)
269 {
270 	memcpy(&xsl_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
271 	xsl_object_handlers.offset = XtOffsetOf(xsl_object, std);
272 	xsl_object_handlers.clone_obj = NULL;
273 	xsl_object_handlers.free_obj = xsl_objects_free_storage;
274 	xsl_object_handlers.get_gc = xsl_objects_get_gc;
275 	xsl_object_handlers.write_property = xsl_objects_write_property;
276 	xsl_object_handlers.get_property_ptr_ptr = xsl_objects_get_property_ptr_ptr;
277 	xsl_object_handlers.read_property = xsl_objects_read_property;
278 	xsl_object_handlers.unset_property = xsl_objects_unset_property;
279 
280 	xsl_xsltprocessor_class_entry = register_class_XSLTProcessor();
281 	xsl_xsltprocessor_class_entry->create_object = xsl_objects_new;
282 	xsl_xsltprocessor_class_entry->default_object_handlers = &xsl_object_handlers;
283 
284 #ifdef HAVE_XSL_EXSLT
285 	exsltRegisterAll();
286 #endif
287 
288 	xsltRegisterExtModuleFunction ((const xmlChar *) "functionString",
289 				   (const xmlChar *) "http://php.net/xsl",
290 				   xsl_ext_function_string_php);
291 	xsltRegisterExtModuleFunction ((const xmlChar *) "function",
292 				   (const xmlChar *) "http://php.net/xsl",
293 				   xsl_ext_function_object_php);
294 	xsltSetGenericErrorFunc(NULL, xsl_libxslt_error_handler);
295 
296 	register_php_xsl_symbols(module_number);
297 
298 	return SUCCESS;
299 }
300 /* }}} */
301 
302 /* {{{ xsl_object_set_data */
xsl_object_set_data(void * obj,zval * wrapper)303 static void xsl_object_set_data(void *obj, zval *wrapper)
304 {
305 	((xsltStylesheetPtr) obj)->_private = wrapper;
306 }
307 /* }}} */
308 
309 /* {{{ php_xsl_set_object */
php_xsl_set_object(zval * wrapper,void * obj)310 void php_xsl_set_object(zval *wrapper, void *obj)
311 {
312 	xsl_object *object;
313 
314 	object = Z_XSL_P(wrapper);
315 	object->ptr = obj;
316 	xsl_object_set_data(obj, wrapper);
317 }
318 /* }}} */
319 
320 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(xsl)321 PHP_MSHUTDOWN_FUNCTION(xsl)
322 {
323 	xsltUnregisterExtModuleFunction ((const xmlChar *) "functionString",
324 				   (const xmlChar *) "http://php.net/xsl");
325 	xsltUnregisterExtModuleFunction ((const xmlChar *) "function",
326 				   (const xmlChar *) "http://php.net/xsl");
327 	xsltSetGenericErrorFunc(NULL, NULL);
328 	xsltCleanupGlobals();
329 
330 	return SUCCESS;
331 }
332 /* }}} */
333 
334 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(xsl)335 PHP_MINFO_FUNCTION(xsl)
336 {
337 	php_info_print_table_start();
338 	{
339 		char buffer[128];
340 		int major, minor, subminor;
341 
342 		php_info_print_table_row(2, "XSL", "enabled");
343 		major = xsltLibxsltVersion/10000;
344 		minor = (xsltLibxsltVersion - major * 10000) / 100;
345 		subminor = (xsltLibxsltVersion - major * 10000 - minor * 100);
346 		snprintf(buffer, 128, "%d.%d.%d", major, minor, subminor);
347 		php_info_print_table_row(2, "libxslt Version", buffer);
348 		major = xsltLibxmlVersion/10000;
349 		minor = (xsltLibxmlVersion - major * 10000) / 100;
350 		subminor = (xsltLibxmlVersion - major * 10000 - minor * 100);
351 		snprintf(buffer, 128, "%d.%d.%d", major, minor, subminor);
352 		php_info_print_table_row(2, "libxslt compiled against libxml Version", buffer);
353 	}
354 #ifdef HAVE_XSL_EXSLT
355 	php_info_print_table_row(2, "EXSLT", "enabled");
356 	php_info_print_table_row(2, "libexslt Version", LIBEXSLT_DOTTED_VERSION);
357 #endif
358 	php_info_print_table_end();
359 }
360 /* }}} */
361