xref: /php-src/ext/xsl/xsltprocessor.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    | Authors: Christian Stocker <chregu@php.net>                          |
14    |          Rob Richards <rrichards@php.net>                            |
15    +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 #include "php_xsl.h"
24 #include <libxslt/variables.h>
25 #include "ext/libxml/php_libxml.h"
26 #include "ext/dom/namespace_compat.h"
27 
28 
php_xsl_xslt_apply_params(xsltTransformContextPtr ctxt,HashTable * params)29 static zend_result php_xsl_xslt_apply_params(xsltTransformContextPtr ctxt, HashTable *params)
30 {
31 	zend_string *string_key;
32 	zval *value;
33 
34 	ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(params, string_key, value) {
35 		ZEND_ASSERT(string_key != NULL);
36 		/* Already a string because of setParameter() */
37 		ZEND_ASSERT(Z_TYPE_P(value) == IS_STRING);
38 
39 		int result = xsltQuoteOneUserParam(ctxt, (const xmlChar *) ZSTR_VAL(string_key), (const xmlChar *) Z_STRVAL_P(value));
40 		if (result < 0) {
41 			php_error_docref(NULL, E_WARNING, "Could not apply parameter \"%s\"", ZSTR_VAL(string_key));
42 			return FAILURE;
43 		}
44 	} ZEND_HASH_FOREACH_END();
45 
46 	return SUCCESS;
47 }
48 
xsl_proxy_factory(xmlNodePtr node,zval * child,dom_object * intern,xmlXPathParserContextPtr ctxt)49 static void xsl_proxy_factory(xmlNodePtr node, zval *child, dom_object *intern, xmlXPathParserContextPtr ctxt)
50 {
51 	ZEND_ASSERT(node->type != XML_NAMESPACE_DECL);
52 
53 	/**
54 	 * Upon freeing libxslt's context, every document that is not the *main* document will be freed by libxslt.
55 	 * If a node of a document that is *not the main* document gets returned to userland, we'd free the node twice:
56 	 * first by the cleanup of the xslt context, and then by our own refcounting mechanism.
57 	 * To prevent this, we'll take a copy if the node is not from the main document.
58 	 * It is important that we do not copy the node unconditionally, because that means that:
59 	 *  - modifications to the node will only modify the copy, and not the original
60 	 *  - accesses to the parent, path, ... will not work
61 	 */
62 	xsltTransformContextPtr transform_ctxt = (xsltTransformContextPtr) ctxt->context->extra;
63 	if (node->doc != transform_ctxt->document->doc) {
64 		node = xmlDocCopyNode(node, intern->document->ptr, 1);
65 	}
66 	php_dom_create_object(node, child, intern);
67 }
68 
xsl_ext_fetch_intern(xmlXPathParserContextPtr ctxt)69 static xsl_object *xsl_ext_fetch_intern(xmlXPathParserContextPtr ctxt)
70 {
71 	if (UNEXPECTED(!zend_is_executing())) {
72 		xsltGenericError(xsltGenericErrorContext,
73 		"xsltExtFunctionTest: Function called from outside of PHP\n");
74 		return NULL;
75 	}
76 
77 	xsltTransformContextPtr tctxt = xsltXPathGetTransformContext(ctxt);
78 	if (UNEXPECTED(tctxt == NULL)) {
79 		xsltGenericError(xsltGenericErrorContext,
80 		"xsltExtFunctionTest: failed to get the transformation context\n");
81 		return NULL;
82 	}
83 
84 	xsl_object *intern = (xsl_object *) tctxt->_private;
85 	if (UNEXPECTED(intern == NULL)) {
86 		xsltGenericError(xsltGenericErrorContext,
87 		"xsltExtFunctionTest: failed to get the internal object\n");
88 		return NULL;
89 	}
90 	return intern;
91 }
92 
xsl_ext_function_php(xmlXPathParserContextPtr ctxt,int nargs,php_dom_xpath_nodeset_evaluation_mode evaluation_mode)93 static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, php_dom_xpath_nodeset_evaluation_mode evaluation_mode) /* {{{ */
94 {
95 	xsl_object *intern = xsl_ext_fetch_intern(ctxt);
96 	if (!intern) {
97 		php_dom_xpath_callbacks_clean_argument_stack(ctxt, nargs);
98 	} else {
99 		php_dom_xpath_callbacks_call_php_ns(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, (dom_object *) intern->doc, xsl_proxy_factory);
100 	}
101 }
102 /* }}} */
103 
xsl_ext_function_string_php(xmlXPathParserContextPtr ctxt,int nargs)104 void xsl_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
105 {
106 	xsl_ext_function_php(ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_STRING);
107 }
108 /* }}} */
109 
xsl_ext_function_object_php(xmlXPathParserContextPtr ctxt,int nargs)110 void xsl_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
111 {
112 	xsl_ext_function_php(ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET);
113 }
114 /* }}} */
115 
xsl_ext_function_trampoline(xmlXPathParserContextPtr ctxt,int nargs)116 static void xsl_ext_function_trampoline(xmlXPathParserContextPtr ctxt, int nargs)
117 {
118 	xsl_object *intern = xsl_ext_fetch_intern(ctxt);
119 	if (!intern) {
120 		php_dom_xpath_callbacks_clean_argument_stack(ctxt, nargs);
121 	} else {
122 		php_dom_xpath_callbacks_call_custom_ns(&intern->xpath_callbacks, ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET, (dom_object *) intern->doc, xsl_proxy_factory);
123 	}
124 }
125 
xsl_add_ns_to_map(xmlHashTablePtr table,xsltStylesheetPtr sheet,const xmlNode * cur,const xmlChar * prefix,const xmlChar * uri)126 static void xsl_add_ns_to_map(xmlHashTablePtr table, xsltStylesheetPtr sheet, const xmlNode *cur, const xmlChar *prefix, const xmlChar *uri)
127 {
128 	const xmlChar *existing_url = xmlHashLookup(table, prefix);
129 	if (existing_url != NULL && !xmlStrEqual(existing_url, uri)) {
130 		xsltTransformError(NULL, sheet, (xmlNodePtr) cur, "Namespaces prefix %s used for multiple namespaces\n", prefix);
131 		sheet->warnings++;
132 	} else if (existing_url == NULL) {
133 		xmlHashUpdateEntry(table, prefix, (void *) uri, NULL);
134 	}
135 }
136 
137 /* Adds all namespace declaration (not using nsDef) into a hash map that maps prefix to uri. Warns on conflicting declarations. */
xsl_build_ns_map(xmlHashTablePtr table,xsltStylesheetPtr sheet,php_dom_libxml_ns_mapper * ns_mapper,const xmlDoc * doc)138 static void xsl_build_ns_map(xmlHashTablePtr table, xsltStylesheetPtr sheet, php_dom_libxml_ns_mapper *ns_mapper, const xmlDoc *doc)
139 {
140 	const xmlNode *cur = xmlDocGetRootElement(doc);
141 
142 	while (cur != NULL) {
143 		if (cur->type == XML_ELEMENT_NODE) {
144 			if (cur->ns != NULL && cur->ns->prefix != NULL) {
145 				xsl_add_ns_to_map(table, sheet, cur, cur->ns->prefix, cur->ns->href);
146 			}
147 
148 			for (const xmlAttr *attr = cur->properties; attr != NULL; attr = attr->next) {
149 				if (attr->ns != NULL && attr->ns->prefix != NULL && php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)
150 					&& attr->children != NULL && attr->children->content != NULL) {
151 					/* This attribute declares a namespace, get the relevant instance.
152 					 * The declared namespace is not the same as the namespace of this attribute (which is xmlns). */
153 					const xmlChar *prefix = attr->name;
154 					xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, (const char *) prefix, (const char *) attr->children->content);
155 					xsl_add_ns_to_map(table, sheet, cur, prefix, ns->href);
156 				}
157 			}
158 
159 			if (cur->children != NULL) {
160 				cur = cur->children;
161 				continue;
162 			}
163 		}
164 
165 		cur = php_dom_next_in_tree_order(cur, (const xmlNode *) doc);
166 	}
167 }
168 
169 /* Apply namespace corrections for new DOM */
170 typedef enum {
171 	XSL_NS_HASH_CORRECTION_NONE = 0,
172 	XSL_NS_HASH_CORRECTION_APPLIED = 1,
173 	XSL_NS_HASH_CORRECTION_FAILED = 2
174 } xsl_ns_hash_correction_status;
175 
xsl_apply_ns_hash_corrections(xsltStylesheetPtr sheetp,xmlNodePtr nodep,xmlDocPtr doc)176 static zend_always_inline xsl_ns_hash_correction_status xsl_apply_ns_hash_corrections(xsltStylesheetPtr sheetp, xmlNodePtr nodep, xmlDocPtr doc)
177 {
178 	if (sheetp->nsHash == NULL) {
179 		dom_object *node_intern = php_dom_object_get_data(nodep);
180 		if (node_intern != NULL && php_dom_follow_spec_intern(node_intern)) {
181 			sheetp->nsHash = xmlHashCreate(10);
182 			if (UNEXPECTED(!sheetp->nsHash)) {
183 				return XSL_NS_HASH_CORRECTION_FAILED;
184 			}
185 			xsl_build_ns_map(sheetp->nsHash, sheetp, php_dom_get_ns_mapper(node_intern), doc);
186 			return XSL_NS_HASH_CORRECTION_APPLIED;
187 		}
188 	}
189 	return XSL_NS_HASH_CORRECTION_NONE;
190 }
191 
192 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#
193 Since:
194 */
PHP_METHOD(XSLTProcessor,importStylesheet)195 PHP_METHOD(XSLTProcessor, importStylesheet)
196 {
197 	zval *id, *docp = NULL;
198 	xmlDoc *doc = NULL, *newdoc = NULL;
199 	xsltStylesheetPtr sheetp;
200 	int clone_docu = 0;
201 	xmlNode *nodep = NULL;
202 	zval *cloneDocu, rv;
203 	zend_string *member;
204 
205 	id = ZEND_THIS;
206 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &docp) == FAILURE) {
207 		RETURN_THROWS();
208 	}
209 
210 	nodep = php_libxml_import_node(docp);
211 
212 	if (nodep) {
213 		doc = nodep->doc;
214 	}
215 	if (doc == NULL) {
216 		zend_argument_type_error(1, "must be a valid XML node");
217 		RETURN_THROWS();
218 	}
219 
220 	/* libxslt uses _private, so we must copy the imported
221 	stylesheet document otherwise the node proxies will be a mess */
222 	newdoc = xmlCopyDoc(doc, 1);
223 	xmlNodeSetBase((xmlNodePtr) newdoc, (xmlChar *)doc->URL);
224 	PHP_LIBXML_SANITIZE_GLOBALS(parse);
225 	ZEND_DIAGNOSTIC_IGNORED_START("-Wdeprecated-declarations")
226 	xmlSubstituteEntitiesDefault(1);
227 	xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
228 	ZEND_DIAGNOSTIC_IGNORED_END
229 
230 	sheetp = xsltParseStylesheetDoc(newdoc);
231 	PHP_LIBXML_RESTORE_GLOBALS(parse);
232 
233 	if (!sheetp) {
234 		xmlFreeDoc(newdoc);
235 		RETURN_FALSE;
236 	}
237 
238 	xsl_object *intern = Z_XSL_P(id);
239 
240 	xsl_ns_hash_correction_status status = xsl_apply_ns_hash_corrections(sheetp, nodep, doc);
241 	if (UNEXPECTED(status == XSL_NS_HASH_CORRECTION_FAILED)) {
242 		xsltFreeStylesheet(sheetp);
243 		xmlFreeDoc(newdoc);
244 		RETURN_FALSE;
245 	} else if (status == XSL_NS_HASH_CORRECTION_APPLIED) {
246 		/* The namespace mappings need to be kept alive.
247 		 * This is stored in the ref obj outside of libxml2, but that means that the sheet won't keep it alive
248 		 * unlike with namespaces from old DOM. */
249 		if (intern->sheet_ref_obj) {
250 			php_libxml_decrement_doc_ref_directly(intern->sheet_ref_obj);
251 		}
252 		intern->sheet_ref_obj = Z_LIBXML_NODE_P(docp)->document;
253 		intern->sheet_ref_obj->refcount++;
254 	}
255 
256 	member = ZSTR_INIT_LITERAL("cloneDocument", 0);
257 	cloneDocu = zend_std_read_property(Z_OBJ_P(id), member, BP_VAR_R, NULL, &rv);
258 	clone_docu = zend_is_true(cloneDocu);
259 	zend_string_release_ex(member, 0);
260 	if (clone_docu == 0) {
261 		/* Check if the stylesheet is using xsl:key, if yes, we have to clone the document _always_ before a transformation.
262 		 * xsl:key elements may only occur at the top level. Furthermore, all elements at the top level must be in a
263 		 * namespace (if not, then the stylesheet is not well-formed and this function will have returned false earlier). */
264 		nodep = xmlDocGetRootElement(sheetp->doc);
265 		if (nodep && (nodep = nodep->children)) {
266 			while (nodep) {
267 				ZEND_ASSERT(nodep->ns != NULL);
268 				if (nodep->type == XML_ELEMENT_NODE && xmlStrEqual(nodep->name, (const xmlChar *) "key") && xmlStrEqual(nodep->ns->href, XSLT_NAMESPACE)) {
269 					intern->hasKeys = true;
270 					break;
271 				}
272 				nodep = nodep->next;
273 			}
274 		}
275 	} else {
276 		intern->hasKeys = true;
277 	}
278 
279 	xsl_free_sheet(intern);
280 
281 	php_xsl_set_object(id, sheetp);
282 	RETVAL_TRUE;
283 }
284 /* }}} end XSLTProcessor::importStylesheet */
285 
php_xsl_delayed_lib_registration(void * ctxt,const zend_string * ns,const zend_string * name)286 static void php_xsl_delayed_lib_registration(void *ctxt, const zend_string *ns, const zend_string *name)
287 {
288 	xsltTransformContextPtr xsl = (xsltTransformContextPtr) ctxt;
289 	xsltRegisterExtFunction(xsl, (const xmlChar *) ZSTR_VAL(name), (const xmlChar *) ZSTR_VAL(ns), xsl_ext_function_trampoline);
290 }
291 
php_xsl_apply_stylesheet(zval * id,xsl_object * intern,xsltStylesheetPtr style,zval * docp)292 static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStylesheetPtr style, zval *docp) /* {{{ */
293 {
294 	xmlDocPtr newdocp = NULL;
295 	xmlDocPtr doc = NULL;
296 	xmlNodePtr node = NULL;
297 	xsltTransformContextPtr ctxt;
298 	php_libxml_node_object *object;
299 	zval *doXInclude, rv;
300 	zend_string *member;
301 	FILE *f;
302 	int secPrefsError = 0;
303 	int secPrefsValue;
304 	xsltSecurityPrefsPtr secPrefs = NULL;
305 
306 	node = php_libxml_import_node(docp);
307 
308 	if (node) {
309 		doc = node->doc;
310 	}
311 
312 	if (doc == NULL) {
313 		zend_argument_type_error(1, "must be a valid XML node");
314 		return NULL;
315 	}
316 
317 	if (style == NULL) {
318 		zend_string *name = get_active_function_or_method_name();
319 		zend_throw_error(NULL, "%s() can only be called after a stylesheet has been imported",
320 			ZSTR_VAL(name));
321 		zend_string_release(name);
322 		return NULL;
323 	}
324 
325 	if (intern->profiling) {
326 		if (php_check_open_basedir(intern->profiling)) {
327 			f = NULL;
328 		} else {
329 			f = VCWD_FOPEN(intern->profiling, "w");
330 		}
331 	} else {
332 		f = NULL;
333 	}
334 
335 	intern->doc = emalloc(sizeof(php_libxml_node_object));
336 	memset(intern->doc, 0, sizeof(php_libxml_node_object));
337 
338 	if (intern->hasKeys) {
339 		doc = xmlCopyDoc(doc, 1);
340 	} else {
341 		object = Z_LIBXML_NODE_P(docp);
342 		intern->doc->document = object->document;
343 	}
344 
345 	php_libxml_increment_doc_ref(intern->doc, doc);
346 
347 	ctxt = xsltNewTransformContext(style, doc);
348 	ctxt->_private = (void *) intern;
349 
350 	if (intern->parameter) {
351 		zend_result status = php_xsl_xslt_apply_params(ctxt, intern->parameter);
352 		if (UNEXPECTED(status != SUCCESS) && EG(exception)) {
353 			goto out;
354 		}
355 	}
356 
357 	member = ZSTR_INIT_LITERAL("doXInclude", 0);
358 	doXInclude = zend_std_read_property(Z_OBJ_P(id), member, BP_VAR_R, NULL, &rv);
359 	ctxt->xinclude = zend_is_true(doXInclude);
360 	zend_string_release_ex(member, 0);
361 
362 	zval *max_template_depth = xsl_prop_max_template_depth(Z_OBJ_P(id));
363 	ZEND_ASSERT(Z_TYPE_P(max_template_depth) == IS_LONG);
364 	ctxt->maxTemplateDepth = Z_LVAL_P(max_template_depth);
365 
366 	zval *max_template_vars = xsl_prop_max_template_vars(Z_OBJ_P(id));
367 	ZEND_ASSERT(Z_TYPE_P(max_template_vars) == IS_LONG);
368 	ctxt->maxTemplateVars = Z_LVAL_P(max_template_vars);
369 
370 	secPrefsValue = intern->securityPrefs;
371 
372 	/* if securityPrefs is set to NONE, we don't have to do any checks, but otherwise... */
373 	if (secPrefsValue != XSL_SECPREF_NONE) {
374 		secPrefs = xsltNewSecurityPrefs();
375 		if (secPrefsValue & XSL_SECPREF_READ_FILE ) {
376 			if (0 != xsltSetSecurityPrefs(secPrefs, XSLT_SECPREF_READ_FILE, xsltSecurityForbid)) {
377 				secPrefsError = 1;
378 			}
379 		}
380 		if (secPrefsValue & XSL_SECPREF_WRITE_FILE ) {
381 			if (0 != xsltSetSecurityPrefs(secPrefs, XSLT_SECPREF_WRITE_FILE, xsltSecurityForbid)) {
382 				secPrefsError = 1;
383 			}
384 		}
385 		if (secPrefsValue & XSL_SECPREF_CREATE_DIRECTORY ) {
386 			if (0 != xsltSetSecurityPrefs(secPrefs, XSLT_SECPREF_CREATE_DIRECTORY, xsltSecurityForbid)) {
387 				secPrefsError = 1;
388 			}
389 		}
390 		if (secPrefsValue & XSL_SECPREF_READ_NETWORK) {
391 			if (0 != xsltSetSecurityPrefs(secPrefs, XSLT_SECPREF_READ_NETWORK, xsltSecurityForbid)) {
392 				secPrefsError = 1;
393 			}
394 		}
395 		if (secPrefsValue & XSL_SECPREF_WRITE_NETWORK) {
396 			if (0 != xsltSetSecurityPrefs(secPrefs, XSLT_SECPREF_WRITE_NETWORK, xsltSecurityForbid)) {
397 				secPrefsError = 1;
398 			}
399 		}
400 
401 		if (0 != xsltSetCtxtSecurityPrefs(secPrefs, ctxt)) {
402 			secPrefsError = 1;
403 		}
404 	}
405 
406 	php_dom_xpath_callbacks_delayed_lib_registration(&intern->xpath_callbacks, ctxt, php_xsl_delayed_lib_registration);
407 
408 	if (secPrefsError == 1) {
409 		php_error_docref(NULL, E_WARNING, "Can't set libxslt security properties, not doing transformation for security reasons");
410 	} else {
411 		newdocp = xsltApplyStylesheetUser(style, doc, /* params (handled manually) */ NULL, /* output */ NULL, f, ctxt);
412 	}
413 
414 out:
415 	if (f) {
416 		fclose(f);
417 	}
418 
419 	xsltFreeTransformContext(ctxt);
420 	if (secPrefs) {
421 		xsltFreeSecurityPrefs(secPrefs);
422 	}
423 
424 	php_dom_xpath_callbacks_clean_node_list(&intern->xpath_callbacks);
425 
426 	php_libxml_decrement_doc_ref(intern->doc);
427 	efree(intern->doc);
428 	intern->doc = NULL;
429 
430 	return newdocp;
431 
432 }
433 /* }}} */
434 
435 /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#
436 Since:
437 */
PHP_METHOD(XSLTProcessor,transformToDoc)438 PHP_METHOD(XSLTProcessor, transformToDoc)
439 {
440 	zval *id, *docp = NULL;
441 	xmlDoc *newdocp;
442 	xsltStylesheetPtr sheetp;
443 	zend_class_entry *ret_class = NULL;
444 	xsl_object *intern;
445 
446 	id = ZEND_THIS;
447 	intern = Z_XSL_P(id);
448 	sheetp = (xsltStylesheetPtr) intern->ptr;
449 
450 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "o|C!", &docp, &ret_class) == FAILURE) {
451 		RETURN_THROWS();
452 	}
453 
454 	newdocp = php_xsl_apply_stylesheet(id, intern, sheetp, docp);
455 
456 	if (newdocp) {
457 		if (ret_class) {
458 			zend_string *curclass_name;
459 			zend_class_entry *curce;
460 			php_libxml_node_object *interndoc;
461 
462 			curce = Z_OBJCE_P(docp);
463 			curclass_name = curce->name;
464 			while (curce->parent != NULL) {
465 				curce = curce->parent;
466 			}
467 
468 			if (!instanceof_function(ret_class, curce)) {
469 				xmlFreeDoc(newdocp);
470 				zend_argument_type_error(2, "must be a class name compatible with %s, %s given",
471 					ZSTR_VAL(curclass_name), ZSTR_VAL(ret_class->name)
472 				);
473 				RETURN_THROWS();
474 			}
475 
476 			object_init_ex(return_value, ret_class);
477 
478 			interndoc = Z_LIBXML_NODE_P(return_value);
479 			php_libxml_increment_doc_ref(interndoc, newdocp);
480 			php_libxml_increment_node_ptr(interndoc, (xmlNodePtr)newdocp, (void *)interndoc);
481 		} else {
482 			php_dom_create_object((xmlNodePtr) newdocp, return_value, NULL);
483 		}
484 	} else {
485 		RETURN_FALSE;
486 	}
487 }
488 /* }}} end XSLTProcessor::transformToDoc */
489 
490 /* {{{ */
PHP_METHOD(XSLTProcessor,transformToUri)491 PHP_METHOD(XSLTProcessor, transformToUri)
492 {
493 	zval *id, *docp = NULL;
494 	xmlDoc *newdocp;
495 	xsltStylesheetPtr sheetp;
496 	int ret;
497 	size_t uri_len;
498 	char *uri;
499 	xsl_object *intern;
500 
501 	id = ZEND_THIS;
502 	intern = Z_XSL_P(id);
503 	sheetp = (xsltStylesheetPtr) intern->ptr;
504 
505 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "op", &docp, &uri, &uri_len) == FAILURE) {
506 		RETURN_THROWS();
507 	}
508 
509 	newdocp = php_xsl_apply_stylesheet(id, intern, sheetp, docp);
510 
511 	ret = -1;
512 	if (newdocp) {
513 		ret = xsltSaveResultToFilename(uri, newdocp, sheetp, 0);
514 		xmlFreeDoc(newdocp);
515 	}
516 
517 	RETVAL_LONG(ret);
518 }
519 /* }}} end XSLTProcessor::transformToUri */
520 
521 /* {{{ */
PHP_METHOD(XSLTProcessor,transformToXml)522 PHP_METHOD(XSLTProcessor, transformToXml)
523 {
524 	zval *id, *docp = NULL;
525 	xmlDoc *newdocp;
526 	xsltStylesheetPtr sheetp;
527 	int ret;
528 	xmlChar *doc_txt_ptr;
529 	int doc_txt_len;
530 	xsl_object *intern;
531 
532 	id = ZEND_THIS;
533 	intern = Z_XSL_P(id);
534 	sheetp = (xsltStylesheetPtr) intern->ptr;
535 
536 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &docp) == FAILURE) {
537 		RETURN_THROWS();
538 	}
539 
540 	newdocp = php_xsl_apply_stylesheet(id, intern, sheetp, docp);
541 
542 	ret = -1;
543 	if (newdocp) {
544 		ret = xsltSaveResultToString(&doc_txt_ptr, &doc_txt_len, newdocp, sheetp);
545 		if (doc_txt_ptr && doc_txt_len) {
546 			RETVAL_STRINGL((char *) doc_txt_ptr, doc_txt_len);
547 			xmlFree(doc_txt_ptr);
548 		}
549 		xmlFreeDoc(newdocp);
550 	}
551 
552 	if (ret < 0) {
553 		RETURN_FALSE;
554 	}
555 }
556 /* }}} end XSLTProcessor::transformToXml */
557 
558 /* {{{ */
PHP_METHOD(XSLTProcessor,setParameter)559 PHP_METHOD(XSLTProcessor, setParameter)
560 {
561 
562 	zval *id = ZEND_THIS;
563 	zval *entry, new_string;
564 	HashTable *array_value;
565 	xsl_object *intern;
566 	char *namespace;
567 	size_t namespace_len;
568 	zend_string *string_key, *name, *value = NULL;
569 
570 	ZEND_PARSE_PARAMETERS_START(2, 3)
571 		Z_PARAM_STRING(namespace, namespace_len)
572 		Z_PARAM_ARRAY_HT_OR_STR(array_value, name)
573 		Z_PARAM_OPTIONAL
574 		Z_PARAM_PATH_STR_OR_NULL(value)
575 	ZEND_PARSE_PARAMETERS_END();
576 
577 	intern = Z_XSL_P(id);
578 
579 	if (array_value) {
580 		if (value) {
581 			zend_argument_value_error(3, "must be null when argument #2 ($name) is an array");
582 			RETURN_THROWS();
583 		}
584 
585 		ZEND_HASH_FOREACH_STR_KEY_VAL(array_value, string_key, entry) {
586 			zval tmp;
587 			zend_string *str;
588 
589 			if (string_key == NULL) {
590 				zend_argument_type_error(2, "must contain only string keys");
591 				RETURN_THROWS();
592 			}
593 
594 			if (UNEXPECTED(CHECK_NULL_PATH(ZSTR_VAL(string_key), ZSTR_LEN(string_key)))) {
595 				zend_argument_value_error(3, "must not contain keys with any null bytes");
596 				RETURN_THROWS();
597 			}
598 
599 			str = zval_try_get_string(entry);
600 			if (UNEXPECTED(!str)) {
601 				RETURN_THROWS();
602 			}
603 
604 			if (UNEXPECTED(CHECK_NULL_PATH(ZSTR_VAL(str), ZSTR_LEN(str)))) {
605 				zend_string_release(str);
606 				zend_argument_value_error(3, "must not contain values with any null bytes");
607 				RETURN_THROWS();
608 			}
609 
610 			ZVAL_STR(&tmp, str);
611 			zend_hash_update(intern->parameter, string_key, &tmp);
612 		} ZEND_HASH_FOREACH_END();
613 		RETURN_TRUE;
614 	} else {
615 		if (!value) {
616 			zend_argument_value_error(3, "cannot be null when argument #2 ($name) is a string");
617 			RETURN_THROWS();
618 		}
619 
620 		if (UNEXPECTED(CHECK_NULL_PATH(ZSTR_VAL(name), ZSTR_LEN(name)))) {
621 			zend_argument_value_error(2, "must not contain any null bytes");
622 			RETURN_THROWS();
623 		}
624 
625 		ZVAL_STR_COPY(&new_string, value);
626 
627 		zend_hash_update(intern->parameter, name, &new_string);
628 		RETURN_TRUE;
629 	}
630 }
631 /* }}} end XSLTProcessor::setParameter */
632 
633 /* {{{ */
PHP_METHOD(XSLTProcessor,getParameter)634 PHP_METHOD(XSLTProcessor, getParameter)
635 {
636 	zval *id = ZEND_THIS;
637 	char *namespace;
638 	size_t namespace_len = 0;
639 	zval *value;
640 	zend_string *name;
641 	xsl_object *intern;
642 
643 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &namespace, &namespace_len, &name) == FAILURE) {
644 		RETURN_THROWS();
645 	}
646 	intern = Z_XSL_P(id);
647 	if ((value = zend_hash_find(intern->parameter, name)) != NULL) {
648 		RETURN_STR_COPY(Z_STR_P(value));
649 	} else {
650 		RETURN_FALSE;
651 	}
652 }
653 /* }}} end XSLTProcessor::getParameter */
654 
655 /* {{{ */
PHP_METHOD(XSLTProcessor,removeParameter)656 PHP_METHOD(XSLTProcessor, removeParameter)
657 {
658 	zval *id = ZEND_THIS;
659 	size_t namespace_len = 0;
660 	char *namespace;
661 	zend_string *name;
662 	xsl_object *intern;
663 
664 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &namespace, &namespace_len, &name) == FAILURE) {
665 		RETURN_THROWS();
666 	}
667 	intern = Z_XSL_P(id);
668 	if (zend_hash_del(intern->parameter, name) == SUCCESS) {
669 		RETURN_TRUE;
670 	} else {
671 		RETURN_FALSE;
672 	}
673 }
674 /* }}} end XSLTProcessor::removeParameter */
675 
676 /* {{{ */
PHP_METHOD(XSLTProcessor,registerPHPFunctions)677 PHP_METHOD(XSLTProcessor, registerPHPFunctions)
678 {
679 	xsl_object *intern = Z_XSL_P(ZEND_THIS);
680 
681 	zend_string *name = NULL;
682 	HashTable *callable_ht = NULL;
683 
684 	ZEND_PARSE_PARAMETERS_START(0, 1)
685 		Z_PARAM_OPTIONAL
686 		Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(callable_ht, name)
687 	ZEND_PARSE_PARAMETERS_END();
688 
689 	php_dom_xpath_callbacks_update_method_handler(
690 		&intern->xpath_callbacks,
691 		NULL,
692 		NULL,
693 		name,
694 		callable_ht,
695 		PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NULLS,
696 		NULL
697 	);
698 }
699 /* }}} end XSLTProcessor::registerPHPFunctions(); */
700 
PHP_METHOD(XSLTProcessor,registerPHPFunctionNS)701 PHP_METHOD(XSLTProcessor, registerPHPFunctionNS)
702 {
703 	xsl_object *intern = Z_XSL_P(ZEND_THIS);
704 
705 	zend_string *namespace, *name;
706 	zend_fcall_info fci;
707 	zend_fcall_info_cache fcc;
708 
709 	ZEND_PARSE_PARAMETERS_START(3, 3)
710 		Z_PARAM_PATH_STR(namespace)
711 		Z_PARAM_PATH_STR(name)
712 		Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
713 	ZEND_PARSE_PARAMETERS_END();
714 
715 	if (zend_string_equals_literal(namespace, "http://php.net/xsl")) {
716 		zend_argument_value_error(1, "must not be \"http://php.net/xsl\" because it is reserved by PHP");
717 		RETURN_THROWS();
718 	}
719 
720 	php_dom_xpath_callbacks_update_single_method_handler(
721 		&intern->xpath_callbacks,
722 		NULL,
723 		namespace,
724 		name,
725 		&fcc,
726 		PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME,
727 		NULL
728 	);
729 }
730 
731 /* {{{ */
PHP_METHOD(XSLTProcessor,setProfiling)732 PHP_METHOD(XSLTProcessor, setProfiling)
733 {
734 	zval *id = ZEND_THIS;
735 	xsl_object *intern;
736 	char *filename = NULL;
737 	size_t filename_len;
738 
739 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "p!", &filename, &filename_len) == FAILURE) {
740 		RETURN_THROWS();
741 	}
742 
743 	intern = Z_XSL_P(id);
744 	if (intern->profiling) {
745 		efree(intern->profiling);
746 	}
747 	if (filename != NULL) {
748 		intern->profiling = estrndup(filename, filename_len);
749 	} else {
750 		intern->profiling = NULL;
751 	}
752 
753 	RETURN_TRUE;
754 }
755 /* }}} end XSLTProcessor::setProfiling */
756 
757 /* {{{ */
PHP_METHOD(XSLTProcessor,setSecurityPrefs)758 PHP_METHOD(XSLTProcessor, setSecurityPrefs)
759 {
760 	zval *id = ZEND_THIS;
761 	xsl_object *intern;
762 	zend_long securityPrefs, oldSecurityPrefs;
763 
764 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &securityPrefs) == FAILURE) {
765 		RETURN_THROWS();
766 	}
767 	intern = Z_XSL_P(id);
768 	oldSecurityPrefs = intern->securityPrefs;
769 	intern->securityPrefs = securityPrefs;
770 	RETURN_LONG(oldSecurityPrefs);
771 }
772 /* }}} end XSLTProcessor::setSecurityPrefs */
773 
774 /* {{{ */
PHP_METHOD(XSLTProcessor,getSecurityPrefs)775 PHP_METHOD(XSLTProcessor, getSecurityPrefs)
776 {
777 	zval *id = ZEND_THIS;
778 	xsl_object *intern;
779 
780 	if (zend_parse_parameters_none() == FAILURE) {
781 		RETURN_THROWS();
782 	}
783 
784 	intern = Z_XSL_P(id);
785 
786 	RETURN_LONG(intern->securityPrefs);
787 }
788 /* }}} end XSLTProcessor::getSecurityPrefs */
789 
790 /* {{{ */
PHP_METHOD(XSLTProcessor,hasExsltSupport)791 PHP_METHOD(XSLTProcessor, hasExsltSupport)
792 {
793 	if (zend_parse_parameters_none() == FAILURE) {
794 		RETURN_THROWS();
795 	}
796 
797 #ifdef HAVE_XSL_EXSLT
798 	RETURN_TRUE;
799 #else
800 	RETURN_FALSE;
801 #endif
802 }
803 /* }}} end XSLTProcessor::hasExsltSupport(); */
804