1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpXmlRpc; |
4
|
|
|
|
5
|
|
|
use PhpXmlRpc\Helper\XMLParser; |
6
|
|
|
use PhpXmlRpc\Helper\Charset; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Allows effortless implementation of XML-RPC servers |
10
|
|
|
*/ |
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
|
489 |
|
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
|
489 |
|
if (function_exists('gzinflate')) { |
104
|
489 |
|
$this->accepted_compression = array('gzip', 'deflate'); |
105
|
489 |
|
$this->compress_response = true; |
106
|
1 |
|
} |
107
|
|
|
|
108
|
|
|
// by default the xml parser can support these 3 charset encodings |
109
|
489 |
|
$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
|
489 |
|
if ($dispatchMap) { |
118
|
488 |
|
$this->dmap = $dispatchMap; |
119
|
488 |
|
if ($serviceNow) { |
120
|
|
|
$this->service(); |
121
|
|
|
} |
122
|
|
|
} |
123
|
489 |
|
} |
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
|
488 |
|
public function setDebug($level) |
140
|
|
|
{ |
141
|
488 |
|
$this->debug = $level; |
142
|
488 |
|
} |
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) |
154
|
|
|
{ |
155
|
2 |
|
static::$_xmlrpc_debuginfo .= $msg . "\n"; |
156
|
2 |
|
} |
157
|
|
|
|
158
|
20 |
|
public static function error_occurred($msg) |
159
|
|
|
{ |
160
|
20 |
|
static::$_xmlrpcs_occurred_errors .= $msg . "\n"; |
161
|
20 |
|
} |
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
|
488 |
|
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
|
488 |
|
$out = ''; |
178
|
488 |
|
if ($this->debug_info != '') { |
179
|
488 |
|
$out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n"; |
180
|
|
|
} |
181
|
488 |
|
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
|
488 |
|
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
|
488 |
|
public function service($data = null, $returnPayload = false) |
202
|
|
|
{ |
203
|
488 |
|
if ($data === null) { |
204
|
488 |
|
$data = file_get_contents('php://input'); |
205
|
|
|
} |
206
|
488 |
|
$rawData = $data; |
207
|
|
|
|
208
|
|
|
// reset internal debug info |
209
|
488 |
|
$this->debug_info = ''; |
210
|
|
|
|
211
|
|
|
// Save what we received, before parsing it |
212
|
488 |
|
if ($this->debug > 1) { |
213
|
488 |
|
$this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++"); |
214
|
|
|
} |
215
|
|
|
|
216
|
488 |
|
$r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding); |
217
|
488 |
|
if (!$r) { |
218
|
|
|
// this actually executes the request |
219
|
488 |
|
$r = $this->parseRequest($data, $reqCharset); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
// save full body of request into response, for more debugging usages |
223
|
488 |
|
$r->raw_data = $rawData; |
224
|
|
|
|
225
|
488 |
|
if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) { |
226
|
20 |
|
$this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" . |
227
|
20 |
|
static::$_xmlrpcs_occurred_errors . "+++END+++"); |
228
|
|
|
} |
229
|
|
|
|
230
|
488 |
|
$payload = $this->xml_header($respCharset); |
231
|
488 |
|
if ($this->debug > 0) { |
232
|
488 |
|
$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
|
488 |
|
if (empty($r->payload)) { |
238
|
488 |
|
$r->serialize($respCharset); |
239
|
|
|
} |
240
|
488 |
|
$payload = $payload . $r->payload; |
241
|
|
|
|
242
|
488 |
|
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
|
488 |
|
if (!headers_sent()) { |
249
|
488 |
|
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
|
488 |
|
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
|
488 |
|
$phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'); |
258
|
488 |
|
if ($this->compress_response && function_exists('gzencode') && $respEncoding != '' |
259
|
488 |
|
&& $phpNoSelfCompress |
260
|
100 |
|
) { |
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
|
100 |
|
} 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
|
488 |
|
if ($phpNoSelfCompress) { |
275
|
488 |
|
header('Content-Length: ' . (int)strlen($payload)); |
276
|
20 |
|
} |
277
|
488 |
|
} else { |
278
|
20 |
|
error_log('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages'); |
279
|
|
|
} |
280
|
|
|
|
281
|
488 |
|
print $payload; |
282
|
|
|
|
283
|
|
|
// return request, in case subclasses want it |
284
|
488 |
|
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
|
467 |
|
protected function verifySignature($in, $sigs) |
319
|
|
|
{ |
320
|
|
|
// check each possible signature in turn |
321
|
467 |
|
if (is_object($in)) { |
322
|
467 |
|
$numParams = $in->getNumParams(); |
323
|
467 |
|
} else { |
324
|
|
|
$numParams = count($in); |
325
|
|
|
} |
326
|
467 |
|
foreach ($sigs as $curSig) { |
327
|
467 |
|
if (count($curSig) == $numParams + 1) { |
328
|
467 |
|
$itsOK = 1; |
329
|
467 |
|
for ($n = 0; $n < $numParams; $n++) { |
330
|
448 |
|
if (is_object($in)) { |
331
|
448 |
|
$p = $in->getParam($n); |
332
|
448 |
|
if ($p->kindOf() == 'scalar') { |
333
|
391 |
|
$pt = $p->scalartyp(); |
334
|
391 |
|
} else { |
335
|
134 |
|
$pt = $p->kindOf(); |
336
|
|
|
} |
337
|
134 |
|
} 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
|
448 |
|
if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) { |
343
|
20 |
|
$itsOK = 0; |
344
|
20 |
|
$pno = $n + 1; |
345
|
20 |
|
$wanted = $curSig[$n + 1]; |
346
|
20 |
|
$got = $pt; |
347
|
20 |
|
break; |
348
|
|
|
} |
349
|
|
|
} |
350
|
467 |
|
if ($itsOK) { |
351
|
467 |
|
return array(1, ''); |
352
|
|
|
} |
353
|
|
|
} |
354
|
20 |
|
} |
355
|
20 |
|
if (isset($wanted)) { |
356
|
|
|
return array(0, "Wanted ${wanted}, got ${got} at param ${pno}"); |
|
|
|
|
357
|
|
|
} else { |
358
|
20 |
|
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
|
488 |
|
if (count($_SERVER) == 0) { |
372
|
|
|
error_log('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated'); |
373
|
|
|
} |
374
|
|
|
|
375
|
488 |
|
if ($this->debug > 1) { |
376
|
488 |
|
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
|
488 |
|
if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { |
385
|
100 |
|
$contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']); |
386
|
100 |
|
} else { |
387
|
388 |
|
$contentEncoding = ''; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
// check if request body has been compressed and decompress it |
391
|
488 |
|
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
|
100 |
|
} 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
|
50 |
|
} 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
|
488 |
|
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
|
79 |
|
if (strpos($accepted, $charset) === 0) { |
432
|
|
|
$respEncoding = $charset; |
433
|
|
|
break; |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
if ($respEncoding) { |
437
|
|
|
break; |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
} |
441
|
|
|
} else { |
442
|
488 |
|
$respEncoding = $this->response_charset_encoding; |
443
|
|
|
} |
444
|
|
|
|
445
|
488 |
|
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { |
446
|
100 |
|
$respCompression = $_SERVER['HTTP_ACCEPT_ENCODING']; |
447
|
100 |
|
} else { |
448
|
388 |
|
$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
|
488 |
|
$reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '', |
454
|
488 |
|
$data); |
455
|
|
|
|
456
|
488 |
|
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
|
489 |
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
|
488 |
|
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
|
2 |
|
} else { |
484
|
2 |
|
if (extension_loaded('mbstring')) { |
485
|
2 |
|
$data = mb_convert_encoding($data, 'UTF-8', $reqEncoding); |
486
|
2 |
|
} else { |
487
|
|
|
error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding); |
488
|
|
|
} |
489
|
|
|
} |
490
|
|
|
} |
491
|
|
|
} |
492
|
|
|
|
493
|
489 |
|
$parser = xml_parser_create(); |
494
|
489 |
|
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
|
489 |
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
|
489 |
|
xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding); |
505
|
|
|
} |
506
|
|
|
|
507
|
489 |
|
$xmlRpcParser = new XMLParser(); |
508
|
489 |
|
xml_set_object($parser, $xmlRpcParser); |
509
|
|
|
|
510
|
489 |
|
if ($this->functions_parameters_type != 'xmlrpcvals') { |
511
|
|
|
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); |
512
|
|
|
} else { |
513
|
489 |
|
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); |
514
|
|
|
} |
515
|
489 |
|
xml_set_character_data_handler($parser, 'xmlrpc_cd'); |
516
|
489 |
|
xml_set_default_handler($parser, 'xmlrpc_dh'); |
517
|
489 |
|
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
|
489 |
|
} 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
|
1 |
|
} else { |
531
|
488 |
|
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
|
488 |
|
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
|
488 |
|
$req = new Request($xmlRpcParser->_xh['method']); |
544
|
|
|
// now add parameters in |
545
|
488 |
View Code Duplication |
for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) { |
|
|
|
|
546
|
469 |
|
$req->addParam($xmlRpcParser->_xh['params'][$i]); |
547
|
469 |
|
} |
548
|
|
|
|
549
|
488 |
|
if ($this->debug > 1) { |
550
|
488 |
|
$this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++"); |
551
|
|
|
} |
552
|
488 |
|
$r = $this->execute($req); |
553
|
|
|
} |
554
|
|
|
} |
555
|
|
|
|
556
|
489 |
|
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
|
488 |
|
static::$_xmlrpcs_occurred_errors = ''; |
573
|
488 |
|
static::$_xmlrpc_debuginfo = ''; |
574
|
|
|
|
575
|
488 |
|
if (is_object($req)) { |
576
|
488 |
|
$methName = $req->method(); |
577
|
488 |
|
} else { |
578
|
|
|
$methName = $req; |
579
|
|
|
} |
580
|
488 |
|
$sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0); |
581
|
488 |
|
$dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; |
582
|
|
|
|
583
|
488 |
View Code Duplication |
if (!isset($dmap[$methName]['function'])) { |
|
|
|
|
584
|
|
|
// No such method |
585
|
77 |
|
return new Response(0, |
586
|
77 |
|
PhpXmlRpc::$xmlrpcerr['unknown_method'], |
587
|
77 |
|
PhpXmlRpc::$xmlrpcstr['unknown_method']); |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
// Check signature |
591
|
488 |
|
if (isset($dmap[$methName]['signature'])) { |
592
|
467 |
|
$sig = $dmap[$methName]['signature']; |
593
|
467 |
|
if (is_object($req)) { |
594
|
467 |
|
list($ok, $errStr) = $this->verifySignature($req, $sig); |
595
|
467 |
|
} else { |
596
|
|
|
list($ok, $errStr) = $this->verifySignature($paramTypes, $sig); |
|
|
|
|
597
|
|
|
} |
598
|
467 |
View Code Duplication |
if (!$ok) { |
|
|
|
|
599
|
|
|
// Didn't match. |
600
|
20 |
|
return new Response( |
601
|
20 |
|
0, |
602
|
20 |
|
PhpXmlRpc::$xmlrpcerr['incorrect_params'], |
603
|
20 |
|
PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}" |
604
|
20 |
|
); |
605
|
|
|
} |
606
|
|
|
} |
607
|
|
|
|
608
|
488 |
|
$func = $dmap[$methName]['function']; |
609
|
|
|
// let the 'class::function' syntax be accepted in dispatch maps |
610
|
488 |
|
if (is_string($func) && strpos($func, '::')) { |
611
|
115 |
|
$func = explode('::', $func); |
612
|
|
|
} |
613
|
|
|
|
614
|
488 |
|
if (is_array($func)) { |
615
|
136 |
View Code Duplication |
if (is_object($func[0])) { |
|
|
|
|
616
|
22 |
|
$funcName = get_class($func[0]) . '->' . $func[1]; |
617
|
22 |
|
} else { |
618
|
115 |
|
$funcName = implode('::', $func); |
619
|
|
|
} |
620
|
467 |
|
} else if ($func instanceof \Closure) { |
621
|
98 |
|
$funcName = 'Closure'; |
622
|
98 |
|
} else { |
623
|
294 |
|
$funcName = $func; |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
// verify that function to be invoked is in fact callable |
627
|
488 |
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
|
488 |
|
if ($this->debug > 2) { |
639
|
488 |
|
self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')); |
|
|
|
|
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
try { |
643
|
|
|
// Allow mixed-convention servers |
644
|
488 |
|
if (is_object($req)) { |
645
|
488 |
|
if ($sysCall) { |
646
|
115 |
|
$r = call_user_func($func, $this, $req); |
647
|
115 |
|
} else { |
648
|
393 |
|
$r = call_user_func($func, $req); |
649
|
|
|
} |
650
|
486 |
|
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
|
41 |
|
} 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
|
41 |
|
switch ($this->exception_handling) { |
697
|
41 |
|
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
|
41 |
|
case 1: |
708
|
2 |
|
$r = new Response(0, $e->getCode(), $e->getMessage()); |
709
|
2 |
|
break; |
710
|
41 |
|
default: |
711
|
41 |
|
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']); |
712
|
41 |
|
} |
713
|
|
|
} |
714
|
488 |
|
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
|
488 |
|
if (self::$_xmlrpcs_prev_ehandler) { |
718
|
58 |
|
set_error_handler(self::$_xmlrpcs_prev_ehandler); |
719
|
58 |
|
} else { |
720
|
431 |
|
restore_error_handler(); |
721
|
|
|
} |
722
|
|
|
} |
723
|
|
|
|
724
|
488 |
|
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
|
488 |
|
$this->debug_info .= $string . "\n"; |
735
|
488 |
|
} |
736
|
|
|
|
737
|
|
|
/** |
738
|
|
|
* @param string $charsetEncoding |
739
|
|
|
* @return string |
740
|
|
|
*/ |
741
|
|
|
protected function xml_header($charsetEncoding = '') |
742
|
|
|
{ |
743
|
488 |
|
if ($charsetEncoding != '') { |
744
|
50 |
|
return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n"; |
745
|
|
|
} else { |
746
|
438 |
|
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
|
115 |
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods', |
760
|
|
|
// listMethods: signature was either a string, or nothing. |
761
|
|
|
// The useless string variant has been removed |
762
|
115 |
|
'signature' => array(array(Value::$xmlrpcArray)), |
763
|
115 |
|
'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch', |
764
|
115 |
|
'signature_docs' => array(array('list of method names')), |
765
|
115 |
|
), |
766
|
|
|
'system.methodHelp' => array( |
767
|
115 |
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp', |
768
|
115 |
|
'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)), |
769
|
115 |
|
'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string', |
770
|
115 |
|
'signature_docs' => array(array('method description', 'name of the method to be described')), |
771
|
115 |
|
), |
772
|
|
|
'system.methodSignature' => array( |
773
|
115 |
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature', |
774
|
115 |
|
'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)), |
775
|
115 |
|
'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
|
115 |
|
'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')), |
777
|
115 |
|
), |
778
|
|
|
'system.multicall' => array( |
779
|
115 |
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall', |
780
|
115 |
|
'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)), |
781
|
115 |
|
'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details', |
782
|
115 |
|
'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
|
115 |
|
), |
784
|
|
|
'system.getCapabilities' => array( |
785
|
115 |
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities', |
786
|
115 |
|
'signature' => array(array(Value::$xmlrpcStruct)), |
787
|
115 |
|
'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
|
115 |
|
'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')), |
789
|
115 |
|
), |
790
|
115 |
|
); |
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) |
|
|
|
|
834
|
|
|
{ |
835
|
|
|
$encoder = new Encoder(); |
836
|
|
|
return new Response($encoder->encode($server->getCapabilities())); |
837
|
|
|
} |
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
|
20 |
|
$outAr = array(); |
847
|
20 |
|
foreach ($server->dmap as $key => $val) { |
848
|
20 |
|
$outAr[] = new Value($key, 'string'); |
849
|
20 |
|
} |
850
|
20 |
|
if ($server->allow_system_funcs) { |
851
|
20 |
|
foreach ($server->getSystemDispatchMap() as $key => $val) { |
852
|
20 |
|
$outAr[] = new Value($key, 'string'); |
853
|
20 |
|
} |
854
|
|
|
} |
855
|
|
|
|
856
|
20 |
|
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
|
96 |
View Code Duplication |
if (is_object($req)) { |
|
|
|
|
868
|
96 |
|
$methName = $req->getParam(0); |
869
|
96 |
|
$methName = $methName->scalarval(); |
870
|
96 |
|
} else { |
871
|
|
|
$methName = $req; |
872
|
|
|
} |
873
|
96 |
View Code Duplication |
if (strpos($methName, "system.") === 0) { |
|
|
|
|
874
|
77 |
|
$dmap = $server->getSystemDispatchMap(); |
875
|
77 |
|
} else { |
876
|
20 |
|
$dmap = $server->dmap; |
877
|
|
|
} |
878
|
96 |
|
if (isset($dmap[$methName])) { |
879
|
96 |
|
if (isset($dmap[$methName]['signature'])) { |
880
|
96 |
|
$sigs = array(); |
881
|
96 |
|
foreach ($dmap[$methName]['signature'] as $inSig) { |
882
|
96 |
|
$curSig = array(); |
883
|
96 |
|
foreach ($inSig as $sig) { |
884
|
96 |
|
$curSig[] = new Value($sig, 'string'); |
885
|
96 |
|
} |
886
|
96 |
|
$sigs[] = new Value($curSig, 'array'); |
|
|
|
|
887
|
96 |
|
} |
888
|
96 |
|
$r = new Response(new Value($sigs, 'array')); |
|
|
|
|
889
|
96 |
|
} 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
|
96 |
|
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
|
77 |
View Code Duplication |
if (is_object($req)) { |
|
|
|
|
910
|
77 |
|
$methName = $req->getParam(0); |
911
|
77 |
|
$methName = $methName->scalarval(); |
912
|
77 |
|
} else { |
913
|
|
|
$methName = $req; |
914
|
|
|
} |
915
|
77 |
View Code Duplication |
if (strpos($methName, "system.") === 0) { |
|
|
|
|
916
|
77 |
|
$dmap = $server->getSystemDispatchMap(); |
917
|
77 |
|
} else { |
918
|
1 |
|
$dmap = $server->dmap; |
919
|
|
|
} |
920
|
77 |
|
if (isset($dmap[$methName])) { |
921
|
77 |
|
if (isset($dmap[$methName]['docstring'])) { |
922
|
77 |
|
$r = new Response(new Value($dmap[$methName]['docstring']), 'string'); |
923
|
77 |
|
} 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
|
77 |
|
return $r; |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
public static function _xmlrpcs_multicall_error($err) |
934
|
|
|
{ |
935
|
58 |
|
if (is_string($err)) { |
936
|
58 |
|
$str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"]; |
937
|
58 |
|
$code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"]; |
938
|
58 |
|
} else { |
939
|
58 |
|
$code = $err->faultCode(); |
940
|
58 |
|
$str = $err->faultString(); |
941
|
|
|
} |
942
|
58 |
|
$struct = array(); |
943
|
58 |
|
$struct['faultCode'] = new Value($code, 'int'); |
944
|
58 |
|
$struct['faultString'] = new Value($str, 'string'); |
945
|
|
|
|
946
|
58 |
|
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
|
58 |
|
if ($call->kindOf() != 'struct') { |
957
|
|
|
return static::_xmlrpcs_multicall_error('notstruct'); |
958
|
|
|
} |
959
|
58 |
|
$methName = @$call['methodName']; |
960
|
58 |
|
if (!$methName) { |
961
|
|
|
return static::_xmlrpcs_multicall_error('nomethod'); |
962
|
|
|
} |
963
|
58 |
|
if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') { |
964
|
|
|
return static::_xmlrpcs_multicall_error('notstring'); |
965
|
|
|
} |
966
|
58 |
|
if ($methName->scalarval() == 'system.multicall') { |
967
|
58 |
|
return static::_xmlrpcs_multicall_error('recursion'); |
968
|
|
|
} |
969
|
|
|
|
970
|
58 |
|
$params = @$call['params']; |
971
|
58 |
|
if (!$params) { |
972
|
|
|
return static::_xmlrpcs_multicall_error('noparams'); |
973
|
|
|
} |
974
|
58 |
|
if ($params->kindOf() != 'array') { |
975
|
|
|
return static::_xmlrpcs_multicall_error('notarray'); |
976
|
|
|
} |
977
|
|
|
|
978
|
58 |
|
$req = new Request($methName->scalarval()); |
979
|
58 |
|
foreach($params as $i => $param) { |
980
|
58 |
|
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
|
58 |
|
} |
987
|
|
|
|
988
|
58 |
|
$result = $server->execute($req); |
989
|
|
|
|
990
|
58 |
|
if ($result->faultCode() != 0) { |
991
|
58 |
|
return static::_xmlrpcs_multicall_error($result); // Method returned fault. |
992
|
|
|
} |
993
|
|
|
|
994
|
58 |
|
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) |
1003
|
|
|
{ |
1004
|
|
|
if (!is_array($call)) { |
1005
|
|
|
return static::_xmlrpcs_multicall_error('notstruct'); |
1006
|
|
|
} |
1007
|
|
|
if (!array_key_exists('methodName', $call)) { |
1008
|
|
|
return static::_xmlrpcs_multicall_error('nomethod'); |
1009
|
|
|
} |
1010
|
|
|
if (!is_string($call['methodName'])) { |
1011
|
|
|
return static::_xmlrpcs_multicall_error('notstring'); |
1012
|
|
|
} |
1013
|
|
|
if ($call['methodName'] == 'system.multicall') { |
1014
|
|
|
return static::_xmlrpcs_multicall_error('recursion'); |
1015
|
|
|
} |
1016
|
|
|
if (!array_key_exists('params', $call)) { |
1017
|
|
|
return static::_xmlrpcs_multicall_error('noparams'); |
1018
|
|
|
} |
1019
|
|
|
if (!is_array($call['params'])) { |
1020
|
|
|
return static::_xmlrpcs_multicall_error('notarray'); |
1021
|
|
|
} |
1022
|
|
|
|
1023
|
|
|
// this is a real dirty and simplistic hack, since we might have received a |
1024
|
|
|
// base64 or datetime values, but they will be listed as strings here... |
1025
|
|
|
$numParams = count($call['params']); |
|
|
|
|
1026
|
|
|
$pt = array(); |
1027
|
|
|
$wrapper = new Wrapper(); |
1028
|
|
|
foreach ($call['params'] as $val) { |
1029
|
|
|
$pt[] = $wrapper->php2XmlrpcType(gettype($val)); |
1030
|
|
|
} |
1031
|
|
|
|
1032
|
|
|
$result = $server->execute($call['methodName'], $call['params'], $pt); |
1033
|
|
|
|
1034
|
|
|
if ($result->faultCode() != 0) { |
1035
|
|
|
return static::_xmlrpcs_multicall_error($result); // Method returned fault. |
1036
|
|
|
} |
1037
|
|
|
|
1038
|
|
|
return new Value(array($result->value()), 'array'); |
|
|
|
|
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
/** |
1042
|
|
|
* @param Server $server |
1043
|
|
|
* @param Request $req |
1044
|
|
|
* @return Response |
1045
|
|
|
*/ |
1046
|
|
|
public static function _xmlrpcs_multicall($server, $req) |
1047
|
|
|
{ |
1048
|
77 |
|
$result = array(); |
1049
|
|
|
// let accept a plain list of php parameters, beside a single xmlrpc msg object |
1050
|
77 |
|
if (is_object($req)) { |
1051
|
77 |
|
$calls = $req->getParam(0); |
1052
|
77 |
|
foreach($calls as $call) { |
1053
|
58 |
|
$result[] = static::_xmlrpcs_multicall_do_call($server, $call); |
1054
|
77 |
|
} |
1055
|
77 |
|
} else { |
1056
|
|
|
$numCalls = count($req); |
1057
|
|
|
for ($i = 0; $i < $numCalls; $i++) { |
1058
|
|
|
$result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]); |
1059
|
|
|
} |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
77 |
|
return new Response(new Value($result, 'array')); |
|
|
|
|
1063
|
|
|
} |
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) |
1074
|
|
|
{ |
1075
|
|
|
// obey the @ protocol |
1076
|
39 |
|
if (error_reporting() == 0) { |
1077
|
20 |
|
return; |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
//if($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING) |
|
|
|
|
1081
|
20 |
|
if ($errCode != E_STRICT) { |
1082
|
20 |
|
\PhpXmlRpc\Server::error_occurred($errString); |
1083
|
|
|
} |
1084
|
|
|
// Try to avoid as much as possible disruption to the previous error handling |
1085
|
|
|
// mechanism in place |
1086
|
20 |
|
if (self::$_xmlrpcs_prev_ehandler == '') { |
1087
|
|
|
// The previous error handler was the default: all we should do is log error |
1088
|
|
|
// to the default error log (if level high enough) |
1089
|
20 |
|
if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) { |
1090
|
20 |
|
error_log($errString); |
1091
|
|
|
} |
1092
|
20 |
|
} else { |
1093
|
|
|
// Pass control on to previous error handler, trying to avoid loops... |
1094
|
|
|
if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) { |
1095
|
|
|
if (is_array(self::$_xmlrpcs_prev_ehandler)) { |
1096
|
|
|
// the following works both with static class methods and plain object methods as error handler |
1097
|
|
|
call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context)); |
1098
|
|
|
} else { |
1099
|
|
|
$method = self::$_xmlrpcs_prev_ehandler; |
1100
|
|
|
$method($errCode, $errString, $filename, $lineNo, $context); |
1101
|
|
|
} |
1102
|
|
|
} |
1103
|
|
|
} |
1104
|
20 |
|
} |
1105
|
|
|
} |
1106
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.