KXStudio Website https://kx.studio/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1344 lines
39KB

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