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