Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Server, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 11 | class Server |
||
| 12 | { |
||
| 13 | /** |
||
| 14 | * Array defining php functions exposed as xmlrpc methods by this server. |
||
| 15 | */ |
||
| 16 | protected $dmap = array(); |
||
| 17 | |||
| 18 | /** |
||
| 19 | * Defines how functions in dmap will be invoked: either using an xmlrpc request object |
||
| 20 | * or plain php values. |
||
| 21 | * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' |
||
| 22 | */ |
||
| 23 | public $functions_parameters_type = 'xmlrpcvals'; |
||
| 24 | |||
| 25 | /** |
||
| 26 | * Option used for fine-tuning the encoding the php values returned from |
||
| 27 | * functions registered in the dispatch map when the functions_parameters_types |
||
| 28 | * member is set to 'phpvals' |
||
| 29 | * @see Encoder::encode for a list of values |
||
| 30 | */ |
||
| 31 | public $phpvals_encoding_options = array('auto_dates'); |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Controls whether the server is going to echo debugging messages back to the client as comments in response body. |
||
| 35 | * Valid values: 0,1,2,3 |
||
| 36 | */ |
||
| 37 | public $debug = 1; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * Controls behaviour of server when the invoked user function throws an exception: |
||
| 41 | * 0 = catch it and return an 'internal error' xmlrpc response (default) |
||
| 42 | * 1 = catch it and return an xmlrpc response with the error corresponding to the exception |
||
| 43 | * 2 = allow the exception to float to the upper layers |
||
| 44 | */ |
||
| 45 | public $exception_handling = 0; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * When set to true, it will enable HTTP compression of the response, in case |
||
| 49 | * the client has declared its support for compression in the request. |
||
| 50 | * Set at constructor time. |
||
| 51 | */ |
||
| 52 | public $compress_response = false; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * List of http compression methods accepted by the server for requests. Set at constructor time. |
||
| 56 | * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib |
||
| 57 | */ |
||
| 58 | public $accepted_compression = array(); |
||
| 59 | |||
| 60 | /// Shall we serve calls to system.* methods? |
||
| 61 | public $allow_system_funcs = true; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * List of charset encodings natively accepted for requests. |
||
| 65 | * Set at constructor time. |
||
| 66 | * UNUSED so far... |
||
| 67 | */ |
||
| 68 | public $accepted_charset_encodings = array(); |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Charset encoding to be used for response. |
||
| 72 | * NB: if we can, we will convert the generated response from internal_encoding to the intended one. |
||
| 73 | * Can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled), |
||
| 74 | * null (leave unspecified in response, convert output stream to US_ASCII), |
||
| 75 | * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed), |
||
| 76 | * or 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway). |
||
| 77 | * NB: pretty dangerous if you accept every charset and do not have mbstring enabled) |
||
| 78 | */ |
||
| 79 | public $response_charset_encoding = ''; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Storage for internal debug info. |
||
| 83 | */ |
||
| 84 | protected $debug_info = ''; |
||
| 85 | |||
| 86 | /** |
||
| 87 | * Extra data passed at runtime to method handling functions. Used only by EPI layer |
||
| 88 | */ |
||
| 89 | public $user_data = null; |
||
| 90 | |||
| 91 | protected static $_xmlrpc_debuginfo = ''; |
||
| 92 | protected static $_xmlrpcs_occurred_errors = ''; |
||
| 93 | protected static $_xmlrpcs_prev_ehandler = ''; |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @param array $dispatchMap the dispatch map with definition of exposed services |
||
| 97 | * @param boolean $serviceNow set to false to prevent the server from running upon construction |
||
| 98 | */ |
||
| 99 | 439 | public function __construct($dispatchMap = null, $serviceNow = true) |
|
| 100 | { |
||
| 101 | // if ZLIB is enabled, let the server by default accept compressed requests, |
||
| 102 | // and compress responses sent to clients that support them |
||
| 103 | 439 | if (function_exists('gzinflate')) { |
|
| 104 | 439 | $this->accepted_compression = array('gzip', 'deflate'); |
|
| 105 | 439 | $this->compress_response = true; |
|
| 106 | } |
||
| 107 | |||
| 108 | // by default the xml parser can support these 3 charset encodings |
||
| 109 | 439 | $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); |
|
| 110 | |||
| 111 | // dispMap is a dispatch array of methods mapped to function names and signatures. |
||
| 112 | // If a method doesn't appear in the map then an unknown method error is generated |
||
| 113 | /* milosch - changed to make passing dispMap optional. |
||
| 114 | * instead, you can use the class add_to_map() function |
||
| 115 | * to add functions manually (borrowed from SOAPX4) |
||
| 116 | */ |
||
| 117 | 439 | if ($dispatchMap) { |
|
| 118 | 438 | $this->dmap = $dispatchMap; |
|
| 119 | 438 | if ($serviceNow) { |
|
| 120 | $this->service(); |
||
| 121 | } |
||
| 122 | } |
||
| 123 | 439 | } |
|
| 124 | |||
| 125 | /** |
||
| 126 | * Set debug level of server. |
||
| 127 | * |
||
| 128 | * @param integer $level debug lvl: determines info added to xmlrpc responses (as xml comments) |
||
| 129 | * 0 = no debug info, |
||
| 130 | * 1 = msgs set from user with debugmsg(), |
||
| 131 | * 2 = add complete xmlrpc request (headers and body), |
||
| 132 | * 3 = add also all processing warnings happened during method processing |
||
| 133 | * (NB: this involves setting a custom error handler, and might interfere |
||
| 134 | * with the standard processing of the php function exposed as method. In |
||
| 135 | * particular, triggering an USER_ERROR level error will not halt script |
||
| 136 | * execution anymore, but just end up logged in the xmlrpc response) |
||
| 137 | * Note that info added at level 2 and 3 will be base64 encoded |
||
| 138 | */ |
||
| 139 | 438 | public function setDebug($level) |
|
| 143 | |||
| 144 | /** |
||
| 145 | * Add a string to the debug info that can be later serialized by the server |
||
| 146 | * as part of the response message. |
||
| 147 | * Note that for best compatibility, the debug string should be encoded using |
||
| 148 | * the PhpXmlRpc::$xmlrpc_internalencoding character set. |
||
| 149 | * |
||
| 150 | * @param string $msg |
||
| 151 | * @access public |
||
| 152 | */ |
||
| 153 | 2 | public static function xmlrpc_debugmsg($msg) |
|
| 157 | |||
| 158 | 18 | public static function error_occurred($msg) |
|
| 162 | |||
| 163 | /** |
||
| 164 | * Return a string with the serialized representation of all debug info. |
||
| 165 | * |
||
| 166 | * @param string $charsetEncoding the target charset encoding for the serialization |
||
| 167 | * |
||
| 168 | * @return string an XML comment (or two) |
||
| 169 | */ |
||
| 170 | 438 | public function serializeDebug($charsetEncoding = '') |
|
| 171 | { |
||
| 172 | // Tough encoding problem: which internal charset should we assume for debug info? |
||
| 173 | // It might contain a copy of raw data received from client, ie with unknown encoding, |
||
| 174 | // intermixed with php generated data and user generated data... |
||
| 175 | // so we split it: system debug is base 64 encoded, |
||
| 176 | // user debug info should be encoded by the end user using the INTERNAL_ENCODING |
||
| 177 | 438 | $out = ''; |
|
| 178 | 438 | if ($this->debug_info != '') { |
|
| 179 | 438 | $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n"; |
|
| 180 | } |
||
| 181 | 438 | if (static::$_xmlrpc_debuginfo != '') { |
|
| 182 | 2 | $out .= "<!-- DEBUG INFO:\n" . Charset::instance()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n"; |
|
| 183 | // NB: a better solution MIGHT be to use CDATA, but we need to insert it |
||
| 184 | // into return payload AFTER the beginning tag |
||
| 185 | //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n"; |
||
| 186 | } |
||
| 187 | |||
| 188 | 438 | return $out; |
|
| 189 | } |
||
| 190 | |||
| 191 | /** |
||
| 192 | * Execute the xmlrpc request, printing the response. |
||
| 193 | * |
||
| 194 | * @param string $data the request body. If null, the http POST request will be examined |
||
| 195 | * @param bool $returnPayload When true, return the response but do not echo it or any http header |
||
| 196 | * |
||
| 197 | * @return Response|string the response object (usually not used by caller...) or its xml serialization |
||
| 198 | * |
||
| 199 | * @throws \Exception in case the executed method does throw an exception (and depending on server configuration) |
||
| 200 | */ |
||
| 201 | 438 | public function service($data = null, $returnPayload = false) |
|
| 202 | { |
||
| 203 | 438 | if ($data === null) { |
|
| 204 | 438 | $data = file_get_contents('php://input'); |
|
| 205 | } |
||
| 206 | 438 | $rawData = $data; |
|
| 207 | |||
| 208 | // reset internal debug info |
||
| 209 | 438 | $this->debug_info = ''; |
|
| 210 | |||
| 211 | // Save what we received, before parsing it |
||
| 212 | 438 | if ($this->debug > 1) { |
|
| 213 | 438 | $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++"); |
|
| 214 | } |
||
| 215 | |||
| 216 | 438 | $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding); |
|
| 217 | 438 | if (!$r) { |
|
| 218 | // this actually executes the request |
||
| 219 | 438 | $r = $this->parseRequest($data, $reqCharset); |
|
| 220 | } |
||
| 221 | |||
| 222 | // save full body of request into response, for more debugging usages |
||
| 223 | 438 | $r->raw_data = $rawData; |
|
| 224 | |||
| 225 | 438 | if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) { |
|
| 226 | 18 | $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" . |
|
| 227 | 18 | static::$_xmlrpcs_occurred_errors . "+++END+++"); |
|
| 228 | } |
||
| 229 | |||
| 230 | 438 | $payload = $this->xml_header($respCharset); |
|
| 231 | 438 | if ($this->debug > 0) { |
|
| 232 | 438 | $payload = $payload . $this->serializeDebug($respCharset); |
|
| 233 | } |
||
| 234 | |||
| 235 | // G. Giunta 2006-01-27: do not create response serialization if it has |
||
| 236 | // already happened. Helps building json magic |
||
| 237 | 438 | if (empty($r->payload)) { |
|
| 238 | 438 | $r->serialize($respCharset); |
|
| 239 | } |
||
| 240 | 438 | $payload = $payload . $r->payload; |
|
| 241 | |||
| 242 | 438 | if ($returnPayload) { |
|
| 243 | return $payload; |
||
| 244 | } |
||
| 245 | |||
| 246 | // if we get a warning/error that has output some text before here, then we cannot |
||
| 247 | // add a new header. We cannot say we are sending xml, either... |
||
| 248 | 438 | if (!headers_sent()) { |
|
| 249 | 438 | header('Content-Type: ' . $r->content_type); |
|
| 250 | // we do not know if client actually told us an accepted charset, but if he did |
||
| 251 | // we have to tell him what we did |
||
| 252 | 438 | header("Vary: Accept-Charset"); |
|
| 253 | |||
| 254 | // http compression of output: only |
||
| 255 | // if we can do it, and we want to do it, and client asked us to, |
||
| 256 | // and php ini settings do not force it already |
||
| 257 | 438 | $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'); |
|
| 258 | 438 | if ($this->compress_response && function_exists('gzencode') && $respEncoding != '' |
|
| 259 | 100 | && $phpNoSelfCompress |
|
| 260 | ) { |
||
| 261 | 100 | if (strpos($respEncoding, 'gzip') !== false) { |
|
| 262 | 50 | $payload = gzencode($payload); |
|
| 263 | 50 | header("Content-Encoding: gzip"); |
|
| 264 | 50 | header("Vary: Accept-Encoding"); |
|
| 265 | 50 | } elseif (strpos($respEncoding, 'deflate') !== false) { |
|
| 266 | 50 | $payload = gzcompress($payload); |
|
| 267 | 50 | header("Content-Encoding: deflate"); |
|
| 268 | 50 | header("Vary: Accept-Encoding"); |
|
| 269 | } |
||
| 270 | } |
||
| 271 | |||
| 272 | // do not output content-length header if php is compressing output for us: |
||
| 273 | // it will mess up measurements |
||
| 274 | 438 | if ($phpNoSelfCompress) { |
|
| 275 | 438 | header('Content-Length: ' . (int)strlen($payload)); |
|
| 276 | } |
||
| 277 | } else { |
||
| 278 | error_log('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages'); |
||
| 279 | } |
||
| 280 | |||
| 281 | 438 | print $payload; |
|
| 282 | |||
| 283 | // return request, in case subclasses want it |
||
| 284 | 438 | return $r; |
|
| 285 | } |
||
| 286 | |||
| 287 | /** |
||
| 288 | * Add a method to the dispatch map. |
||
| 289 | * |
||
| 290 | * @param string $methodName the name with which the method will be made available |
||
| 291 | * @param string $function the php function that will get invoked |
||
| 292 | * @param array $sig the array of valid method signatures |
||
| 293 | * @param string $doc method documentation |
||
| 294 | * @param array $sigDoc the array of valid method signatures docs (one string per param, one for return type) |
||
| 295 | */ |
||
| 296 | public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false) |
||
| 297 | { |
||
| 298 | $this->dmap[$methodName] = array( |
||
| 299 | 'function' => $function, |
||
| 300 | 'docstring' => $doc, |
||
| 301 | ); |
||
| 302 | if ($sig) { |
||
| 303 | $this->dmap[$methodName]['signature'] = $sig; |
||
| 304 | } |
||
| 305 | if ($sigDoc) { |
||
| 306 | $this->dmap[$methodName]['signature_docs'] = $sigDoc; |
||
| 307 | } |
||
| 308 | } |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Verify type and number of parameters received against a list of known signatures. |
||
| 312 | * |
||
| 313 | * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions |
||
| 314 | * @param array $sigs array of known signatures to match against |
||
| 315 | * |
||
| 316 | * @return array |
||
| 317 | */ |
||
| 318 | 419 | protected function verifySignature($in, $sigs) |
|
| 319 | { |
||
| 320 | // check each possible signature in turn |
||
| 321 | 419 | if (is_object($in)) { |
|
| 322 | 419 | $numParams = $in->getNumParams(); |
|
| 323 | } else { |
||
| 324 | $numParams = count($in); |
||
| 325 | } |
||
| 326 | 419 | foreach ($sigs as $curSig) { |
|
| 327 | 419 | if (count($curSig) == $numParams + 1) { |
|
| 328 | 419 | $itsOK = 1; |
|
| 329 | 419 | for ($n = 0; $n < $numParams; $n++) { |
|
| 330 | 402 | if (is_object($in)) { |
|
| 331 | 402 | $p = $in->getParam($n); |
|
| 332 | 402 | if ($p->kindOf() == 'scalar') { |
|
| 333 | 351 | $pt = $p->scalartyp(); |
|
| 334 | } else { |
||
| 335 | 120 | $pt = $p->kindOf(); |
|
| 336 | } |
||
| 337 | } else { |
||
| 338 | $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4... |
||
| 339 | } |
||
| 340 | |||
| 341 | // param index is $n+1, as first member of sig is return type |
||
| 342 | 402 | if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) { |
|
| 343 | 18 | $itsOK = 0; |
|
| 344 | 18 | $pno = $n + 1; |
|
| 345 | 18 | $wanted = $curSig[$n + 1]; |
|
| 346 | 18 | $got = $pt; |
|
| 347 | 18 | break; |
|
| 348 | } |
||
| 349 | } |
||
| 350 | 419 | if ($itsOK) { |
|
| 351 | 419 | return array(1, ''); |
|
| 352 | } |
||
| 353 | } |
||
| 354 | } |
||
| 355 | 18 | if (isset($wanted)) { |
|
| 356 | return array(0, "Wanted ${wanted}, got ${got} at param ${pno}"); |
||
| 357 | } else { |
||
| 358 | 18 | return array(0, "No method signature matches number of parameters"); |
|
| 359 | } |
||
| 360 | } |
||
| 361 | |||
| 362 | /** |
||
| 363 | * Parse http headers received along with xmlrpc request. If needed, inflate request. |
||
| 364 | * |
||
| 365 | * @return mixed Response|null on success or an error Response |
||
| 366 | */ |
||
| 367 | protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression) |
||
| 368 | { |
||
| 369 | // check if $_SERVER is populated: it might have been disabled via ini file |
||
| 370 | // (this is true even when in CLI mode) |
||
| 371 | 438 | if (count($_SERVER) == 0) { |
|
| 372 | error_log('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated'); |
||
| 373 | } |
||
| 374 | |||
| 375 | 438 | if ($this->debug > 1) { |
|
| 376 | 438 | if (function_exists('getallheaders')) { |
|
| 377 | 438 | $this->debugmsg(''); // empty line |
|
| 378 | 438 | foreach (getallheaders() as $name => $val) { |
|
| 379 | 438 | $this->debugmsg("HEADER: $name: $val"); |
|
| 380 | } |
||
| 381 | } |
||
| 382 | } |
||
| 383 | |||
| 384 | 438 | if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { |
|
| 385 | 100 | $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']); |
|
| 386 | } else { |
||
| 387 | 338 | $contentEncoding = ''; |
|
| 388 | } |
||
| 389 | |||
| 390 | // check if request body has been compressed and decompress it |
||
| 391 | 438 | if ($contentEncoding != '' && strlen($data)) { |
|
| 392 | 100 | if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') { |
|
| 393 | // if decoding works, use it. else assume data wasn't gzencoded |
||
| 394 | 100 | if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) { |
|
| 395 | 100 | if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) { |
|
| 396 | 50 | $data = $degzdata; |
|
| 397 | 50 | View Code Duplication | if ($this->debug > 1) { |
| 398 | 50 | $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++"); |
|
| 399 | } |
||
| 400 | 50 | } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) { |
|
| 401 | 50 | $data = $degzdata; |
|
| 402 | 50 | View Code Duplication | if ($this->debug > 1) { |
| 403 | 50 | $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++"); |
|
| 404 | } |
||
| 405 | } else { |
||
| 406 | $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']); |
||
| 407 | |||
| 408 | return $r; |
||
| 409 | } |
||
| 410 | } else { |
||
| 411 | $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']); |
||
| 412 | |||
| 413 | return $r; |
||
| 414 | } |
||
| 415 | } |
||
| 416 | } |
||
| 417 | |||
| 418 | // check if client specified accepted charsets, and if we know how to fulfill |
||
| 419 | // the request |
||
| 420 | 438 | if ($this->response_charset_encoding == 'auto') { |
|
| 421 | $respEncoding = ''; |
||
| 422 | if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) { |
||
| 423 | // here we should check if we can match the client-requested encoding |
||
| 424 | // with the encodings we know we can generate. |
||
| 425 | /// @todo we should parse q=0.x preferences instead of getting first charset specified... |
||
| 426 | $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET'])); |
||
| 427 | // Give preference to internal encoding |
||
| 428 | $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII'); |
||
| 429 | foreach ($knownCharsets as $charset) { |
||
| 430 | foreach ($clientAcceptedCharsets as $accepted) { |
||
| 431 | if (strpos($accepted, $charset) === 0) { |
||
| 432 | $respEncoding = $charset; |
||
| 433 | break; |
||
| 434 | } |
||
| 435 | } |
||
| 436 | if ($respEncoding) { |
||
| 437 | break; |
||
| 438 | } |
||
| 439 | } |
||
| 440 | } |
||
| 441 | } else { |
||
| 442 | 438 | $respEncoding = $this->response_charset_encoding; |
|
| 443 | } |
||
| 444 | |||
| 445 | 438 | if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { |
|
| 446 | 100 | $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING']; |
|
| 447 | } else { |
||
| 448 | 338 | $respCompression = ''; |
|
| 449 | } |
||
| 450 | |||
| 451 | // 'guestimate' request encoding |
||
| 452 | /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check??? |
||
| 453 | 438 | $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '', |
|
| 454 | $data); |
||
| 455 | |||
| 456 | 438 | return; |
|
| 457 | } |
||
| 458 | |||
| 459 | /** |
||
| 460 | * Parse an xml chunk containing an xmlrpc request and execute the corresponding |
||
| 461 | * php function registered with the server. |
||
| 462 | * |
||
| 463 | * @param string $data the xml request |
||
| 464 | * @param string $reqEncoding (optional) the charset encoding of the xml request |
||
| 465 | * |
||
| 466 | * @return Response |
||
| 467 | * |
||
| 468 | * @throws \Exception in case the executed method does throw an exception (and depending on server configuration) |
||
| 469 | */ |
||
| 470 | public function parseRequest($data, $reqEncoding = '') |
||
| 471 | { |
||
| 472 | // decompose incoming XML into request structure |
||
| 473 | |||
| 474 | 439 | View Code Duplication | if ($reqEncoding != '') { |
| 475 | // Since parsing will fail if charset is not specified in the xml prologue, |
||
| 476 | // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that... |
||
| 477 | // The following code might be better for mb_string enabled installs, but |
||
| 478 | // makes the lib about 200% slower... |
||
| 479 | //if (!is_valid_charset($reqEncoding, array('UTF-8'))) |
||
| 480 | 438 | if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) { |
|
| 481 | 4 | if ($reqEncoding == 'ISO-8859-1') { |
|
| 482 | 2 | $data = utf8_encode($data); |
|
| 483 | } else { |
||
| 484 | 2 | if (extension_loaded('mbstring')) { |
|
| 485 | 2 | $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding); |
|
| 486 | } else { |
||
| 487 | error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding); |
||
| 488 | } |
||
| 489 | } |
||
| 490 | } |
||
| 491 | } |
||
| 492 | |||
| 493 | 439 | $parser = xml_parser_create(); |
|
| 494 | 439 | xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); |
|
| 495 | // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell |
||
| 496 | // the xml parser to give us back data in the expected charset |
||
| 497 | // What if internal encoding is not in one of the 3 allowed? |
||
| 498 | // we use the broadest one, ie. utf8 |
||
| 499 | // This allows to send data which is native in various charset, |
||
| 500 | // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding |
||
| 501 | 439 | View Code Duplication | if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { |
| 502 | xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); |
||
| 503 | } else { |
||
| 504 | 439 | xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding); |
|
| 505 | } |
||
| 506 | |||
| 507 | 439 | $xmlRpcParser = new XMLParser(); |
|
| 508 | 439 | xml_set_object($parser, $xmlRpcParser); |
|
| 509 | |||
| 510 | 439 | if ($this->functions_parameters_type != 'xmlrpcvals') { |
|
| 511 | xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); |
||
| 512 | } else { |
||
| 513 | 439 | xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); |
|
| 514 | } |
||
| 515 | 439 | xml_set_character_data_handler($parser, 'xmlrpc_cd'); |
|
| 516 | 439 | xml_set_default_handler($parser, 'xmlrpc_dh'); |
|
| 517 | 439 | if (!xml_parse($parser, $data, 1)) { |
|
| 518 | // return XML error as a faultCode |
||
| 519 | $r = new Response(0, |
||
| 520 | PhpXmlRpc::$xmlrpcerrxml + xml_get_error_code($parser), |
||
| 521 | sprintf('XML error: %s at line %d, column %d', |
||
| 522 | xml_error_string(xml_get_error_code($parser)), |
||
| 523 | xml_get_current_line_number($parser), xml_get_current_column_number($parser))); |
||
| 524 | xml_parser_free($parser); |
||
| 525 | 439 | } elseif ($xmlRpcParser->_xh['isf']) { |
|
| 526 | 1 | xml_parser_free($parser); |
|
| 527 | 1 | $r = new Response(0, |
|
| 528 | 1 | PhpXmlRpc::$xmlrpcerr['invalid_request'], |
|
| 529 | 1 | PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']); |
|
| 530 | } else { |
||
| 531 | 438 | xml_parser_free($parser); |
|
| 532 | // small layering violation in favor of speed and memory usage: |
||
| 533 | // we should allow the 'execute' method handle this, but in the |
||
| 534 | // most common scenario (xmlrpc values type server with some methods |
||
| 535 | // registered as phpvals) that would mean a useless encode+decode pass |
||
| 536 | 438 | if ($this->functions_parameters_type != 'xmlrpcvals' || (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) && ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals'))) { |
|
| 537 | if ($this->debug > 1) { |
||
| 538 | $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++"); |
||
| 539 | } |
||
| 540 | $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']); |
||
| 541 | } else { |
||
| 542 | // build a Request object with data parsed from xml |
||
| 543 | 438 | $req = new Request($xmlRpcParser->_xh['method']); |
|
| 544 | // now add parameters in |
||
| 545 | 438 | View Code Duplication | for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) { |
| 546 | 421 | $req->addParam($xmlRpcParser->_xh['params'][$i]); |
|
| 547 | } |
||
| 548 | |||
| 549 | 438 | if ($this->debug > 1) { |
|
| 550 | 438 | $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++"); |
|
| 551 | } |
||
| 552 | 438 | $r = $this->execute($req); |
|
| 553 | } |
||
| 554 | } |
||
| 555 | |||
| 556 | 439 | return $r; |
|
| 557 | } |
||
| 558 | |||
| 559 | /** |
||
| 560 | * Execute a method invoked by the client, checking parameters used. |
||
| 561 | * |
||
| 562 | * @param mixed $req either a Request obj or a method name |
||
| 563 | * @param array $params array with method parameters as php types (if m is method name only) |
||
| 564 | * @param array $paramTypes array with xmlrpc types of method parameters (if m is method name only) |
||
| 565 | * |
||
| 566 | * @return Response |
||
| 567 | * |
||
| 568 | * @throws \Exception in case the executed method does throw an exception (and depending on server configuration) |
||
| 569 | */ |
||
| 570 | protected function execute($req, $params = null, $paramTypes = null) |
||
| 571 | { |
||
| 572 | 438 | static::$_xmlrpcs_occurred_errors = ''; |
|
| 573 | 438 | static::$_xmlrpc_debuginfo = ''; |
|
| 574 | |||
| 575 | 438 | if (is_object($req)) { |
|
| 576 | 438 | $methName = $req->method(); |
|
| 577 | } else { |
||
| 578 | $methName = $req; |
||
| 579 | } |
||
| 580 | 438 | $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0); |
|
| 581 | 438 | $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; |
|
| 582 | |||
| 583 | 438 | View Code Duplication | if (!isset($dmap[$methName]['function'])) { |
| 584 | // No such method |
||
| 585 | 69 | return new Response(0, |
|
| 586 | 69 | PhpXmlRpc::$xmlrpcerr['unknown_method'], |
|
| 587 | 69 | PhpXmlRpc::$xmlrpcstr['unknown_method']); |
|
| 588 | } |
||
| 589 | |||
| 590 | // Check signature |
||
| 591 | 438 | if (isset($dmap[$methName]['signature'])) { |
|
| 592 | 419 | $sig = $dmap[$methName]['signature']; |
|
| 593 | 419 | if (is_object($req)) { |
|
| 594 | 419 | list($ok, $errStr) = $this->verifySignature($req, $sig); |
|
| 595 | } else { |
||
| 596 | list($ok, $errStr) = $this->verifySignature($paramTypes, $sig); |
||
| 597 | } |
||
| 598 | 419 | View Code Duplication | if (!$ok) { |
| 599 | // Didn't match. |
||
| 600 | 18 | return new Response( |
|
| 601 | 18 | 0, |
|
| 602 | 18 | PhpXmlRpc::$xmlrpcerr['incorrect_params'], |
|
| 603 | 18 | PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}" |
|
| 604 | ); |
||
| 605 | } |
||
| 606 | } |
||
| 607 | |||
| 608 | 438 | $func = $dmap[$methName]['function']; |
|
| 609 | // let the 'class::function' syntax be accepted in dispatch maps |
||
| 610 | 438 | if (is_string($func) && strpos($func, '::')) { |
|
| 611 | 103 | $func = explode('::', $func); |
|
| 612 | } |
||
| 613 | |||
| 614 | 438 | if (is_array($func)) { |
|
| 615 | 122 | View Code Duplication | if (is_object($func[0])) { |
| 616 | 20 | $funcName = get_class($func[0]) . '->' . $func[1]; |
|
| 617 | } else { |
||
| 618 | 103 | $funcName = implode('::', $func); |
|
| 619 | } |
||
| 620 | 334 | } else if ($func instanceof \Closure) { |
|
| 621 | 88 | $funcName = 'Closure'; |
|
| 622 | } else { |
||
| 623 | 264 | $funcName = $func; |
|
| 624 | } |
||
| 625 | |||
| 626 | // verify that function to be invoked is in fact callable |
||
| 627 | 438 | View Code Duplication | if (!is_callable($func)) { |
| 628 | error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable"); |
||
| 629 | return new Response( |
||
| 630 | 0, |
||
| 631 | PhpXmlRpc::$xmlrpcerr['server_error'], |
||
| 632 | PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method" |
||
| 633 | ); |
||
| 634 | } |
||
| 635 | |||
| 636 | // If debug level is 3, we should catch all errors generated during |
||
| 637 | // processing of user function, and log them as part of response |
||
| 638 | 438 | if ($this->debug > 2) { |
|
| 639 | 438 | self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')); |
|
| 640 | } |
||
| 641 | |||
| 642 | try { |
||
| 643 | // Allow mixed-convention servers |
||
| 644 | 438 | if (is_object($req)) { |
|
| 645 | 438 | if ($sysCall) { |
|
| 646 | 103 | $r = call_user_func($func, $this, $req); |
|
| 647 | } else { |
||
| 648 | 353 | $r = call_user_func($func, $req); |
|
| 649 | } |
||
| 650 | 436 | if (!is_a($r, 'PhpXmlRpc\Response')) { |
|
| 651 | error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r)); |
||
| 652 | View Code Duplication | if (is_a($r, 'PhpXmlRpc\Value')) { |
|
| 653 | $r = new Response($r); |
||
| 654 | } else { |
||
| 655 | $r = new Response( |
||
| 656 | 0, |
||
| 657 | PhpXmlRpc::$xmlrpcerr['server_error'], |
||
| 658 | PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object" |
||
| 659 | ); |
||
| 660 | } |
||
| 661 | } |
||
| 662 | } else { |
||
| 663 | // call a 'plain php' function |
||
| 664 | if ($sysCall) { |
||
| 665 | array_unshift($params, $this); |
||
| 666 | $r = call_user_func_array($func, $params); |
||
| 667 | } else { |
||
| 668 | // 3rd API convention for method-handling functions: EPI-style |
||
| 669 | if ($this->functions_parameters_type == 'epivals') { |
||
| 670 | $r = call_user_func_array($func, array($methName, $params, $this->user_data)); |
||
| 671 | // mimic EPI behaviour: if we get an array that looks like an error, make it |
||
| 672 | // an eror response |
||
| 673 | if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) { |
||
| 674 | $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']); |
||
| 675 | } else { |
||
| 676 | // functions using EPI api should NOT return resp objects, |
||
| 677 | // so make sure we encode the return type correctly |
||
| 678 | $encoder = new Encoder(); |
||
| 679 | $r = new Response($encoder->encode($r, array('extension_api'))); |
||
| 680 | } |
||
| 681 | } else { |
||
| 682 | $r = call_user_func_array($func, $params); |
||
| 683 | } |
||
| 684 | } |
||
| 685 | // the return type can be either a Response object or a plain php value... |
||
| 686 | if (!is_a($r, '\PhpXmlRpc\Response')) { |
||
| 687 | // what should we assume here about automatic encoding of datetimes |
||
| 688 | // and php classes instances??? |
||
| 689 | $encoder = new Encoder(); |
||
| 690 | $r = new Response($encoder->encode($r, $this->phpvals_encoding_options)); |
||
| 691 | } |
||
| 692 | } |
||
| 693 | 37 | } catch (\Exception $e) { |
|
| 694 | // (barring errors in the lib) an uncatched exception happened |
||
| 695 | // in the called function, we wrap it in a proper error-response |
||
| 696 | 37 | switch ($this->exception_handling) { |
|
| 697 | 37 | case 2: |
|
| 698 | if ($this->debug > 2) { |
||
| 699 | if (self::$_xmlrpcs_prev_ehandler) { |
||
| 700 | set_error_handler(self::$_xmlrpcs_prev_ehandler); |
||
| 701 | } else { |
||
| 702 | restore_error_handler(); |
||
| 703 | } |
||
| 704 | } |
||
| 705 | throw $e; |
||
| 706 | break; |
||
| 707 | 37 | case 1: |
|
| 708 | 2 | $r = new Response(0, $e->getCode(), $e->getMessage()); |
|
| 709 | 2 | break; |
|
| 710 | default: |
||
| 711 | 37 | $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']); |
|
| 712 | } |
||
| 713 | } |
||
| 714 | 438 | if ($this->debug > 2) { |
|
| 715 | // note: restore the error handler we found before calling the |
||
| 716 | // user func, even if it has been changed inside the func itself |
||
| 717 | 438 | if (self::$_xmlrpcs_prev_ehandler) { |
|
| 718 | 52 | set_error_handler(self::$_xmlrpcs_prev_ehandler); |
|
| 719 | } else { |
||
| 720 | 387 | restore_error_handler(); |
|
| 721 | } |
||
| 722 | } |
||
| 723 | |||
| 724 | 438 | return $r; |
|
| 725 | } |
||
| 726 | |||
| 727 | /** |
||
| 728 | * Add a string to the 'internal debug message' (separate from 'user debug message'). |
||
| 729 | * |
||
| 730 | * @param string $string |
||
| 731 | */ |
||
| 732 | protected function debugmsg($string) |
||
| 736 | |||
| 737 | /** |
||
| 738 | * @param string $charsetEncoding |
||
| 739 | * @return string |
||
| 740 | */ |
||
| 741 | protected function xml_header($charsetEncoding = '') |
||
| 749 | |||
| 750 | /* Functions that implement system.XXX methods of xmlrpc servers */ |
||
| 751 | |||
| 752 | /** |
||
| 753 | * @return array |
||
| 754 | */ |
||
| 755 | public function getSystemDispatchMap() |
||
| 756 | { |
||
| 757 | return array( |
||
| 758 | 'system.listMethods' => array( |
||
| 759 | 103 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods', |
|
| 760 | // listMethods: signature was either a string, or nothing. |
||
| 761 | // The useless string variant has been removed |
||
| 762 | 103 | 'signature' => array(array(Value::$xmlrpcArray)), |
|
| 763 | 103 | 'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch', |
|
| 764 | 'signature_docs' => array(array('list of method names')), |
||
| 765 | ), |
||
| 766 | 'system.methodHelp' => array( |
||
| 767 | 103 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp', |
|
| 768 | 103 | 'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)), |
|
| 769 | 103 | 'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string', |
|
| 770 | 'signature_docs' => array(array('method description', 'name of the method to be described')), |
||
| 771 | ), |
||
| 772 | 'system.methodSignature' => array( |
||
| 773 | 103 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature', |
|
| 774 | 103 | 'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)), |
|
| 775 | 103 | 'docstring' => 'Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)', |
|
| 776 | 'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')), |
||
| 777 | ), |
||
| 778 | 'system.multicall' => array( |
||
| 779 | 103 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall', |
|
| 780 | 103 | 'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)), |
|
| 781 | 103 | 'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details', |
|
| 782 | 'signature_docs' => array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"')), |
||
| 783 | ), |
||
| 784 | 'system.getCapabilities' => array( |
||
| 785 | 103 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities', |
|
| 786 | 103 | 'signature' => array(array(Value::$xmlrpcStruct)), |
|
| 787 | 103 | 'docstring' => 'This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to', |
|
| 788 | 'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')), |
||
| 789 | ), |
||
| 790 | ); |
||
| 791 | } |
||
| 792 | |||
| 793 | /** |
||
| 794 | * @return array |
||
| 795 | */ |
||
| 796 | public function getCapabilities() |
||
| 797 | { |
||
| 798 | $outAr = array( |
||
| 799 | // xmlrpc spec: always supported |
||
| 800 | 'xmlrpc' => array( |
||
| 801 | 'specUrl' => 'http://www.xmlrpc.com/spec', |
||
| 802 | 'specVersion' => 1 |
||
| 803 | ), |
||
| 804 | // if we support system.xxx functions, we always support multicall, too... |
||
| 805 | // Note that, as of 2006/09/17, the following URL does not respond anymore |
||
| 806 | 'system.multicall' => array( |
||
| 807 | 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', |
||
| 808 | 'specVersion' => 1 |
||
| 809 | ), |
||
| 810 | // introspection: version 2! we support 'mixed', too |
||
| 811 | 'introspection' => array( |
||
| 812 | 'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html', |
||
| 813 | 'specVersion' => 2, |
||
| 814 | ), |
||
| 815 | ); |
||
| 816 | |||
| 817 | // NIL extension |
||
| 818 | if (PhpXmlRpc::$xmlrpc_null_extension) { |
||
| 819 | $outAr['nil'] = array( |
||
| 820 | 'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php', |
||
| 821 | 'specVersion' => 1 |
||
| 822 | ); |
||
| 823 | } |
||
| 824 | |||
| 825 | return $outAr; |
||
| 826 | } |
||
| 827 | |||
| 828 | /** |
||
| 829 | * @param Server $server |
||
| 830 | * @param Request $req |
||
| 831 | * @return Response |
||
| 832 | */ |
||
| 833 | public static function _xmlrpcs_getCapabilities($server, $req = null) |
||
| 838 | |||
| 839 | /** |
||
| 840 | * @param Server $server |
||
| 841 | * @param Request $req |
||
| 842 | * @return Response |
||
| 843 | */ |
||
| 844 | public static function _xmlrpcs_listMethods($server, $req = null) // if called in plain php values mode, second param is missing |
||
| 845 | { |
||
| 846 | 18 | $outAr = array(); |
|
| 847 | 18 | foreach ($server->dmap as $key => $val) { |
|
| 848 | 18 | $outAr[] = new Value($key, 'string'); |
|
| 849 | } |
||
| 850 | 18 | if ($server->allow_system_funcs) { |
|
| 851 | 18 | foreach ($server->getSystemDispatchMap() as $key => $val) { |
|
| 852 | 18 | $outAr[] = new Value($key, 'string'); |
|
| 853 | } |
||
| 854 | } |
||
| 855 | |||
| 856 | 18 | return new Response(new Value($outAr, 'array')); |
|
| 857 | } |
||
| 858 | |||
| 859 | /** |
||
| 860 | * @param Server $server |
||
| 861 | * @param Request $req |
||
| 862 | * @return Response |
||
| 863 | */ |
||
| 864 | public static function _xmlrpcs_methodSignature($server, $req) |
||
| 865 | { |
||
| 866 | // let accept as parameter both an xmlrpc value or string |
||
| 867 | 86 | View Code Duplication | if (is_object($req)) { |
| 868 | 86 | $methName = $req->getParam(0); |
|
| 869 | 86 | $methName = $methName->scalarval(); |
|
| 870 | } else { |
||
| 871 | $methName = $req; |
||
| 872 | } |
||
| 873 | 86 | View Code Duplication | if (strpos($methName, "system.") === 0) { |
| 874 | 69 | $dmap = $server->getSystemDispatchMap(); |
|
| 875 | } else { |
||
| 876 | 18 | $dmap = $server->dmap; |
|
| 877 | } |
||
| 878 | 86 | if (isset($dmap[$methName])) { |
|
| 879 | 86 | if (isset($dmap[$methName]['signature'])) { |
|
| 880 | 86 | $sigs = array(); |
|
| 881 | 86 | foreach ($dmap[$methName]['signature'] as $inSig) { |
|
| 882 | 86 | $curSig = array(); |
|
| 883 | 86 | foreach ($inSig as $sig) { |
|
| 884 | 86 | $curSig[] = new Value($sig, 'string'); |
|
| 885 | } |
||
| 886 | 86 | $sigs[] = new Value($curSig, 'array'); |
|
| 887 | } |
||
| 888 | 86 | $r = new Response(new Value($sigs, 'array')); |
|
| 889 | } else { |
||
| 890 | // NB: according to the official docs, we should be returning a |
||
| 891 | // "none-array" here, which means not-an-array |
||
| 892 | $r = new Response(new Value('undef', 'string')); |
||
| 893 | } |
||
| 894 | } else { |
||
| 895 | $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']); |
||
| 896 | } |
||
| 897 | |||
| 898 | 86 | return $r; |
|
| 899 | } |
||
| 900 | |||
| 901 | /** |
||
| 902 | * @param Server $server |
||
| 903 | * @param Request $req |
||
| 904 | * @return Response |
||
| 905 | */ |
||
| 906 | public static function _xmlrpcs_methodHelp($server, $req) |
||
| 907 | { |
||
| 908 | // let accept as parameter both an xmlrpc value or string |
||
| 909 | 69 | View Code Duplication | if (is_object($req)) { |
| 910 | 69 | $methName = $req->getParam(0); |
|
| 911 | 69 | $methName = $methName->scalarval(); |
|
| 912 | } else { |
||
| 913 | $methName = $req; |
||
| 914 | } |
||
| 915 | 69 | View Code Duplication | if (strpos($methName, "system.") === 0) { |
| 916 | 69 | $dmap = $server->getSystemDispatchMap(); |
|
| 917 | } else { |
||
| 918 | 1 | $dmap = $server->dmap; |
|
| 919 | } |
||
| 920 | 69 | if (isset($dmap[$methName])) { |
|
| 921 | 69 | if (isset($dmap[$methName]['docstring'])) { |
|
| 922 | 69 | $r = new Response(new Value($dmap[$methName]['docstring']), 'string'); |
|
| 923 | } else { |
||
| 924 | $r = new Response(new Value('', 'string')); |
||
| 925 | } |
||
| 926 | } else { |
||
| 927 | $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']); |
||
| 928 | } |
||
| 929 | |||
| 930 | 69 | return $r; |
|
| 931 | } |
||
| 932 | |||
| 933 | public static function _xmlrpcs_multicall_error($err) |
||
| 934 | { |
||
| 935 | 52 | if (is_string($err)) { |
|
| 936 | 52 | $str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"]; |
|
| 937 | 52 | $code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"]; |
|
| 938 | } else { |
||
| 939 | 52 | $code = $err->faultCode(); |
|
| 940 | 52 | $str = $err->faultString(); |
|
| 941 | } |
||
| 942 | 52 | $struct = array(); |
|
| 943 | 52 | $struct['faultCode'] = new Value($code, 'int'); |
|
| 944 | 52 | $struct['faultString'] = new Value($str, 'string'); |
|
| 945 | |||
| 946 | 52 | return new Value($struct, 'struct'); |
|
| 947 | } |
||
| 948 | |||
| 949 | /** |
||
| 950 | * @param Server $server |
||
| 951 | * @param Value $call |
||
| 952 | * @return Value |
||
| 953 | */ |
||
| 954 | public static function _xmlrpcs_multicall_do_call($server, $call) |
||
| 955 | { |
||
| 956 | 52 | if ($call->kindOf() != 'struct') { |
|
| 957 | return static::_xmlrpcs_multicall_error('notstruct'); |
||
| 958 | } |
||
| 959 | 52 | $methName = @$call['methodName']; |
|
| 960 | 52 | if (!$methName) { |
|
| 961 | return static::_xmlrpcs_multicall_error('nomethod'); |
||
| 962 | } |
||
| 963 | 52 | if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') { |
|
| 964 | return static::_xmlrpcs_multicall_error('notstring'); |
||
| 965 | } |
||
| 966 | 52 | if ($methName->scalarval() == 'system.multicall') { |
|
| 967 | 52 | return static::_xmlrpcs_multicall_error('recursion'); |
|
| 968 | } |
||
| 969 | |||
| 970 | 52 | $params = @$call['params']; |
|
| 971 | 52 | if (!$params) { |
|
| 972 | return static::_xmlrpcs_multicall_error('noparams'); |
||
| 973 | } |
||
| 974 | 52 | if ($params->kindOf() != 'array') { |
|
| 975 | return static::_xmlrpcs_multicall_error('notarray'); |
||
| 976 | } |
||
| 977 | |||
| 978 | 52 | $req = new Request($methName->scalarval()); |
|
| 979 | 52 | foreach($params as $i => $param) { |
|
| 980 | 52 | if (!$req->addParam($param)) { |
|
| 981 | $i++; // for error message, we count params from 1 |
||
| 982 | return static::_xmlrpcs_multicall_error(new Response(0, |
||
| 983 | PhpXmlRpc::$xmlrpcerr['incorrect_params'], |
||
| 984 | PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i)); |
||
| 985 | } |
||
| 986 | } |
||
| 987 | |||
| 988 | 52 | $result = $server->execute($req); |
|
| 989 | |||
| 990 | 52 | if ($result->faultCode() != 0) { |
|
| 991 | 52 | return static::_xmlrpcs_multicall_error($result); // Method returned fault. |
|
| 992 | } |
||
| 993 | |||
| 994 | 52 | return new Value(array($result->value()), 'array'); |
|
| 995 | } |
||
| 996 | |||
| 997 | /** |
||
| 998 | * @param Server $server |
||
| 999 | * @param Value $call |
||
| 1000 | * @return Value |
||
| 1001 | */ |
||
| 1002 | public static function _xmlrpcs_multicall_do_call_phpvals($server, $call) |
||
| 1040 | |||
| 1041 | /** |
||
| 1042 | * @param Server $server |
||
| 1043 | * @param Request $req |
||
| 1044 | * @return Response |
||
| 1045 | */ |
||
| 1046 | public static function _xmlrpcs_multicall($server, $req) |
||
| 1047 | { |
||
| 1048 | 69 | $result = array(); |
|
| 1049 | // let accept a plain list of php parameters, beside a single xmlrpc msg object |
||
| 1050 | 69 | if (is_object($req)) { |
|
| 1051 | 69 | $calls = $req->getParam(0); |
|
| 1052 | 69 | foreach($calls as $call) { |
|
| 1053 | 52 | $result[] = static::_xmlrpcs_multicall_do_call($server, $call); |
|
| 1064 | |||
| 1065 | /** |
||
| 1066 | * Error handler used to track errors that occur during server-side execution of PHP code. |
||
| 1067 | * This allows to report back to the client whether an internal error has occurred or not |
||
| 1068 | * using an xmlrpc response object, instead of letting the client deal with the html junk |
||
| 1069 | * that a PHP execution error on the server generally entails. |
||
| 1070 | * |
||
| 1071 | * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors. |
||
| 1072 | */ |
||
| 1073 | public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null) |
||
| 1105 | } |
||
| 1106 |
If you define a variable conditionally, it can happen that it is not defined for all execution paths.
Let’s take a look at an example:
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.
Available Fixes
Check for existence of the variable explicitly:
Define a default value for the variable:
Add a value for the missing path: