xpath.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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. #ifdef HAVE_CONFIG_H
  18. #include "config.h"
  19. #endif
  20. #include "php.h"
  21. #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
  22. #include "php_dom.h"
  23. #define PHP_DOM_XPATH_QUERY 0
  24. #define PHP_DOM_XPATH_EVALUATE 1
  25. /*
  26. * class DOMXPath
  27. */
  28. #ifdef LIBXML_XPATH_ENABLED
  29. static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int type) /* {{{ */
  30. {
  31. zval retval;
  32. int result, i;
  33. int error = 0;
  34. zend_fcall_info fci;
  35. xmlXPathObjectPtr obj;
  36. char *str;
  37. zend_string *callable = NULL;
  38. dom_xpath_object *intern;
  39. if (! zend_is_executing()) {
  40. xmlGenericError(xmlGenericErrorContext,
  41. "xmlExtFunctionTest: Function called from outside of PHP\n");
  42. error = 1;
  43. } else {
  44. intern = (dom_xpath_object *) ctxt->context->userData;
  45. if (intern == NULL) {
  46. xmlGenericError(xmlGenericErrorContext,
  47. "xmlExtFunctionTest: failed to get the internal object\n");
  48. error = 1;
  49. }
  50. else if (intern->registerPhpFunctions == 0) {
  51. xmlGenericError(xmlGenericErrorContext,
  52. "xmlExtFunctionTest: PHP Object did not register PHP functions\n");
  53. error = 1;
  54. }
  55. }
  56. if (error == 1) {
  57. for (i = nargs - 1; i >= 0; i--) {
  58. obj = valuePop(ctxt);
  59. xmlXPathFreeObject(obj);
  60. }
  61. return;
  62. }
  63. fci.param_count = nargs - 1;
  64. if (fci.param_count > 0) {
  65. fci.params = safe_emalloc(fci.param_count, sizeof(zval), 0);
  66. }
  67. /* Reverse order to pop values off ctxt stack */
  68. for (i = nargs - 2; i >= 0; i--) {
  69. obj = valuePop(ctxt);
  70. switch (obj->type) {
  71. case XPATH_STRING:
  72. ZVAL_STRING(&fci.params[i], (char *)obj->stringval);
  73. break;
  74. case XPATH_BOOLEAN:
  75. ZVAL_BOOL(&fci.params[i], obj->boolval);
  76. break;
  77. case XPATH_NUMBER:
  78. ZVAL_DOUBLE(&fci.params[i], obj->floatval);
  79. break;
  80. case XPATH_NODESET:
  81. if (type == 1) {
  82. str = (char *)xmlXPathCastToString(obj);
  83. ZVAL_STRING(&fci.params[i], str);
  84. xmlFree(str);
  85. } else if (type == 2) {
  86. int j;
  87. if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
  88. array_init(&fci.params[i]);
  89. for (j = 0; j < obj->nodesetval->nodeNr; j++) {
  90. xmlNodePtr node = obj->nodesetval->nodeTab[j];
  91. zval child;
  92. /* not sure, if we need this... it's copied from xpath.c */
  93. if (node->type == XML_NAMESPACE_DECL) {
  94. xmlNsPtr curns;
  95. xmlNodePtr nsparent;
  96. nsparent = node->_private;
  97. curns = xmlNewNs(NULL, node->name, NULL);
  98. if (node->children) {
  99. curns->prefix = xmlStrdup((xmlChar *) node->children);
  100. }
  101. if (node->children) {
  102. node = xmlNewDocNode(node->doc, NULL, (xmlChar *) node->children, node->name);
  103. } else {
  104. node = xmlNewDocNode(node->doc, NULL, (xmlChar *) "xmlns", node->name);
  105. }
  106. node->type = XML_NAMESPACE_DECL;
  107. node->parent = nsparent;
  108. node->ns = curns;
  109. }
  110. php_dom_create_object(node, &child, &intern->dom);
  111. add_next_index_zval(&fci.params[i], &child);
  112. }
  113. } else {
  114. ZVAL_EMPTY_ARRAY(&fci.params[i]);
  115. }
  116. }
  117. break;
  118. default:
  119. ZVAL_STRING(&fci.params[i], (char *)xmlXPathCastToString(obj));
  120. }
  121. xmlXPathFreeObject(obj);
  122. }
  123. fci.size = sizeof(fci);
  124. obj = valuePop(ctxt);
  125. if (obj->stringval == NULL) {
  126. zend_type_error("Handler name must be a string");
  127. xmlXPathFreeObject(obj);
  128. goto cleanup;
  129. }
  130. ZVAL_STRING(&fci.function_name, (char *) obj->stringval);
  131. xmlXPathFreeObject(obj);
  132. fci.object = NULL;
  133. fci.named_params = NULL;
  134. fci.retval = &retval;
  135. if (!zend_make_callable(&fci.function_name, &callable)) {
  136. zend_throw_error(NULL, "Unable to call handler %s()", ZSTR_VAL(callable));
  137. goto cleanup;
  138. } else if (intern->registerPhpFunctions == 2 && zend_hash_exists(intern->registered_phpfunctions, callable) == 0) {
  139. zend_throw_error(NULL, "Not allowed to call handler '%s()'.", ZSTR_VAL(callable));
  140. goto cleanup;
  141. } else {
  142. result = zend_call_function(&fci, NULL);
  143. if (result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
  144. if (Z_TYPE(retval) == IS_OBJECT && instanceof_function(Z_OBJCE(retval), dom_node_class_entry)) {
  145. xmlNode *nodep;
  146. dom_object *obj;
  147. if (intern->node_list == NULL) {
  148. intern->node_list = zend_new_array(0);
  149. }
  150. Z_ADDREF(retval);
  151. zend_hash_next_index_insert(intern->node_list, &retval);
  152. obj = Z_DOMOBJ_P(&retval);
  153. nodep = dom_object_get_node(obj);
  154. valuePush(ctxt, xmlXPathNewNodeSet(nodep));
  155. } else if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) {
  156. valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(retval) == IS_TRUE));
  157. } else if (Z_TYPE(retval) == IS_OBJECT) {
  158. zend_type_error("A PHP Object cannot be converted to a XPath-string");
  159. return;
  160. } else {
  161. zend_string *str = zval_get_string(&retval);
  162. valuePush(ctxt, xmlXPathNewString((xmlChar *) ZSTR_VAL(str)));
  163. zend_string_release_ex(str, 0);
  164. }
  165. zval_ptr_dtor(&retval);
  166. }
  167. }
  168. cleanup:
  169. zend_string_release_ex(callable, 0);
  170. zval_ptr_dtor_str(&fci.function_name);
  171. if (fci.param_count > 0) {
  172. for (i = 0; i < nargs - 1; i++) {
  173. zval_ptr_dtor(&fci.params[i]);
  174. }
  175. efree(fci.params);
  176. }
  177. }
  178. /* }}} */
  179. static void dom_xpath_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
  180. {
  181. dom_xpath_ext_function_php(ctxt, nargs, 1);
  182. }
  183. /* }}} */
  184. static void dom_xpath_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
  185. {
  186. dom_xpath_ext_function_php(ctxt, nargs, 2);
  187. }
  188. /* }}} */
  189. /* {{{ */
  190. PHP_METHOD(DOMXPath, __construct)
  191. {
  192. zval *doc;
  193. bool register_node_ns = 1;
  194. xmlDocPtr docp = NULL;
  195. dom_object *docobj;
  196. dom_xpath_object *intern;
  197. xmlXPathContextPtr ctx, oldctx;
  198. if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &doc, dom_document_class_entry, &register_node_ns) == FAILURE) {
  199. RETURN_THROWS();
  200. }
  201. DOM_GET_OBJ(docp, doc, xmlDocPtr, docobj);
  202. ctx = xmlXPathNewContext(docp);
  203. if (ctx == NULL) {
  204. php_dom_throw_error(INVALID_STATE_ERR, 1);
  205. RETURN_THROWS();
  206. }
  207. intern = Z_XPATHOBJ_P(ZEND_THIS);
  208. if (intern != NULL) {
  209. oldctx = (xmlXPathContextPtr)intern->dom.ptr;
  210. if (oldctx != NULL) {
  211. php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
  212. xmlXPathFreeContext(oldctx);
  213. }
  214. xmlXPathRegisterFuncNS (ctx, (const xmlChar *) "functionString",
  215. (const xmlChar *) "http://php.net/xpath",
  216. dom_xpath_ext_function_string_php);
  217. xmlXPathRegisterFuncNS (ctx, (const xmlChar *) "function",
  218. (const xmlChar *) "http://php.net/xpath",
  219. dom_xpath_ext_function_object_php);
  220. intern->dom.ptr = ctx;
  221. ctx->userData = (void *)intern;
  222. intern->dom.document = docobj->document;
  223. intern->register_node_ns = register_node_ns;
  224. php_libxml_increment_doc_ref((php_libxml_node_object *) &intern->dom, docp);
  225. }
  226. }
  227. /* }}} end DOMXPath::__construct */
  228. /* {{{ document DOMDocument*/
  229. int dom_xpath_document_read(dom_object *obj, zval *retval)
  230. {
  231. xmlDoc *docp = NULL;
  232. xmlXPathContextPtr ctx = (xmlXPathContextPtr) obj->ptr;
  233. if (ctx) {
  234. docp = (xmlDocPtr) ctx->doc;
  235. }
  236. php_dom_create_object((xmlNodePtr) docp, retval, obj);
  237. return SUCCESS;
  238. }
  239. /* }}} */
  240. /* {{{ registerNodeNamespaces bool*/
  241. static inline dom_xpath_object *php_xpath_obj_from_dom_obj(dom_object *obj) {
  242. return (dom_xpath_object*)((char*)(obj) - XtOffsetOf(dom_xpath_object, dom));
  243. }
  244. int dom_xpath_register_node_ns_read(dom_object *obj, zval *retval)
  245. {
  246. ZVAL_BOOL(retval, php_xpath_obj_from_dom_obj(obj)->register_node_ns);
  247. return SUCCESS;
  248. }
  249. int dom_xpath_register_node_ns_write(dom_object *obj, zval *newval)
  250. {
  251. php_xpath_obj_from_dom_obj(obj)->register_node_ns = zend_is_true(newval);
  252. return SUCCESS;
  253. }
  254. /* }}} */
  255. /* {{{ */
  256. PHP_METHOD(DOMXPath, registerNamespace)
  257. {
  258. zval *id;
  259. xmlXPathContextPtr ctxp;
  260. size_t prefix_len, ns_uri_len;
  261. dom_xpath_object *intern;
  262. unsigned char *prefix, *ns_uri;
  263. id = ZEND_THIS;
  264. if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &prefix, &prefix_len, &ns_uri, &ns_uri_len) == FAILURE) {
  265. RETURN_THROWS();
  266. }
  267. intern = Z_XPATHOBJ_P(id);
  268. ctxp = (xmlXPathContextPtr) intern->dom.ptr;
  269. if (ctxp == NULL) {
  270. zend_throw_error(NULL, "Invalid XPath Context");
  271. RETURN_THROWS();
  272. }
  273. if (xmlXPathRegisterNs(ctxp, prefix, ns_uri) != 0) {
  274. RETURN_FALSE;
  275. }
  276. RETURN_TRUE;
  277. }
  278. /* }}} */
  279. static void dom_xpath_iter(zval *baseobj, dom_object *intern) /* {{{ */
  280. {
  281. dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr;
  282. ZVAL_COPY_VALUE(&mapptr->baseobj_zv, baseobj);
  283. mapptr->nodetype = DOM_NODESET;
  284. }
  285. /* }}} */
  286. static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */
  287. {
  288. zval *id, retval, *context = NULL;
  289. xmlXPathContextPtr ctxp;
  290. xmlNodePtr nodep = NULL;
  291. xmlXPathObjectPtr xpathobjp;
  292. size_t expr_len, nsnbr = 0, xpath_type;
  293. dom_xpath_object *intern;
  294. dom_object *nodeobj;
  295. char *expr;
  296. xmlDoc *docp = NULL;
  297. xmlNsPtr *ns = NULL;
  298. bool register_node_ns;
  299. id = ZEND_THIS;
  300. intern = Z_XPATHOBJ_P(id);
  301. register_node_ns = intern->register_node_ns;
  302. if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|O!b", &expr, &expr_len, &context, dom_node_class_entry, &register_node_ns) == FAILURE) {
  303. RETURN_THROWS();
  304. }
  305. ctxp = (xmlXPathContextPtr) intern->dom.ptr;
  306. if (ctxp == NULL) {
  307. zend_throw_error(NULL, "Invalid XPath Context");
  308. RETURN_THROWS();
  309. }
  310. docp = (xmlDocPtr) ctxp->doc;
  311. if (docp == NULL) {
  312. php_error_docref(NULL, E_WARNING, "Invalid XPath Document Pointer");
  313. RETURN_FALSE;
  314. }
  315. if (context != NULL) {
  316. DOM_GET_OBJ(nodep, context, xmlNodePtr, nodeobj);
  317. }
  318. if (!nodep) {
  319. nodep = xmlDocGetRootElement(docp);
  320. }
  321. if (nodep && docp != nodep->doc) {
  322. zend_throw_error(NULL, "Node from wrong document");
  323. RETURN_THROWS();
  324. }
  325. ctxp->node = nodep;
  326. if (register_node_ns) {
  327. /* Register namespaces in the node */
  328. ns = xmlGetNsList(docp, nodep);
  329. if (ns != NULL) {
  330. while (ns[nsnbr] != NULL)
  331. nsnbr++;
  332. }
  333. }
  334. ctxp->namespaces = ns;
  335. ctxp->nsNr = nsnbr;
  336. xpathobjp = xmlXPathEvalExpression((xmlChar *) expr, ctxp);
  337. ctxp->node = NULL;
  338. if (ns != NULL) {
  339. xmlFree(ns);
  340. ctxp->namespaces = NULL;
  341. ctxp->nsNr = 0;
  342. }
  343. if (! xpathobjp) {
  344. /* TODO Add Warning? */
  345. RETURN_FALSE;
  346. }
  347. if (type == PHP_DOM_XPATH_QUERY) {
  348. xpath_type = XPATH_NODESET;
  349. } else {
  350. xpath_type = xpathobjp->type;
  351. }
  352. switch (xpath_type) {
  353. case XPATH_NODESET:
  354. {
  355. int i;
  356. xmlNodeSetPtr nodesetp;
  357. if (xpathobjp->type == XPATH_NODESET && NULL != (nodesetp = xpathobjp->nodesetval) && nodesetp->nodeNr) {
  358. array_init(&retval);
  359. for (i = 0; i < nodesetp->nodeNr; i++) {
  360. xmlNodePtr node = nodesetp->nodeTab[i];
  361. zval child;
  362. if (node->type == XML_NAMESPACE_DECL) {
  363. xmlNsPtr curns;
  364. xmlNodePtr nsparent;
  365. nsparent = node->_private;
  366. curns = xmlNewNs(NULL, node->name, NULL);
  367. if (node->children) {
  368. curns->prefix = xmlStrdup((xmlChar *) node->children);
  369. }
  370. if (node->children) {
  371. node = xmlNewDocNode(docp, NULL, (xmlChar *) node->children, node->name);
  372. } else {
  373. node = xmlNewDocNode(docp, NULL, (xmlChar *) "xmlns", node->name);
  374. }
  375. node->type = XML_NAMESPACE_DECL;
  376. node->parent = nsparent;
  377. node->ns = curns;
  378. }
  379. php_dom_create_object(node, &child, &intern->dom);
  380. add_next_index_zval(&retval, &child);
  381. }
  382. } else {
  383. ZVAL_EMPTY_ARRAY(&retval);
  384. }
  385. php_dom_create_iterator(return_value, DOM_NODELIST);
  386. nodeobj = Z_DOMOBJ_P(return_value);
  387. dom_xpath_iter(&retval, nodeobj);
  388. break;
  389. }
  390. case XPATH_BOOLEAN:
  391. RETVAL_BOOL(xpathobjp->boolval);
  392. break;
  393. case XPATH_NUMBER:
  394. RETVAL_DOUBLE(xpathobjp->floatval);
  395. break;
  396. case XPATH_STRING:
  397. RETVAL_STRING((char *) xpathobjp->stringval);
  398. break;
  399. default:
  400. RETVAL_NULL();
  401. break;
  402. }
  403. xmlXPathFreeObject(xpathobjp);
  404. }
  405. /* }}} */
  406. /* {{{ */
  407. PHP_METHOD(DOMXPath, query)
  408. {
  409. php_xpath_eval(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_DOM_XPATH_QUERY);
  410. }
  411. /* }}} end dom_xpath_query */
  412. /* {{{ */
  413. PHP_METHOD(DOMXPath, evaluate)
  414. {
  415. php_xpath_eval(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_DOM_XPATH_EVALUATE);
  416. }
  417. /* }}} end dom_xpath_evaluate */
  418. /* {{{ */
  419. PHP_METHOD(DOMXPath, registerPhpFunctions)
  420. {
  421. zval *id = ZEND_THIS;
  422. dom_xpath_object *intern = Z_XPATHOBJ_P(id);
  423. zval *entry, new_string;
  424. zend_string *name = NULL;
  425. HashTable *ht = NULL;
  426. ZEND_PARSE_PARAMETERS_START(0, 1)
  427. Z_PARAM_OPTIONAL
  428. Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(ht, name)
  429. ZEND_PARSE_PARAMETERS_END();
  430. if (ht) {
  431. ZEND_HASH_FOREACH_VAL(ht, entry) {
  432. zend_string *str = zval_get_string(entry);
  433. ZVAL_LONG(&new_string, 1);
  434. zend_hash_update(intern->registered_phpfunctions, str, &new_string);
  435. zend_string_release_ex(str, 0);
  436. } ZEND_HASH_FOREACH_END();
  437. intern->registerPhpFunctions = 2;
  438. } else if (name) {
  439. ZVAL_LONG(&new_string, 1);
  440. zend_hash_update(intern->registered_phpfunctions, name, &new_string);
  441. intern->registerPhpFunctions = 2;
  442. } else {
  443. intern->registerPhpFunctions = 1;
  444. }
  445. }
  446. /* }}} end dom_xpath_register_php_functions */
  447. #endif /* LIBXML_XPATH_ENABLED */
  448. #endif