PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/services/xml_rpc_codec.php

https://github.com/agnesrambaud/yacs
PHP | 494 lines | 200 code | 61 blank | 233 comment | 66 complexity | 6751a78a33456d37380dbd77e2c89436 MD5 | raw file
  1. <?php
  2. /**
  3. * web service encoder and decoder
  4. *
  5. * @see services/codec.php
  6. * @see services/xml_rpc.php
  7. *
  8. * @link http://www.xmlrpc.com/spec XML-RPC Specification
  9. *
  10. * @author Bernard Paques
  11. * @reference
  12. * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
  13. */
  14. Class XML_RPC_Codec extends Codec {
  15. /**
  16. * parse a XML request according to the XML-RPC specification
  17. *
  18. * This script uses the standard XML parser included in the PHP library.
  19. * The objective of the decoding functions is to transform the XML tree into stemming PHP arrays.
  20. *
  21. * Following tags are used for cdata conversion
  22. * - &lt;base64&gt;
  23. * - &lt;boolean&gt;
  24. * - &lt;date&gt;
  25. * - &lt;double&gt;
  26. * - &lt;integer&gt;
  27. * - &lt;string&gt;
  28. *
  29. * Following tags are processed as leaves of the tree:
  30. * - &lt;/value&gt;
  31. * - &lt;/methodName&gt;
  32. *
  33. * Following tags are processed as nodes of the tree
  34. * - &lt;methodCall&gt;: push 'methodCall' (stems 'methodName' and 'params')
  35. * - &lt;/methodCall&gt;: pop 'methodCall'
  36. * - &lt;methodResponse&gt;: push 'methodResponse' (stem 'params' or 'fault')
  37. * - &lt;/methodResponse&gt;: pop 'methodResponse'
  38. * - &lt;fault&gt;: push 'fault' (stems 'faultCode' and 'faultString')
  39. * - &lt;/fault&gt;: pop 'fault'
  40. * - &lt;params&gt;: push 'params', then '-1' (list of anonymous stems)
  41. * - &lt;/params&gt;: pop index, then pop 'params'
  42. * - &lt;value&gt; under an index: increment index (works for &lt;params&gt; and for &lt;array&gt;)
  43. * - &lt;/name&gt;: push cdata (named stem)
  44. * - &lt;/member&gt;: pop cdata
  45. * - &lt;array&gt;: push '-1' (list of anonymous stems)
  46. * - &lt;/array&gt;: pop index
  47. *
  48. * @param string raw data received
  49. * @return array a status code (TRUE is ok) and the parsing result
  50. */
  51. function decode($data) {
  52. global $context;
  53. // create a parser
  54. $parser = xml_parser_create();
  55. xml_set_object($parser, $this);
  56. xml_set_element_handler($parser, 'parse_tag_open', 'parse_tag_close');
  57. xml_set_character_data_handler($parser, 'parse_cdata');
  58. // case is meaningful
  59. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, FALSE);
  60. // parse data
  61. $this->result = array();
  62. $this->stack = array();
  63. if(!xml_parse($parser, $data)) {
  64. if($context['with_debug'] == 'Y')
  65. Logger::remember('services/xml_rpc_codec.php', 'invalid packet to decode', str_replace("\r\n", "\n", $data), 'debug');
  66. return array(FALSE, 'Parsing error: '.xml_error_string(xml_get_error_code($parser))
  67. .' at line '.xml_get_current_line_number($parser));
  68. }
  69. xml_parser_free($parser);
  70. // return parsing result
  71. return array(TRUE, $this->result);
  72. }
  73. /**
  74. * encode some PHP value into XML
  75. *
  76. * This function tries to guess the adequate type to use.
  77. * Following types are supported:
  78. * - array
  79. * - base64 - type has to be set explicitly for the encoding to take place
  80. * - boolean
  81. * - date - type has to be set explicitly to get a &lt;dateTime.iso8601> element
  82. * - double
  83. * - integer
  84. * - struct
  85. * - string - type has to be set explicitly for the encoding to take place
  86. *
  87. * @param mixed the parameter to encode
  88. * @param type, if any
  89. * @return string some XML
  90. */
  91. function encode($parameter, $type='') {
  92. // a date
  93. if($type == 'date') {
  94. if(is_string($parameter))
  95. $parameter = strtotime($parameter);
  96. $items = getdate($parameter);
  97. return '<dateTime.iso8601>'.sprintf('%02d%02d%02dT%02d:%02d:%02d', $items['year'], $items['mon'], $items['mday'], $items['hours'], $items['minutes'], $items['seconds']).'</dateTime.iso8601>';
  98. }
  99. // base 64
  100. if($type == 'base64')
  101. return '<base64>'.base64_encode($parameter).'</base64>';
  102. // a string --also fix possible errors in HTML image references
  103. if($type == 'string')
  104. return '<string>'.htmlspecialchars(trim(preg_replace('|<img (.+?[^/])>|mi', '<img $1 />', $parameter))).'</string>';
  105. // a boolean
  106. if($parameter === true || $parameter === false)
  107. return '<boolean>'.(($parameter) ? '1' : '0').'</boolean>';
  108. // an integer
  109. if(is_integer($parameter))
  110. return '<int>'.$parameter.'</int>';
  111. // a double
  112. if(is_double($parameter))
  113. return '<double>'.$parameter.'</double>';
  114. // an array
  115. if(is_array($parameter)) {
  116. // it's a struct
  117. if(key($parameter)) {
  118. $text = '';
  119. foreach($parameter as $name => $value)
  120. $text .= "\t".'<member><name>'.$name.'</name><value>'.XML_RPC_Codec::encode($value)."</value></member>\n";
  121. return '<struct>'."\n".$text.'</struct>';
  122. // else it's a plain array
  123. } else {
  124. $text = '';
  125. foreach($parameter as $item)
  126. $text .= "\t".'<value>'.XML_RPC_Codec::encode($item)."</value>\n";
  127. if($text)
  128. return '<array><data>'."\n".$text.'</data></array>';
  129. else
  130. return '<array><data /></array>';
  131. }
  132. }
  133. // encode strings
  134. if(is_string($parameter) && ($parameter = trim($parameter)) && (substr($parameter, 0, 1) != '<'))
  135. return '<string>'.htmlspecialchars(trim(preg_replace('|<img (.+?[^/])>|mi', '<img $1 />', $parameter))).'</string>';
  136. // do not encode possibly encoded strings
  137. return $parameter;
  138. }
  139. /**
  140. * build a request according to the XML-RPC specification
  141. *
  142. * Example:
  143. * [php]
  144. * $service = 'search';
  145. * $parameter = array( 'search' => $_REQUEST['search']);
  146. * $result = $codec->export_request($service, $parameter);
  147. * if(!$result[0])
  148. * echo $result[1]; // print error code
  149. * else
  150. * ... // send xml data from $result[1] to the remote web server
  151. * [/php]
  152. *
  153. * Resulting xml:
  154. * [snippet]
  155. * <?xml version="1.0"?>
  156. * <methodCall>
  157. * <methodName>search</methodName>
  158. * <params>
  159. * <param><value><struct>
  160. * <member><name>search</name><value><string>...</string></value></member>
  161. * </struct></value></param>
  162. * </params>
  163. * </methodCall>
  164. * [/snippet]
  165. *
  166. * @param string name of the remote service
  167. * @param mixed transmitted parameters, if any
  168. * @return an array of which the first value indicates call success or failure
  169. * @see services/codec.php
  170. */
  171. function export_request($service, $parameters = NULL) {
  172. // xml header
  173. $xml = '<?xml version="1.0"?>'."\n"
  174. .'<methodCall>'."\n"
  175. .'<methodName>'.$service.'</methodName>'."\n"
  176. .'<params>'."\n";
  177. // encode values only
  178. if(is_array($parameters))
  179. foreach($parameters as $parameter)
  180. $xml .= '<param><value>'.XML_RPC_Codec::encode($parameter)."</value></param>\n";
  181. else
  182. $xml .= '<param><value>'.XML_RPC_Codec::encode($parameters)."</value></param>\n";
  183. // xml tail
  184. $xml .= '</params></methodCall>';
  185. // xml tail
  186. return array(TRUE, $xml);
  187. }
  188. /**
  189. * build a response according to the XML-RPC specification
  190. *
  191. * Example to generate a response with a single string:
  192. * [php]
  193. * $value = $codec->encode('hello world', 'string');
  194. * $result = $codec->export_response($value);
  195. * [/php]
  196. *
  197. * Resulting xml:
  198. * [snippet]
  199. * <?xml version="1.0"?>
  200. * <methodResponse>
  201. * <params>
  202. * <param><value><string>hello world</string></value></param>
  203. * </params>
  204. * </methodResponse>
  205. * [/snippet]
  206. *
  207. * Example to generate an error response:
  208. * [php]
  209. * $values = array('faultCode' => 123, 'faultString' => 'hello world');
  210. * $result = $codec->export_response($values);
  211. * [/php]
  212. *
  213. * Resulting xml:
  214. * [snippet]
  215. * <?xml version="1.0"?>
  216. * <methodResponse>
  217. * <fault>
  218. * <value><struct>
  219. * <member><name>faultCode</name><value><int>...</int></value></member>
  220. * <member><name>faultString</name><value>...</value></member>
  221. * </struct></value>
  222. * </fault>
  223. * </methodResponse>
  224. * [/snippet]
  225. *
  226. * @param mixed transmitted values, if any
  227. * @param string name of the remote service, if any
  228. * @return an array of which the first value indicates call success or failure
  229. * @see services/codec.php
  230. */
  231. function export_response($values=NULL, $service=NULL) {
  232. // request header
  233. $xml = '<methodResponse>'."\n";
  234. // encode the response
  235. if(is_array($values) && isset($values['faultCode']) && $values['faultCode'])
  236. $xml .= '<fault><value>'.XML_RPC_Codec::encode($values).'</value></fault>';
  237. else
  238. $xml .= '<params><param><value>'.XML_RPC_Codec::encode($values).'</value></param></params>';
  239. // request tail
  240. $xml .= '</methodResponse>';
  241. // request tail
  242. return array(TRUE, $xml);
  243. }
  244. /**
  245. * parse a XML request according to the XML-RPC specification
  246. *
  247. * @param string raw data received
  248. * @return an array of which the first value indicates call success or failure
  249. */
  250. function import_request($data) {
  251. // parse the input packet
  252. $result = $this->decode($data);
  253. $status = @$result[0];
  254. $values = @$result[1];
  255. if(!$status)
  256. return array($status, $values);
  257. // ensure we have a valid request
  258. if(!isset($values['methodCall']) || !($values = $values['methodCall']))
  259. return array(FALSE, 'no methodCall');
  260. if(!isset($values['methodName']) || !$values['methodName'])
  261. return array(FALSE, 'no methodName');
  262. // return methodName and params
  263. return array(TRUE, $values);
  264. }
  265. /**
  266. * decode a XML response
  267. *
  268. * @param string the received HTTP body
  269. * @param string the received HTTP headers
  270. * @param mixed the submitted parameters
  271. * @return an array of which the first value indicates call success or failure
  272. */
  273. function import_response($data, $headers=NULL, $parameters=NULL) {
  274. // parse the input packet
  275. $result = $this->decode($data);
  276. $status = @$result[0];
  277. $values = @$result[1];
  278. if(!$status)
  279. return array($status, $values);
  280. // ensure we have a valid response
  281. if(!$values = $values['methodResponse'])
  282. return array(FALSE, 'no methodResponse');
  283. if(isset($values['params']) && is_array($values['params']) && (count($values['params']) == 1))
  284. return array(TRUE, $values['params'][0]);
  285. if(isset($values['params']) && is_array($values['params']))
  286. return array(TRUE, $values['params']);
  287. if(isset($values['fault']) && is_array($values['fault']))
  288. return array(TRUE, $values['fault']);
  289. return array(FALSE, 'no params nor fault');
  290. }
  291. var $stack;
  292. /**
  293. * update the stack on opening tags
  294. *
  295. * Following tags are processed as nodes of the tree
  296. * - &lt;methodCall&gt;: push 'methodCall' (stems 'methodName' and 'params')
  297. * - &lt;methodResponse&gt;: push 'methodResponse' (stem 'params')
  298. * - &lt;fault&gt;: push 'fault' (stems 'faultCode' and 'faultString')
  299. * - &lt;params&gt;: push 'params', then push '-1' (list of anonymous stems)
  300. * - &lt;array&gt;: push '-1' (list of anonymous stems)
  301. * - &lt;value&gt; under an index: increment index (works for &lt;params&gt; and for &lt;array&gt;)
  302. *
  303. */
  304. function parse_tag_open($parser, $tag, $attributes) {
  305. if(preg_match('/^(methodCall|methodResponse|fault)$/', $tag)) {
  306. array_push($this->stack, $tag);
  307. } elseif($tag == 'array') {
  308. array_push($this->stack, -1);
  309. } elseif($tag == 'params') {
  310. array_push($this->stack, 'params');
  311. array_push($this->stack, -1);
  312. } elseif($tag == 'value') {
  313. // if we are in an array, increment the element index
  314. if(is_int($this->stack[count($this->stack)-1])) {
  315. $index = array_pop($this->stack);
  316. array_push($this->stack, ++$index);
  317. }
  318. }
  319. }
  320. var $cdata;
  321. /**
  322. * capture cdata for further processing
  323. */
  324. function parse_cdata($parser, $cdata) {
  325. // preserve embedded hard coded new lines
  326. if(isset($this->cdata))
  327. $this->cdata = ltrim($this->cdata.$cdata);
  328. else
  329. $this->cdata = ltrim($cdata);
  330. }
  331. var $result;
  332. var $name;
  333. /**
  334. * update the stack on closing tags
  335. *
  336. * The value of cdata is converted explicitly on following tags:
  337. * - &lt;/base64&gt;
  338. * - &lt;/boolean&gt;
  339. * - &lt;/date&gt;
  340. * - &lt;/double&gt;
  341. * - &lt;/integer&gt;
  342. * - &lt;/string&gt;
  343. *
  344. * The result is updated on following tags:
  345. * - &lt;/value&gt;
  346. * - &lt;/methodName&gt;
  347. *
  348. * The stack is updated on following tags:
  349. * - &lt;/methodCall&gt;: pop 'methodCall'
  350. * - &lt;/methodResponse&gt;: pop 'methodResponse'
  351. * - &lt;/fault&gt;: pop 'fault'
  352. * - &lt;/params&gt;: pop index, then pop 'params'
  353. * - &lt;/name&gt;: push cdata (named stem)
  354. * - &lt;/member&gt;: pop cdata
  355. * - &lt;/array&gt;: pop index
  356. *
  357. */
  358. function parse_tag_close($parser, $tag) {
  359. global $context;
  360. if(isset($this->cdata) && is_string($this->cdata))
  361. $this->cdata = trim($this->cdata);
  362. // expand the stack
  363. if($tag == 'name') {
  364. array_push($this->stack, $this->cdata);
  365. unset($this->cdata);
  366. return;
  367. }
  368. // convert cdata
  369. switch($tag) {
  370. case 'base64':
  371. $this->cdata = base64_decode($this->cdata);
  372. return;
  373. case 'boolean':
  374. if(preg_match('/^(1|true)$/i', $this->cdata))
  375. $this->cdata = TRUE;
  376. else
  377. $this->cdata = FALSE;
  378. return;
  379. case 'dateTime.iso8601':
  380. $value = (string)$this->cdata;
  381. $year = (int)substr($value, 0, 4);
  382. $month = (int)substr($value, 4, 2);
  383. $day = (int)substr($value, 6, 2);
  384. $hour = (int)substr($value, 9, 2);
  385. $minute = (int)substr($value, 12, 2);
  386. $second = (int)substr($value, 15, 2);
  387. $this->cdata = mktime($hour, $minute, $second, $month, $day, $year );
  388. return;
  389. case 'double':
  390. $this->cdata = (double)$this->cdata;
  391. return;
  392. case 'i4':
  393. case 'int':
  394. $this->cdata = (int)$this->cdata;
  395. return;
  396. case 'string':
  397. // transcode to our internal charset, if unicode
  398. if($context['charset'] == 'utf-8')
  399. $this->cdata = utf8::encode($this->cdata);
  400. return;
  401. }
  402. // sanity check
  403. if(!isset($this->cdata))
  404. $this->cdata = '';
  405. // update the result tree
  406. if(($tag == 'value') || ($tag == 'methodName')){
  407. // browse containers
  408. $here =& $this->result;
  409. $container_id = NULL;
  410. foreach($this->stack as $container_id) {
  411. if(!isset($here[$container_id]))
  412. $here[$container_id] = array();
  413. $here =& $here[$container_id];
  414. }
  415. // update a leaf
  416. if($tag == 'value' && !@count($here)) {
  417. $here = $this->cdata;
  418. unset($this->cdata);
  419. } elseif($tag == 'methodName') {
  420. $here[$tag] = $this->cdata;
  421. unset($this->cdata);
  422. }
  423. }
  424. // update the stack
  425. if(preg_match('/^(array|fault|member|methodCall|methodResponse)$/', $tag)) {
  426. array_pop($this->stack);
  427. } elseif($tag == 'params') {
  428. array_pop($this->stack);
  429. array_pop($this->stack);
  430. }
  431. }
  432. }
  433. ?>