Parser.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more |
  17. // | Authors: Dietrich Ayala <dietrich@ganx4.com> Original Author |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: Parser.php,v 1.33 2003/04/20 01:05:34 shane Exp $
  21. //
  22. require_once 'SOAP/Base.php';
  23. require_once 'SOAP/Value.php';
  24. /**
  25. * SOAP Parser
  26. * this class is used by SOAP::Message and SOAP::Server to parse soap packets
  27. *
  28. * originaly based on SOAPx4 by Dietrich Ayala http://dietrich.ganx4.com/soapx4
  29. *
  30. * @access public
  31. * @version $Id: Parser.php,v 1.33 2003/04/20 01:05:34 shane Exp $
  32. * @package SOAP::Parser
  33. * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
  34. * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
  35. */
  36. class SOAP_Parser extends SOAP_Base
  37. {
  38. var $status = '';
  39. var $position = 0;
  40. var $pos_stat = 0;
  41. var $depth = 0;
  42. var $default_namespace = '';
  43. var $message = array();
  44. var $depth_array = array();
  45. var $previous_element = '';
  46. var $soapresponse = NULL;
  47. var $soapheaders = NULL;
  48. var $parent = 0;
  49. var $root_struct_name = array();
  50. var $header_struct_name = array();
  51. var $curent_root_struct_name = '';
  52. var $entities = array ( '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', "'" => '&apos;', '"' => '&quot;' );
  53. var $root_struct = array();
  54. var $header_struct = array();
  55. var $curent_root_struct = 0;
  56. var $references = array();
  57. var $need_references = array();
  58. var $XMLSchemaVersion;
  59. var $bodyDepth; // used to handle non-root elements before root body element
  60. /**
  61. * SOAP_Parser constructor
  62. *
  63. * @param string xml content
  64. * @param string xml character encoding, defaults to 'UTF-8'
  65. */
  66. function SOAP_Parser(&$xml, $encoding = SOAP_DEFAULT_ENCODING, $attachments=NULL)
  67. {
  68. parent::SOAP_Base('Parser');
  69. $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION);
  70. $this->attachments = $attachments;
  71. // check the xml tag for encoding
  72. if (preg_match('/<\?xml[^>]+encoding\s*?=\s*?(\'([^\']*)\'|"([^"]*)")[^>]*?[\?]>/',$xml,$m)) {
  73. $encoding = strtoupper($m[2]?$m[2]:$m[3]);
  74. }
  75. // determines where in the message we are (envelope,header,body,method)
  76. // Check whether content has been read.
  77. if (!empty($xml)) {
  78. // prepare the xml parser
  79. $parser = xml_parser_create($encoding);
  80. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  81. xml_set_object($parser, $this);
  82. xml_set_element_handler($parser, 'startElement','endElement');
  83. xml_set_character_data_handler($parser,'characterData');
  84. // some lame soap implementations add null bytes at the
  85. // end of the soap stream, and expat choaks on that
  86. if ($xml[strlen($xml)-1]==0)
  87. $xml = trim($xml);
  88. // Parse the XML file.
  89. if (!xml_parse($parser,$xml,true)) {
  90. $err = sprintf('XML error on line %d col %d byte %d %s',
  91. xml_get_current_line_number($parser),
  92. xml_get_current_column_number($parser),
  93. xml_get_current_byte_index($parser),
  94. xml_error_string(xml_get_error_code($parser)));
  95. $this->_raiseSoapFault($err,htmlspecialchars($xml));
  96. }
  97. xml_parser_free($parser);
  98. }
  99. }
  100. /**
  101. * domulti
  102. * recurse to build a multi-dim array, used by buildResponse
  103. *
  104. * @access private
  105. */
  106. function domulti($d, &$ar, &$r, &$v, $ad=0)
  107. {
  108. if ($d) {
  109. $this->domulti($d-1, $ar, $r[$ar[$ad]], $v, $ad+1);
  110. } else {
  111. $r = $v;
  112. }
  113. }
  114. /**
  115. * buildResponse
  116. * loop through msg, building response structures
  117. *
  118. * @param int position
  119. * @return SOAP_Value
  120. * @access private
  121. */
  122. function &buildResponse($pos)
  123. {
  124. $response = NULL;
  125. if (isset($this->message[$pos]['children'])) {
  126. $children = explode('|',$this->message[$pos]['children']);
  127. foreach ($children as $c => $child_pos) {
  128. if ($this->message[$child_pos]['type'] != NULL) {
  129. $response[] =& $this->buildResponse($child_pos);
  130. }
  131. }
  132. if (array_key_exists('arraySize',$this->message[$pos])) {
  133. $ardepth = count($this->message[$pos]['arraySize']);
  134. if ($ardepth > 1) {
  135. $ar = array_pad(array(), $ardepth, 0);
  136. if (array_key_exists('arrayOffset',$this->message[$pos])) {
  137. for ($i = 0; $i < $ardepth; $i++) {
  138. $ar[$i] += $this->message[$pos]['arrayOffset'][$i];
  139. }
  140. }
  141. $elc = count($response);
  142. for ($i = 0; $i < $elc; $i++) {
  143. // recurse to build a multi-dim array
  144. $this->domulti($ardepth, $ar, $newresp, $response[$i]);
  145. # increment our array pointers
  146. $ad = $ardepth - 1;
  147. $ar[$ad]++;
  148. while ($ad > 0 && $ar[$ad] >= $this->message[$pos]['arraySize'][$ad]) {
  149. $ar[$ad] = 0;
  150. $ad--;
  151. $ar[$ad]++;
  152. }
  153. }
  154. $response = $newresp;
  155. } else if (isset($this->message[$pos]['arrayOffset']) &&
  156. $this->message[$pos]['arrayOffset'][0] > 0) {
  157. # check for padding
  158. $pad = $this->message[$pos]['arrayOffset'][0]+count($response)*-1;
  159. $response = array_pad($response,$pad,NULL);
  160. }
  161. }
  162. }
  163. // build attributes
  164. $attrs = array();
  165. foreach ($this->message[$pos]['attrs'] as $atn => $atv) {
  166. if (!strstr($atn, 'xmlns') &&
  167. !strpos($atn, ':')) $attrs[$atn]=$atv;
  168. }
  169. // add current node's value
  170. if ($response) {
  171. $nqn =& new Qname($this->message[$pos]['name'],$this->message[$pos]['namespace']);
  172. $tqn =& new Qname($this->message[$pos]['type'],$this->message[$pos]['type_namespace']);
  173. $response =& new SOAP_Value($nqn->fqn(), $tqn->fqn(), $response, $attrs);
  174. if (isset($this->message[$pos]['arrayType'])) $response->arrayType = $this->message[$pos]['arrayType'];
  175. } else {
  176. $nqn =& new Qname($this->message[$pos]['name'],$this->message[$pos]['namespace']);
  177. $tqn =& new Qname($this->message[$pos]['type'],$this->message[$pos]['type_namespace']);
  178. $response =& new SOAP_Value($nqn->fqn(), $tqn->fqn(), $this->message[$pos]['cdata'], $attrs);
  179. }
  180. // handle header attribute that we need
  181. if (array_key_exists('actor',$this->message[$pos])) {
  182. $response->actor = $this->message[$pos]['actor'];
  183. }
  184. if (array_key_exists('mustUnderstand',$this->message[$pos])) {
  185. $response->mustunderstand = $this->message[$pos]['mustUnderstand'];
  186. }
  187. return $response;
  188. }
  189. /**
  190. * startElement
  191. * start-element handler used with xml parser
  192. *
  193. * @access private
  194. */
  195. function startElement($parser, $name, $attrs)
  196. {
  197. // position in a total number of elements, starting from 0
  198. // update class level pos
  199. $pos = $this->position++;
  200. // and set mine
  201. $this->message[$pos] = array();
  202. $this->message[$pos]['type'] = '';
  203. $this->message[$pos]['type_namespace'] = '';
  204. $this->message[$pos]['cdata'] = '';
  205. $this->message[$pos]['pos'] = $pos;
  206. $this->message[$pos]['id'] = '';
  207. // parent/child/depth determinations
  208. // depth = how many levels removed from root?
  209. // set mine as current global depth and increment global depth value
  210. $this->message[$pos]['depth'] = $this->depth++;
  211. // else add self as child to whoever the current parent is
  212. if ($pos != 0) {
  213. if (isset($this->message[$this->parent]['children']))
  214. $this->message[$this->parent]['children'] .= "|$pos";
  215. else
  216. $this->message[$this->parent]['children'] = $pos;
  217. }
  218. // set my parent
  219. $this->message[$pos]['parent'] = $this->parent;
  220. // set self as current value for this depth
  221. $this->depth_array[$this->depth] = $pos;
  222. // set self as current parent
  223. $this->parent = $pos;
  224. $qname =& new QName($name);
  225. // set status
  226. if (strcasecmp('envelope',$qname->name)==0) {
  227. $this->status = 'envelope';
  228. } elseif (strcasecmp('header',$qname->name)==0) {
  229. $this->status = 'header';
  230. $this->header_struct_name[] = $this->curent_root_struct_name = $qname->name;
  231. $this->header_struct[] = $this->curent_root_struct = $pos;
  232. $this->message[$pos]['type'] = 'Struct';
  233. } elseif (strcasecmp('body',$qname->name)==0) {
  234. $this->status = 'body';
  235. $this->bodyDepth = $this->depth;
  236. // set method
  237. } elseif ($this->status == 'body') {
  238. // is this element allowed to be a root?
  239. // XXX this needs to be optimized, we loop through attrs twice now
  240. $can_root = $this->depth == $this->bodyDepth + 1;
  241. if ($can_root) {
  242. foreach ($attrs as $key => $value) {
  243. if (stristr($key, ':root') && !$value) {
  244. $can_root = FALSE;
  245. }
  246. }
  247. }
  248. if ($can_root) {
  249. $this->status = 'method';
  250. $this->root_struct_name[] = $this->curent_root_struct_name = $qname->name;
  251. $this->root_struct[] = $this->curent_root_struct = $pos;
  252. $this->message[$pos]['type'] = 'Struct';
  253. }
  254. }
  255. // set my status
  256. $this->message[$pos]['status'] = $this->status;
  257. // set name
  258. $this->message[$pos]['name'] = htmlspecialchars($qname->name);
  259. // set attrs
  260. $this->message[$pos]['attrs'] = $attrs;
  261. // loop through atts, logging ns and type declarations
  262. foreach ($attrs as $key => $value) {
  263. // if ns declarations, add to class level array of valid namespaces
  264. $kqn =& new QName($key);
  265. if ($kqn->ns == 'xmlns') {
  266. $prefix = $kqn->name;
  267. if (in_array($value, $this->_XMLSchema)) {
  268. $this->_setSchemaVersion($value);
  269. }
  270. $this->_namespaces[$value] = $prefix;
  271. // set method namespace
  272. # XXX unused???
  273. #if ($name == $this->curent_root_struct_name) {
  274. # $this->methodNamespace = $value;
  275. #}
  276. } elseif ($key == 'xmlns') {
  277. $qname->ns = $this->_getNamespacePrefix($value);
  278. $qname->namespace = $value;
  279. } elseif ($kqn->name == 'actor') {
  280. $this->message[$pos]['actor'] = $value;
  281. } elseif ($kqn->name == 'mustUnderstand') {
  282. $this->message[$pos]['mustUnderstand'] = $value;
  283. // if it's a type declaration, set type
  284. } elseif ($kqn->name == 'type') {
  285. $vqn =& new QName($value);
  286. $this->message[$pos]['type'] = $vqn->name;
  287. $this->message[$pos]['type_namespace'] = $this->_getNamespaceForPrefix($vqn->ns);
  288. #print "set type for {$this->message[$pos]['name']} to {$this->message[$pos]['type']}\n";
  289. // should do something here with the namespace of specified type?
  290. } elseif ($kqn->name == 'arrayType') {
  291. $vqn =& new QName($value);
  292. $this->message[$pos]['type'] = 'Array';
  293. #$type = $vqn->name;
  294. if (isset($vqn->arraySize))
  295. $this->message[$pos]['arraySize'] = $vqn->arraySize;
  296. #$sa = strpos($type,'[');
  297. #if ($sa > 0) {
  298. # $this->message[$pos]['arraySize'] = split(',',substr($type,$sa+1, strlen($type)-$sa-2));
  299. # $type = substr($type, 0, $sa);
  300. #}
  301. $this->message[$pos]['arrayType'] = $vqn->name;
  302. } elseif ($kqn->name == 'offset') {
  303. $this->message[$pos]['arrayOffset'] = split(',',substr($value, 1, strlen($value)-2));
  304. } elseif ($kqn->name == 'id') {
  305. # save id to reference array
  306. $this->references[$value] = $pos;
  307. $this->message[$pos]['id'] = $value;
  308. } elseif ($kqn->name == 'href') {
  309. if ($value[0] == '#') {
  310. $ref = substr($value, 1);
  311. if (array_key_exists($ref,$this->references)) {
  312. # cdata, type, inval
  313. $ref_pos = $this->references[$ref];
  314. $this->message[$pos]['children'] = &$this->message[$ref_pos]['children'];
  315. $this->message[$pos]['cdata'] = &$this->message[$ref_pos]['cdata'];
  316. $this->message[$pos]['type'] = &$this->message[$ref_pos]['type'];
  317. $this->message[$pos]['arraySize'] = &$this->message[$ref_pos]['arraySize'];
  318. $this->message[$pos]['arrayType'] = &$this->message[$ref_pos]['arrayType'];
  319. } else {
  320. # reverse reference, store in 'need reference'
  321. if (!isset($this->need_references[$ref])) $this->need_references[$ref] = array();
  322. $this->need_references[$ref][] = $pos;
  323. }
  324. } else if (isset($this->attachments[$value])) {
  325. $this->message[$pos]['cdata'] = $this->attachments[$value];
  326. }
  327. }
  328. }
  329. // see if namespace is defined in tag
  330. if (array_key_exists('xmlns:'.$qname->ns,$attrs)) {
  331. $namespace = $attrs['xmlns:'.$qname->ns];
  332. } else if ($qname->ns && !$qname->namespace) {
  333. $namespace = $this->_getNamespaceForPrefix($qname->ns);
  334. } else {
  335. // get namespace
  336. $namespace = $qname->namespace?$qname->namespace:$this->default_namespace;
  337. }
  338. $this->message[$pos]['namespace'] = $namespace;
  339. $this->default_namespace = $namespace;
  340. }
  341. /**
  342. * endElement
  343. * end-element handler used with xml parser
  344. *
  345. * @access private
  346. */
  347. function endElement($parser, $name)
  348. {
  349. // position of current element is equal to the last value left in depth_array for my depth
  350. $pos = $this->depth_array[$this->depth];
  351. // bring depth down a notch
  352. $this->depth--;
  353. $qname =& new QName($name);
  354. // get type if not explicitly declared in an xsi:type attribute
  355. // XXX check on integrating wsdl validation here
  356. if ($this->message[$pos]['type'] == '') {
  357. if (isset($this->message[$pos]['children'])) {
  358. /* this is slow, need to look at some faster method
  359. $children = explode('|',$this->message[$pos]['children']);
  360. if (count($children) > 2 &&
  361. $this->message[$children[1]]['name'] == $this->message[$children[2]]['name']) {
  362. $this->message[$pos]['type'] = 'Array';
  363. } else {
  364. $this->message[$pos]['type'] = 'Struct';
  365. }*/
  366. $this->message[$pos]['type'] = 'Struct';
  367. } else {
  368. $parent = $this->message[$pos]['parent'];
  369. if ($this->message[$parent]['type'] == 'Array' &&
  370. array_key_exists('arrayType', $this->message[$parent])) {
  371. $this->message[$pos]['type'] = $this->message[$parent]['arrayType'];
  372. } else {
  373. $this->message[$pos]['type'] = 'string';
  374. }
  375. }
  376. }
  377. // if tag we are currently closing is the method wrapper
  378. if ($pos == $this->curent_root_struct) {
  379. $this->status = 'body';
  380. } elseif ($qname->name == 'Body' || $qname->name == 'Header') {
  381. $this->status = 'envelope';
  382. }
  383. // set parent back to my parent
  384. $this->parent = $this->message[$pos]['parent'];
  385. # handle any reverse references now
  386. $idref = $this->message[$pos]['id'];
  387. if ($idref != '' && array_key_exists($idref,$this->need_references)) {
  388. foreach ($this->need_references[$idref] as $ref_pos) {
  389. #XXX is this stuff there already?
  390. $this->message[$ref_pos]['children'] = &$this->message[$pos]['children'];
  391. $this->message[$ref_pos]['cdata'] = &$this->message[$pos]['cdata'];
  392. $this->message[$ref_pos]['type'] = &$this->message[$pos]['type'];
  393. $this->message[$ref_pos]['arraySize'] = &$this->message[$pos]['arraySize'];
  394. $this->message[$ref_pos]['arrayType'] = &$this->message[$pos]['arrayType'];
  395. }
  396. # wipe out our waiting list
  397. # $this->need_references[$idref] = array();
  398. }
  399. }
  400. /**
  401. * characterData
  402. * element content handler used with xml parser
  403. *
  404. * @access private
  405. */
  406. function characterData($parser, $data)
  407. {
  408. $pos = $this->depth_array[$this->depth];
  409. if (isset($this->message[$pos]['cdata']))
  410. $this->message[$pos]['cdata'] .= $data;
  411. else
  412. $this->message[$pos]['cdata'] = $data;
  413. }
  414. /**
  415. * getResponse
  416. *
  417. * returns an array of responses
  418. * after parsing a soap message, use this to get the response
  419. *
  420. * @return array
  421. * @access public
  422. */
  423. function &getResponse()
  424. {
  425. if (isset($this->root_struct[0]) &&
  426. $this->root_struct[0]) {
  427. return $this->buildResponse($this->root_struct[0]);
  428. }
  429. return $this->_raiseSoapFault("couldn't build response");
  430. }
  431. /**
  432. * getHeaders
  433. *
  434. * returns an array of header responses
  435. * after parsing a soap message, use this to get the response
  436. *
  437. * @return array
  438. * @access public
  439. */
  440. function &getHeaders()
  441. {
  442. if (isset($this->header_struct[0]) &&
  443. $this->header_struct[0]) {
  444. return $this->buildResponse($this->header_struct[0]);
  445. }
  446. // we don't fault if there are no headers
  447. // that can be handled by the app if necessary
  448. return NULL;
  449. }
  450. /**
  451. * decodeEntities
  452. *
  453. * removes entities from text
  454. *
  455. * @param string
  456. * @return string
  457. * @access private
  458. */
  459. function decodeEntities($text)
  460. {
  461. $trans_tbl = array_flip($this->entities);
  462. return strtr($text, $trans_tbl);
  463. }
  464. }
  465. ?>