These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | use dokuwiki\HTTP\DokuHTTPClient; |
||
0 ignored issues
–
show
|
|||
4 | |||
5 | /** |
||
6 | * IXR - The Incutio XML-RPC Library |
||
7 | * |
||
8 | * Copyright (c) 2010, Incutio Ltd. |
||
9 | * All rights reserved. |
||
10 | * |
||
11 | * Redistribution and use in source and binary forms, with or without |
||
12 | * modification, are permitted provided that the following conditions are met: |
||
13 | * |
||
14 | * - Redistributions of source code must retain the above copyright notice, |
||
15 | * this list of conditions and the following disclaimer. |
||
16 | * - Redistributions in binary form must reproduce the above copyright |
||
17 | * notice, this list of conditions and the following disclaimer in the |
||
18 | * documentation and/or other materials provided with the distribution. |
||
19 | * - Neither the name of Incutio Ltd. nor the names of its contributors |
||
20 | * may be used to endorse or promote products derived from this software |
||
21 | * without specific prior written permission. |
||
22 | * |
||
23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
||
24 | * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
||
25 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||
26 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
||
27 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||
28 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||
29 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||
30 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
||
31 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
||
33 | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
34 | * |
||
35 | * @package IXR |
||
36 | * @since 1.5 |
||
37 | * |
||
38 | * @copyright Incutio Ltd 2010 (http://www.incutio.com) |
||
39 | * @version 1.7.4 7th September 2010 |
||
40 | * @author Simon Willison |
||
41 | * @link http://scripts.incutio.com/xmlrpc/ Site/manual |
||
42 | * |
||
43 | * Modified for DokuWiki |
||
44 | * @author Andreas Gohr <[email protected]> |
||
45 | */ |
||
46 | class IXR_Value { |
||
47 | |||
48 | /** @var IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */ |
||
49 | var $data; |
||
50 | /** @var string */ |
||
51 | var $type; |
||
52 | |||
53 | /** |
||
54 | * @param mixed $data |
||
55 | * @param bool $type |
||
56 | */ |
||
57 | function __construct($data, $type = false) { |
||
58 | $this->data = $data; |
||
59 | if(!$type) { |
||
60 | $type = $this->calculateType(); |
||
61 | } |
||
62 | $this->type = $type; |
||
63 | if($type == 'struct') { |
||
64 | // Turn all the values in the array in to new IXR_Value objects |
||
65 | foreach($this->data as $key => $value) { |
||
66 | $this->data[$key] = new IXR_Value($value); |
||
67 | } |
||
68 | } |
||
69 | if($type == 'array') { |
||
70 | for($i = 0, $j = count($this->data); $i < $j; $i++) { |
||
71 | $this->data[$i] = new IXR_Value($this->data[$i]); |
||
72 | } |
||
73 | } |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * @return string |
||
78 | */ |
||
79 | function calculateType() { |
||
80 | if($this->data === true || $this->data === false) { |
||
81 | return 'boolean'; |
||
82 | } |
||
83 | if(is_integer($this->data)) { |
||
84 | return 'int'; |
||
85 | } |
||
86 | if(is_double($this->data)) { |
||
87 | return 'double'; |
||
88 | } |
||
89 | |||
90 | // Deal with IXR object types base64 and date |
||
91 | if(is_object($this->data) && is_a($this->data, 'IXR_Date')) { |
||
92 | return 'date'; |
||
93 | } |
||
94 | if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) { |
||
95 | return 'base64'; |
||
96 | } |
||
97 | |||
98 | // If it is a normal PHP object convert it in to a struct |
||
99 | if(is_object($this->data)) { |
||
100 | $this->data = get_object_vars($this->data); |
||
101 | return 'struct'; |
||
102 | } |
||
103 | if(!is_array($this->data)) { |
||
104 | return 'string'; |
||
105 | } |
||
106 | |||
107 | // We have an array - is it an array or a struct? |
||
108 | if($this->isStruct($this->data)) { |
||
109 | return 'struct'; |
||
110 | } else { |
||
111 | return 'array'; |
||
112 | } |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * @return bool|string |
||
117 | */ |
||
118 | function getXml() { |
||
119 | // Return XML for this value |
||
120 | switch($this->type) { |
||
121 | case 'boolean': |
||
122 | return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>'; |
||
123 | break; |
||
124 | case 'int': |
||
125 | return '<int>' . $this->data . '</int>'; |
||
126 | break; |
||
127 | case 'double': |
||
128 | return '<double>' . $this->data . '</double>'; |
||
129 | break; |
||
130 | case 'string': |
||
131 | return '<string>' . htmlspecialchars($this->data) . '</string>'; |
||
132 | break; |
||
133 | case 'array': |
||
134 | $return = '<array><data>' . "\n"; |
||
135 | foreach($this->data as $item) { |
||
136 | $return .= ' <value>' . $item->getXml() . "</value>\n"; |
||
137 | } |
||
138 | $return .= '</data></array>'; |
||
139 | return $return; |
||
140 | break; |
||
141 | case 'struct': |
||
142 | $return = '<struct>' . "\n"; |
||
143 | foreach($this->data as $name => $value) { |
||
144 | $return .= " <member><name>$name</name><value>"; |
||
145 | $return .= $value->getXml() . "</value></member>\n"; |
||
146 | } |
||
147 | $return .= '</struct>'; |
||
148 | return $return; |
||
149 | break; |
||
150 | case 'date': |
||
151 | case 'base64': |
||
152 | return $this->data->getXml(); |
||
153 | break; |
||
154 | } |
||
155 | return false; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Checks whether or not the supplied array is a struct or not |
||
160 | * |
||
161 | * @param array $array |
||
162 | * @return boolean |
||
163 | */ |
||
164 | function isStruct($array) { |
||
165 | $expected = 0; |
||
166 | foreach($array as $key => $value) { |
||
167 | if((string) $key != (string) $expected) { |
||
168 | return true; |
||
169 | } |
||
170 | $expected++; |
||
171 | } |
||
172 | return false; |
||
173 | } |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * IXR_MESSAGE |
||
178 | * |
||
179 | * @package IXR |
||
180 | * @since 1.5 |
||
181 | * |
||
182 | */ |
||
183 | class IXR_Message { |
||
184 | var $message; |
||
185 | var $messageType; // methodCall / methodResponse / fault |
||
186 | var $faultCode; |
||
187 | var $faultString; |
||
188 | var $methodName; |
||
189 | var $params; |
||
190 | |||
191 | // Current variable stacks |
||
192 | var $_arraystructs = array(); // The stack used to keep track of the current array/struct |
||
193 | var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array |
||
194 | var $_currentStructName = array(); // A stack as well |
||
195 | var $_param; |
||
196 | var $_value; |
||
197 | var $_currentTag; |
||
198 | var $_currentTagContents; |
||
199 | var $_lastseen; |
||
200 | // The XML parser |
||
201 | var $_parser; |
||
202 | |||
203 | /** |
||
204 | * @param string $message |
||
205 | */ |
||
206 | function __construct($message) { |
||
207 | $this->message =& $message; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * @return bool |
||
212 | */ |
||
213 | function parse() { |
||
214 | // first remove the XML declaration |
||
215 | // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages |
||
216 | $header = preg_replace('/<\?xml.*?\?' . '>/', '', substr($this->message, 0, 100), 1); |
||
217 | $this->message = substr_replace($this->message, $header, 0, 100); |
||
218 | |||
219 | // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996 |
||
220 | $this->message = str_replace('<', '<', $this->message); |
||
221 | $this->message = str_replace('>', '>', $this->message); |
||
222 | $this->message = str_replace('&', '&', $this->message); |
||
223 | $this->message = str_replace(''', ''', $this->message); |
||
224 | $this->message = str_replace('"', '"', $this->message); |
||
225 | $this->message = str_replace("\x0b", ' ', $this->message); //vertical tab |
||
226 | if(trim($this->message) == '') { |
||
227 | return false; |
||
228 | } |
||
229 | $this->_parser = xml_parser_create(); |
||
230 | // Set XML parser to take the case of tags in to account |
||
231 | xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); |
||
232 | // Set XML parser callback functions |
||
233 | xml_set_object($this->_parser, $this); |
||
234 | xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); |
||
235 | xml_set_character_data_handler($this->_parser, 'cdata'); |
||
236 | $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages |
||
237 | $final = false; |
||
238 | do { |
||
239 | if(strlen($this->message) <= $chunk_size) { |
||
240 | $final = true; |
||
241 | } |
||
242 | $part = substr($this->message, 0, $chunk_size); |
||
243 | $this->message = substr($this->message, $chunk_size); |
||
244 | if(!xml_parse($this->_parser, $part, $final)) { |
||
245 | return false; |
||
246 | } |
||
247 | if($final) { |
||
248 | break; |
||
249 | } |
||
250 | } while(true); |
||
251 | xml_parser_free($this->_parser); |
||
252 | |||
253 | // Grab the error messages, if any |
||
254 | if($this->messageType == 'fault') { |
||
255 | $this->faultCode = $this->params[0]['faultCode']; |
||
256 | $this->faultString = $this->params[0]['faultString']; |
||
257 | } |
||
258 | return true; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * @param $parser |
||
263 | * @param string $tag |
||
264 | * @param $attr |
||
265 | */ |
||
266 | function tag_open($parser, $tag, $attr) { |
||
267 | $this->_currentTagContents = ''; |
||
268 | $this->_currentTag = $tag; |
||
269 | |||
270 | switch($tag) { |
||
271 | case 'methodCall': |
||
272 | case 'methodResponse': |
||
273 | case 'fault': |
||
274 | $this->messageType = $tag; |
||
275 | break; |
||
276 | /* Deal with stacks of arrays and structs */ |
||
277 | case 'data': // data is to all intents and purposes more interesting than array |
||
278 | $this->_arraystructstypes[] = 'array'; |
||
279 | $this->_arraystructs[] = array(); |
||
280 | break; |
||
281 | case 'struct': |
||
282 | $this->_arraystructstypes[] = 'struct'; |
||
283 | $this->_arraystructs[] = array(); |
||
284 | break; |
||
285 | } |
||
286 | $this->_lastseen = $tag; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * @param $parser |
||
291 | * @param string $cdata |
||
292 | */ |
||
293 | function cdata($parser, $cdata) { |
||
294 | $this->_currentTagContents .= $cdata; |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param $parser |
||
299 | * @param $tag |
||
300 | */ |
||
301 | function tag_close($parser, $tag) { |
||
302 | $value = null; |
||
303 | $valueFlag = false; |
||
304 | switch($tag) { |
||
305 | case 'int': |
||
306 | case 'i4': |
||
307 | $value = (int) trim($this->_currentTagContents); |
||
308 | $valueFlag = true; |
||
309 | break; |
||
310 | case 'double': |
||
311 | $value = (double) trim($this->_currentTagContents); |
||
312 | $valueFlag = true; |
||
313 | break; |
||
314 | case 'string': |
||
315 | $value = (string) $this->_currentTagContents; |
||
316 | $valueFlag = true; |
||
317 | break; |
||
318 | case 'dateTime.iso8601': |
||
319 | $value = new IXR_Date(trim($this->_currentTagContents)); |
||
320 | $valueFlag = true; |
||
321 | break; |
||
322 | case 'value': |
||
323 | // "If no type is indicated, the type is string." |
||
324 | if($this->_lastseen == 'value') { |
||
325 | $value = (string) $this->_currentTagContents; |
||
326 | $valueFlag = true; |
||
327 | } |
||
328 | break; |
||
329 | case 'boolean': |
||
330 | $value = (boolean) trim($this->_currentTagContents); |
||
331 | $valueFlag = true; |
||
332 | break; |
||
333 | case 'base64': |
||
334 | $value = base64_decode($this->_currentTagContents); |
||
335 | $valueFlag = true; |
||
336 | break; |
||
337 | /* Deal with stacks of arrays and structs */ |
||
338 | case 'data': |
||
339 | case 'struct': |
||
340 | $value = array_pop($this->_arraystructs); |
||
341 | array_pop($this->_arraystructstypes); |
||
342 | $valueFlag = true; |
||
343 | break; |
||
344 | case 'member': |
||
345 | array_pop($this->_currentStructName); |
||
346 | break; |
||
347 | case 'name': |
||
348 | $this->_currentStructName[] = trim($this->_currentTagContents); |
||
349 | break; |
||
350 | case 'methodName': |
||
351 | $this->methodName = trim($this->_currentTagContents); |
||
352 | break; |
||
353 | } |
||
354 | |||
355 | if($valueFlag) { |
||
356 | if(count($this->_arraystructs) > 0) { |
||
357 | // Add value to struct or array |
||
358 | if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') { |
||
359 | // Add to struct |
||
360 | $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value; |
||
361 | } else { |
||
362 | // Add to array |
||
363 | $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; |
||
364 | } |
||
365 | } else { |
||
366 | // Just add as a parameter |
||
367 | $this->params[] = $value; |
||
368 | } |
||
369 | } |
||
370 | $this->_currentTagContents = ''; |
||
371 | $this->_lastseen = $tag; |
||
372 | } |
||
373 | } |
||
374 | |||
375 | /** |
||
376 | * IXR_Server |
||
377 | * |
||
378 | * @package IXR |
||
379 | * @since 1.5 |
||
380 | */ |
||
381 | class IXR_Server { |
||
382 | var $data; |
||
383 | /** @var array */ |
||
384 | var $callbacks = array(); |
||
385 | var $message; |
||
386 | /** @var array */ |
||
387 | var $capabilities; |
||
388 | |||
389 | /** |
||
390 | * @param array|bool $callbacks |
||
391 | * @param bool $data |
||
392 | * @param bool $wait |
||
393 | */ |
||
394 | function __construct($callbacks = false, $data = false, $wait = false) { |
||
395 | $this->setCapabilities(); |
||
396 | if($callbacks) { |
||
397 | $this->callbacks = $callbacks; |
||
398 | } |
||
399 | $this->setCallbacks(); |
||
400 | |||
401 | if(!$wait) { |
||
402 | $this->serve($data); |
||
403 | } |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * @param bool|string $data |
||
408 | */ |
||
409 | function serve($data = false) { |
||
410 | if(!$data) { |
||
411 | |||
412 | $postData = trim(http_get_raw_post_data()); |
||
413 | if(!$postData) { |
||
414 | header('Content-Type: text/plain'); // merged from WP #9093 |
||
415 | die('XML-RPC server accepts POST requests only.'); |
||
416 | } |
||
417 | $data = $postData; |
||
418 | } |
||
419 | $this->message = new IXR_Message($data); |
||
420 | if(!$this->message->parse()) { |
||
421 | $this->error(-32700, 'parse error. not well formed'); |
||
422 | } |
||
423 | if($this->message->messageType != 'methodCall') { |
||
424 | $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); |
||
425 | } |
||
426 | $result = $this->call($this->message->methodName, $this->message->params); |
||
427 | |||
428 | // Is the result an error? |
||
429 | if(is_a($result, 'IXR_Error')) { |
||
430 | $this->error($result); |
||
431 | } |
||
432 | |||
433 | // Encode the result |
||
434 | $r = new IXR_Value($result); |
||
435 | $resultxml = $r->getXml(); |
||
436 | |||
437 | // Create the XML |
||
438 | $xml = <<<EOD |
||
439 | <methodResponse> |
||
440 | <params> |
||
441 | <param> |
||
442 | <value> |
||
443 | $resultxml |
||
444 | </value> |
||
445 | </param> |
||
446 | </params> |
||
447 | </methodResponse> |
||
448 | |||
449 | EOD; |
||
450 | // Send it |
||
451 | $this->output($xml); |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * @param string $methodname |
||
456 | * @param array $args |
||
457 | * @return IXR_Error|mixed |
||
458 | */ |
||
459 | function call($methodname, $args) { |
||
0 ignored issues
–
show
|
|||
460 | if(!$this->hasMethod($methodname)) { |
||
461 | return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.'); |
||
462 | } |
||
463 | $method = $this->callbacks[$methodname]; |
||
464 | |||
465 | // Perform the callback and send the response |
||
466 | |||
467 | # Removed for DokuWiki to have a more consistent interface |
||
468 | # if (count($args) == 1) { |
||
469 | # // If only one parameter just send that instead of the whole array |
||
470 | # $args = $args[0]; |
||
471 | # } |
||
472 | |||
473 | # Adjusted for DokuWiki to use call_user_func_array |
||
474 | |||
475 | // args need to be an array |
||
476 | $args = (array) $args; |
||
477 | |||
478 | // Are we dealing with a function or a method? |
||
479 | if(is_string($method) && substr($method, 0, 5) == 'this:') { |
||
480 | // It's a class method - check it exists |
||
481 | $method = substr($method, 5); |
||
482 | if(!method_exists($this, $method)) { |
||
483 | return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.'); |
||
484 | } |
||
485 | // Call the method |
||
486 | #$result = $this->$method($args); |
||
487 | $result = call_user_func_array(array(&$this, $method), $args); |
||
488 | } elseif(substr($method, 0, 7) == 'plugin:') { |
||
489 | list($pluginname, $callback) = explode(':', substr($method, 7), 2); |
||
490 | if(!plugin_isdisabled($pluginname)) { |
||
491 | $plugin = plugin_load('action', $pluginname); |
||
492 | return call_user_func_array(array($plugin, $callback), $args); |
||
493 | } else { |
||
494 | return new IXR_Error(-99999, 'server error'); |
||
495 | } |
||
496 | } else { |
||
497 | // It's a function - does it exist? |
||
498 | if(is_array($method)) { |
||
499 | if(!is_callable(array($method[0], $method[1]))) { |
||
500 | return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.'); |
||
501 | } |
||
502 | } else if(!function_exists($method)) { |
||
503 | return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.'); |
||
504 | } |
||
505 | |||
506 | // Call the function |
||
507 | $result = call_user_func($method, $args); |
||
508 | } |
||
509 | return $result; |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * @param int $error |
||
514 | * @param string|bool $message |
||
515 | */ |
||
516 | function error($error, $message = false) { |
||
517 | // Accepts either an error object or an error code and message |
||
518 | if($message && !is_object($error)) { |
||
519 | $error = new IXR_Error($error, $message); |
||
520 | } |
||
521 | $this->output($error->getXml()); |
||
522 | } |
||
523 | |||
524 | /** |
||
525 | * @param string $xml |
||
526 | */ |
||
527 | function output($xml) { |
||
528 | header('Content-Type: text/xml; charset=utf-8'); |
||
529 | echo '<?xml version="1.0"?>', "\n", $xml; |
||
530 | exit; |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * @param string $method |
||
535 | * @return bool |
||
536 | */ |
||
537 | function hasMethod($method) { |
||
538 | return in_array($method, array_keys($this->callbacks)); |
||
539 | } |
||
540 | |||
541 | function setCapabilities() { |
||
542 | // Initialises capabilities array |
||
543 | $this->capabilities = array( |
||
544 | 'xmlrpc' => array( |
||
545 | 'specUrl' => 'http://www.xmlrpc.com/spec', |
||
546 | 'specVersion' => 1 |
||
547 | ), |
||
548 | 'faults_interop' => array( |
||
549 | 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', |
||
550 | 'specVersion' => 20010516 |
||
551 | ), |
||
552 | 'system.multicall' => array( |
||
553 | 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', |
||
554 | 'specVersion' => 1 |
||
555 | ), |
||
556 | ); |
||
557 | } |
||
558 | |||
559 | /** |
||
560 | * @return mixed |
||
561 | */ |
||
562 | function getCapabilities() { |
||
563 | return $this->capabilities; |
||
564 | } |
||
565 | |||
566 | function setCallbacks() { |
||
567 | $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; |
||
568 | $this->callbacks['system.listMethods'] = 'this:listMethods'; |
||
569 | $this->callbacks['system.multicall'] = 'this:multiCall'; |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * @return array |
||
574 | */ |
||
575 | function listMethods() { |
||
576 | // Returns a list of methods - uses array_reverse to ensure user defined |
||
577 | // methods are listed before server defined methods |
||
578 | return array_reverse(array_keys($this->callbacks)); |
||
579 | } |
||
580 | |||
581 | /** |
||
582 | * @param array $methodcalls |
||
583 | * @return array |
||
584 | */ |
||
585 | function multiCall($methodcalls) { |
||
586 | // See http://www.xmlrpc.com/discuss/msgReader$1208 |
||
587 | $return = array(); |
||
588 | foreach($methodcalls as $call) { |
||
589 | $method = $call['methodName']; |
||
590 | $params = $call['params']; |
||
591 | if($method == 'system.multicall') { |
||
592 | $result = new IXR_Error(-32800, 'Recursive calls to system.multicall are forbidden'); |
||
593 | } else { |
||
594 | $result = $this->call($method, $params); |
||
595 | } |
||
596 | if(is_a($result, 'IXR_Error')) { |
||
597 | $return[] = array( |
||
598 | 'faultCode' => $result->code, |
||
599 | 'faultString' => $result->message |
||
600 | ); |
||
601 | } else { |
||
602 | $return[] = array($result); |
||
603 | } |
||
604 | } |
||
605 | return $return; |
||
606 | } |
||
607 | } |
||
608 | |||
609 | /** |
||
610 | * IXR_Request |
||
611 | * |
||
612 | * @package IXR |
||
613 | * @since 1.5 |
||
614 | */ |
||
615 | class IXR_Request { |
||
616 | /** @var string */ |
||
617 | var $method; |
||
618 | /** @var array */ |
||
619 | var $args; |
||
620 | /** @var string */ |
||
621 | var $xml; |
||
622 | |||
623 | /** |
||
624 | * @param string $method |
||
625 | * @param array $args |
||
626 | */ |
||
627 | function __construct($method, $args) { |
||
628 | $this->method = $method; |
||
629 | $this->args = $args; |
||
630 | $this->xml = <<<EOD |
||
631 | <?xml version="1.0"?> |
||
632 | <methodCall> |
||
633 | <methodName>{$this->method}</methodName> |
||
634 | <params> |
||
635 | |||
636 | EOD; |
||
637 | foreach($this->args as $arg) { |
||
638 | $this->xml .= '<param><value>'; |
||
639 | $v = new IXR_Value($arg); |
||
640 | $this->xml .= $v->getXml(); |
||
641 | $this->xml .= "</value></param>\n"; |
||
642 | } |
||
643 | $this->xml .= '</params></methodCall>'; |
||
644 | } |
||
645 | |||
646 | /** |
||
647 | * @return int |
||
648 | */ |
||
649 | function getLength() { |
||
650 | return strlen($this->xml); |
||
651 | } |
||
652 | |||
653 | /** |
||
654 | * @return string |
||
655 | */ |
||
656 | function getXml() { |
||
657 | return $this->xml; |
||
658 | } |
||
659 | } |
||
660 | |||
661 | /** |
||
662 | * IXR_Client |
||
663 | * |
||
664 | * @package IXR |
||
665 | * @since 1.5 |
||
666 | * |
||
667 | * Changed for DokuWiki to use DokuHTTPClient |
||
668 | * |
||
669 | * This should be compatible to the original class, but uses DokuWiki's |
||
670 | * HTTP client library which will respect proxy settings |
||
671 | * |
||
672 | * Because the XMLRPC client is not used in DokuWiki currently this is completely |
||
673 | * untested |
||
674 | */ |
||
675 | class IXR_Client extends DokuHTTPClient { |
||
676 | var $posturl = ''; |
||
677 | /** @var IXR_Message|bool */ |
||
678 | var $message = false; |
||
679 | |||
680 | // Storage place for an error message |
||
681 | /** @var IXR_Error|bool */ |
||
682 | var $xmlerror = false; |
||
683 | |||
684 | /** |
||
685 | * @param string $server |
||
686 | * @param string|bool $path |
||
687 | * @param int $port |
||
688 | * @param int $timeout |
||
689 | */ |
||
690 | function __construct($server, $path = false, $port = 80, $timeout = 15) { |
||
691 | parent::__construct(); |
||
692 | if(!$path) { |
||
693 | // Assume we have been given a URL instead |
||
694 | $this->posturl = $server; |
||
695 | } else { |
||
696 | $this->posturl = 'http://' . $server . ':' . $port . $path; |
||
697 | } |
||
698 | $this->timeout = $timeout; |
||
699 | } |
||
700 | |||
701 | /** |
||
702 | * parameters: method and arguments |
||
703 | * @return bool success or error |
||
704 | */ |
||
705 | function query() { |
||
706 | $args = func_get_args(); |
||
707 | $method = array_shift($args); |
||
708 | $request = new IXR_Request($method, $args); |
||
709 | $xml = $request->getXml(); |
||
710 | |||
711 | $this->headers['Content-Type'] = 'text/xml'; |
||
712 | if(!$this->sendRequest($this->posturl, $xml, 'POST')) { |
||
713 | $this->xmlerror = new IXR_Error(-32300, 'transport error - ' . $this->error); |
||
714 | return false; |
||
715 | } |
||
716 | |||
717 | // Check HTTP Response code |
||
718 | if($this->status < 200 || $this->status > 206) { |
||
719 | $this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status ' . $this->status); |
||
720 | return false; |
||
721 | } |
||
722 | |||
723 | // Now parse what we've got back |
||
724 | $this->message = new IXR_Message($this->resp_body); |
||
725 | if(!$this->message->parse()) { |
||
726 | // XML error |
||
727 | $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed'); |
||
728 | return false; |
||
729 | } |
||
730 | |||
731 | // Is the message a fault? |
||
732 | if($this->message->messageType == 'fault') { |
||
733 | $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString); |
||
734 | return false; |
||
735 | } |
||
736 | |||
737 | // Message must be OK |
||
738 | return true; |
||
739 | } |
||
740 | |||
741 | /** |
||
742 | * @return mixed |
||
743 | */ |
||
744 | function getResponse() { |
||
745 | // methodResponses can only have one param - return that |
||
746 | return $this->message->params[0]; |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * @return bool |
||
751 | */ |
||
752 | function isError() { |
||
753 | return (is_object($this->xmlerror)); |
||
754 | } |
||
755 | |||
756 | /** |
||
757 | * @return int |
||
758 | */ |
||
759 | function getErrorCode() { |
||
760 | return $this->xmlerror->code; |
||
761 | } |
||
762 | |||
763 | /** |
||
764 | * @return string |
||
765 | */ |
||
766 | function getErrorMessage() { |
||
767 | return $this->xmlerror->message; |
||
768 | } |
||
769 | } |
||
770 | |||
771 | /** |
||
772 | * IXR_Error |
||
773 | * |
||
774 | * @package IXR |
||
775 | * @since 1.5 |
||
776 | */ |
||
777 | class IXR_Error { |
||
778 | var $code; |
||
779 | var $message; |
||
780 | |||
781 | /** |
||
782 | * @param int $code |
||
783 | * @param string $message |
||
784 | */ |
||
785 | function __construct($code, $message) { |
||
786 | $this->code = $code; |
||
787 | $this->message = htmlspecialchars($message); |
||
788 | } |
||
789 | |||
790 | /** |
||
791 | * @return string |
||
792 | */ |
||
793 | function getXml() { |
||
794 | $xml = <<<EOD |
||
795 | <methodResponse> |
||
796 | <fault> |
||
797 | <value> |
||
798 | <struct> |
||
799 | <member> |
||
800 | <name>faultCode</name> |
||
801 | <value><int>{$this->code}</int></value> |
||
802 | </member> |
||
803 | <member> |
||
804 | <name>faultString</name> |
||
805 | <value><string>{$this->message}</string></value> |
||
806 | </member> |
||
807 | </struct> |
||
808 | </value> |
||
809 | </fault> |
||
810 | </methodResponse> |
||
811 | |||
812 | EOD; |
||
813 | return $xml; |
||
814 | } |
||
815 | } |
||
816 | |||
817 | /** |
||
818 | * IXR_Date |
||
819 | * |
||
820 | * @package IXR |
||
821 | * @since 1.5 |
||
822 | */ |
||
823 | class IXR_Date { |
||
824 | |||
825 | /** @var DateTime */ |
||
826 | protected $date; |
||
827 | |||
828 | /** |
||
829 | * @param int|string $time |
||
830 | */ |
||
831 | public function __construct($time) { |
||
832 | // $time can be a PHP timestamp or an ISO one |
||
833 | if(is_numeric($time)) { |
||
834 | $this->parseTimestamp($time); |
||
835 | } else { |
||
836 | $this->parseIso($time); |
||
837 | } |
||
838 | } |
||
839 | |||
840 | /** |
||
841 | * Parse unix timestamp |
||
842 | * |
||
843 | * @param int $timestamp |
||
844 | */ |
||
845 | protected function parseTimestamp($timestamp) { |
||
846 | $this->date = new DateTime('@' . $timestamp); |
||
847 | } |
||
848 | |||
849 | /** |
||
850 | * Parses less or more complete iso dates and much more, if no timezone given assumes UTC |
||
851 | * |
||
852 | * @param string $iso |
||
853 | */ |
||
854 | protected function parseIso($iso) { |
||
855 | $this->date = new DateTime($iso, new DateTimeZone("UTC")); |
||
856 | } |
||
857 | |||
858 | /** |
||
859 | * Returns date in ISO 8601 format |
||
860 | * |
||
861 | * @return string |
||
862 | */ |
||
863 | public function getIso() { |
||
864 | return $this->date->format(DateTime::ISO8601); |
||
865 | } |
||
866 | |||
867 | /** |
||
868 | * Returns date in valid xml |
||
869 | * |
||
870 | * @return string |
||
871 | */ |
||
872 | public function getXml() { |
||
873 | return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>'; |
||
874 | } |
||
875 | |||
876 | /** |
||
877 | * Returns Unix timestamp |
||
878 | * |
||
879 | * @return int |
||
880 | */ |
||
881 | function getTimestamp() { |
||
882 | return $this->date->getTimestamp(); |
||
883 | } |
||
884 | } |
||
885 | |||
886 | /** |
||
887 | * IXR_Base64 |
||
888 | * |
||
889 | * @package IXR |
||
890 | * @since 1.5 |
||
891 | */ |
||
892 | class IXR_Base64 { |
||
893 | var $data; |
||
894 | |||
895 | /** |
||
896 | * @param string $data |
||
897 | */ |
||
898 | function __construct($data) { |
||
899 | $this->data = $data; |
||
900 | } |
||
901 | |||
902 | /** |
||
903 | * @return string |
||
904 | */ |
||
905 | function getXml() { |
||
906 | return '<base64>' . base64_encode($this->data) . '</base64>'; |
||
907 | } |
||
908 | } |
||
909 | |||
910 | /** |
||
911 | * IXR_IntrospectionServer |
||
912 | * |
||
913 | * @package IXR |
||
914 | * @since 1.5 |
||
915 | */ |
||
916 | class IXR_IntrospectionServer extends IXR_Server { |
||
917 | /** @var array[] */ |
||
918 | var $signatures; |
||
919 | /** @var string[] */ |
||
920 | var $help; |
||
921 | |||
922 | /** |
||
923 | * Constructor |
||
924 | */ |
||
925 | function __construct() { |
||
926 | $this->setCallbacks(); |
||
927 | $this->setCapabilities(); |
||
928 | $this->capabilities['introspection'] = array( |
||
929 | 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', |
||
930 | 'specVersion' => 1 |
||
931 | ); |
||
932 | $this->addCallback( |
||
933 | 'system.methodSignature', |
||
934 | 'this:methodSignature', |
||
935 | array('array', 'string'), |
||
936 | 'Returns an array describing the return type and required parameters of a method' |
||
937 | ); |
||
938 | $this->addCallback( |
||
939 | 'system.getCapabilities', |
||
940 | 'this:getCapabilities', |
||
941 | array('struct'), |
||
942 | 'Returns a struct describing the XML-RPC specifications supported by this server' |
||
943 | ); |
||
944 | $this->addCallback( |
||
945 | 'system.listMethods', |
||
946 | 'this:listMethods', |
||
947 | array('array'), |
||
948 | 'Returns an array of available methods on this server' |
||
949 | ); |
||
950 | $this->addCallback( |
||
951 | 'system.methodHelp', |
||
952 | 'this:methodHelp', |
||
953 | array('string', 'string'), |
||
954 | 'Returns a documentation string for the specified method' |
||
955 | ); |
||
956 | } |
||
957 | |||
958 | /** |
||
959 | * @param string $method |
||
960 | * @param string $callback |
||
961 | * @param string[] $args |
||
962 | * @param string $help |
||
963 | */ |
||
964 | function addCallback($method, $callback, $args, $help) { |
||
965 | $this->callbacks[$method] = $callback; |
||
966 | $this->signatures[$method] = $args; |
||
967 | $this->help[$method] = $help; |
||
968 | } |
||
969 | |||
970 | /** |
||
971 | * @param string $methodname |
||
972 | * @param array $args |
||
973 | * @return IXR_Error|mixed |
||
974 | */ |
||
975 | function call($methodname, $args) { |
||
976 | // Make sure it's in an array |
||
977 | if($args && !is_array($args)) { |
||
978 | $args = array($args); |
||
979 | } |
||
980 | |||
981 | // Over-rides default call method, adds signature check |
||
982 | if(!$this->hasMethod($methodname)) { |
||
983 | return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.'); |
||
984 | } |
||
985 | $method = $this->callbacks[$methodname]; |
||
986 | $signature = $this->signatures[$methodname]; |
||
987 | $returnType = array_shift($signature); |
||
988 | // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible. |
||
989 | // This is a hack to allow optional parameters... |
||
990 | if(count($args) < count($signature)) { |
||
991 | // print 'Num of args: '.count($args).' Num in signature: '.count($signature); |
||
992 | return new IXR_Error(-32602, 'server error. wrong number of method parameters'); |
||
993 | } |
||
994 | |||
995 | // Check the argument types |
||
996 | $ok = true; |
||
997 | $argsbackup = $args; |
||
998 | for($i = 0, $j = count($args); $i < $j; $i++) { |
||
999 | $arg = array_shift($args); |
||
1000 | $type = array_shift($signature); |
||
1001 | switch($type) { |
||
1002 | case 'int': |
||
1003 | case 'i4': |
||
1004 | if(is_array($arg) || !is_int($arg)) { |
||
1005 | $ok = false; |
||
1006 | } |
||
1007 | break; |
||
1008 | case 'base64': |
||
1009 | case 'string': |
||
1010 | if(!is_string($arg)) { |
||
1011 | $ok = false; |
||
1012 | } |
||
1013 | break; |
||
1014 | case 'boolean': |
||
1015 | if($arg !== false && $arg !== true) { |
||
1016 | $ok = false; |
||
1017 | } |
||
1018 | break; |
||
1019 | case 'float': |
||
1020 | case 'double': |
||
1021 | if(!is_float($arg)) { |
||
1022 | $ok = false; |
||
1023 | } |
||
1024 | break; |
||
1025 | case 'date': |
||
1026 | case 'dateTime.iso8601': |
||
1027 | if(!is_a($arg, 'IXR_Date')) { |
||
1028 | $ok = false; |
||
1029 | } |
||
1030 | break; |
||
1031 | } |
||
1032 | if(!$ok) { |
||
1033 | return new IXR_Error(-32602, 'server error. invalid method parameters'); |
||
1034 | } |
||
1035 | } |
||
1036 | // It passed the test - run the "real" method call |
||
1037 | return parent::call($methodname, $argsbackup); |
||
1038 | } |
||
1039 | |||
1040 | /** |
||
1041 | * @param string $method |
||
1042 | * @return array|IXR_Error |
||
1043 | */ |
||
1044 | function methodSignature($method) { |
||
1045 | if(!$this->hasMethod($method)) { |
||
1046 | return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.'); |
||
1047 | } |
||
1048 | // We should be returning an array of types |
||
1049 | $types = $this->signatures[$method]; |
||
1050 | $return = array(); |
||
1051 | foreach($types as $type) { |
||
1052 | switch($type) { |
||
1053 | case 'string': |
||
1054 | $return[] = 'string'; |
||
1055 | break; |
||
1056 | case 'int': |
||
1057 | case 'i4': |
||
1058 | $return[] = 42; |
||
1059 | break; |
||
1060 | case 'double': |
||
1061 | $return[] = 3.1415; |
||
1062 | break; |
||
1063 | case 'dateTime.iso8601': |
||
1064 | $return[] = new IXR_Date(time()); |
||
1065 | break; |
||
1066 | case 'boolean': |
||
1067 | $return[] = true; |
||
1068 | break; |
||
1069 | case 'base64': |
||
1070 | $return[] = new IXR_Base64('base64'); |
||
1071 | break; |
||
1072 | case 'array': |
||
1073 | $return[] = array('array'); |
||
1074 | break; |
||
1075 | case 'struct': |
||
1076 | $return[] = array('struct' => 'struct'); |
||
1077 | break; |
||
1078 | } |
||
1079 | } |
||
1080 | return $return; |
||
1081 | } |
||
1082 | |||
1083 | /** |
||
1084 | * @param string $method |
||
1085 | * @return mixed |
||
1086 | */ |
||
1087 | function methodHelp($method) { |
||
1088 | return $this->help[$method]; |
||
1089 | } |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * IXR_ClientMulticall |
||
1094 | * |
||
1095 | * @package IXR |
||
1096 | * @since 1.5 |
||
1097 | */ |
||
1098 | class IXR_ClientMulticall extends IXR_Client { |
||
1099 | |||
1100 | /** @var array[] */ |
||
1101 | var $calls = array(); |
||
1102 | |||
1103 | /** |
||
1104 | * @param string $server |
||
1105 | * @param string|bool $path |
||
1106 | * @param int $port |
||
1107 | */ |
||
1108 | function __construct($server, $path = false, $port = 80) { |
||
1109 | parent::__construct($server, $path, $port); |
||
1110 | //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; |
||
1111 | } |
||
1112 | |||
1113 | /** |
||
1114 | * Add a call |
||
1115 | */ |
||
1116 | function addCall() { |
||
1117 | $args = func_get_args(); |
||
1118 | $methodName = array_shift($args); |
||
1119 | $struct = array( |
||
1120 | 'methodName' => $methodName, |
||
1121 | 'params' => $args |
||
1122 | ); |
||
1123 | $this->calls[] = $struct; |
||
1124 | } |
||
1125 | |||
1126 | /** |
||
1127 | * @return bool |
||
1128 | */ |
||
1129 | function query() { |
||
1130 | // Prepare multicall, then call the parent::query() method |
||
1131 | return parent::query('system.multicall', $this->calls); |
||
1132 | } |
||
1133 | } |
||
1134 | |||
1135 |
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: