PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/external/php-ixr/IXR_Library.php

https://bitbucket.org/navigatecms/navigatecms
PHP | 1431 lines | 1025 code | 139 blank | 267 comment | 133 complexity | 0afb36133502b154214dc8cac51c4167 MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-2.1, BSD-3-Clause, AGPL-3.0, Apache-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. */
  40. class IXR_Value
  41. {
  42. var $data;
  43. var $type;
  44. function __construct($data, $type = false)
  45. {
  46. $this->IXR_Value($data, $type);
  47. }
  48. function IXR_Value($data, $type = false)
  49. {
  50. $this->data = $data;
  51. if (!$type) {
  52. $type = $this->calculateType();
  53. }
  54. $this->type = $type;
  55. if ($type == 'struct') {
  56. // Turn all the values in the array in to new IXR_Value objects
  57. foreach ($this->data as $key => $value) {
  58. $this->data[$key] = new IXR_Value($value);
  59. }
  60. }
  61. if ($type == 'array') {
  62. for ($i = 0, $j = count($this->data); $i < $j; $i++) {
  63. $this->data[$i] = new IXR_Value($this->data[$i]);
  64. }
  65. }
  66. }
  67. function calculateType()
  68. {
  69. if ($this->data === true || $this->data === false) {
  70. return 'boolean';
  71. }
  72. if (is_integer($this->data)) {
  73. return 'int';
  74. }
  75. if (is_double($this->data)) {
  76. return 'double';
  77. }
  78. // Deal with IXR object types base64 and date
  79. if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
  80. return 'date';
  81. }
  82. if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
  83. return 'base64';
  84. }
  85. // If it is a normal PHP object convert it in to a struct
  86. if (is_object($this->data)) {
  87. $this->data = get_object_vars($this->data);
  88. return 'struct';
  89. }
  90. if (!is_array($this->data)) {
  91. return 'string';
  92. }
  93. // We have an array - is it an array or a struct?
  94. if ($this->isStruct($this->data)) {
  95. return 'struct';
  96. } else {
  97. return 'array';
  98. }
  99. }
  100. function getXml()
  101. {
  102. // Return XML for this value
  103. switch ($this->type) {
  104. case 'boolean':
  105. return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
  106. break;
  107. case 'int':
  108. return '<int>'.$this->data.'</int>';
  109. break;
  110. case 'double':
  111. return '<double>'.$this->data.'</double>';
  112. break;
  113. case 'string':
  114. return '<string>'.htmlspecialchars($this->data).'</string>';
  115. break;
  116. case 'array':
  117. $return = '<array><data>'."\n";
  118. foreach ($this->data as $item) {
  119. $return .= ' <value>'.$item->getXml()."</value>\n";
  120. }
  121. $return .= '</data></array>';
  122. return $return;
  123. break;
  124. case 'struct':
  125. $return = '<struct>'."\n";
  126. foreach ($this->data as $name => $value) {
  127. $return .= " <member><name>$name</name><value>";
  128. $return .= $value->getXml()."</value></member>\n";
  129. }
  130. $return .= '</struct>';
  131. return $return;
  132. break;
  133. case 'date':
  134. case 'base64':
  135. return $this->data->getXml();
  136. break;
  137. }
  138. return false;
  139. }
  140. /**
  141. * Checks whether or not the supplied array is a struct or not
  142. *
  143. * @param unknown_type $array
  144. * @return boolean
  145. */
  146. function isStruct($array)
  147. {
  148. $expected = 0;
  149. foreach ($array as $key => $value) {
  150. if ((string)$key != (string)$expected) {
  151. return true;
  152. }
  153. $expected++;
  154. }
  155. return false;
  156. }
  157. }
  158. /**
  159. * IXR_MESSAGE
  160. *
  161. * @package IXR
  162. * @since 1.5
  163. *
  164. */
  165. class IXR_Message
  166. {
  167. var $message;
  168. var $messageType; // methodCall / methodResponse / fault
  169. var $faultCode;
  170. var $faultString;
  171. var $methodName;
  172. var $params;
  173. // Current variable stacks
  174. var $_arraystructs = array(); // The stack used to keep track of the current array/struct
  175. var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
  176. var $_currentStructName = array(); // A stack as well
  177. var $_param;
  178. var $_value;
  179. var $_currentTag;
  180. var $_currentTagContents;
  181. // The XML parser
  182. var $_parser;
  183. function __construct($message)
  184. {
  185. $this->IXR_Message($message);
  186. }
  187. function IXR_Message($message)
  188. {
  189. $this->message =& $message;
  190. }
  191. function parse()
  192. {
  193. // first remove the XML declaration
  194. // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
  195. $header = preg_replace( '/<\?xml.*?\?'.'>/', '', substr($this->message, 0, 100), 1);
  196. $this->message = substr_replace($this->message, $header, 0, 100);
  197. if (trim($this->message) == '') {
  198. return false;
  199. }
  200. $this->_parser = xml_parser_create();
  201. // Set XML parser to take the case of tags in to account
  202. xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
  203. // Set XML parser callback functions
  204. xml_set_object($this->_parser, $this);
  205. xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
  206. xml_set_character_data_handler($this->_parser, 'cdata');
  207. $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
  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 __construct($callbacks = false, $data = false, $wait = false)
  338. {
  339. $this->IXR_Server($callbacks, $data, $wait);
  340. }
  341. function IXR_Server($callbacks = false, $data = false, $wait = false)
  342. {
  343. $this->setCapabilities();
  344. if ($callbacks) {
  345. $this->callbacks = $callbacks;
  346. }
  347. $this->setCallbacks();
  348. if (!$wait) {
  349. $this->serve($data);
  350. }
  351. }
  352. function serve($data = false)
  353. {
  354. if (!$data) {
  355. if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
  356. header('Content-Type: text/plain'); // merged from WP #9093
  357. die('XML-RPC server accepts POST requests only.');
  358. }
  359. //global $HTTP_RAW_POST_DATA;
  360. //if (empty($HTTP_RAW_POST_DATA)) {
  361. // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
  362. $data = file_get_contents('php://input');
  363. //} else {
  364. // $data =& $HTTP_RAW_POST_DATA;
  365. //}
  366. }
  367. $this->message = new IXR_Message($data);
  368. if (!$this->message->parse()) {
  369. $this->error(-32700, 'parse error. not well formed');
  370. }
  371. if ($this->message->messageType != 'methodCall') {
  372. $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
  373. }
  374. $result = $this->call($this->message->methodName, $this->message->params);
  375. // Is the result an error?
  376. if (is_a($result, 'IXR_Error')) {
  377. $this->error($result);
  378. }
  379. // Encode the result
  380. $r = new IXR_Value($result);
  381. $resultxml = $r->getXml();
  382. // Create the XML
  383. $xml = <<<EOD
  384. <methodResponse>
  385. <params>
  386. <param>
  387. <value>
  388. $resultxml
  389. </value>
  390. </param>
  391. </params>
  392. </methodResponse>
  393. EOD;
  394. // Send it
  395. $this->output($xml);
  396. }
  397. function call($methodname, $args)
  398. {
  399. if (!$this->hasMethod($methodname)) {
  400. return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
  401. }
  402. $method = $this->callbacks[$methodname];
  403. // Perform the callback and send the response
  404. if (count($args) == 1) {
  405. // If only one paramater just send that instead of the whole array
  406. $args = $args[0];
  407. }
  408. // Are we dealing with a function or a method?
  409. if (is_string($method) && substr($method, 0, 5) == 'this:') {
  410. // It's a class method - check it exists
  411. $method = substr($method, 5);
  412. if (!method_exists($this, $method)) {
  413. return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
  414. }
  415. //Call the method
  416. $result = $this->$method($args);
  417. } else {
  418. // It's a function - does it exist?
  419. if (is_array($method)) {
  420. if (!method_exists($method[0], $method[1])) {
  421. return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
  422. }
  423. } else if (!function_exists($method)) {
  424. return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
  425. }
  426. // Call the function
  427. $result = call_user_func($method, $args);
  428. }
  429. return $result;
  430. }
  431. function error($error, $message = false)
  432. {
  433. // Accepts either an error object or an error code and message
  434. if ($message && !is_object($error)) {
  435. $error = new IXR_Error($error, $message);
  436. }
  437. $this->output($error->getXml());
  438. }
  439. function output($xml)
  440. {
  441. $xml = '<?xml version="1.0"?>'."\n".$xml;
  442. $length = strlen($xml);
  443. header('Connection: close');
  444. header('Content-Length: '.$length);
  445. header('Content-Type: text/xml');
  446. header('Date: '.date('r'));
  447. echo $xml;
  448. exit;
  449. }
  450. function hasMethod($method)
  451. {
  452. return in_array($method, array_keys($this->callbacks));
  453. }
  454. function setCapabilities()
  455. {
  456. // Initialises capabilities array
  457. $this->capabilities = array(
  458. 'xmlrpc' => array(
  459. 'specUrl' => 'http://www.xmlrpc.com/spec',
  460. 'specVersion' => 1
  461. ),
  462. 'faults_interop' => array(
  463. 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
  464. 'specVersion' => 20010516
  465. ),
  466. 'system.multicall' => array(
  467. 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
  468. 'specVersion' => 1
  469. ),
  470. );
  471. }
  472. function getCapabilities($args)
  473. {
  474. return $this->capabilities;
  475. }
  476. function setCallbacks()
  477. {
  478. $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
  479. $this->callbacks['system.listMethods'] = 'this:listMethods';
  480. $this->callbacks['system.multicall'] = 'this:multiCall';
  481. }
  482. function listMethods($args)
  483. {
  484. // Returns a list of methods - uses array_reverse to ensure user defined
  485. // methods are listed before server defined methods
  486. return array_reverse(array_keys($this->callbacks));
  487. }
  488. function multiCall($methodcalls)
  489. {
  490. // See http://www.xmlrpc.com/discuss/msgReader$1208
  491. $return = array();
  492. foreach ($methodcalls as $call) {
  493. $method = $call['methodName'];
  494. $params = $call['params'];
  495. if ($method == 'system.multicall') {
  496. $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
  497. } else {
  498. $result = $this->call($method, $params);
  499. }
  500. if (is_a($result, 'IXR_Error')) {
  501. $return[] = array(
  502. 'faultCode' => $result->code,
  503. 'faultString' => $result->message
  504. );
  505. } else {
  506. $return[] = array($result);
  507. }
  508. }
  509. return $return;
  510. }
  511. }
  512. /**
  513. * IXR_Request
  514. *
  515. * @package IXR
  516. * @since 1.5
  517. */
  518. class IXR_Request
  519. {
  520. var $method;
  521. var $args;
  522. var $xml;
  523. function __construct($method, $args)
  524. {
  525. $this->IXR_Request($method, $args);
  526. }
  527. function IXR_Request($method, $args)
  528. {
  529. $this->method = $method;
  530. $this->args = $args;
  531. $this->xml = <<<EOD
  532. <?xml version="1.0"?>
  533. <methodCall>
  534. <methodName>{$this->method}</methodName>
  535. <params>
  536. EOD;
  537. foreach ($this->args as $arg) {
  538. $this->xml .= '<param><value>';
  539. $v = new IXR_Value($arg);
  540. $this->xml .= $v->getXml();
  541. $this->xml .= "</value></param>\n";
  542. }
  543. $this->xml .= '</params></methodCall>';
  544. }
  545. function getLength()
  546. {
  547. return strlen($this->xml);
  548. }
  549. function getXml()
  550. {
  551. return $this->xml;
  552. }
  553. }
  554. /**
  555. * IXR_Client
  556. *
  557. * @package IXR
  558. * @since 1.5
  559. *
  560. */
  561. class IXR_Client
  562. {
  563. var $server;
  564. var $port;
  565. var $path;
  566. var $useragent;
  567. var $response;
  568. var $message = false;
  569. var $debug = false;
  570. var $timeout;
  571. // Storage place for an error message
  572. var $error = false;
  573. function __construct($server, $path = false, $port = 80, $timeout = 15)
  574. {
  575. $this->IXR_Client($server, $path, $port, $timeout);
  576. }
  577. function IXR_Client($server, $path = false, $port = 80, $timeout = 15)
  578. {
  579. if (!$path) {
  580. // Assume we have been given a URL instead
  581. $bits = parse_url($server);
  582. $this->server = $bits['host'];
  583. $this->port = isset($bits['port']) ? $bits['port'] : 80;
  584. $this->path = isset($bits['path']) ? $bits['path'] : '/';
  585. // Make absolutely sure we have a path
  586. if (!$this->path) {
  587. $this->path = '/';
  588. }
  589. } else {
  590. $this->server = $server;
  591. $this->path = $path;
  592. $this->port = $port;
  593. }
  594. $this->useragent = 'The Incutio XML-RPC PHP Library';
  595. $this->timeout = $timeout;
  596. }
  597. function query()
  598. {
  599. $args = func_get_args();
  600. $method = array_shift($args);
  601. $request = new IXR_Request($method, $args);
  602. $length = $request->getLength();
  603. $xml = $request->getXml();
  604. $r = "\r\n";
  605. $request = "POST {$this->path} HTTP/1.0$r";
  606. // Merged from WP #8145 - allow custom headers
  607. $this->headers['Host'] = $this->server;
  608. $this->headers['Content-Type'] = 'text/xml';
  609. $this->headers['User-Agent'] = $this->useragent;
  610. $this->headers['Content-Length']= $length;
  611. foreach( $this->headers as $header => $value ) {
  612. $request .= "{$header}: {$value}{$r}";
  613. }
  614. $request .= $r;
  615. $request .= $xml;
  616. // Now send the request
  617. if ($this->debug) {
  618. echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
  619. }
  620. if ($this->timeout) {
  621. $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
  622. } else {
  623. $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
  624. }
  625. if (!$fp) {
  626. $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
  627. return false;
  628. }
  629. fputs($fp, $request);
  630. $contents = '';
  631. $debugContents = '';
  632. $gotFirstLine = false;
  633. $gettingHeaders = true;
  634. while (!feof($fp)) {
  635. $line = fgets($fp, 4096);
  636. if (!$gotFirstLine) {
  637. // Check line for '200'
  638. if (strstr($line, '200') === false) {
  639. $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
  640. return false;
  641. }
  642. $gotFirstLine = true;
  643. }
  644. if (trim($line) == '') {
  645. $gettingHeaders = false;
  646. }
  647. if (!$gettingHeaders) {
  648. // merged from WP #12559 - remove trim
  649. $contents .= $line;
  650. }
  651. if ($this->debug) {
  652. $debugContents .= $line;
  653. }
  654. }
  655. if ($this->debug) {
  656. echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
  657. }
  658. // Now parse what we've got back
  659. $this->message = new IXR_Message($contents);
  660. if (!$this->message->parse()) {
  661. // XML error
  662. $this->error = new IXR_Error(-32700, 'parse error. not well formed');
  663. return false;
  664. }
  665. // Is the message a fault?
  666. if ($this->message->messageType == 'fault') {
  667. $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
  668. return false;
  669. }
  670. // Message must be OK
  671. return true;
  672. }
  673. function getResponse()
  674. {
  675. // methodResponses can only have one param - return that
  676. return $this->message->params[0];
  677. }
  678. function isError()
  679. {
  680. return (is_object($this->error));
  681. }
  682. function getErrorCode()
  683. {
  684. return $this->error->code;
  685. }
  686. function getErrorMessage()
  687. {
  688. return $this->error->message;
  689. }
  690. }
  691. /**
  692. * IXR_Error
  693. *
  694. * @package IXR
  695. * @since 1.5
  696. */
  697. class IXR_Error
  698. {
  699. var $code;
  700. var $message;
  701. function __construct($code, $message)
  702. {
  703. $this->IXR_Error($code, $message);
  704. }
  705. function IXR_Error($code, $message)
  706. {
  707. $this->code = $code;
  708. $this->message = htmlspecialchars($message);
  709. }
  710. function getXml()
  711. {
  712. $xml = <<<EOD
  713. <methodResponse>
  714. <fault>
  715. <value>
  716. <struct>
  717. <member>
  718. <name>faultCode</name>
  719. <value><int>{$this->code}</int></value>
  720. </member>
  721. <member>
  722. <name>faultString</name>
  723. <value><string>{$this->message}</string></value>
  724. </member>
  725. </struct>
  726. </value>
  727. </fault>
  728. </methodResponse>
  729. EOD;
  730. return $xml;
  731. }
  732. }
  733. /**
  734. * IXR_Date
  735. *
  736. * @package IXR
  737. * @since 1.5
  738. */
  739. class IXR_Date {
  740. var $year;
  741. var $month;
  742. var $day;
  743. var $hour;
  744. var $minute;
  745. var $second;
  746. var $timezone;
  747. function __construct($time)
  748. {
  749. $this->IXR_Date($time);
  750. }
  751. function IXR_Date($time)
  752. {
  753. // $time can be a PHP timestamp or an ISO one
  754. if (is_numeric($time)) {
  755. $this->parseTimestamp($time);
  756. } else {
  757. $this->parseIso($time);
  758. }
  759. }
  760. function parseTimestamp($timestamp)
  761. {
  762. $this->year = date('Y', $timestamp);
  763. $this->month = date('m', $timestamp);
  764. $this->day = date('d', $timestamp);
  765. $this->hour = date('H', $timestamp);
  766. $this->minute = date('i', $timestamp);
  767. $this->second = date('s', $timestamp);
  768. $this->timezone = '';
  769. }
  770. function parseIso($iso)
  771. {
  772. $this->year = substr($iso, 0, 4);
  773. $this->month = substr($iso, 4, 2);
  774. $this->day = substr($iso, 6, 2);
  775. $this->hour = substr($iso, 9, 2);
  776. $this->minute = substr($iso, 12, 2);
  777. $this->second = substr($iso, 15, 2);
  778. $this->timezone = substr($iso, 17);
  779. }
  780. function getIso()
  781. {
  782. return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
  783. }
  784. function getXml()
  785. {
  786. return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
  787. }
  788. function getTimestamp()
  789. {
  790. return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
  791. }
  792. }
  793. /**
  794. * IXR_Base64
  795. *
  796. * @package IXR
  797. * @since 1.5
  798. */
  799. class IXR_Base64
  800. {
  801. var $data;
  802. function __construct($data)
  803. {
  804. $this->IXR_Base64($data);
  805. }
  806. function IXR_Base64($data)
  807. {
  808. $this->data = $data;
  809. }
  810. function getXml()
  811. {
  812. return '<base64>'.base64_encode($this->data).'</base64>';
  813. }
  814. }
  815. /**
  816. * IXR_IntrospectionServer
  817. *
  818. * @package IXR
  819. * @since 1.5
  820. */
  821. class IXR_IntrospectionServer extends IXR_Server
  822. {
  823. var $signatures;
  824. var $help;
  825. function __construct()
  826. {
  827. $this->IXR_IntrospectionServer();
  828. }
  829. function IXR_IntrospectionServer()
  830. {
  831. $this->setCallbacks();
  832. $this->setCapabilities();
  833. $this->capabilities['introspection'] = array(
  834. 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
  835. 'specVersion' => 1
  836. );
  837. $this->addCallback(
  838. 'system.methodSignature',
  839. 'this:methodSignature',
  840. array('array', 'string'),
  841. 'Returns an array describing the return type and required parameters of a method'
  842. );
  843. $this->addCallback(
  844. 'system.getCapabilities',
  845. 'this:getCapabilities',
  846. array('struct'),
  847. 'Returns a struct describing the XML-RPC specifications supported by this server'
  848. );
  849. $this->addCallback(
  850. 'system.listMethods',
  851. 'this:listMethods',
  852. array('array'),
  853. 'Returns an array of available methods on this server'
  854. );
  855. $this->addCallback(
  856. 'system.methodHelp',
  857. 'this:methodHelp',
  858. array('string', 'string'),
  859. 'Returns a documentation string for the specified method'
  860. );
  861. }
  862. function addCallback($method, $callback, $args, $help)
  863. {
  864. $this->callbacks[$method] = $callback;
  865. $this->signatures[$method] = $args;
  866. $this->help[$method] = $help;
  867. }
  868. function call($methodname, $args)
  869. {
  870. // Make sure it's in an array
  871. if ($args && !is_array($args)) {
  872. $args = array($args);
  873. }
  874. // Over-rides default call method, adds signature check
  875. if (!$this->hasMethod($methodname)) {
  876. return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
  877. }
  878. $method = $this->callbacks[$methodname];
  879. $signature = $this->signatures[$methodname];
  880. $returnType = array_shift($signature);
  881. // Check the number of arguments
  882. if (count($args) != count($signature)) {
  883. return new IXR_Error(-32602, 'server error. wrong number of method parameters');
  884. }
  885. // Check the argument types
  886. $ok = true;
  887. $argsbackup = $args;
  888. for ($i = 0, $j = count($args); $i < $j; $i++) {
  889. $arg = array_shift($args);
  890. $type = array_shift($signature);
  891. switch ($type) {
  892. case 'int':
  893. case 'i4':
  894. if (is_array($arg) || !is_int($arg)) {
  895. $ok = false;
  896. }
  897. break;
  898. case 'base64':
  899. case 'string':
  900. if (!is_string($arg)) {
  901. $ok = false;
  902. }
  903. break;
  904. case 'boolean':
  905. if ($arg !== false && $arg !== true) {
  906. $ok = false;
  907. }
  908. break;
  909. case 'float':
  910. case 'double':
  911. if (!is_float($arg)) {
  912. $ok = false;
  913. }
  914. break;
  915. case 'date':
  916. case 'dateTime.iso8601':
  917. if (!is_a($arg, 'IXR_Date')) {
  918. $ok = false;
  919. }
  920. break;
  921. }
  922. if (!$ok) {
  923. return new IXR_Error(-32602, 'server error. invalid method parameters');
  924. }
  925. }
  926. // It passed the test - run the "real" method call
  927. return parent::call($methodname, $argsbackup);
  928. }
  929. function methodSignature($method)
  930. {
  931. if (!$this->hasMethod($method)) {
  932. return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
  933. }
  934. // We should be returning an array of types
  935. $types = $this->signatures[$method];
  936. $return = array();
  937. foreach ($types as $type) {
  938. switch ($type) {
  939. case 'string':
  940. $return[] = 'string';
  941. break;
  942. case 'int':
  943. case 'i4':
  944. $return[] = 42;
  945. break;
  946. case 'double':
  947. $return[] = 3.1415;
  948. break;
  949. case 'dateTime.iso8601':
  950. $return[] = new IXR_Date(time());
  951. break;
  952. case 'boolean':
  953. $return[] = true;
  954. break;
  955. case 'base64':
  956. $return[] = new IXR_Base64('base64');
  957. break;
  958. case 'array':
  959. $return[] = array('array');
  960. break;
  961. case 'struct':
  962. $return[] = array('struct' => 'struct');
  963. break;
  964. }
  965. }
  966. return $return;
  967. }
  968. function methodHelp($method)
  969. {
  970. return $this->help[$method];
  971. }
  972. }
  973. /**
  974. * IXR_ClientMulticall
  975. *
  976. * @package IXR
  977. * @since 1.5
  978. */
  979. class IXR_ClientMulticall extends IXR_Client
  980. {
  981. var $calls = array();
  982. function __construct($server, $path = false, $port = 80)
  983. {
  984. $this->IXR_ClientMulticall($server, $path, $port);
  985. }
  986. function IXR_ClientMulticall($server, $path = false, $port = 80)
  987. {
  988. parent::IXR_Client($server, $path, $port);
  989. $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
  990. }
  991. function addCall()
  992. {
  993. $args = func_get_args();
  994. $methodName = array_shift($args);
  995. $struct = array(
  996. 'methodName' => $methodName,
  997. 'params' => $args
  998. );
  999. $this->calls[] = $struct;
  1000. }
  1001. function query()
  1002. {
  1003. // Prepare multicall, then call the parent::query() method
  1004. return parent::query('system.multicall', $this->calls);
  1005. }
  1006. }
  1007. /**
  1008. * Client for communicating with a XML-RPC Server over HTTPS.
  1009. *
  1010. * @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/)
  1011. * @version 0.2.0 26May2005 08:34 +0800
  1012. * @copyright (c) 2004-2005 Jason Stirk
  1013. * @package IXR
  1014. */
  1015. class IXR_ClientSSL extends IXR_Client
  1016. {
  1017. /**
  1018. * Filename of the SSL Client Certificate
  1019. * @access private
  1020. * @since 0.1.0
  1021. * @var string
  1022. */
  1023. var $_certFile;
  1024. /**
  1025. * Filename of the SSL CA Certificate
  1026. * @access private
  1027. * @since 0.1.0
  1028. * @var string
  1029. */
  1030. var $_caFile;
  1031. /**
  1032. * Filename of the SSL Client Private Key
  1033. * @access private
  1034. * @since 0.1.0
  1035. * @var string
  1036. */
  1037. var $_keyFile;
  1038. /**
  1039. * Passphrase to unlock the private key
  1040. * @access private
  1041. * @since 0.1.0
  1042. * @var string
  1043. */
  1044. var $_passphrase;
  1045. function __construct($server, $path = false, $port = 443, $timeout = false)
  1046. {
  1047. $this->IXR_ClientSSL($server, $path, $port, $timeout);
  1048. }
  1049. /**
  1050. * Constructor
  1051. * @param string $server URL of the Server to connect to
  1052. * @since 0.1.0
  1053. */
  1054. function IXR_ClientSSL($server, $path = false, $port = 443, $timeout = false)
  1055. {
  1056. parent::IXR_Client($server, $path, $port, $timeout);
  1057. $this->useragent = 'The Incutio XML-RPC PHP Library for SSL';
  1058. // Set class fields
  1059. $this->_certFile=false;
  1060. $this->_caFile=false;
  1061. $this->_keyFile=false;
  1062. $this->_passphrase='';
  1063. }
  1064. /**
  1065. * Set the client side certificates to communicate with the server.
  1066. *
  1067. * @since 0.1.0
  1068. * @param string $certificateFile Filename of the client side certificate to use
  1069. * @param string $keyFile Filename of the client side certificate's private key
  1070. * @param string $keyPhrase Passphrase to unlock the private key
  1071. */
  1072. function setCertificate($certificateFile, $keyFile, $keyPhrase='')
  1073. {
  1074. // Check the files all exist
  1075. if (is_file($certificateFile)) {
  1076. $this->_certFile = $certificateFile;
  1077. } else {
  1078. die('Could not open certificate: ' . $certificateFile);
  1079. }
  1080. if (is_file($keyFile)) {
  1081. $this->_keyFile = $keyFile;
  1082. } else {
  1083. die('Could not open private key: ' . $keyFile);
  1084. }
  1085. $this->_passphrase=(string)$keyPhrase;
  1086. }
  1087. function setCACertificate($caFile)
  1088. {
  1089. if (is_file($caFile)) {
  1090. $this->_caFile = $caFile;
  1091. } else {
  1092. die('Could not open CA certificate: ' . $caFile);
  1093. }
  1094. }
  1095. /**
  1096. * Sets the connection timeout (in seconds)
  1097. * @param int $newTimeOut Timeout in seconds
  1098. * @returns void
  1099. * @since 0.1.2
  1100. */
  1101. function setTimeOut($newTimeOut)
  1102. {
  1103. $this->timeout = (int)$newTimeOut;
  1104. }
  1105. /**
  1106. * Returns the connection timeout (in seconds)
  1107. * @returns int
  1108. * @since 0.1.2
  1109. */
  1110. function getTimeOut()
  1111. {
  1112. return $this->timeout;
  1113. }
  1114. /**
  1115. * Set the query to send to the XML-RPC Server
  1116. * @since 0.1.0
  1117. */
  1118. function query()
  1119. {
  1120. $args = func_get_args();
  1121. $method = array_shift($args);
  1122. $request = new IXR_Request($method, $args);
  1123. $length = $request->getLength();
  1124. $xml = $request->getXml();
  1125. if ($this->debug) {
  1126. echo '<pre>'.htmlspecialchars($xml)."\n</pre>\n\n";
  1127. }
  1128. //This is where we deviate from the normal query()
  1129. //Rather than open a normal sock, we will actually use the cURL
  1130. //extensions to make the calls, and handle the SSL stuff.
  1131. //Since 04Aug2004 (0.1.3) - Need to include the port (duh...)
  1132. //Since 06Oct2004 (0.1.4) - Need to include the colon!!!
  1133. // (I swear I've fixed this before... ESP in live... But anyhu...)
  1134. $curl=curl_init('https://' . $this->server . ':' . $this->port . $this->path);
  1135. curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
  1136. //Since 23Jun2004 (0.1.2) - Made timeout a class field
  1137. curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout);
  1138. if ($this->debug) {
  1139. curl_setopt($curl, CURLOPT_VERBOSE, 1);
  1140. }
  1141. curl_setopt($curl, CURLOPT_HEADER, 1);
  1142. curl_setopt($curl, CURLOPT_POST, 1);
  1143. curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);
  1144. curl_setopt($curl, CURLOPT_PORT, $this->port);
  1145. curl_setopt($curl, CURLOPT_HTTPHEADER, array(
  1146. "Content-Type: text/xml",
  1147. "Content-length: {$length}"));
  1148. // Process the SSL certificates, etc. to use
  1149. if (!($this->_certFile === false)) {
  1150. // We have a certificate file set, so add these to the cURL handler
  1151. curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile);
  1152. curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile);
  1153. if ($this->debug) {
  1154. echo "SSL Cert at : " . $this->_certFile . "\n";
  1155. echo "SSL Key at : " . $this->_keyFile . "\n";
  1156. }
  1157. // See if we need to give a passphrase
  1158. if (!($this->_passphrase === '')) {
  1159. curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase);
  1160. }
  1161. if ($this->_caFile === false) {
  1162. // Don't verify their certificate, as we don't have a CA to verify against
  1163. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
  1164. } else {
  1165. // Verify against a CA
  1166. curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile);
  1167. }
  1168. }
  1169. // Call cURL to do it's stuff and return us the content
  1170. $contents = curl_exec($curl);
  1171. curl_close($curl);
  1172. // Check for 200 Code in $contents
  1173. if (!strstr($contents, '200 OK')) {
  1174. //There was no "200 OK" returned - we failed
  1175. $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
  1176. return false;
  1177. }
  1178. if ($this->debug) {
  1179. echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n";
  1180. }
  1181. // Now parse what we've got back
  1182. // Since 20Jun2004 (0.1.1) - We need to remove the headers first
  1183. // Why I have only just found this, I will never know...
  1184. // So, remove everything before the first <
  1185. $contents = substr($contents,strpos($contents, '<'));
  1186. $this->message = new IXR_Message($contents);
  1187. if (!$this->message->parse()) {
  1188. // XML error
  1189. $this->error = new IXR_Error(-32700, 'parse error. not well formed');
  1190. return false;
  1191. }
  1192. // Is the message a fault?
  1193. if ($this->message->messageType == 'fault') {
  1194. $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
  1195. return false;
  1196. }
  1197. // Message must be OK
  1198. return true;
  1199. }
  1200. }
  1201. /**
  1202. * Extension of the {@link IXR_Server} class to easily wrap objects.
  1203. *
  1204. * Class is designed to extend the existing XML-RPC server to allow the
  1205. * presentation of methods from a variety of different objects via an
  1206. * XML-RPC server.
  1207. * It is intended to assist in organization of your XML-RPC methods by allowing
  1208. * you to "write once" in your existing model classes and present them.
  1209. *
  1210. * @author Jason Stirk <jstirk@gmm.com.au>
  1211. * @version 1.0.1 19Apr2005 17:40 +0800
  1212. * @copyright Copyright (c) 2005 Jason Stirk
  1213. * @package IXR
  1214. */
  1215. class IXR_ClassServer extends IXR_Server
  1216. {
  1217. var $_objects;
  1218. var $_delim;
  1219. function __construct($delim = '.', $wait = false)
  1220. {
  1221. $this->IXR_ClassServer($delim, $wait);
  1222. }
  1223. function IXR_ClassServer($delim = '.', $wait = false)
  1224. {
  1225. $this->IXR_Server(array(), false, $wait);
  1226. $this->_delimiter = $delim;
  1227. $this->_objects = array();
  1228. }
  1229. function addMethod($rpcName, $functionName)
  1230. {
  1231. $this->callbacks[$rpcName] = $functionName;
  1232. }
  1233. function registerObject($object, $methods, $prefix=null)
  1234. {
  1235. if (is_null($prefix))
  1236. {
  1237. $prefix = get_class($object);
  1238. }
  1239. $this->_objects[$prefix] = $object;
  1240. // Add to our callbacks array
  1241. foreach($methods as $method)
  1242. {
  1243. if (is_array($method))
  1244. {
  1245. $targetMethod = $method[0];
  1246. $method = $method[1];
  1247. }
  1248. else
  1249. {
  1250. $targetMethod = $method;
  1251. }
  1252. $this->callbacks[$prefix . $this->_delimiter . $method]=array($prefix, $targetMethod);
  1253. }
  1254. }
  1255. function call($methodname, $args)
  1256. {
  1257. if (!$this->hasMethod($methodname)) {
  1258. return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
  1259. }
  1260. $method = $this->callbacks[$methodname];
  1261. // Perform the callback and send the response
  1262. if (count($args) == 1) {
  1263. // If only one paramater just send that instead of the whole array
  1264. $args = $args[0];
  1265. }
  1266. // See if this method comes from one of our objects or maybe self
  1267. if (is_array($method) || (substr($method, 0, 5) == 'this:')) {
  1268. if (is_array($method)) {
  1269. $object=$this->_objects[$method[0]];
  1270. $method=$method[1];
  1271. } else {
  1272. $object=$this;
  1273. $method = substr($method, 5);
  1274. }
  1275. // It's a class method - check it exists
  1276. if (!method_exists($object, $method)) {
  1277. return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
  1278. }
  1279. // Call the method
  1280. $result = $object->$method($args);
  1281. } else {
  1282. // It's a function - does it exist?
  1283. if (!function_exists($method)) {
  1284. return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
  1285. }
  1286. // Call the function
  1287. $result = $method($args);
  1288. }
  1289. return $result;
  1290. }
  1291. }
  1292. ?>