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 | 1 | 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 | 1 | if (function_exists('gzinflate')) { |
|
104 | 1 | $this->accepted_compression = array('gzip', 'deflate'); |
|
105 | 1 | $this->compress_response = true; |
|
106 | } |
||
107 | |||
108 | // by default the xml parser can support these 3 charset encodings |
||
109 | 1 | $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 | 1 | if ($dispatchMap) { |
|
118 | $this->dmap = $dispatchMap; |
||
119 | if ($serviceNow) { |
||
120 | $this->service(); |
||
121 | } |
||
122 | } |
||
123 | 1 | } |
|
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 | public function setDebug($level) |
||
140 | { |
||
141 | $this->debug = $level; |
||
142 | } |
||
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 | public static function xmlrpc_debugmsg($msg) |
||
154 | { |
||
155 | static::$_xmlrpc_debuginfo .= $msg . "\n"; |
||
156 | } |
||
157 | |||
158 | public static function error_occurred($msg) |
||
159 | { |
||
160 | static::$_xmlrpcs_occurred_errors .= $msg . "\n"; |
||
161 | } |
||
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 | 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 | $out = ''; |
||
178 | if ($this->debug_info != '') { |
||
179 | $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n"; |
||
180 | } |
||
181 | if (static::$_xmlrpc_debuginfo != '') { |
||
182 | $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 | 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 | public function service($data = null, $returnPayload = false) |
||
202 | { |
||
203 | if ($data === null) { |
||
204 | $data = file_get_contents('php://input'); |
||
205 | } |
||
206 | $rawData = $data; |
||
207 | |||
208 | // reset internal debug info |
||
209 | $this->debug_info = ''; |
||
210 | |||
211 | // Save what we received, before parsing it |
||
212 | if ($this->debug > 1) { |
||
213 | $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++"); |
||
214 | } |
||
215 | |||
216 | $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding); |
||
217 | if (!$r) { |
||
218 | // this actually executes the request |
||
219 | $r = $this->parseRequest($data, $reqCharset); |
||
220 | } |
||
221 | |||
222 | // save full body of request into response, for more debugging usages |
||
223 | $r->raw_data = $rawData; |
||
224 | |||
225 | if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) { |
||
226 | $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" . |
||
227 | static::$_xmlrpcs_occurred_errors . "+++END+++"); |
||
228 | } |
||
229 | |||
230 | $payload = $this->xml_header($respCharset); |
||
231 | if ($this->debug > 0) { |
||
232 | $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 | if (empty($r->payload)) { |
||
238 | $r->serialize($respCharset); |
||
239 | } |
||
240 | $payload = $payload . $r->payload; |
||
241 | |||
242 | 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 | if (!headers_sent()) { |
||
249 | 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 | 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 | $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'); |
||
258 | if ($this->compress_response && function_exists('gzencode') && $respEncoding != '' |
||
259 | && $phpNoSelfCompress |
||
260 | ) { |
||
261 | if (strpos($respEncoding, 'gzip') !== false) { |
||
262 | $payload = gzencode($payload); |
||
263 | header("Content-Encoding: gzip"); |
||
264 | header("Vary: Accept-Encoding"); |
||
265 | } elseif (strpos($respEncoding, 'deflate') !== false) { |
||
266 | $payload = gzcompress($payload); |
||
267 | header("Content-Encoding: deflate"); |
||
268 | 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 | if ($phpNoSelfCompress) { |
||
275 | 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 | print $payload; |
||
282 | |||
283 | // return request, in case subclasses want it |
||
284 | 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 | protected function verifySignature($in, $sigs) |
||
319 | { |
||
320 | // check each possible signature in turn |
||
321 | if (is_object($in)) { |
||
322 | $numParams = $in->getNumParams(); |
||
323 | } else { |
||
324 | $numParams = count($in); |
||
325 | } |
||
326 | foreach ($sigs as $curSig) { |
||
327 | if (count($curSig) == $numParams + 1) { |
||
328 | $itsOK = 1; |
||
329 | for ($n = 0; $n < $numParams; $n++) { |
||
330 | if (is_object($in)) { |
||
331 | $p = $in->getParam($n); |
||
332 | if ($p->kindOf() == 'scalar') { |
||
333 | $pt = $p->scalartyp(); |
||
334 | } else { |
||
335 | $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 | if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) { |
||
343 | $itsOK = 0; |
||
344 | $pno = $n + 1; |
||
345 | $wanted = $curSig[$n + 1]; |
||
346 | $got = $pt; |
||
347 | break; |
||
348 | } |
||
349 | } |
||
350 | if ($itsOK) { |
||
351 | return array(1, ''); |
||
352 | } |
||
353 | } |
||
354 | } |
||
355 | if (isset($wanted)) { |
||
356 | return array(0, "Wanted ${wanted}, got ${got} at param ${pno}"); |
||
357 | } else { |
||
358 | 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 | if (count($_SERVER) == 0) { |
||
372 | error_log('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated'); |
||
373 | } |
||
374 | |||
375 | if ($this->debug > 1) { |
||
376 | if (function_exists('getallheaders')) { |
||
377 | $this->debugmsg(''); // empty line |
||
378 | foreach (getallheaders() as $name => $val) { |
||
379 | $this->debugmsg("HEADER: $name: $val"); |
||
380 | } |
||
381 | } |
||
382 | } |
||
383 | |||
384 | if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { |
||
385 | $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']); |
||
386 | } else { |
||
387 | $contentEncoding = ''; |
||
388 | } |
||
389 | |||
390 | // check if request body has been compressed and decompress it |
||
391 | if ($contentEncoding != '' && strlen($data)) { |
||
392 | if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') { |
||
393 | // if decoding works, use it. else assume data wasn't gzencoded |
||
394 | if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) { |
||
395 | if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) { |
||
396 | $data = $degzdata; |
||
397 | View Code Duplication | if ($this->debug > 1) { |
|
398 | $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++"); |
||
399 | } |
||
400 | } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) { |
||
401 | $data = $degzdata; |
||
402 | View Code Duplication | if ($this->debug > 1) { |
|
403 | $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 | 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 | $respEncoding = $this->response_charset_encoding; |
||
443 | } |
||
444 | |||
445 | if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { |
||
446 | $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING']; |
||
447 | } else { |
||
448 | $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 | $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '', |
||
454 | $data); |
||
455 | |||
456 | 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 | 1 | 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 | if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) { |
||
481 | if ($reqEncoding == 'ISO-8859-1') { |
||
482 | $data = utf8_encode($data); |
||
483 | } else { |
||
484 | if (extension_loaded('mbstring')) { |
||
485 | $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 | 1 | $parser = xml_parser_create(); |
|
494 | 1 | 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 | 1 | 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 | 1 | xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding); |
|
505 | } |
||
506 | |||
507 | 1 | $xmlRpcParser = new XMLParser(); |
|
508 | 1 | xml_set_object($parser, $xmlRpcParser); |
|
509 | |||
510 | 1 | if ($this->functions_parameters_type != 'xmlrpcvals') { |
|
511 | xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); |
||
512 | } else { |
||
513 | 1 | xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); |
|
514 | } |
||
515 | 1 | xml_set_character_data_handler($parser, 'xmlrpc_cd'); |
|
516 | 1 | xml_set_default_handler($parser, 'xmlrpc_dh'); |
|
517 | 1 | 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 | 1 | } 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 | 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 | 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 | $req = new Request($xmlRpcParser->_xh['method']); |
||
544 | // now add parameters in |
||
545 | View Code Duplication | for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) { |
|
546 | $req->addParam($xmlRpcParser->_xh['params'][$i]); |
||
547 | } |
||
548 | |||
549 | if ($this->debug > 1) { |
||
550 | $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++"); |
||
551 | } |
||
552 | $r = $this->execute($req); |
||
553 | } |
||
554 | } |
||
555 | |||
556 | 1 | 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 | static::$_xmlrpcs_occurred_errors = ''; |
||
573 | static::$_xmlrpc_debuginfo = ''; |
||
574 | |||
575 | if (is_object($req)) { |
||
576 | $methName = $req->method(); |
||
577 | } else { |
||
578 | $methName = $req; |
||
579 | } |
||
580 | $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0); |
||
581 | $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; |
||
582 | |||
583 | View Code Duplication | if (!isset($dmap[$methName]['function'])) { |
|
584 | // No such method |
||
585 | return new Response(0, |
||
586 | PhpXmlRpc::$xmlrpcerr['unknown_method'], |
||
587 | PhpXmlRpc::$xmlrpcstr['unknown_method']); |
||
588 | } |
||
589 | |||
590 | // Check signature |
||
591 | if (isset($dmap[$methName]['signature'])) { |
||
592 | $sig = $dmap[$methName]['signature']; |
||
593 | if (is_object($req)) { |
||
594 | list($ok, $errStr) = $this->verifySignature($req, $sig); |
||
595 | } else { |
||
596 | list($ok, $errStr) = $this->verifySignature($paramTypes, $sig); |
||
597 | } |
||
598 | View Code Duplication | if (!$ok) { |
|
599 | // Didn't match. |
||
600 | return new Response( |
||
601 | 0, |
||
602 | PhpXmlRpc::$xmlrpcerr['incorrect_params'], |
||
603 | PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}" |
||
604 | ); |
||
605 | } |
||
606 | } |
||
607 | |||
608 | $func = $dmap[$methName]['function']; |
||
609 | // let the 'class::function' syntax be accepted in dispatch maps |
||
610 | if (is_string($func) && strpos($func, '::')) { |
||
611 | $func = explode('::', $func); |
||
612 | } |
||
613 | |||
614 | if (is_array($func)) { |
||
615 | View Code Duplication | if (is_object($func[0])) { |
|
616 | $funcName = get_class($func[0]) . '->' . $func[1]; |
||
617 | } else { |
||
618 | $funcName = implode('::', $func); |
||
619 | } |
||
620 | } else if ($func instanceof \Closure) { |
||
621 | $funcName = 'Closure'; |
||
622 | } else { |
||
623 | $funcName = $func; |
||
624 | } |
||
625 | |||
626 | // verify that function to be invoked is in fact callable |
||
627 | 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 | if ($this->debug > 2) { |
||
639 | self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')); |
||
640 | } |
||
641 | |||
642 | try { |
||
643 | // Allow mixed-convention servers |
||
644 | if (is_object($req)) { |
||
645 | if ($sysCall) { |
||
646 | $r = call_user_func($func, $this, $req); |
||
647 | } else { |
||
648 | $r = call_user_func($func, $req); |
||
649 | } |
||
650 | 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 | } 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 | switch ($this->exception_handling) { |
||
697 | 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 | case 1: |
||
708 | $r = new Response(0, $e->getCode(), $e->getMessage()); |
||
709 | break; |
||
710 | default: |
||
711 | $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']); |
||
712 | } |
||
713 | } |
||
714 | 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 | if (self::$_xmlrpcs_prev_ehandler) { |
||
718 | set_error_handler(self::$_xmlrpcs_prev_ehandler); |
||
719 | } else { |
||
720 | restore_error_handler(); |
||
721 | } |
||
722 | } |
||
723 | |||
724 | 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) |
||
733 | { |
||
734 | $this->debug_info .= $string . "\n"; |
||
735 | } |
||
736 | |||
737 | /** |
||
738 | * @param string $charsetEncoding |
||
739 | * @return string |
||
740 | */ |
||
741 | protected function xml_header($charsetEncoding = '') |
||
742 | { |
||
743 | if ($charsetEncoding != '') { |
||
744 | return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n"; |
||
745 | } else { |
||
746 | return "<?xml version=\"1.0\"?" . ">\n"; |
||
747 | } |
||
748 | } |
||
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 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods', |
||
760 | // listMethods: signature was either a string, or nothing. |
||
761 | // The useless string variant has been removed |
||
762 | 'signature' => array(array(Value::$xmlrpcArray)), |
||
763 | '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 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp', |
||
768 | 'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)), |
||
769 | '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 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature', |
||
774 | 'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)), |
||
775 | '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 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall', |
||
780 | 'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)), |
||
781 | '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 | 'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities', |
||
786 | 'signature' => array(array(Value::$xmlrpcStruct)), |
||
787 | '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 | $outAr = array(); |
||
847 | foreach ($server->dmap as $key => $val) { |
||
848 | $outAr[] = new Value($key, 'string'); |
||
849 | } |
||
850 | if ($server->allow_system_funcs) { |
||
851 | foreach ($server->getSystemDispatchMap() as $key => $val) { |
||
852 | $outAr[] = new Value($key, 'string'); |
||
853 | } |
||
858 | |||
859 | /** |
||
860 | * @param Server $server |
||
861 | * @param Request $req |
||
862 | * @return Response |
||
863 | */ |
||
864 | public static function _xmlrpcs_methodSignature($server, $req) |
||
900 | |||
901 | /** |
||
902 | * @param Server $server |
||
903 | * @param Request $req |
||
904 | * @return Response |
||
905 | */ |
||
906 | public static function _xmlrpcs_methodHelp($server, $req) |
||
932 | |||
933 | public static function _xmlrpcs_multicall_error($err) |
||
948 | |||
949 | /** |
||
950 | * @param Server $server |
||
951 | * @param Value $call |
||
952 | * @return Value |
||
953 | */ |
||
954 | public static function _xmlrpcs_multicall_do_call($server, $call) |
||
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) |
||
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: