PageRenderTime 75ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/htdocs/wp-includes/class-IXR.php

https://bitbucket.org/dkrzos/phc
PHP | 1070 lines | 819 code | 88 blank | 163 comment | 106 complexity | 995e6554bf0e0c16b516bae9fe452e86 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * IXR - The Incutio XML-RPC Library
  4. *
  5. * Copyright (c) 2010, Incutio Ltd.
  6. * All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions are met:
  10. *
  11. * - Redistributions of source code must retain the above copyright notice,
  12. * this list of conditions and the following disclaimer.
  13. * - Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. * - Neither the name of Incutio Ltd. nor the names of its contributors
  17. * may be used to endorse or promote products derived from this software
  18. * without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  21. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  22. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  28. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  30. * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. *
  32. * @package IXR
  33. * @since 1.5
  34. *
  35. * @copyright Incutio Ltd 2010 (http://www.incutio.com)
  36. * @version 1.7.4 7th September 2010
  37. * @author Simon Willison
  38. * @link http://scripts.incutio.com/xmlrpc/ Site/manual
  39. * @license http://www.opensource.org/licenses/bsd-license.php BSD
  40. */
  41. /**
  42. * IXR_Value
  43. *
  44. * @package IXR
  45. * @since 1.5
  46. */
  47. class IXR_Value {
  48. var $data;
  49. var $type;
  50. function IXR_Value($data, $type = false)
  51. {
  52. $this->data = $data;
  53. if (!$type) {
  54. $type = $this->calculateType();
  55. }
  56. $this->type = $type;
  57. if ($type == 'struct') {
  58. // Turn all the values in the array in to new IXR_Value objects
  59. foreach ($this->data as $key => $value) {
  60. $this->data[$key] = new IXR_Value($value);
  61. }
  62. }
  63. if ($type == 'array') {
  64. for ($i = 0, $j = count($this->data); $i < $j; $i++) {
  65. $this->data[$i] = new IXR_Value($this->data[$i]);
  66. }
  67. }
  68. }
  69. function calculateType()
  70. {
  71. if ($this->data === true || $this->data === false) {
  72. return 'boolean';
  73. }
  74. if (is_integer($this->data)) {
  75. return 'int';
  76. }
  77. if (is_double($this->data)) {
  78. return 'double';
  79. }
  80. // Deal with IXR object types base64 and date
  81. if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
  82. return 'date';
  83. }
  84. if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
  85. return 'base64';
  86. }
  87. // If it is a normal PHP object convert it in to a struct
  88. if (is_object($this->data)) {
  89. $this->data = get_object_vars($this->data);
  90. return 'struct';
  91. }
  92. if (!is_array($this->data)) {
  93. return 'string';
  94. }
  95. // We have an array - is it an array or a struct?
  96. if ($this->isStruct($this->data)) {
  97. return 'struct';
  98. } else {
  99. return 'array';
  100. }
  101. }
  102. function getXml()
  103. {
  104. // Return XML for this value
  105. switch ($this->type) {
  106. case 'boolean':
  107. return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
  108. break;
  109. case 'int':
  110. return '<int>'.$this->data.'</int>';
  111. break;
  112. case 'double':
  113. return '<double>'.$this->data.'</double>';
  114. break;
  115. case 'string':
  116. return '<string>'.htmlspecialchars($this->data).'</string>';
  117. break;
  118. case 'array':
  119. $return = '<array><data>'."\n";
  120. foreach ($this->data as $item) {
  121. $return .= ' <value>'.$item->getXml()."</value>\n";
  122. }
  123. $return .= '</data></array>';
  124. return $return;
  125. break;
  126. case 'struct':
  127. $return = '<struct>'."\n";
  128. foreach ($this->data as $name => $value) {
  129. $name = htmlspecialchars($name);
  130. $return .= " <member><name>$name</name><value>";
  131. $return .= $value->getXml()."</value></member>\n";
  132. }
  133. $return .= '</struct>';
  134. return $return;
  135. break;
  136. case 'date':
  137. case 'base64':
  138. return $this->data->getXml();
  139. break;
  140. }
  141. return false;
  142. }
  143. /**
  144. * Checks whether or not the supplied array is a struct or not
  145. *
  146. * @param unknown_type $array
  147. * @return boolean
  148. */
  149. function isStruct($array)
  150. {
  151. $expected = 0;
  152. foreach ($array as $key => $value) {
  153. if ((string)$key != (string)$expected) {
  154. return true;
  155. }
  156. $expected++;
  157. }
  158. return false;
  159. }
  160. }
  161. /**
  162. * IXR_MESSAGE
  163. *
  164. * @package IXR
  165. * @since 1.5
  166. *
  167. */
  168. class IXR_Message
  169. {
  170. var $message;
  171. var $messageType; // methodCall / methodResponse / fault
  172. var $faultCode;
  173. var $faultString;
  174. var $methodName;
  175. var $params;
  176. // Current variable stacks
  177. var $_arraystructs = array(); // The stack used to keep track of the current array/struct
  178. var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
  179. var $_currentStructName = array(); // A stack as well
  180. var $_param;
  181. var $_value;
  182. var $_currentTag;
  183. var $_currentTagContents;
  184. // The XML parser
  185. var $_parser;
  186. function IXR_Message($message)
  187. {
  188. $this->message =& $message;
  189. }
  190. function parse()
  191. {
  192. // first remove the XML declaration
  193. // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
  194. $header = preg_replace( '/<\?xml.*?\?'.'>/', '', substr($this->message, 0, 100), 1);
  195. $this->message = substr_replace($this->message, $header, 0, 100);
  196. if (trim($this->message) == '') {
  197. return false;
  198. }
  199. $this->_parser = xml_parser_create();
  200. // Set XML parser to take the case of tags in to account
  201. xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
  202. // Set XML parser callback functions
  203. xml_set_object($this->_parser, $this);
  204. xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
  205. xml_set_character_data_handler($this->_parser, 'cdata');
  206. $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
  207. $final = false;
  208. do {
  209. if (strlen($this->message) <= $chunk_size) {
  210. $final = true;
  211. }
  212. $part = substr($this->message, 0, $chunk_size);
  213. $this->message = substr($this->message, $chunk_size);
  214. if (!xml_parse($this->_parser, $part, $final)) {
  215. return false;
  216. }
  217. if ($final) {
  218. break;
  219. }
  220. } while (true);
  221. xml_parser_free($this->_parser);
  222. // Grab the error messages, if any
  223. if ($this->messageType == 'fault') {
  224. $this->faultCode = $this->params[0]['faultCode'];
  225. $this->faultString = $this->params[0]['faultString'];
  226. }
  227. return true;
  228. }
  229. function tag_open($parser, $tag, $attr)
  230. {
  231. $this->_currentTagContents = '';
  232. $this->currentTag = $tag;
  233. switch($tag) {
  234. case 'methodCall':
  235. case 'methodResponse':
  236. case 'fault':
  237. $this->messageType = $tag;
  238. break;
  239. /* Deal with stacks of arrays and structs */
  240. case 'data': // data is to all intents and puposes more interesting than array
  241. $this->_arraystructstypes[] = 'array';
  242. $this->_arraystructs[] = array();
  243. break;
  244. case 'struct':
  245. $this->_arraystructstypes[] = 'struct';
  246. $this->_arraystructs[] = array();
  247. break;
  248. }
  249. }
  250. function cdata($parser, $cdata)
  251. {
  252. $this->_currentTagContents .= $cdata;
  253. }
  254. function tag_close($parser, $tag)
  255. {
  256. $valueFlag = false;
  257. switch($tag) {
  258. case 'int':
  259. case 'i4':
  260. $value = (int)trim($this->_currentTagContents);
  261. $valueFlag = true;
  262. break;
  263. case 'double':
  264. $value = (double)trim($this->_currentTagContents);
  265. $valueFlag = true;
  266. break;
  267. case 'string':
  268. $value = (string)trim($this->_currentTagContents);
  269. $valueFlag = true;
  270. break;
  271. case 'dateTime.iso8601':
  272. $value = new IXR_Date(trim($this->_currentTagContents));
  273. $valueFlag = true;
  274. break;
  275. case 'value':
  276. // "If no type is indicated, the type is string."
  277. if (trim($this->_currentTagContents) != '') {
  278. $value = (string)$this->_currentTagContents;
  279. $valueFlag = true;
  280. }
  281. break;
  282. case 'boolean':
  283. $value = (boolean)trim($this->_currentTagContents);
  284. $valueFlag = true;
  285. break;
  286. case 'base64':
  287. $value = base64_decode($this->_currentTagContents);
  288. $valueFlag = true;
  289. break;
  290. /* Deal with stacks of arrays and structs */
  291. case 'data':
  292. case 'struct':
  293. $value = array_pop($this->_arraystructs);
  294. array_pop($this->_arraystructstypes);
  295. $valueFlag = true;
  296. break;
  297. case 'member':
  298. array_pop($this->_currentStructName);
  299. break;
  300. case 'name':
  301. $this->_currentStructName[] = trim($this->_currentTagContents);
  302. break;
  303. case 'methodName':
  304. $this->methodName = trim($this->_currentTagContents);
  305. break;
  306. }
  307. if ($valueFlag) {
  308. if (count($this->_arraystructs) > 0) {
  309. // Add value to struct or array
  310. if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
  311. // Add to struct
  312. $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
  313. } else {
  314. // Add to array
  315. $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
  316. }
  317. } else {
  318. // Just add as a paramater
  319. $this->params[] = $value;
  320. }
  321. }
  322. $this->_currentTagContents = '';
  323. }
  324. }
  325. /**
  326. * IXR_Server
  327. *
  328. * @package IXR
  329. * @since 1.5
  330. */
  331. class IXR_Server
  332. {
  333. var $data;
  334. var $callbacks = array();
  335. var $message;
  336. var $capabilities;
  337. function IXR_Server($callbacks = false, $data = false, $wait = false)
  338. {
  339. $this->setCapabilities();
  340. if ($callbacks) {
  341. $this->callbacks = $callbacks;
  342. }
  343. $this->setCallbacks();
  344. if (!$wait) {
  345. $this->serve($data);
  346. }
  347. }
  348. function serve($data = false)
  349. {
  350. if (!$data) {
  351. if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
  352. header('Content-Type: text/plain'); // merged from WP #9093
  353. die('XML-RPC server accepts POST requests only.');
  354. }
  355. global $HTTP_RAW_POST_DATA;
  356. if (empty($HTTP_RAW_POST_DATA)) {
  357. // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
  358. $data = file_get_contents('php://input');
  359. } else {
  360. $data =& $HTTP_RAW_POST_DATA;
  361. }
  362. }
  363. $this->message = new IXR_Message($data);
  364. if (!$this->message->parse()) {
  365. $this->error(-32700, 'parse error. not well formed');
  366. }
  367. if ($this->message->messageType != 'methodCall') {
  368. $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
  369. }
  370. $result = $this->call($this->message->methodName, $this->message->params);
  371. // Is the result an error?
  372. if (is_a($result, 'IXR_Error')) {
  373. $this->error($result);
  374. }
  375. // Encode the result
  376. $r = new IXR_Value($result);
  377. $resultxml = $r->getXml();
  378. // Create the XML
  379. $xml = <<<EOD
  380. <methodResponse>
  381. <params>
  382. <param>
  383. <value>
  384. $resultxml
  385. </value>
  386. </param>
  387. </params>
  388. </methodResponse>
  389. EOD;
  390. // Send it
  391. $this->output($xml);
  392. }
  393. function call($methodname, $args)
  394. {
  395. if (!$this->hasMethod($methodname)) {
  396. return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
  397. }
  398. $method = $this->callbacks[$methodname];
  399. // Perform the callback and send the response
  400. if (count($args) == 1) {
  401. // If only one paramater just send that instead of the whole array
  402. $args = $args[0];
  403. }
  404. // Are we dealing with a function or a method?
  405. if (is_string($method) && substr($method, 0, 5) == 'this:') {
  406. // It's a class method - check it exists
  407. $method = substr($method, 5);
  408. if (!method_exists($this, $method)) {
  409. return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
  410. }
  411. //Call the method
  412. $result = $this->$method($args);
  413. } else {
  414. // It's a function - does it exist?
  415. if (is_array($method)) {
  416. if (!is_callable(array($method[0], $method[1]))) {
  417. return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
  418. }
  419. } else if (!function_exists($method)) {
  420. return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
  421. }
  422. // Call the function
  423. $result = call_user_func($method, $args);
  424. }
  425. return $result;
  426. }
  427. function error($error, $message = false)
  428. {
  429. // Accepts either an error object or an error code and message
  430. if ($message && !is_object($error)) {
  431. $error = new IXR_Error($error, $message);
  432. }
  433. $this->output($error->getXml());
  434. }
  435. function output($xml)
  436. {
  437. $charset = function_exists('get_option') ? get_option('blog_charset') : '';
  438. if ($charset)
  439. $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;
  440. else
  441. $xml = '<?xml version="1.0"?>'."\n".$xml;
  442. $length = strlen($xml);
  443. header('Connection: close');
  444. header('Content-Length: '.$length);
  445. if ($charset)
  446. header('Content-Type: text/xml; charset='.$charset);
  447. else
  448. header('Content-Type: text/xml');
  449. header('Date: '.date('r'));
  450. echo $xml;
  451. exit;
  452. }
  453. function hasMethod($method)
  454. {
  455. return in_array($method, array_keys($this->callbacks));
  456. }
  457. function setCapabilities()
  458. {
  459. // Initialises capabilities array
  460. $this->capabilities = array(
  461. 'xmlrpc' => array(
  462. 'specUrl' => 'http://www.xmlrpc.com/spec',
  463. 'specVersion' => 1
  464. ),
  465. 'faults_interop' => array(
  466. 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
  467. 'specVersion' => 20010516
  468. ),
  469. 'system.multicall' => array(
  470. 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
  471. 'specVersion' => 1
  472. ),
  473. );
  474. }
  475. function getCapabilities($args)
  476. {
  477. return $this->capabilities;
  478. }
  479. function setCallbacks()
  480. {
  481. $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
  482. $this->callbacks['system.listMethods'] = 'this:listMethods';
  483. $this->callbacks['system.multicall'] = 'this:multiCall';
  484. }
  485. function listMethods($args)
  486. {
  487. // Returns a list of methods - uses array_reverse to ensure user defined
  488. // methods are listed before server defined methods
  489. return array_reverse(array_keys($this->callbacks));
  490. }
  491. function multiCall($methodcalls)
  492. {
  493. // See http://www.xmlrpc.com/discuss/msgReader$1208
  494. $return = array();
  495. foreach ($methodcalls as $call) {
  496. $method = $call['methodName'];
  497. $params = $call['params'];
  498. if ($method == 'system.multicall') {
  499. $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
  500. } else {
  501. $result = $this->call($method, $params);
  502. }
  503. if (is_a($result, 'IXR_Error')) {
  504. $return[] = array(
  505. 'faultCode' => $result->code,
  506. 'faultString' => $result->message
  507. );
  508. } else {
  509. $return[] = array($result);
  510. }
  511. }
  512. return $return;
  513. }
  514. }
  515. /**
  516. * IXR_Request
  517. *
  518. * @package IXR
  519. * @since 1.5
  520. */
  521. class IXR_Request
  522. {
  523. var $method;
  524. var $args;
  525. var $xml;
  526. function IXR_Request($method, $args)
  527. {
  528. $this->method = $method;
  529. $this->args = $args;
  530. $this->xml = <<<EOD
  531. <?xml version="1.0"?>
  532. <methodCall>
  533. <methodName>{$this->method}</methodName>
  534. <params>
  535. EOD;
  536. foreach ($this->args as $arg) {
  537. $this->xml .= '<param><value>';
  538. $v = new IXR_Value($arg);
  539. $this->xml .= $v->getXml();
  540. $this->xml .= "</value></param>\n";
  541. }
  542. $this->xml .= '</params></methodCall>';
  543. }
  544. function getLength()
  545. {
  546. return strlen($this->xml);
  547. }
  548. function getXml()
  549. {
  550. return $this->xml;
  551. }
  552. }
  553. /**
  554. * IXR_Client
  555. *
  556. * @package IXR
  557. * @since 1.5
  558. *
  559. */
  560. class IXR_Client
  561. {
  562. var $server;
  563. var $port;
  564. var $path;
  565. var $useragent;
  566. var $response;
  567. var $message = false;
  568. var $debug = false;
  569. var $timeout;
  570. var $headers = array();
  571. // Storage place for an error message
  572. var $error = false;
  573. function IXR_Client($server, $path = false, $port = 80, $timeout = 15)
  574. {
  575. if (!$path) {
  576. // Assume we have been given a URL instead
  577. $bits = parse_url($server);
  578. $this->server = $bits['host'];
  579. $this->port = isset($bits['port']) ? $bits['port'] : 80;
  580. $this->path = isset($bits['path']) ? $bits['path'] : '/';
  581. // Make absolutely sure we have a path
  582. if (!$this->path) {
  583. $this->path = '/';
  584. }
  585. } else {
  586. $this->server = $server;
  587. $this->path = $path;
  588. $this->port = $port;
  589. }
  590. $this->useragent = 'The Incutio XML-RPC PHP Library';
  591. $this->timeout = $timeout;
  592. }
  593. function query()
  594. {
  595. $args = func_get_args();
  596. $method = array_shift($args);
  597. $request = new IXR_Request($method, $args);
  598. $length = $request->getLength();
  599. $xml = $request->getXml();
  600. $r = "\r\n";
  601. $request = "POST {$this->path} HTTP/1.0$r";
  602. // Merged from WP #8145 - allow custom headers
  603. $this->headers['Host'] = $this->server;
  604. $this->headers['Content-Type'] = 'text/xml';
  605. $this->headers['User-Agent'] = $this->useragent;
  606. $this->headers['Content-Length']= $length;
  607. foreach( $this->headers as $header => $value ) {
  608. $request .= "{$header}: {$value}{$r}";
  609. }
  610. $request .= $r;
  611. $request .= $xml;
  612. // Now send the request
  613. if ($this->debug) {
  614. echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
  615. }
  616. if ($this->timeout) {
  617. $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
  618. } else {
  619. $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
  620. }
  621. if (!$fp) {
  622. $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
  623. return false;
  624. }
  625. fputs($fp, $request);
  626. $contents = '';
  627. $debugContents = '';
  628. $gotFirstLine = false;
  629. $gettingHeaders = true;
  630. while (!feof($fp)) {
  631. $line = fgets($fp, 4096);
  632. if (!$gotFirstLine) {
  633. // Check line for '200'
  634. if (strstr($line, '200') === false) {
  635. $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
  636. return false;
  637. }
  638. $gotFirstLine = true;
  639. }
  640. if (trim($line) == '') {
  641. $gettingHeaders = false;
  642. }
  643. if (!$gettingHeaders) {
  644. // merged from WP #12559 - remove trim
  645. $contents .= $line;
  646. }
  647. if ($this->debug) {
  648. $debugContents .= $line;
  649. }
  650. }
  651. if ($this->debug) {
  652. echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
  653. }
  654. // Now parse what we've got back
  655. $this->message = new IXR_Message($contents);
  656. if (!$this->message->parse()) {
  657. // XML error
  658. $this->error = new IXR_Error(-32700, 'parse error. not well formed');
  659. return false;
  660. }
  661. // Is the message a fault?
  662. if ($this->message->messageType == 'fault') {
  663. $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
  664. return false;
  665. }
  666. // Message must be OK
  667. return true;
  668. }
  669. function getResponse()
  670. {
  671. // methodResponses can only have one param - return that
  672. return $this->message->params[0];
  673. }
  674. function isError()
  675. {
  676. return (is_object($this->error));
  677. }
  678. function getErrorCode()
  679. {
  680. return $this->error->code;
  681. }
  682. function getErrorMessage()
  683. {
  684. return $this->error->message;
  685. }
  686. }
  687. /**
  688. * IXR_Error
  689. *
  690. * @package IXR
  691. * @since 1.5
  692. */
  693. class IXR_Error
  694. {
  695. var $code;
  696. var $message;
  697. function IXR_Error($code, $message)
  698. {
  699. $this->code = $code;
  700. $this->message = htmlspecialchars($message);
  701. }
  702. function getXml()
  703. {
  704. $xml = <<<EOD
  705. <methodResponse>
  706. <fault>
  707. <value>
  708. <struct>
  709. <member>
  710. <name>faultCode</name>
  711. <value><int>{$this->code}</int></value>
  712. </member>
  713. <member>
  714. <name>faultString</name>
  715. <value><string>{$this->message}</string></value>
  716. </member>
  717. </struct>
  718. </value>
  719. </fault>
  720. </methodResponse>
  721. EOD;
  722. return $xml;
  723. }
  724. }
  725. /**
  726. * IXR_Date
  727. *
  728. * @package IXR
  729. * @since 1.5
  730. */
  731. class IXR_Date {
  732. var $year;
  733. var $month;
  734. var $day;
  735. var $hour;
  736. var $minute;
  737. var $second;
  738. var $timezone;
  739. function IXR_Date($time)
  740. {
  741. // $time can be a PHP timestamp or an ISO one
  742. if (is_numeric($time)) {
  743. $this->parseTimestamp($time);
  744. } else {
  745. $this->parseIso($time);
  746. }
  747. }
  748. function parseTimestamp($timestamp)
  749. {
  750. $this->year = date('Y', $timestamp);
  751. $this->month = date('m', $timestamp);
  752. $this->day = date('d', $timestamp);
  753. $this->hour = date('H', $timestamp);
  754. $this->minute = date('i', $timestamp);
  755. $this->second = date('s', $timestamp);
  756. $this->timezone = '';
  757. }
  758. function parseIso($iso)
  759. {
  760. $this->year = substr($iso, 0, 4);
  761. $this->month = substr($iso, 4, 2);
  762. $this->day = substr($iso, 6, 2);
  763. $this->hour = substr($iso, 9, 2);
  764. $this->minute = substr($iso, 12, 2);
  765. $this->second = substr($iso, 15, 2);
  766. $this->timezone = substr($iso, 17);
  767. }
  768. function getIso()
  769. {
  770. return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
  771. }
  772. function getXml()
  773. {
  774. return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
  775. }
  776. function getTimestamp()
  777. {
  778. return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
  779. }
  780. }
  781. /**
  782. * IXR_Base64
  783. *
  784. * @package IXR
  785. * @since 1.5
  786. */
  787. class IXR_Base64
  788. {
  789. var $data;
  790. function IXR_Base64($data)
  791. {
  792. $this->data = $data;
  793. }
  794. function getXml()
  795. {
  796. return '<base64>'.base64_encode($this->data).'</base64>';
  797. }
  798. }
  799. /**
  800. * IXR_IntrospectionServer
  801. *
  802. * @package IXR
  803. * @since 1.5
  804. */
  805. class IXR_IntrospectionServer extends IXR_Server
  806. {
  807. var $signatures;
  808. var $help;
  809. function IXR_IntrospectionServer()
  810. {
  811. $this->setCallbacks();
  812. $this->setCapabilities();
  813. $this->capabilities['introspection'] = array(
  814. 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
  815. 'specVersion' => 1
  816. );
  817. $this->addCallback(
  818. 'system.methodSignature',
  819. 'this:methodSignature',
  820. array('array', 'string'),
  821. 'Returns an array describing the return type and required parameters of a method'
  822. );
  823. $this->addCallback(
  824. 'system.getCapabilities',
  825. 'this:getCapabilities',
  826. array('struct'),
  827. 'Returns a struct describing the XML-RPC specifications supported by this server'
  828. );
  829. $this->addCallback(
  830. 'system.listMethods',
  831. 'this:listMethods',
  832. array('array'),
  833. 'Returns an array of available methods on this server'
  834. );
  835. $this->addCallback(
  836. 'system.methodHelp',
  837. 'this:methodHelp',
  838. array('string', 'string'),
  839. 'Returns a documentation string for the specified method'
  840. );
  841. }
  842. function addCallback($method, $callback, $args, $help)
  843. {
  844. $this->callbacks[$method] = $callback;
  845. $this->signatures[$method] = $args;
  846. $this->help[$method] = $help;
  847. }
  848. function call($methodname, $args)
  849. {
  850. // Make sure it's in an array
  851. if ($args && !is_array($args)) {
  852. $args = array($args);
  853. }
  854. // Over-rides default call method, adds signature check
  855. if (!$this->hasMethod($methodname)) {
  856. return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
  857. }
  858. $method = $this->callbacks[$methodname];
  859. $signature = $this->signatures[$methodname];
  860. $returnType = array_shift($signature);
  861. // Check the number of arguments
  862. if (count($args) != count($signature)) {
  863. return new IXR_Error(-32602, 'server error. wrong number of method parameters');
  864. }
  865. // Check the argument types
  866. $ok = true;
  867. $argsbackup = $args;
  868. for ($i = 0, $j = count($args); $i < $j; $i++) {
  869. $arg = array_shift($args);
  870. $type = array_shift($signature);
  871. switch ($type) {
  872. case 'int':
  873. case 'i4':
  874. if (is_array($arg) || !is_int($arg)) {
  875. $ok = false;
  876. }
  877. break;
  878. case 'base64':
  879. case 'string':
  880. if (!is_string($arg)) {
  881. $ok = false;
  882. }
  883. break;
  884. case 'boolean':
  885. if ($arg !== false && $arg !== true) {
  886. $ok = false;
  887. }
  888. break;
  889. case 'float':
  890. case 'double':
  891. if (!is_float($arg)) {
  892. $ok = false;
  893. }
  894. break;
  895. case 'date':
  896. case 'dateTime.iso8601':
  897. if (!is_a($arg, 'IXR_Date')) {
  898. $ok = false;
  899. }
  900. break;
  901. }
  902. if (!$ok) {
  903. return new IXR_Error(-32602, 'server error. invalid method parameters');
  904. }
  905. }
  906. // It passed the test - run the "real" method call
  907. return parent::call($methodname, $argsbackup);
  908. }
  909. function methodSignature($method)
  910. {
  911. if (!$this->hasMethod($method)) {
  912. return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
  913. }
  914. // We should be returning an array of types
  915. $types = $this->signatures[$method];
  916. $return = array();
  917. foreach ($types as $type) {
  918. switch ($type) {
  919. case 'string':
  920. $return[] = 'string';
  921. break;
  922. case 'int':
  923. case 'i4':
  924. $return[] = 42;
  925. break;
  926. case 'double':
  927. $return[] = 3.1415;
  928. break;
  929. case 'dateTime.iso8601':
  930. $return[] = new IXR_Date(time());
  931. break;
  932. case 'boolean':
  933. $return[] = true;
  934. break;
  935. case 'base64':
  936. $return[] = new IXR_Base64('base64');
  937. break;
  938. case 'array':
  939. $return[] = array('array');
  940. break;
  941. case 'struct':
  942. $return[] = array('struct' => 'struct');
  943. break;
  944. }
  945. }
  946. return $return;
  947. }
  948. function methodHelp($method)
  949. {
  950. return $this->help[$method];
  951. }
  952. }
  953. /**
  954. * IXR_ClientMulticall
  955. *
  956. * @package IXR
  957. * @since 1.5
  958. */
  959. class IXR_ClientMulticall extends IXR_Client
  960. {
  961. var $calls = array();
  962. function IXR_ClientMulticall($server, $path = false, $port = 80)
  963. {
  964. parent::IXR_Client($server, $path, $port);
  965. $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
  966. }
  967. function addCall()
  968. {
  969. $args = func_get_args();
  970. $methodName = array_shift($args);
  971. $struct = array(
  972. 'methodName' => $methodName,
  973. 'params' => $args
  974. );
  975. $this->calls[] = $struct;
  976. }
  977. function query()
  978. {
  979. // Prepare multicall, then call the parent::query() method
  980. return parent::query('system.multicall', $this->calls);
  981. }
  982. }