1<?php 2/** 3 * Zend Framework 4 * 5 * LICENSE 6 * 7 * This source file is subject to the new BSD license that is bundled 8 * with this package in the file LICENSE.txt. 9 * It is also available through the world-wide-web at this URL: 10 * http://framework.zend.com/license/new-bsd 11 * If you did not receive a copy of the license and are unable to 12 * obtain it through the world-wide-web, please send an email 13 * to license@zend.com so we can send you a copy immediately. 14 * 15 * @category Zend 16 * @package Zend_XmlRpc 17 * @subpackage Server 18 * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) 19 * @license http://framework.zend.com/license/new-bsd New BSD License 20 * @version $Id: Server.php 23775 2011-03-01 17:25:24Z ralph $ 21 */ 22 23/** 24 * Extends Zend_Server_Abstract 25 */ 26require_once 'Zend/Server/Abstract.php'; 27 28/** 29 * XMLRPC Request 30 */ 31require_once 'Zend/XmlRpc/Request.php'; 32 33/** 34 * XMLRPC Response 35 */ 36require_once 'Zend/XmlRpc/Response.php'; 37 38/** 39 * XMLRPC HTTP Response 40 */ 41require_once 'Zend/XmlRpc/Response/Http.php'; 42 43/** 44 * XMLRPC server fault class 45 */ 46require_once 'Zend/XmlRpc/Server/Fault.php'; 47 48/** 49 * XMLRPC server system methods class 50 */ 51require_once 'Zend/XmlRpc/Server/System.php'; 52 53/** 54 * Convert PHP to and from xmlrpc native types 55 */ 56require_once 'Zend/XmlRpc/Value.php'; 57 58/** 59 * Reflection API for function/method introspection 60 */ 61require_once 'Zend/Server/Reflection.php'; 62 63/** 64 * Zend_Server_Reflection_Function_Abstract 65 */ 66require_once 'Zend/Server/Reflection/Function/Abstract.php'; 67 68/** 69 * Specifically grab the Zend_Server_Reflection_Method for manually setting up 70 * system.* methods and handling callbacks in {@link loadFunctions()}. 71 */ 72require_once 'Zend/Server/Reflection/Method.php'; 73 74/** 75 * An XML-RPC server implementation 76 * 77 * Example: 78 * <code> 79 * require_once 'Zend/XmlRpc/Server.php'; 80 * require_once 'Zend/XmlRpc/Server/Cache.php'; 81 * require_once 'Zend/XmlRpc/Server/Fault.php'; 82 * require_once 'My/Exception.php'; 83 * require_once 'My/Fault/Observer.php'; 84 * 85 * // Instantiate server 86 * $server = new Zend_XmlRpc_Server(); 87 * 88 * // Allow some exceptions to report as fault responses: 89 * Zend_XmlRpc_Server_Fault::attachFaultException('My_Exception'); 90 * Zend_XmlRpc_Server_Fault::attachObserver('My_Fault_Observer'); 91 * 92 * // Get or build dispatch table: 93 * if (!Zend_XmlRpc_Server_Cache::get($filename, $server)) { 94 * require_once 'Some/Service/Class.php'; 95 * require_once 'Another/Service/Class.php'; 96 * 97 * // Attach Some_Service_Class in 'some' namespace 98 * $server->setClass('Some_Service_Class', 'some'); 99 * 100 * // Attach Another_Service_Class in 'another' namespace 101 * $server->setClass('Another_Service_Class', 'another'); 102 * 103 * // Create dispatch table cache file 104 * Zend_XmlRpc_Server_Cache::save($filename, $server); 105 * } 106 * 107 * $response = $server->handle(); 108 * echo $response; 109 * </code> 110 * 111 * @category Zend 112 * @package Zend_XmlRpc 113 * @subpackage Server 114 * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) 115 * @license http://framework.zend.com/license/new-bsd New BSD License 116 */ 117class Zend_XmlRpc_Server extends Zend_Server_Abstract 118{ 119 /** 120 * Character encoding 121 * @var string 122 */ 123 protected $_encoding = 'UTF-8'; 124 125 /** 126 * Request processed 127 * @var null|Zend_XmlRpc_Request 128 */ 129 protected $_request = null; 130 131 /** 132 * Class to use for responses; defaults to {@link Zend_XmlRpc_Response_Http} 133 * @var string 134 */ 135 protected $_responseClass = 'Zend_XmlRpc_Response_Http'; 136 137 /** 138 * Dispatch table of name => method pairs 139 * @var Zend_Server_Definition 140 */ 141 protected $_table; 142 143 /** 144 * PHP types => XML-RPC types 145 * @var array 146 */ 147 protected $_typeMap = array( 148 'i4' => 'i4', 149 'int' => 'int', 150 'integer' => 'int', 151 'Zend_Crypt_Math_BigInteger' => 'i8', 152 'i8' => 'i8', 153 'ex:i8' => 'i8', 154 'double' => 'double', 155 'float' => 'double', 156 'real' => 'double', 157 'boolean' => 'boolean', 158 'bool' => 'boolean', 159 'true' => 'boolean', 160 'false' => 'boolean', 161 'string' => 'string', 162 'str' => 'string', 163 'base64' => 'base64', 164 'dateTime.iso8601' => 'dateTime.iso8601', 165 'date' => 'dateTime.iso8601', 166 'time' => 'dateTime.iso8601', 167 'Zend_Date' => 'dateTime.iso8601', 168 'DateTime' => 'dateTime.iso8601', 169 'array' => 'array', 170 'struct' => 'struct', 171 'null' => 'nil', 172 'nil' => 'nil', 173 'ex:nil' => 'nil', 174 'void' => 'void', 175 'mixed' => 'struct', 176 ); 177 178 /** 179 * Send arguments to all methods or just constructor? 180 * 181 * @var bool 182 */ 183 protected $_sendArgumentsToAllMethods = true; 184 185 /** 186 * Constructor 187 * 188 * Creates system.* methods. 189 * 190 * @return void 191 */ 192 public function __construct() 193 { 194 $this->_table = new Zend_Server_Definition(); 195 $this->_registerSystemMethods(); 196 } 197 198 /** 199 * Proxy calls to system object 200 * 201 * @param string $method 202 * @param array $params 203 * @return mixed 204 * @throws Zend_XmlRpc_Server_Exception 205 */ 206 public function __call($method, $params) 207 { 208 $system = $this->getSystem(); 209 if (!method_exists($system, $method)) { 210 require_once 'Zend/XmlRpc/Server/Exception.php'; 211 throw new Zend_XmlRpc_Server_Exception('Unknown instance method called on server: '.$method); 212 } 213 return call_user_func_array(array($system, $method), $params); 214 } 215 216 /** 217 * Attach a callback as an XMLRPC method 218 * 219 * Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name 220 * with $namespace, if provided. Reflection is done on the callback's 221 * docblock to create the methodHelp for the XMLRPC method. 222 * 223 * Additional arguments to pass to the function at dispatch may be passed; 224 * any arguments following the namespace will be aggregated and passed at 225 * dispatch time. 226 * 227 * @param string|array $function Valid callback 228 * @param string $namespace Optional namespace prefix 229 * @return void 230 * @throws Zend_XmlRpc_Server_Exception 231 */ 232 public function addFunction($function, $namespace = '') 233 { 234 if (!is_string($function) && !is_array($function)) { 235 require_once 'Zend/XmlRpc/Server/Exception.php'; 236 throw new Zend_XmlRpc_Server_Exception('Unable to attach function; invalid', 611); 237 } 238 239 $argv = null; 240 if (2 < func_num_args()) { 241 $argv = func_get_args(); 242 $argv = array_slice($argv, 2); 243 } 244 245 $function = (array) $function; 246 foreach ($function as $func) { 247 if (!is_string($func) || !function_exists($func)) { 248 require_once 'Zend/XmlRpc/Server/Exception.php'; 249 throw new Zend_XmlRpc_Server_Exception('Unable to attach function; invalid', 611); 250 } 251 $reflection = Zend_Server_Reflection::reflectFunction($func, $argv, $namespace); 252 $this->_buildSignature($reflection); 253 } 254 } 255 256 /** 257 * Attach class methods as XMLRPC method handlers 258 * 259 * $class may be either a class name or an object. Reflection is done on the 260 * class or object to determine the available public methods, and each is 261 * attached to the server as an available method; if a $namespace has been 262 * provided, that namespace is used to prefix the XMLRPC method names. 263 * 264 * Any additional arguments beyond $namespace will be passed to a method at 265 * invocation. 266 * 267 * @param string|object $class 268 * @param string $namespace Optional 269 * @param mixed $argv Optional arguments to pass to methods 270 * @return void 271 * @throws Zend_XmlRpc_Server_Exception on invalid input 272 */ 273 public function setClass($class, $namespace = '', $argv = null) 274 { 275 if (is_string($class) && !class_exists($class)) { 276 require_once 'Zend/XmlRpc/Server/Exception.php'; 277 throw new Zend_XmlRpc_Server_Exception('Invalid method class', 610); 278 } 279 280 $argv = null; 281 if (2 < func_num_args()) { 282 $argv = func_get_args(); 283 $argv = array_slice($argv, 2); 284 } 285 286 $dispatchable = Zend_Server_Reflection::reflectClass($class, $argv, $namespace); 287 foreach ($dispatchable->getMethods() as $reflection) { 288 $this->_buildSignature($reflection, $class); 289 } 290 } 291 292 /** 293 * Raise an xmlrpc server fault 294 * 295 * @param string|Exception $fault 296 * @param int $code 297 * @return Zend_XmlRpc_Server_Fault 298 */ 299 public function fault($fault = null, $code = 404) 300 { 301 if (!$fault instanceof Exception) { 302 $fault = (string) $fault; 303 if (empty($fault)) { 304 $fault = 'Unknown Error'; 305 } 306 require_once 'Zend/XmlRpc/Server/Exception.php'; 307 $fault = new Zend_XmlRpc_Server_Exception($fault, $code); 308 } 309 310 return Zend_XmlRpc_Server_Fault::getInstance($fault); 311 } 312 313 /** 314 * Handle an xmlrpc call 315 * 316 * @param Zend_XmlRpc_Request $request Optional 317 * @return Zend_XmlRpc_Response|Zend_XmlRpc_Fault 318 */ 319 public function handle($request = false) 320 { 321 // Get request 322 if ((!$request || !$request instanceof Zend_XmlRpc_Request) 323 && (null === ($request = $this->getRequest())) 324 ) { 325 require_once 'Zend/XmlRpc/Request/Http.php'; 326 $request = new Zend_XmlRpc_Request_Http(); 327 $request->setEncoding($this->getEncoding()); 328 } 329 330 $this->setRequest($request); 331 332 if ($request->isFault()) { 333 $response = $request->getFault(); 334 } else { 335 try { 336 $response = $this->_handle($request); 337 } catch (Exception $e) { 338 $response = $this->fault($e); 339 } 340 } 341 342 // Set output encoding 343 $response->setEncoding($this->getEncoding()); 344 345 return $response; 346 } 347 348 /** 349 * Load methods as returned from {@link getFunctions} 350 * 351 * Typically, you will not use this method; it will be called using the 352 * results pulled from {@link Zend_XmlRpc_Server_Cache::get()}. 353 * 354 * @param array|Zend_Server_Definition $definition 355 * @return void 356 * @throws Zend_XmlRpc_Server_Exception on invalid input 357 */ 358 public function loadFunctions($definition) 359 { 360 if (!is_array($definition) && (!$definition instanceof Zend_Server_Definition)) { 361 if (is_object($definition)) { 362 $type = get_class($definition); 363 } else { 364 $type = gettype($definition); 365 } 366 require_once 'Zend/XmlRpc/Server/Exception.php'; 367 throw new Zend_XmlRpc_Server_Exception('Unable to load server definition; must be an array or Zend_Server_Definition, received '.$type, 612); 368 } 369 370 $this->_table->clearMethods(); 371 $this->_registerSystemMethods(); 372 373 if ($definition instanceof Zend_Server_Definition) { 374 $definition = $definition->getMethods(); 375 } 376 377 foreach ($definition as $key => $method) { 378 if ('system.' == substr($key, 0, 7)) { 379 continue; 380 } 381 $this->_table->addMethod($method, $key); 382 } 383 } 384 385 /** 386 * Set encoding 387 * 388 * @param string $encoding 389 * @return Zend_XmlRpc_Server 390 */ 391 public function setEncoding($encoding) 392 { 393 $this->_encoding = $encoding; 394 Zend_XmlRpc_Value::setEncoding($encoding); 395 return $this; 396 } 397 398 /** 399 * Retrieve current encoding 400 * 401 * @return string 402 */ 403 public function getEncoding() 404 { 405 return $this->_encoding; 406 } 407 408 /** 409 * Do nothing; persistence is handled via {@link Zend_XmlRpc_Server_Cache} 410 * 411 * @param mixed $mode 412 * @return void 413 */ 414 public function setPersistence($mode) 415 { 416 } 417 418 /** 419 * Set the request object 420 * 421 * @param string|Zend_XmlRpc_Request $request 422 * @return Zend_XmlRpc_Server 423 * @throws Zend_XmlRpc_Server_Exception on invalid request class or object 424 */ 425 public function setRequest($request) 426 { 427 if (is_string($request) && class_exists($request)) { 428 $request = new $request(); 429 if (!$request instanceof Zend_XmlRpc_Request) { 430 require_once 'Zend/XmlRpc/Server/Exception.php'; 431 throw new Zend_XmlRpc_Server_Exception('Invalid request class'); 432 } 433 $request->setEncoding($this->getEncoding()); 434 } elseif (!$request instanceof Zend_XmlRpc_Request) { 435 require_once 'Zend/XmlRpc/Server/Exception.php'; 436 throw new Zend_XmlRpc_Server_Exception('Invalid request object'); 437 } 438 439 $this->_request = $request; 440 return $this; 441 } 442 443 /** 444 * Return currently registered request object 445 * 446 * @return null|Zend_XmlRpc_Request 447 */ 448 public function getRequest() 449 { 450 return $this->_request; 451 } 452 453 /** 454 * Set the class to use for the response 455 * 456 * @param string $class 457 * @return boolean True if class was set, false if not 458 */ 459 public function setResponseClass($class) 460 { 461 if (!class_exists($class) or 462 ($c = new ReflectionClass($class) and !$c->isSubclassOf('Zend_XmlRpc_Response'))) { 463 464 require_once 'Zend/XmlRpc/Server/Exception.php'; 465 throw new Zend_XmlRpc_Server_Exception('Invalid response class'); 466 } 467 $this->_responseClass = $class; 468 return true; 469 } 470 471 /** 472 * Retrieve current response class 473 * 474 * @return string 475 */ 476 public function getResponseClass() 477 { 478 return $this->_responseClass; 479 } 480 481 /** 482 * Retrieve dispatch table 483 * 484 * @return Zend_Server_Definition 485 */ 486 public function getDispatchTable() 487 { 488 return $this->_table; 489 } 490 491 /** 492 * Returns a list of registered methods 493 * 494 * Returns an array of dispatchables (Zend_Server_Reflection_Function, 495 * _Method, and _Class items). 496 * 497 * @return array 498 */ 499 public function getFunctions() 500 { 501 return $this->_table->toArray(); 502 } 503 504 /** 505 * Retrieve system object 506 * 507 * @return Zend_XmlRpc_Server_System 508 */ 509 public function getSystem() 510 { 511 return $this->_system; 512 } 513 514 /** 515 * Send arguments to all methods? 516 * 517 * If setClass() is used to add classes to the server, this flag defined 518 * how to handle arguments. If set to true, all methods including constructor 519 * will receive the arguments. If set to false, only constructor will receive the 520 * arguments 521 * @param boolean $flag 522 */ 523 public function sendArgumentsToAllMethods($flag = null) 524 { 525 if ($flag === null) { 526 return $this->_sendArgumentsToAllMethods; 527 } 528 529 $this->_sendArgumentsToAllMethods = (bool) $flag; 530 return $this; 531 } 532 533 /** 534 * Map PHP type to XML-RPC type 535 * 536 * @param string $type 537 * @return string 538 */ 539 protected function _fixType($type) 540 { 541 if (isset($this->_typeMap[$type])) { 542 return $this->_typeMap[$type]; 543 } 544 return 'void'; 545 } 546 547 /** 548 * Handle an xmlrpc call (actual work) 549 * 550 * @param Zend_XmlRpc_Request $request 551 * @return Zend_XmlRpc_Response 552 * @throws Zend_XmlRpcServer_Exception|Exception 553 * Zend_XmlRpcServer_Exceptions are thrown for internal errors; otherwise, 554 * any other exception may be thrown by the callback 555 */ 556 protected function _handle(Zend_XmlRpc_Request $request) 557 { 558 $method = $request->getMethod(); 559 560 // Check for valid method 561 if (!$this->_table->hasMethod($method)) { 562 require_once 'Zend/XmlRpc/Server/Exception.php'; 563 throw new Zend_XmlRpc_Server_Exception('Method "'.$method.'" does not exist', 620); 564 } 565 566 $info = $this->_table->getMethod($method); 567 $params = $request->getParams(); 568 $argv = $info->getInvokeArguments(); 569 if (0 < count($argv) and $this->sendArgumentsToAllMethods()) { 570 $params = array_merge($params, $argv); 571 } 572 573 // Check calling parameters against signatures 574 $matched = false; 575 $sigCalled = $request->getTypes(); 576 577 $sigLength = count($sigCalled); 578 $paramsLen = count($params); 579 if ($sigLength < $paramsLen) { 580 for ($i = $sigLength; $i < $paramsLen; ++$i) { 581 $xmlRpcValue = Zend_XmlRpc_Value::getXmlRpcValue($params[$i]); 582 $sigCalled[] = $xmlRpcValue->getType(); 583 } 584 } 585 586 $signatures = $info->getPrototypes(); 587 foreach ($signatures as $signature) { 588 $sigParams = $signature->getParameters(); 589 if ($sigCalled === $sigParams) { 590 $matched = true; 591 break; 592 } 593 } 594 if (!$matched) { 595 require_once 'Zend/XmlRpc/Server/Exception.php'; 596 throw new Zend_XmlRpc_Server_Exception('Calling parameters do not match signature', 623); 597 } 598 599 $return = $this->_dispatch($info, $params); 600 $responseClass = $this->getResponseClass(); 601 return new $responseClass($return); 602 } 603 604 /** 605 * Register system methods with the server 606 * 607 * @return void 608 */ 609 protected function _registerSystemMethods() 610 { 611 $system = new Zend_XmlRpc_Server_System($this); 612 $this->_system = $system; 613 $this->setClass($system, 'system'); 614 } 615} 616