|
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-2010 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 20096 2010-01-06 02:05:09Z bkarwin $ |
|
21 */ |
|
22 |
|
23 /** |
|
24 * Extends Zend_Server_Abstract |
|
25 */ |
|
26 require_once 'Zend/Server/Abstract.php'; |
|
27 |
|
28 /** |
|
29 * XMLRPC Request |
|
30 */ |
|
31 require_once 'Zend/XmlRpc/Request.php'; |
|
32 |
|
33 /** |
|
34 * XMLRPC Response |
|
35 */ |
|
36 require_once 'Zend/XmlRpc/Response.php'; |
|
37 |
|
38 /** |
|
39 * XMLRPC HTTP Response |
|
40 */ |
|
41 require_once 'Zend/XmlRpc/Response/Http.php'; |
|
42 |
|
43 /** |
|
44 * XMLRPC server fault class |
|
45 */ |
|
46 require_once 'Zend/XmlRpc/Server/Fault.php'; |
|
47 |
|
48 /** |
|
49 * XMLRPC server system methods class |
|
50 */ |
|
51 require_once 'Zend/XmlRpc/Server/System.php'; |
|
52 |
|
53 /** |
|
54 * Convert PHP to and from xmlrpc native types |
|
55 */ |
|
56 require_once 'Zend/XmlRpc/Value.php'; |
|
57 |
|
58 /** |
|
59 * Reflection API for function/method introspection |
|
60 */ |
|
61 require_once 'Zend/Server/Reflection.php'; |
|
62 |
|
63 /** |
|
64 * Zend_Server_Reflection_Function_Abstract |
|
65 */ |
|
66 require_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 */ |
|
72 require_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-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
115 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
116 */ |
|
117 class 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 'time' => 'dateTime.iso8601', |
|
168 'Zend_Date' => 'dateTime.iso8601', |
|
169 'DateTime' => 'dateTime.iso8601', |
|
170 'array' => 'array', |
|
171 'struct' => 'struct', |
|
172 'null' => 'nil', |
|
173 'nil' => 'nil', |
|
174 'ex:nil' => 'nil', |
|
175 'void' => 'void', |
|
176 'mixed' => 'struct', |
|
177 ); |
|
178 |
|
179 /** |
|
180 * Send arguments to all methods or just constructor? |
|
181 * |
|
182 * @var bool |
|
183 */ |
|
184 protected $_sendArgumentsToAllMethods = true; |
|
185 |
|
186 /** |
|
187 * Constructor |
|
188 * |
|
189 * Creates system.* methods. |
|
190 * |
|
191 * @return void |
|
192 */ |
|
193 public function __construct() |
|
194 { |
|
195 $this->_table = new Zend_Server_Definition(); |
|
196 $this->_registerSystemMethods(); |
|
197 } |
|
198 |
|
199 /** |
|
200 * Proxy calls to system object |
|
201 * |
|
202 * @param string $method |
|
203 * @param array $params |
|
204 * @return mixed |
|
205 * @throws Zend_XmlRpc_Server_Exception |
|
206 */ |
|
207 public function __call($method, $params) |
|
208 { |
|
209 $system = $this->getSystem(); |
|
210 if (!method_exists($system, $method)) { |
|
211 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
212 throw new Zend_XmlRpc_Server_Exception('Unknown instance method called on server: ' . $method); |
|
213 } |
|
214 return call_user_func_array(array($system, $method), $params); |
|
215 } |
|
216 |
|
217 /** |
|
218 * Attach a callback as an XMLRPC method |
|
219 * |
|
220 * Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name |
|
221 * with $namespace, if provided. Reflection is done on the callback's |
|
222 * docblock to create the methodHelp for the XMLRPC method. |
|
223 * |
|
224 * Additional arguments to pass to the function at dispatch may be passed; |
|
225 * any arguments following the namespace will be aggregated and passed at |
|
226 * dispatch time. |
|
227 * |
|
228 * @param string|array $function Valid callback |
|
229 * @param string $namespace Optional namespace prefix |
|
230 * @return void |
|
231 * @throws Zend_XmlRpc_Server_Exception |
|
232 */ |
|
233 public function addFunction($function, $namespace = '') |
|
234 { |
|
235 if (!is_string($function) && !is_array($function)) { |
|
236 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
237 throw new Zend_XmlRpc_Server_Exception('Unable to attach function; invalid', 611); |
|
238 } |
|
239 |
|
240 $argv = null; |
|
241 if (2 < func_num_args()) { |
|
242 $argv = func_get_args(); |
|
243 $argv = array_slice($argv, 2); |
|
244 } |
|
245 |
|
246 $function = (array) $function; |
|
247 foreach ($function as $func) { |
|
248 if (!is_string($func) || !function_exists($func)) { |
|
249 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
250 throw new Zend_XmlRpc_Server_Exception('Unable to attach function; invalid', 611); |
|
251 } |
|
252 $reflection = Zend_Server_Reflection::reflectFunction($func, $argv, $namespace); |
|
253 $this->_buildSignature($reflection); |
|
254 } |
|
255 } |
|
256 |
|
257 /** |
|
258 * Attach class methods as XMLRPC method handlers |
|
259 * |
|
260 * $class may be either a class name or an object. Reflection is done on the |
|
261 * class or object to determine the available public methods, and each is |
|
262 * attached to the server as an available method; if a $namespace has been |
|
263 * provided, that namespace is used to prefix the XMLRPC method names. |
|
264 * |
|
265 * Any additional arguments beyond $namespace will be passed to a method at |
|
266 * invocation. |
|
267 * |
|
268 * @param string|object $class |
|
269 * @param string $namespace Optional |
|
270 * @param mixed $argv Optional arguments to pass to methods |
|
271 * @return void |
|
272 * @throws Zend_XmlRpc_Server_Exception on invalid input |
|
273 */ |
|
274 public function setClass($class, $namespace = '', $argv = null) |
|
275 { |
|
276 if (is_string($class) && !class_exists($class)) { |
|
277 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
278 throw new Zend_XmlRpc_Server_Exception('Invalid method class', 610); |
|
279 } |
|
280 |
|
281 $argv = null; |
|
282 if (2 < func_num_args()) { |
|
283 $argv = func_get_args(); |
|
284 $argv = array_slice($argv, 2); |
|
285 } |
|
286 |
|
287 $dispatchable = Zend_Server_Reflection::reflectClass($class, $argv, $namespace); |
|
288 foreach ($dispatchable->getMethods() as $reflection) { |
|
289 $this->_buildSignature($reflection, $class); |
|
290 } |
|
291 } |
|
292 |
|
293 /** |
|
294 * Raise an xmlrpc server fault |
|
295 * |
|
296 * @param string|Exception $fault |
|
297 * @param int $code |
|
298 * @return Zend_XmlRpc_Server_Fault |
|
299 */ |
|
300 public function fault($fault = null, $code = 404) |
|
301 { |
|
302 if (!$fault instanceof Exception) { |
|
303 $fault = (string) $fault; |
|
304 if (empty($fault)) { |
|
305 $fault = 'Unknown Error'; |
|
306 } |
|
307 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
308 $fault = new Zend_XmlRpc_Server_Exception($fault, $code); |
|
309 } |
|
310 |
|
311 return Zend_XmlRpc_Server_Fault::getInstance($fault); |
|
312 } |
|
313 |
|
314 /** |
|
315 * Handle an xmlrpc call |
|
316 * |
|
317 * @param Zend_XmlRpc_Request $request Optional |
|
318 * @return Zend_XmlRpc_Response|Zend_XmlRpc_Fault |
|
319 */ |
|
320 public function handle($request = false) |
|
321 { |
|
322 // Get request |
|
323 if ((!$request || !$request instanceof Zend_XmlRpc_Request) |
|
324 && (null === ($request = $this->getRequest())) |
|
325 ) { |
|
326 require_once 'Zend/XmlRpc/Request/Http.php'; |
|
327 $request = new Zend_XmlRpc_Request_Http(); |
|
328 $request->setEncoding($this->getEncoding()); |
|
329 } |
|
330 |
|
331 $this->setRequest($request); |
|
332 |
|
333 if ($request->isFault()) { |
|
334 $response = $request->getFault(); |
|
335 } else { |
|
336 try { |
|
337 $response = $this->_handle($request); |
|
338 } catch (Exception $e) { |
|
339 $response = $this->fault($e); |
|
340 } |
|
341 } |
|
342 |
|
343 // Set output encoding |
|
344 $response->setEncoding($this->getEncoding()); |
|
345 |
|
346 return $response; |
|
347 } |
|
348 |
|
349 /** |
|
350 * Load methods as returned from {@link getFunctions} |
|
351 * |
|
352 * Typically, you will not use this method; it will be called using the |
|
353 * results pulled from {@link Zend_XmlRpc_Server_Cache::get()}. |
|
354 * |
|
355 * @param array|Zend_Server_Definition $definition |
|
356 * @return void |
|
357 * @throws Zend_XmlRpc_Server_Exception on invalid input |
|
358 */ |
|
359 public function loadFunctions($definition) |
|
360 { |
|
361 if (!is_array($definition) && (!$definition instanceof Zend_Server_Definition)) { |
|
362 if (is_object($definition)) { |
|
363 $type = get_class($definition); |
|
364 } else { |
|
365 $type = gettype($definition); |
|
366 } |
|
367 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
368 throw new Zend_XmlRpc_Server_Exception('Unable to load server definition; must be an array or Zend_Server_Definition, received ' . $type, 612); |
|
369 } |
|
370 |
|
371 $this->_table->clearMethods(); |
|
372 $this->_registerSystemMethods(); |
|
373 |
|
374 if ($definition instanceof Zend_Server_Definition) { |
|
375 $definition = $definition->getMethods(); |
|
376 } |
|
377 |
|
378 foreach ($definition as $key => $method) { |
|
379 if ('system.' == substr($key, 0, 7)) { |
|
380 continue; |
|
381 } |
|
382 $this->_table->addMethod($method, $key); |
|
383 } |
|
384 } |
|
385 |
|
386 /** |
|
387 * Set encoding |
|
388 * |
|
389 * @param string $encoding |
|
390 * @return Zend_XmlRpc_Server |
|
391 */ |
|
392 public function setEncoding($encoding) |
|
393 { |
|
394 $this->_encoding = $encoding; |
|
395 Zend_XmlRpc_Value::setEncoding($encoding); |
|
396 return $this; |
|
397 } |
|
398 |
|
399 /** |
|
400 * Retrieve current encoding |
|
401 * |
|
402 * @return string |
|
403 */ |
|
404 public function getEncoding() |
|
405 { |
|
406 return $this->_encoding; |
|
407 } |
|
408 |
|
409 /** |
|
410 * Do nothing; persistence is handled via {@link Zend_XmlRpc_Server_Cache} |
|
411 * |
|
412 * @param mixed $mode |
|
413 * @return void |
|
414 */ |
|
415 public function setPersistence($mode) |
|
416 { |
|
417 } |
|
418 |
|
419 /** |
|
420 * Set the request object |
|
421 * |
|
422 * @param string|Zend_XmlRpc_Request $request |
|
423 * @return Zend_XmlRpc_Server |
|
424 * @throws Zend_XmlRpc_Server_Exception on invalid request class or object |
|
425 */ |
|
426 public function setRequest($request) |
|
427 { |
|
428 if (is_string($request) && class_exists($request)) { |
|
429 $request = new $request(); |
|
430 if (!$request instanceof Zend_XmlRpc_Request) { |
|
431 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
432 throw new Zend_XmlRpc_Server_Exception('Invalid request class'); |
|
433 } |
|
434 $request->setEncoding($this->getEncoding()); |
|
435 } elseif (!$request instanceof Zend_XmlRpc_Request) { |
|
436 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
437 throw new Zend_XmlRpc_Server_Exception('Invalid request object'); |
|
438 } |
|
439 |
|
440 $this->_request = $request; |
|
441 return $this; |
|
442 } |
|
443 |
|
444 /** |
|
445 * Return currently registered request object |
|
446 * |
|
447 * @return null|Zend_XmlRpc_Request |
|
448 */ |
|
449 public function getRequest() |
|
450 { |
|
451 return $this->_request; |
|
452 } |
|
453 |
|
454 /** |
|
455 * Set the class to use for the response |
|
456 * |
|
457 * @param string $class |
|
458 * @return boolean True if class was set, false if not |
|
459 */ |
|
460 public function setResponseClass($class) |
|
461 { |
|
462 if (!class_exists($class) or |
|
463 ($c = new ReflectionClass($class) and !$c->isSubclassOf('Zend_XmlRpc_Response'))) { |
|
464 |
|
465 require_once 'Zend/XmlRpc/Server/Exception.php'; |
|
466 throw new Zend_XmlRpc_Server_Exception('Invalid response class'); |
|
467 } |
|
468 $this->_responseClass = $class; |
|
469 return true; |
|
470 } |
|
471 |
|
472 /** |
|
473 * Retrieve current response class |
|
474 * |
|
475 * @return string |
|
476 */ |
|
477 public function getResponseClass() |
|
478 { |
|
479 return $this->_responseClass; |
|
480 } |
|
481 |
|
482 /** |
|
483 * Retrieve dispatch table |
|
484 * |
|
485 * @return array |
|
486 */ |
|
487 public function getDispatchTable() |
|
488 { |
|
489 return $this->_table; |
|
490 } |
|
491 |
|
492 /** |
|
493 * Returns a list of registered methods |
|
494 * |
|
495 * Returns an array of dispatchables (Zend_Server_Reflection_Function, |
|
496 * _Method, and _Class items). |
|
497 * |
|
498 * @return array |
|
499 */ |
|
500 public function getFunctions() |
|
501 { |
|
502 return $this->_table->toArray(); |
|
503 } |
|
504 |
|
505 /** |
|
506 * Retrieve system object |
|
507 * |
|
508 * @return Zend_XmlRpc_Server_System |
|
509 */ |
|
510 public function getSystem() |
|
511 { |
|
512 return $this->_system; |
|
513 } |
|
514 |
|
515 /** |
|
516 * Send arguments to all methods? |
|
517 * |
|
518 * If setClass() is used to add classes to the server, this flag defined |
|
519 * how to handle arguments. If set to true, all methods including constructor |
|
520 * will receive the arguments. If set to false, only constructor will receive the |
|
521 * arguments |
|
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 } |