1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpXmlRpc; |
4
|
|
|
|
5
|
|
|
use PhpXmlRpc\Exception\NoSuchMethodException; |
6
|
|
|
use PhpXmlRpc\Exception\ValueErrorException; |
7
|
|
|
use PhpXmlRpc\Helper\Http; |
8
|
|
|
use PhpXmlRpc\Helper\Interop; |
9
|
|
|
use PhpXmlRpc\Helper\Logger; |
10
|
|
|
use PhpXmlRpc\Helper\XMLParser; |
11
|
|
|
use PhpXmlRpc\Traits\CharsetEncoderAware; |
12
|
|
|
use PhpXmlRpc\Traits\DeprecationLogger; |
13
|
|
|
use PhpXmlRpc\Traits\ParserAware; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Allows effortless implementation of XML-RPC servers |
17
|
|
|
* |
18
|
|
|
* @property string[] $accepted_compression deprecated - public access left in purely for BC. Access via getOption()/setOption() |
19
|
|
|
* @property bool $allow_system_funcs deprecated - public access left in purely for BC. Access via getOption()/setOption() |
20
|
|
|
* @property bool $compress_response deprecated - public access left in purely for BC. Access via getOption()/setOption() |
21
|
|
|
* @property int $debug deprecated - public access left in purely for BC. Access via getOption()/setOption() |
22
|
|
|
* @property int $exception_handling deprecated - public access left in purely for BC. Access via getOption()/setOption() |
23
|
|
|
* @property string $functions_parameters_type deprecated - public access left in purely for BC. Access via getOption()/setOption() |
24
|
|
|
* @property array $phpvals_encoding_options deprecated - public access left in purely for BC. Access via getOption()/setOption() |
25
|
|
|
* @property string $response_charset_encoding deprecated - public access left in purely for BC. Access via getOption()/setOption() |
26
|
|
|
*/ |
27
|
|
|
class Server |
28
|
|
|
{ |
29
|
|
|
use CharsetEncoderAware; |
30
|
|
|
use DeprecationLogger; |
31
|
|
|
use ParserAware; |
32
|
|
|
|
33
|
|
|
const OPT_ACCEPTED_COMPRESSION = 'accepted_compression'; |
34
|
|
|
const OPT_ALLOW_SYSTEM_FUNCS = 'allow_system_funcs'; |
35
|
|
|
const OPT_COMPRESS_RESPONSE = 'compress_response'; |
36
|
|
|
const OPT_DEBUG = 'debug'; |
37
|
|
|
const OPT_EXCEPTION_HANDLING = 'exception_handling'; |
38
|
|
|
const OPT_FUNCTIONS_PARAMETERS_TYPE = 'functions_parameters_type'; |
39
|
|
|
const OPT_PHPVALS_ENCODING_OPTIONS = 'phpvals_encoding_options'; |
40
|
|
|
const OPT_RESPONSE_CHARSET_ENCODING = 'response_charset_encoding'; |
41
|
|
|
|
42
|
|
|
/** @var string */ |
43
|
|
|
protected static $responseClass = '\\PhpXmlRpc\\Response'; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var string |
47
|
|
|
* Defines how functions in $dmap will be invoked: either using an xml-rpc Request object or plain php values. |
48
|
|
|
* Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' (only for use by polyfill-xmlrpc). |
49
|
|
|
* |
50
|
|
|
* @todo create class constants for these |
51
|
|
|
*/ |
52
|
|
|
protected $functions_parameters_type = 'xmlrpcvals'; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var array |
56
|
|
|
* Option used for fine-tuning the encoding the php values returned from functions registered in the dispatch map |
57
|
|
|
* when the functions_parameters_type member is set to 'phpvals'. |
58
|
|
|
* @see Encoder::encode for a list of values |
59
|
|
|
*/ |
60
|
|
|
protected $phpvals_encoding_options = array('auto_dates'); |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var int |
64
|
|
|
* Controls whether the server is going to echo debugging messages back to the client as comments in response body. |
65
|
|
|
* SECURITY SENSITIVE! |
66
|
|
|
* Valid values: |
67
|
|
|
* 0 = |
68
|
|
|
* 1 = |
69
|
|
|
* 2 = |
70
|
|
|
* 3 = |
71
|
|
|
*/ |
72
|
|
|
protected $debug = 1; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @var int |
76
|
|
|
* Controls behaviour of server when the invoked method-handler function throws an exception (within the `execute` method): |
77
|
|
|
* 0 = catch it and return an 'internal error' xml-rpc response (default) |
78
|
|
|
* 1 = SECURITY SENSITIVE DO NOT ENABLE ON PUBLIC SERVERS!!! catch it and return an xml-rpc response with the error |
79
|
|
|
* corresponding to the exception, both its code and message. |
80
|
|
|
* 2 = allow the exception to float to the upper layers |
81
|
|
|
* Can be overridden per-method-handler in the dispatch map |
82
|
|
|
*/ |
83
|
|
|
protected $exception_handling = 0; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @var bool |
87
|
|
|
* When set to true, it will enable HTTP compression of the response, in case the client has declared its support |
88
|
|
|
* for compression in the request. |
89
|
|
|
* Automatically set at constructor time. |
90
|
|
|
*/ |
91
|
|
|
protected $compress_response = false; |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @var string[] |
95
|
|
|
* List of http compression methods accepted by the server for requests. Automatically set at constructor time. |
96
|
|
|
* NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib |
97
|
|
|
*/ |
98
|
|
|
protected $accepted_compression = array(); |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @var bool |
102
|
|
|
* Shall we serve calls to system.* methods? |
103
|
|
|
*/ |
104
|
|
|
protected $allow_system_funcs = true; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* List of charset encodings natively accepted for requests. |
108
|
|
|
* Set at constructor time. |
109
|
|
|
* @deprecated UNUSED so far by this library. It is still accessible by subclasses but will be dropped in the future. |
110
|
|
|
*/ |
111
|
|
|
private $accepted_charset_encodings = array(); |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @var string |
115
|
562 |
|
* Charset encoding to be used for response. |
116
|
|
|
* NB: if we can, we will convert the generated response from internal_encoding to the intended one. |
117
|
562 |
|
* Can be: |
118
|
562 |
|
* - a supported xml encoding (only UTF-8 and ISO-8859-1, unless mbstring is enabled), |
119
|
|
|
* - null (leave unspecified in response, convert output stream to US_ASCII), |
120
|
562 |
|
* - '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). |
121
|
|
|
* NB: pretty dangerous if you accept every charset and do not have mbstring enabled) |
122
|
|
|
*/ |
123
|
|
|
protected $response_charset_encoding = ''; |
124
|
|
|
|
125
|
|
|
protected static $options = array( |
126
|
|
|
self::OPT_ACCEPTED_COMPRESSION, |
127
|
|
|
self::OPT_ALLOW_SYSTEM_FUNCS, |
128
|
2 |
|
self::OPT_COMPRESS_RESPONSE, |
129
|
|
|
self::OPT_DEBUG, |
130
|
2 |
|
self::OPT_EXCEPTION_HANDLING, |
131
|
2 |
|
self::OPT_FUNCTIONS_PARAMETERS_TYPE, |
132
|
|
|
self::OPT_PHPVALS_ENCODING_OPTIONS, |
133
|
2 |
|
self::OPT_RESPONSE_CHARSET_ENCODING, |
134
|
|
|
); |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* @var mixed |
138
|
|
|
* Extra data passed at runtime to method handling functions. Used only by EPI layer |
139
|
|
|
* @internal |
140
|
|
|
*/ |
141
|
|
|
public $user_data = null; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Array defining php functions exposed as xml-rpc methods by this server. |
145
|
|
|
* @var array[] $dmap |
146
|
|
|
*/ |
147
|
|
|
protected $dmap = array(); |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Storage for internal debug info. |
151
|
|
|
*/ |
152
|
562 |
|
protected $debug_info = ''; |
153
|
|
|
|
154
|
|
|
protected static $_xmlrpc_debuginfo = ''; |
155
|
|
|
protected static $_xmlrpcs_occurred_errors = ''; |
156
|
562 |
|
protected static $_xmlrpcs_prev_ehandler = ''; |
157
|
562 |
|
|
158
|
562 |
|
/** |
159
|
|
|
* @param array[] $dispatchMap the dispatch map with definition of exposed services |
160
|
|
|
* Array keys are the names of the method names. |
161
|
|
|
* Each array value is an array with the following members: |
162
|
562 |
|
* - function (callable) |
163
|
|
|
* - docstring (optional) |
164
|
|
|
* - signature (array, optional) |
165
|
|
|
* - signature_docs (array, optional) |
166
|
|
|
* - parameters_type (string, optional) |
167
|
|
|
* - exception_handling (int, optional) |
168
|
|
|
* @param boolean $serviceNow set to false in order to prevent the server from running upon construction |
169
|
|
|
*/ |
170
|
562 |
|
public function __construct($dispatchMap = null, $serviceNow = true) |
171
|
561 |
|
{ |
172
|
561 |
|
// if ZLIB is enabled, let the server by default accept compressed requests, |
173
|
2 |
|
// and compress responses sent to clients that support them |
174
|
|
|
if (function_exists('gzinflate')) { |
175
|
|
|
$this->accepted_compression[] = 'gzip'; |
176
|
562 |
|
} |
177
|
|
|
if (function_exists('gzuncompress')) { |
178
|
|
|
$this->accepted_compression[] = 'deflate'; |
179
|
|
|
} |
180
|
|
|
if (function_exists('gzencode') || function_exists('gzcompress')) { |
181
|
|
|
$this->compress_response = true; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
// by default the xml parser can support these 3 charset encodings |
185
|
|
|
$this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); |
|
|
|
|
186
|
|
|
|
187
|
|
|
// dispMap is a dispatch array of methods mapped to function names and signatures. |
188
|
|
|
// If a method doesn't appear in the map then an unknown method error is generated. |
189
|
|
|
// milosch - changed to make passing dispMap optional. Instead, you can use the addToMap() function |
190
|
|
|
// to add functions manually (borrowed from SOAPX4) |
191
|
|
|
if ($dispatchMap) { |
192
|
559 |
|
$this->setDispatchMap($dispatchMap); |
193
|
|
|
if ($serviceNow) { |
194
|
559 |
|
$this->service(); |
195
|
559 |
|
} |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* @param string $name see all the OPT_ constants |
201
|
|
|
* @param mixed $value |
202
|
|
|
* @return $this |
203
|
|
|
* @throws ValueErrorException on unsupported option |
204
|
2 |
|
*/ |
205
|
|
|
public function setOption($name, $value) |
206
|
2 |
|
{ |
207
|
2 |
|
switch ($name) { |
208
|
|
|
case self::OPT_ACCEPTED_COMPRESSION : |
209
|
|
|
case self::OPT_ALLOW_SYSTEM_FUNCS: |
210
|
|
|
case self::OPT_COMPRESS_RESPONSE: |
211
|
|
|
case self::OPT_DEBUG: |
212
|
|
|
case self::OPT_EXCEPTION_HANDLING: |
213
|
|
|
case self::OPT_FUNCTIONS_PARAMETERS_TYPE: |
214
|
|
|
case self::OPT_PHPVALS_ENCODING_OPTIONS: |
215
|
|
|
case self::OPT_RESPONSE_CHARSET_ENCODING: |
216
|
22 |
|
$this->$name = $value; |
217
|
|
|
break; |
218
|
22 |
|
default: |
219
|
22 |
|
throw new ValueErrorException("Unsupported option '$name'"); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
return $this; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* @param string $name see all the OPT_ constants |
227
|
|
|
* @return mixed |
228
|
561 |
|
* @throws ValueErrorException on unsupported option |
229
|
|
|
*/ |
230
|
|
|
public function getOption($name) |
231
|
|
|
{ |
232
|
|
|
switch ($name) { |
233
|
|
|
case self::OPT_ACCEPTED_COMPRESSION: |
234
|
|
|
case self::OPT_ALLOW_SYSTEM_FUNCS: |
235
|
561 |
|
case self::OPT_COMPRESS_RESPONSE: |
236
|
561 |
|
case self::OPT_DEBUG: |
237
|
559 |
|
case self::OPT_EXCEPTION_HANDLING: |
238
|
|
|
case self::OPT_FUNCTIONS_PARAMETERS_TYPE: |
239
|
561 |
|
case self::OPT_PHPVALS_ENCODING_OPTIONS: |
240
|
2 |
|
case self::OPT_RESPONSE_CHARSET_ENCODING: |
241
|
|
|
return $this->$name; |
242
|
|
|
default: |
243
|
|
|
throw new ValueErrorException("Unsupported option '$name'"); |
244
|
|
|
} |
245
|
|
|
} |
246
|
561 |
|
|
247
|
|
|
/** |
248
|
|
|
* Returns the complete list of Server options. |
249
|
|
|
* @return array |
250
|
|
|
*/ |
251
|
|
|
public function getOptions() |
252
|
|
|
{ |
253
|
|
|
$values = array(); |
254
|
|
|
foreach(static::$options as $opt) { |
255
|
|
|
$values[$opt] = $this->getOption($opt); |
256
|
|
|
} |
257
|
|
|
return $values; |
258
|
|
|
} |
259
|
561 |
|
|
260
|
|
|
/** |
261
|
561 |
|
* @param array $options key: see all the OPT_ constants |
262
|
561 |
|
* @return $this |
263
|
|
|
* @throws ValueErrorException on unsupported option |
264
|
561 |
|
*/ |
265
|
|
|
public function setOptions($options) |
266
|
|
|
{ |
267
|
561 |
|
foreach($options as $name => $value) { |
268
|
|
|
$this->setOption($name, $value); |
269
|
|
|
} |
270
|
561 |
|
|
271
|
559 |
|
return $this; |
272
|
|
|
} |
273
|
|
|
|
274
|
561 |
|
/** |
275
|
561 |
|
* Set debug level of server. |
276
|
|
|
* |
277
|
561 |
|
* @param integer $level debug lvl: determines info added to xml-rpc responses (as xml comments) |
278
|
|
|
* 0 = no debug info, |
279
|
|
|
* 1 = msgs set from user with debugmsg(), |
280
|
|
|
* 2 = add complete xml-rpc request (headers and body), |
281
|
|
|
* 3 = add also all processing warnings happened during method processing |
282
|
561 |
|
* (NB: this involves setting a custom error handler, and might interfere |
283
|
|
|
* with the standard processing of the php function exposed as method. In |
284
|
|
|
* particular, triggering a USER_ERROR level error will not halt script |
285
|
561 |
|
* execution anymore, but just end up logged in the xml-rpc response) |
286
|
22 |
|
* Note that info added at level 2 and 3 will be base64 encoded |
287
|
22 |
|
* @return $this |
288
|
|
|
*/ |
289
|
|
|
public function setDebug($level) |
290
|
561 |
|
{ |
291
|
561 |
|
$this->debug = $level; |
292
|
561 |
|
return $this; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
561 |
|
* Add a string to the debug info that can be later serialized by the server as part of the response message. |
297
|
561 |
|
* Note that for best compatibility, the debug string should be encoded using the PhpXmlRpc::$xmlrpc_internalencoding |
298
|
|
|
* character set. |
299
|
561 |
|
* |
300
|
|
|
* @param string $msg |
301
|
561 |
|
* @return void |
302
|
|
|
*/ |
303
|
|
|
public static function xmlrpc_debugmsg($msg) |
304
|
|
|
{ |
305
|
|
|
static::$_xmlrpc_debuginfo .= $msg . "\n"; |
306
|
|
|
} |
307
|
561 |
|
|
308
|
561 |
|
/** |
309
|
|
|
* Add a string to the debug info that will be later serialized by the server as part of the response message |
310
|
|
|
* (base64 encoded) when debug level >= 2 |
311
|
561 |
|
* |
312
|
|
|
* @param string $msg |
313
|
|
|
* @return void |
314
|
|
|
*/ |
315
|
|
|
public static function error_occurred($msg) |
316
|
|
|
{ |
317
|
561 |
|
static::$_xmlrpcs_occurred_errors .= $msg . "\n"; |
318
|
561 |
|
} |
319
|
104 |
|
|
320
|
|
|
/** |
321
|
104 |
|
* Return a string with the serialized representation of all debug info. |
322
|
52 |
|
* |
323
|
52 |
|
* @internal this function will become protected in the future |
324
|
52 |
|
* |
325
|
52 |
|
* @param string $charsetEncoding the target charset encoding for the serialization |
326
|
52 |
|
* |
327
|
52 |
|
* @return string an XML comment (or two) |
328
|
52 |
|
*/ |
329
|
|
|
public function serializeDebug($charsetEncoding = '') |
330
|
|
|
{ |
331
|
|
|
// Tough encoding problem: which internal charset should we assume for debug info? |
332
|
|
|
// It might contain a copy of raw data received from client, ie with unknown encoding, |
333
|
|
|
// intermixed with php generated data and user generated data... |
334
|
|
|
// so we split it: system debug is base 64 encoded, |
335
|
|
|
// user debug info should be encoded by the end user using the INTERNAL_ENCODING |
336
|
561 |
|
$out = ''; |
337
|
561 |
|
if ($this->debug_info != '') { |
338
|
|
|
$out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n"; |
339
|
|
|
} |
340
|
|
|
if (static::$_xmlrpc_debuginfo != '') { |
341
|
|
|
$out .= "<!-- DEBUG INFO:\n" . $this->getCharsetEncoder()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n"; |
342
|
|
|
// NB: a better solution MIGHT be to use CDATA, but we need to insert it |
343
|
561 |
|
// into return payload AFTER the beginning tag |
344
|
|
|
//$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n"; |
345
|
|
|
} |
346
|
561 |
|
|
347
|
|
|
return $out; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* Execute the xml-rpc request, printing the response. |
352
|
|
|
* |
353
|
|
|
* @param string $data the request body. If null, the http POST request will be examined |
354
|
|
|
* @param bool $returnPayload When true, return the response but do not echo it or any http header |
355
|
|
|
* |
356
|
|
|
* @return Response|string the response object (usually not used by caller...) or its xml serialization |
357
|
|
|
* @throws \Exception in case the executed method does throw an exception (and depending on server configuration) |
358
|
|
|
*/ |
359
|
|
|
public function service($data = null, $returnPayload = false) |
360
|
|
|
{ |
361
|
|
|
if ($data === null) { |
362
|
|
|
$data = file_get_contents('php://input'); |
363
|
|
|
} |
364
|
|
|
$rawData = $data; |
365
|
|
|
|
366
|
|
|
// reset internal debug info |
367
|
|
|
$this->debug_info = ''; |
368
|
|
|
|
369
|
|
|
// Save what we received, before parsing it |
370
|
|
|
if ($this->debug > 1) { |
371
|
|
|
$this->debugMsg("+++GOT+++\n" . $data . "\n+++END+++"); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
$resp = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding); |
375
|
|
|
if (!$resp) { |
376
|
|
|
// this actually executes the request |
377
|
|
|
$resp = $this->parseRequest($data, $reqCharset); |
378
|
|
|
|
379
|
|
|
// save full body of request into response, for debugging purposes. |
380
|
|
|
// NB: this is the _request_ data, not the response's own data, unlike what happens client-side |
381
|
|
|
/// @todo try to move this injection to the resp. constructor or use a non-deprecated access method. Or, even |
382
|
|
|
/// better: just avoid setting this, and set debug info of the received http request in the request |
383
|
|
|
/// object instead? It's not like the developer misses access to _SERVER, _COOKIES though... |
384
|
|
|
/// Last but not least: the raw data might be of use to handler functions - but in decompressed form... |
385
|
|
|
$resp->raw_data = $rawData; |
386
|
536 |
|
} |
387
|
|
|
|
388
|
|
|
if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors != '') { |
389
|
536 |
|
$this->debugMsg("+++PROCESSING ERRORS AND WARNINGS+++\n" . |
390
|
536 |
|
static::$_xmlrpcs_occurred_errors . "+++END+++"); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
$header = $resp->xml_header($respCharset); |
394
|
536 |
|
if ($this->debug > 0) { |
395
|
536 |
|
$header .= $this->serializeDebug($respCharset); |
396
|
536 |
|
} |
397
|
536 |
|
|
398
|
515 |
|
// Do not create response serialization if it has already happened. Helps to build json magic |
399
|
515 |
|
/// @todo what if the payload was created targeting a different charset than $respCharset? |
400
|
515 |
|
/// Also, if we do not call serialize(), the request will not set its content-type to have the charset declared |
401
|
452 |
|
$payload = $resp->getPayload(); |
402
|
|
|
if (empty($payload)) { |
403
|
148 |
|
$payload = $resp->serialize($respCharset); |
404
|
|
|
} |
405
|
|
|
$payload = $header . $payload; |
406
|
|
|
|
407
|
|
|
if ($returnPayload) { |
408
|
|
|
return $payload; |
409
|
|
|
} |
410
|
515 |
|
|
411
|
22 |
|
// if we get a warning/error that has output some text before here, then we cannot |
412
|
22 |
|
// add a new header. We cannot say we are sending xml, either... |
413
|
22 |
|
if (!headers_sent()) { |
414
|
22 |
|
header('Content-Type: ' . $resp->getContentType()); |
415
|
22 |
|
// we do not know if client actually told us an accepted charset, but if it did we have to tell it what we did |
416
|
|
|
header("Vary: Accept-Charset"); |
417
|
|
|
|
418
|
536 |
|
// http compression of output: only if we can do it, and we want to do it, and client asked us to, |
419
|
536 |
|
// and php ini settings do not force it already |
420
|
|
|
$phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'); |
421
|
|
|
if ($this->compress_response && $respEncoding != '' && $phpNoSelfCompress) { |
422
|
|
|
if (strpos($respEncoding, 'gzip') !== false && function_exists('gzencode')) { |
423
|
22 |
|
$payload = gzencode($payload); |
424
|
|
|
header("Content-Encoding: gzip"); |
425
|
|
|
header("Vary: Accept-Encoding"); |
426
|
22 |
|
} elseif (strpos($respEncoding, 'deflate') !== false && function_exists('gzcompress')) { |
427
|
|
|
$payload = gzcompress($payload); |
428
|
|
|
header("Content-Encoding: deflate"); |
429
|
|
|
header("Vary: Accept-Encoding"); |
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
// Do not output content-length header if php is compressing output for us: it will mess up measurements. |
434
|
|
|
// Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for |
435
|
561 |
|
// responses up to 8000 bytes |
436
|
|
|
if ($phpNoSelfCompress) { |
437
|
|
|
header('Content-Length: ' . (int)strlen($payload)); |
438
|
|
|
} |
439
|
561 |
|
} else { |
440
|
|
|
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages'); |
441
|
|
|
} |
442
|
|
|
|
443
|
561 |
|
print $payload; |
444
|
559 |
|
|
445
|
559 |
|
// return response, in case subclasses want it |
446
|
559 |
|
return $resp; |
447
|
559 |
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Add a method to the dispatch map. |
451
|
|
|
* |
452
|
561 |
|
* @param string $methodName the name with which the method will be made available |
453
|
104 |
|
* @param callable $function the php function that will get invoked |
454
|
|
|
* @param array[] $sig the array of valid method signatures. |
455
|
457 |
|
* Each element is one signature: an array of strings with at least one element |
456
|
|
|
* First element = type of returned value. Elements 2..N = types of parameters 1..N |
457
|
|
|
* @param string $doc method documentation |
458
|
561 |
|
* @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with |
459
|
|
|
* descriptions instead of types (one string for return type, one per param) |
460
|
|
|
* @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa |
461
|
561 |
|
* @param int $exceptionHandling @see $this->exception_handling |
462
|
104 |
|
* @return void |
463
|
|
|
* |
464
|
104 |
|
* @todo raise a warning if the user tries to register a 'system.' method |
465
|
104 |
|
*/ |
466
|
52 |
|
public function addToMap($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false, |
467
|
52 |
|
$exceptionHandling = false) |
468
|
52 |
|
{ |
469
|
|
|
$this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, $parametersType, $exceptionHandling); |
|
|
|
|
470
|
52 |
|
} |
471
|
52 |
|
|
472
|
52 |
|
/** |
473
|
52 |
|
* Add a method to the dispatch map. |
474
|
|
|
* |
475
|
|
|
* @param string $methodName the name with which the method will be made available |
476
|
|
|
* @param callable $function the php function that will get invoked |
477
|
|
|
* @param array[] $sig the array of valid method signatures. |
478
|
|
|
* Each element is one signature: an array of strings with at least one element |
479
|
|
|
* First element = type of returned value. Elements 2..N = types of parameters 1..N |
480
|
|
|
* @param string $doc method documentation |
481
|
|
|
* @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with |
482
|
|
|
* descriptions instead of types (one string for return type, one per param) |
483
|
|
|
* @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa |
484
|
|
|
* @param int $exceptionHandling @see $this->exception_handling |
485
|
|
|
* @return void |
486
|
|
|
* |
487
|
|
|
* @todo raise a warning if the user tries to register a 'system.' method |
488
|
|
|
* @deprecated use addToMap instead |
489
|
|
|
*/ |
490
|
|
|
public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false, |
491
|
|
|
$exceptionHandling = false) |
492
|
|
|
{ |
493
|
|
|
$this->logDeprecationUnlessCalledBy('addToMap'); |
494
|
561 |
|
|
495
|
|
|
$this->dmap[$methodName] = array( |
496
|
|
|
'function' => $function, |
497
|
|
|
'docstring' => $doc, |
498
|
|
|
); |
499
|
|
|
if ($sig) { |
500
|
|
|
$this->dmap[$methodName]['signature'] = $sig; |
501
|
|
|
} |
502
|
|
|
if ($sigDoc) { |
503
|
|
|
$this->dmap[$methodName]['signature_docs'] = $sigDoc; |
504
|
|
|
} |
505
|
|
|
if ($parametersType) { |
506
|
|
|
$this->dmap[$methodName]['parameters_type'] = $parametersType; |
507
|
|
|
} |
508
|
|
|
if ($exceptionHandling !== false) { |
509
|
|
|
$this->dmap[$methodName]['exception_handling'] = $exceptionHandling; |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* Verify type and number of parameters received against a list of known signatures. |
515
|
|
|
* |
516
|
561 |
|
* @param array|Request $in array of either xml-rpc value objects or xml-rpc type definitions |
517
|
|
|
* @param array $sigs array of known signatures to match against |
518
|
|
|
* @return array int, string |
519
|
561 |
|
*/ |
520
|
104 |
|
protected function verifySignature($in, $sigs) |
521
|
|
|
{ |
522
|
457 |
|
// check each possible signature in turn |
523
|
|
|
if (is_object($in)) { |
524
|
|
|
$numParams = $in->getNumParams(); |
525
|
|
|
} else { |
526
|
|
|
$numParams = count($in); |
527
|
561 |
|
} |
528
|
|
|
foreach ($sigs as $curSig) { |
529
|
|
|
if (count($curSig) == $numParams + 1) { |
530
|
561 |
|
$itsOK = 1; |
531
|
|
|
for ($n = 0; $n < $numParams; $n++) { |
532
|
|
|
if (is_object($in)) { |
533
|
|
|
$p = $in->getParam($n); |
534
|
|
|
if ($p->kindOf() == 'scalar') { |
535
|
|
|
$pt = $p->scalarTyp(); |
536
|
|
|
} else { |
537
|
|
|
$pt = $p->kindOf(); |
538
|
|
|
} |
539
|
|
|
} else { |
540
|
|
|
$pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4... |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
// param index is $n+1, as first member of sig is return type |
544
|
|
|
if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) { |
545
|
|
|
$itsOK = 0; |
546
|
|
|
$pno = $n + 1; |
547
|
562 |
|
$wanted = $curSig[$n + 1]; |
548
|
|
|
$got = $pt; |
549
|
|
|
break; |
550
|
|
|
} |
551
|
562 |
|
} |
552
|
|
|
if ($itsOK) { |
553
|
|
|
return array(1, ''); |
554
|
|
|
} |
555
|
|
|
} |
556
|
|
|
} |
557
|
|
|
if (isset($wanted)) { |
558
|
|
|
return array(0, "Wanted {$wanted}, got {$got} at param {$pno}"); |
|
|
|
|
559
|
|
|
} else { |
560
|
561 |
|
return array(0, "No method signature matches number of parameters"); |
561
|
4 |
|
} |
562
|
2 |
|
} |
563
|
|
|
|
564
|
2 |
|
/** |
565
|
2 |
|
* Parse http headers received along with xml-rpc request. If needed, inflate request. |
566
|
|
|
* |
567
|
|
|
* @return Response|null null on success or an error Response |
568
|
|
|
*/ |
569
|
|
|
protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression) |
570
|
|
|
{ |
571
|
|
|
// check if $_SERVER is populated: it might have been disabled via ini file |
572
|
|
|
// (this is true even when in CLI mode) |
573
|
|
|
if (count($_SERVER) == 0) { |
574
|
|
|
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated'); |
575
|
|
|
} |
576
|
|
|
|
577
|
562 |
|
if ($this->debug > 1) { |
578
|
|
|
if (function_exists('getallheaders')) { |
579
|
|
|
$this->debugMsg(''); // empty line |
580
|
|
|
foreach (getallheaders() as $name => $val) { |
581
|
562 |
|
$this->debugMsg("HEADER: $name: $val"); |
582
|
|
|
} |
583
|
|
|
} |
584
|
562 |
|
} |
585
|
562 |
|
|
586
|
562 |
|
if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { |
587
|
|
|
$contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']); |
588
|
2 |
|
} else { |
589
|
2 |
|
$contentEncoding = ''; |
590
|
2 |
|
} |
591
|
2 |
|
|
592
|
560 |
|
$rawData = $data; |
593
|
1 |
|
|
594
|
1 |
|
// check if request body has been compressed and decompress it |
595
|
1 |
|
if ($contentEncoding != '' && strlen($data)) { |
596
|
|
|
if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') { |
597
|
|
|
// if decoding works, use it. else assume data wasn't gzencoded |
598
|
|
|
/// @todo test separately for gzinflate and gzuncompress |
599
|
|
|
if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) { |
600
|
|
|
if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) { |
601
|
559 |
|
$data = $degzdata; |
602
|
559 |
|
if ($this->debug > 1) { |
603
|
|
|
$this->debugMsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++"); |
604
|
|
|
} |
605
|
|
|
} elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) { |
606
|
|
|
$data = $degzdata; |
607
|
|
|
if ($this->debug > 1) { |
608
|
|
|
$this->debugMsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++"); |
609
|
|
|
} |
610
|
|
|
} else { |
611
|
|
|
$r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], |
612
|
559 |
|
PhpXmlRpc::$xmlrpcstr['server_decompress_fail'], '', array('raw_data' => $rawData) |
613
|
|
|
); |
614
|
559 |
|
|
615
|
538 |
|
return $r; |
616
|
|
|
} |
617
|
|
|
} else { |
618
|
559 |
|
$r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], |
619
|
559 |
|
PhpXmlRpc::$xmlrpcstr['server_cannot_decompress'], '', array('raw_data' => $rawData) |
620
|
|
|
); |
621
|
559 |
|
|
622
|
|
|
return $r; |
623
|
|
|
} |
624
|
|
|
} |
625
|
562 |
|
} |
626
|
|
|
|
627
|
|
|
// check if client specified accepted charsets, and if we know how to fulfill the request |
628
|
|
|
if ($this->response_charset_encoding == 'auto') { |
629
|
|
|
$respEncoding = ''; |
630
|
|
|
if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) { |
631
|
|
|
// here we check if we can match the client-requested encoding with the encodings we know we can generate. |
632
|
|
|
// we parse q=0.x preferences instead of preferring the first charset specified |
633
|
|
|
$http = new Http(); |
634
|
|
|
$clientAcceptedCharsets = $http->parseAcceptHeader($_SERVER['HTTP_ACCEPT_CHARSET']); |
635
|
|
|
$knownCharsets = $this->getCharsetEncoder()->knownCharsets(); |
636
|
|
|
foreach ($clientAcceptedCharsets as $accepted) { |
637
|
|
|
foreach ($knownCharsets as $charset) { |
638
|
|
|
if (strtoupper($accepted) == strtoupper($charset)) { |
639
|
559 |
|
$respEncoding = $charset; |
640
|
|
|
break 2; |
641
|
559 |
|
} |
642
|
559 |
|
} |
643
|
|
|
} |
644
|
559 |
|
} |
645
|
559 |
|
} else { |
646
|
|
|
$respEncoding = $this->response_charset_encoding; |
647
|
|
|
} |
648
|
|
|
|
649
|
559 |
|
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { |
650
|
559 |
|
$respCompression = $_SERVER['HTTP_ACCEPT_ENCODING']; |
651
|
|
|
} else { |
652
|
559 |
|
$respCompression = ''; |
653
|
|
|
} |
654
|
85 |
|
|
655
|
85 |
|
// 'guestimate' request encoding |
656
|
85 |
|
/// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check??? |
657
|
|
|
$parser = $this->getParser(); |
658
|
|
|
$reqEncoding = $parser->guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '', |
659
|
|
|
$data); |
660
|
559 |
|
|
661
|
536 |
|
return null; |
662
|
536 |
|
} |
663
|
536 |
|
|
664
|
|
|
/** |
665
|
|
|
* Parse an xml chunk containing an xml-rpc request and execute the corresponding php function registered with the |
666
|
|
|
* server. |
667
|
536 |
|
* @internal this function will become protected in the future |
668
|
|
|
* |
669
|
22 |
|
* @param string $data the xml request |
670
|
22 |
|
* @param string $reqEncoding (optional) the charset encoding of the xml request |
671
|
22 |
|
* @return Response |
672
|
22 |
|
* @throws \Exception in case the executed method does throw an exception (and depending on server configuration) |
673
|
|
|
* |
674
|
|
|
* @todo either rename this function or move the 'execute' part out of it... |
675
|
|
|
*/ |
676
|
|
|
public function parseRequest($data, $reqEncoding = '') |
677
|
559 |
|
{ |
678
|
|
|
// decompose incoming XML into request structure |
679
|
559 |
|
|
680
|
127 |
|
/// @todo move this block of code into the XMLParser |
681
|
|
|
if ($reqEncoding != '') { |
682
|
|
|
// Since parsing will fail if |
683
|
559 |
|
// - charset is not specified in the xml declaration, |
684
|
150 |
|
// - the encoding is not UTF8 and |
685
|
24 |
|
// - there are non-ascii chars in the text, |
686
|
|
|
// we try to work round that... |
687
|
127 |
|
// The following code might be better for mb_string enabled installs, but it makes the lib about 200% slower... |
688
|
|
|
//if (!is_valid_charset($reqEncoding, array('UTF-8'))) |
689
|
431 |
|
if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) { |
690
|
129 |
|
if (function_exists('mb_convert_encoding')) { |
691
|
|
|
$data = mb_convert_encoding($data, 'UTF-8', $reqEncoding); |
692
|
324 |
|
} else { |
693
|
|
|
if ($reqEncoding == 'ISO-8859-1') { |
694
|
|
|
$data = utf8_encode($data); |
695
|
|
|
} else { |
696
|
559 |
|
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': unsupported charset encoding of received request: ' . $reqEncoding); |
697
|
|
|
} |
698
|
|
|
} |
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
// PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset. |
702
|
|
|
// What if internal encoding is not in one of the 3 allowed? We use the broadest one, i.e. utf8 |
703
|
|
|
if (in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { |
704
|
|
|
$options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); |
705
|
|
|
} else { |
706
|
|
|
$options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8', 'target_charset' => PhpXmlRpc::$xmlrpc_internalencoding); |
707
|
559 |
|
} |
708
|
559 |
|
// register a callback with the xml parser for when it finds the method name |
709
|
|
|
$options['methodname_callback'] = array($this, 'methodNameCallback'); |
710
|
|
|
|
711
|
|
|
$xmlRpcParser = $this->getParser(); |
712
|
|
|
try { |
713
|
559 |
|
$_xh = $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options); |
714
|
559 |
|
// BC |
715
|
127 |
|
if (!is_array($_xh)) { |
716
|
|
|
$_xh = $xmlRpcParser->_xh; |
717
|
454 |
|
} |
718
|
|
|
} catch (NoSuchMethodException $e) { |
719
|
557 |
|
return new static::$responseClass(0, $e->getCode(), $e->getMessage()); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
if ($_xh['isf'] == 3) { |
723
|
|
|
// (BC) we return XML error as a faultCode |
724
|
|
|
preg_match('/^XML error ([0-9]+)/', $_xh['isf_reason'], $matches); |
725
|
|
|
return new static::$responseClass( |
726
|
|
|
0, |
727
|
|
|
PhpXmlRpc::$xmlrpcerrxml + (int)$matches[1], |
728
|
|
|
$_xh['isf_reason']); |
729
|
|
|
} elseif ($_xh['isf']) { |
730
|
|
|
/// @todo separate better the various cases, as we have done in Request::parseResponse: invalid xml-rpc vs. |
731
|
|
|
/// parsing error |
732
|
|
|
return new static::$responseClass( |
733
|
|
|
0, |
734
|
|
|
PhpXmlRpc::$xmlrpcerr['invalid_request'], |
735
|
|
|
PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $_xh['isf_reason']); |
736
|
|
|
} else { |
737
|
|
|
// small layering violation in favor of speed and memory usage: we should allow the 'execute' method handle |
738
|
|
|
// this, but in the most common scenario (xml-rpc values type server with some methods registered as phpvals) |
739
|
|
|
// that would mean a useless encode+decode pass |
740
|
|
|
if ($this->functions_parameters_type != 'xmlrpcvals' || |
741
|
|
|
(isset($this->dmap[$_xh['method']]['parameters_type']) && |
742
|
|
|
($this->dmap[$_xh['method']]['parameters_type'] != 'xmlrpcvals') |
743
|
|
|
) |
744
|
|
|
) { |
745
|
|
|
if ($this->debug > 1) { |
746
|
|
|
$this->debugMsg("\n+++PARSED+++\n" . var_export($_xh['params'], true) . "\n+++END+++"); |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
return $this->execute($_xh['method'], $_xh['params'], $_xh['pt']); |
750
|
|
|
} else { |
751
|
|
|
// build a Request object with data parsed from xml and add parameters in |
752
|
|
|
$req = new Request($_xh['method']); |
753
|
|
|
/// @todo for more speed, we could just pass in the array to the constructor (and loose the type validation)... |
754
|
|
|
for ($i = 0; $i < count($_xh['params']); $i++) { |
|
|
|
|
755
|
|
|
$req->addParam($_xh['params'][$i]); |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
if ($this->debug > 1) { |
759
|
|
|
$this->debugMsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++"); |
760
|
|
|
} |
761
|
|
|
|
762
|
45 |
|
return $this->execute($req); |
763
|
|
|
} |
764
|
|
|
} |
765
|
45 |
|
} |
766
|
45 |
|
|
767
|
|
|
/** |
768
|
|
|
* Execute a method invoked by the client, checking parameters used. |
769
|
|
|
* |
770
|
|
|
* @param Request|string $req either a Request obj or a method name |
771
|
|
|
* @param mixed[] $params array with method parameters as php types (only if $req is method name) |
772
|
|
|
* @param string[] $paramTypes array with xml-rpc types of method parameters (only if $req is method name) |
773
|
|
|
* @return Response |
774
|
|
|
* |
775
|
45 |
|
* @throws \Exception in case the executed method does throw an exception (and depending on server configuration) |
776
|
2 |
|
*/ |
777
|
2 |
|
protected function execute($req, $params = null, $paramTypes = null) |
778
|
|
|
{ |
779
|
45 |
|
static::$_xmlrpcs_occurred_errors = ''; |
780
|
|
|
static::$_xmlrpc_debuginfo = ''; |
781
|
|
|
|
782
|
559 |
|
if (is_object($req)) { |
783
|
|
|
$methodName = $req->method(); |
784
|
|
|
} else { |
785
|
559 |
|
$methodName = $req; |
786
|
64 |
|
} |
787
|
|
|
|
788
|
496 |
|
$sysCall = $this->isSyscall($methodName); |
789
|
|
|
$dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; |
790
|
|
|
|
791
|
|
|
if (!isset($dmap[$methodName]['function'])) { |
792
|
559 |
|
// No such method |
793
|
|
|
return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unknown_method'], PhpXmlRpc::$xmlrpcstr['unknown_method']); |
794
|
|
|
} |
795
|
|
|
|
796
|
|
|
// Check signature |
797
|
|
|
if (isset($dmap[$methodName]['signature'])) { |
798
|
|
|
$sig = $dmap[$methodName]['signature']; |
799
|
|
|
if (is_object($req)) { |
800
|
559 |
|
list($ok, $errStr) = $this->verifySignature($req, $sig); |
801
|
|
|
} else { |
802
|
559 |
|
list($ok, $errStr) = $this->verifySignature($paramTypes, $sig); |
803
|
559 |
|
} |
804
|
|
|
if (!$ok) { |
805
|
|
|
// Didn't match. |
806
|
|
|
return new static::$responseClass( |
807
|
|
|
0, |
808
|
|
|
PhpXmlRpc::$xmlrpcerr['incorrect_params'], |
809
|
561 |
|
PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": {$errStr}" |
810
|
|
|
); |
811
|
561 |
|
} |
812
|
52 |
|
} |
813
|
|
|
|
814
|
509 |
|
$func = $dmap[$methodName]['function']; |
815
|
|
|
|
816
|
|
|
// let the 'class::function' syntax be accepted in dispatch maps |
817
|
|
|
if (is_string($func) && strpos($func, '::')) { |
818
|
|
|
$func = explode('::', $func); |
819
|
|
|
} |
820
|
|
|
|
821
|
|
|
// build string representation of function 'name' |
822
|
559 |
|
if (is_array($func)) { |
823
|
|
|
if (is_object($func[0])) { |
824
|
559 |
|
$funcName = get_class($func[0]) . '->' . $func[1]; |
825
|
|
|
} else { |
826
|
|
|
$funcName = implode('::', $func); |
827
|
|
|
} |
828
|
|
|
} else if ($func instanceof \Closure) { |
829
|
|
|
$funcName = 'Closure'; |
830
|
|
|
} else { |
831
|
|
|
$funcName = $func; |
832
|
|
|
} |
833
|
|
|
|
834
|
|
|
// verify that function to be invoked is in fact callable |
835
|
|
|
if (!is_callable($func)) { |
836
|
|
|
$this->getLogger()->error("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable"); |
837
|
|
|
return new static::$responseClass( |
838
|
127 |
|
0, |
839
|
|
|
PhpXmlRpc::$xmlrpcerr['server_error'], |
840
|
127 |
|
PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method" |
841
|
|
|
); |
842
|
|
|
} |
843
|
|
|
|
844
|
|
|
if (isset($dmap[$methodName]['exception_handling'])) { |
845
|
|
|
$exception_handling = (int)$dmap[$methodName]['exception_handling']; |
846
|
127 |
|
} else { |
847
|
|
|
$exception_handling = $this->exception_handling; |
848
|
|
|
} |
849
|
127 |
|
|
850
|
127 |
|
// We always catch all errors generated during processing of user function, and log them as part of response; |
851
|
|
|
// if debug level is 3 or above, we also serialize them in the response as comments |
852
|
|
|
self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')); |
853
|
|
|
|
854
|
127 |
|
/// @todo what about using output-buffering as well, in case user code echoes anything to screen? |
855
|
127 |
|
|
856
|
127 |
|
try { |
857
|
|
|
// Allow mixed-convention servers |
858
|
|
|
if (is_object($req)) { |
859
|
|
|
// call an 'xml-rpc aware' function |
860
|
127 |
|
if ($sysCall) { |
861
|
127 |
|
$r = call_user_func($func, $this, $req); |
862
|
127 |
|
} else { |
863
|
|
|
$r = call_user_func($func, $req); |
864
|
|
|
} |
865
|
|
|
if (!is_a($r, 'PhpXmlRpc\Response')) { |
866
|
127 |
|
$this->getLogger()->error("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r)); |
867
|
127 |
|
if (is_a($r, 'PhpXmlRpc\Value')) { |
868
|
127 |
|
$r = new static::$responseClass($r); |
869
|
|
|
} else { |
870
|
|
|
$r = new static::$responseClass( |
871
|
|
|
0, |
872
|
127 |
|
PhpXmlRpc::$xmlrpcerr['server_error'], |
873
|
127 |
|
PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object" |
874
|
127 |
|
); |
875
|
|
|
} |
876
|
|
|
} |
877
|
|
|
} else { |
878
|
|
|
// call a 'plain php' function |
879
|
|
|
if ($sysCall) { |
880
|
|
|
array_unshift($params, $this); |
|
|
|
|
881
|
|
|
$r = call_user_func_array($func, $params); |
882
|
|
|
} else { |
883
|
|
|
// 3rd API convention for method-handling functions: EPI-style |
884
|
|
|
if ($this->functions_parameters_type == 'epivals') { |
885
|
|
|
$r = call_user_func_array($func, array($methodName, $params, $this->user_data)); |
886
|
|
|
// mimic EPI behaviour: if we get an array that looks like an error, make it an error response |
887
|
|
|
if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) { |
888
|
|
|
$r = new static::$responseClass(0, (integer)$r['faultCode'], (string)$r['faultString']); |
889
|
|
|
} else { |
890
|
|
|
// functions using EPI api should NOT return resp objects, so make sure we encode the |
891
|
|
|
// return type correctly |
892
|
|
|
$encoder = new Encoder(); |
893
|
|
|
$r = new static::$responseClass($encoder->encode($r, array('extension_api'))); |
894
|
|
|
} |
895
|
|
|
} else { |
896
|
|
|
$r = call_user_func_array($func, $params); |
|
|
|
|
897
|
|
|
} |
898
|
|
|
} |
899
|
|
|
// the return type can be either a Response object or a plain php value... |
900
|
|
|
if (!is_a($r, '\PhpXmlRpc\Response')) { |
901
|
|
|
// q: what should we assume here about automatic encoding of datetimes and php classes instances? |
902
|
|
|
// a: let the user decide |
903
|
|
|
$encoder = new Encoder(); |
904
|
|
|
$r = new static::$responseClass($encoder->encode($r, $this->phpvals_encoding_options)); |
905
|
|
|
} |
906
|
|
|
} |
907
|
|
|
/// @todo bump minimum php version to 7.1 and use a single catch clause instead of the duplicate blocks |
908
|
|
|
} catch (\Exception $e) { |
909
|
|
|
// (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a |
910
|
|
|
// proper error-response |
911
|
|
|
switch ($exception_handling) { |
912
|
|
|
case 2: |
913
|
|
|
if (self::$_xmlrpcs_prev_ehandler) { |
914
|
|
|
set_error_handler(self::$_xmlrpcs_prev_ehandler); |
915
|
|
|
self::$_xmlrpcs_prev_ehandler = null; |
916
|
|
|
} else { |
917
|
|
|
restore_error_handler(); |
918
|
|
|
} |
919
|
|
|
throw $e; |
920
|
|
|
case 1: |
921
|
|
|
$errCode = $e->getCode(); |
922
|
|
|
if ($errCode == 0) { |
923
|
|
|
$errCode = PhpXmlRpc::$xmlrpcerr['server_error']; |
924
|
|
|
} |
925
|
|
|
$r = new static::$responseClass(0, $errCode, $e->getMessage()); |
926
|
|
|
break; |
927
|
|
|
default: |
928
|
|
|
$r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']); |
929
|
|
|
} |
930
|
|
|
} catch (\Error $e) { |
931
|
|
|
// (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a |
932
|
|
|
// proper error-response |
933
|
22 |
|
switch ($exception_handling) { |
934
|
|
|
case 2: |
935
|
22 |
|
if (self::$_xmlrpcs_prev_ehandler) { |
936
|
22 |
|
set_error_handler(self::$_xmlrpcs_prev_ehandler); |
937
|
22 |
|
self::$_xmlrpcs_prev_ehandler = null; |
938
|
|
|
} else { |
939
|
22 |
|
restore_error_handler(); |
940
|
22 |
|
} |
941
|
|
|
throw $e; |
942
|
|
|
case 1: |
943
|
22 |
|
$errCode = $e->getCode(); |
944
|
|
|
if ($errCode == 0) { |
945
|
|
|
$errCode = PhpXmlRpc::$xmlrpcerr['server_error']; |
946
|
|
|
} |
947
|
|
|
$r = new static::$responseClass(0, $errCode, $e->getMessage()); |
948
|
|
|
break; |
949
|
|
|
default: |
950
|
|
|
$r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']); |
951
|
106 |
|
} |
952
|
|
|
} |
953
|
|
|
|
954
|
106 |
|
// note: restore the error handler we found before calling the user func, even if it has been changed |
955
|
106 |
|
// inside the func itself |
956
|
106 |
|
if (self::$_xmlrpcs_prev_ehandler) { |
957
|
|
|
set_error_handler(self::$_xmlrpcs_prev_ehandler); |
958
|
|
|
self::$_xmlrpcs_prev_ehandler = null; |
959
|
|
|
} else { |
960
|
106 |
|
restore_error_handler(); |
961
|
85 |
|
} |
962
|
|
|
|
963
|
22 |
|
return $r; |
964
|
|
|
} |
965
|
106 |
|
|
966
|
106 |
|
/** |
967
|
106 |
|
* Registered as callback for when the XMLParser has found the name of the method to execute. |
968
|
106 |
|
* Handling that early allows to 1. stop parsing the rest of the xml if there is no such method registered, and |
969
|
106 |
|
* 2. tweak the type of data that the parser will return, in case the server uses mixed-calling-convention |
970
|
106 |
|
* |
971
|
106 |
|
* @internal |
972
|
|
|
* @param $methodName |
973
|
106 |
|
* @param XMLParser $xmlParser |
974
|
|
|
* @param null|resource $parser |
975
|
106 |
|
* @return void |
976
|
|
|
* @throws NoSuchMethodException |
977
|
|
|
* |
978
|
|
|
* @todo feature creep - we could validate here that the method in the dispatch map is valid, but that would mean |
979
|
|
|
* dirtying a lot the logic, as we would have back to both parseRequest() and execute() methods the info |
980
|
|
|
* about the matched method handler, in order to avoid doing the work twice... |
981
|
|
|
*/ |
982
|
1 |
|
public function methodNameCallback($methodName, $xmlParser, $parser = null) |
983
|
|
|
{ |
984
|
|
|
$sysCall = $this->isSyscall($methodName); |
985
|
106 |
|
$dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; |
986
|
|
|
|
987
|
|
|
if (!isset($dmap[$methodName]['function'])) { |
988
|
|
|
// No such method |
989
|
|
|
throw new NoSuchMethodException(PhpXmlRpc::$xmlrpcstr['unknown_method'], PhpXmlRpc::$xmlrpcerr['unknown_method']); |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
// alter on-the-fly the config of the xml parser if needed |
993
|
85 |
|
if (isset($dmap[$methodName]['parameters_type']) && |
994
|
|
|
$dmap[$methodName]['parameters_type'] != $this->functions_parameters_type) { |
995
|
|
|
/// @todo this should be done by a method of the XMLParser |
996
|
85 |
|
switch ($dmap[$methodName]['parameters_type']) { |
997
|
85 |
|
case XMLParser::RETURN_PHP: |
998
|
85 |
|
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); |
999
|
|
|
break; |
1000
|
|
|
case XMLParser::RETURN_EPIVALS: |
1001
|
|
|
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi'); |
1002
|
85 |
|
break; |
1003
|
85 |
|
/// @todo log a warning on unsupported return type |
1004
|
|
|
case XMLParser::RETURN_XMLRPCVALS: |
1005
|
1 |
|
default: |
1006
|
|
|
xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); |
1007
|
85 |
|
} |
1008
|
85 |
|
} |
1009
|
85 |
|
} |
1010
|
|
|
|
1011
|
|
|
/** |
1012
|
|
|
* Add a string to the 'internal debug message' (separate from 'user debug message'). |
1013
|
|
|
* |
1014
|
|
|
* @param string $string |
1015
|
|
|
* @return void |
1016
|
|
|
*/ |
1017
|
85 |
|
protected function debugMsg($string) |
1018
|
|
|
{ |
1019
|
|
|
$this->debug_info .= $string . "\n"; |
1020
|
64 |
|
} |
1021
|
|
|
|
1022
|
64 |
|
/** |
1023
|
64 |
|
* @param string $methName |
1024
|
64 |
|
* @return bool |
1025
|
|
|
*/ |
1026
|
64 |
|
protected function isSyscall($methName) |
1027
|
64 |
|
{ |
1028
|
|
|
return (strpos($methName, "system.") === 0); |
1029
|
64 |
|
} |
1030
|
64 |
|
|
1031
|
64 |
|
/** |
1032
|
|
|
* @param array $dmap |
1033
|
64 |
|
* @return $this |
1034
|
|
|
*/ |
1035
|
|
|
public function setDispatchMap($dmap) |
1036
|
|
|
{ |
1037
|
|
|
$this->dmap = $dmap; |
1038
|
|
|
return $this; |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
64 |
|
/** |
1042
|
|
|
* @return array[] |
1043
|
64 |
|
*/ |
1044
|
|
|
public function getDispatchMap() |
1045
|
|
|
{ |
1046
|
64 |
|
return $this->dmap; |
1047
|
64 |
|
} |
1048
|
|
|
|
1049
|
|
|
/** |
1050
|
64 |
|
* @return array[] |
1051
|
|
|
*/ |
1052
|
|
|
public function getSystemDispatchMap() |
1053
|
64 |
|
{ |
1054
|
64 |
|
if (!$this->allow_system_funcs) { |
1055
|
|
|
return array(); |
1056
|
|
|
} |
1057
|
64 |
|
|
1058
|
64 |
|
return array( |
1059
|
|
|
'system.listMethods' => array( |
1060
|
|
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods', |
1061
|
64 |
|
// listMethods: signature was either a string, or nothing. |
1062
|
|
|
// The useless string variant has been removed |
1063
|
|
|
'signature' => array(array(Value::$xmlrpcArray)), |
1064
|
|
|
'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch', |
1065
|
64 |
|
'signature_docs' => array(array('list of method names')), |
1066
|
64 |
|
), |
1067
|
64 |
|
'system.methodHelp' => array( |
1068
|
|
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp', |
1069
|
|
|
'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)), |
1070
|
|
|
'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string', |
1071
|
|
|
'signature_docs' => array(array('method description', 'name of the method to be described')), |
1072
|
|
|
), |
1073
|
|
|
'system.methodSignature' => array( |
1074
|
|
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature', |
1075
|
64 |
|
'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)), |
1076
|
|
|
'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)', |
1077
|
64 |
|
'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')), |
1078
|
64 |
|
), |
1079
|
|
|
'system.multicall' => array( |
1080
|
|
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall', |
1081
|
64 |
|
'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)), |
1082
|
|
|
'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details', |
1083
|
|
|
'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"')), |
1084
|
|
|
), |
1085
|
|
|
'system.getCapabilities' => array( |
1086
|
|
|
'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities', |
1087
|
|
|
'signature' => array(array(Value::$xmlrpcStruct)), |
1088
|
|
|
'docstring' => 'This method lists all the capabilities that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to', |
1089
|
|
|
'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')), |
1090
|
|
|
), |
1091
|
|
|
); |
1092
|
|
|
} |
1093
|
|
|
|
1094
|
|
|
/** |
1095
|
|
|
* @return array[] |
1096
|
|
|
*/ |
1097
|
|
|
public function getCapabilities() |
1098
|
|
|
{ |
1099
|
|
|
$outAr = array( |
1100
|
|
|
// xml-rpc spec: always supported |
1101
|
|
|
'xmlrpc' => array( |
1102
|
|
|
'specUrl' => 'http://www.xmlrpc.com/spec', // NB: the spec sits now at http://xmlrpc.com/spec.md |
1103
|
|
|
'specVersion' => 1 |
1104
|
|
|
), |
1105
|
|
|
// if we support system.xxx functions, we always support multicall, too... |
1106
|
|
|
'system.multicall' => array( |
1107
|
|
|
// Note that, as of 2006/09/17, the following URL does not respond anymore |
1108
|
|
|
'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', |
1109
|
|
|
'specVersion' => 1 |
1110
|
|
|
), |
1111
|
|
|
// introspection: version 2! we support 'mixed', too. |
1112
|
|
|
// note: the php xml-rpc extension says this instead: |
1113
|
|
|
// url http://xmlrpc-epi.sourceforge.net/specs/rfc.introspection.php, version 20010516 |
1114
|
|
|
'introspection' => array( |
1115
|
|
|
'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html', |
1116
|
|
|
'specVersion' => 2, |
1117
|
|
|
), |
1118
|
|
|
); |
1119
|
|
|
|
1120
|
|
|
// NIL extension |
1121
|
|
|
if (PhpXmlRpc::$xmlrpc_null_extension) { |
1122
|
|
|
$outAr['nil'] = array( |
1123
|
|
|
// Note that, as of 2023/01, the following URL does not respond anymore |
1124
|
|
|
'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php', |
1125
|
|
|
'specVersion' => 1 |
1126
|
|
|
); |
1127
|
|
|
} |
1128
|
|
|
|
1129
|
|
|
// support for "standard" error codes |
1130
|
|
|
if (PhpXmlRpc::$xmlrpcerr['unknown_method'] === Interop::$xmlrpcerr['unknown_method']) { |
1131
|
|
|
$outAr['faults_interop'] = array( |
1132
|
|
|
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', |
1133
|
|
|
'specVersion' => 20010516 |
1134
|
|
|
); |
1135
|
|
|
} |
1136
|
|
|
|
1137
|
85 |
|
return $outAr; |
1138
|
|
|
} |
1139
|
85 |
|
|
1140
|
|
|
/** |
1141
|
85 |
|
* @internal handler of a system. method |
1142
|
85 |
|
* |
1143
|
85 |
|
* @param Server $server |
1144
|
64 |
|
* @param Request $req |
1145
|
|
|
* @return Response |
1146
|
|
|
*/ |
1147
|
|
|
public static function _xmlrpcs_getCapabilities($server, $req = null) |
|
|
|
|
1148
|
|
|
{ |
1149
|
|
|
$encoder = new Encoder(); |
1150
|
|
|
return new static::$responseClass($encoder->encode($server->getCapabilities())); |
1151
|
|
|
} |
1152
|
|
|
|
1153
|
85 |
|
/** |
1154
|
|
|
* @internal handler of a system. method |
1155
|
|
|
* |
1156
|
|
|
* @param Server $server |
1157
|
|
|
* @param Request $req if called in plain php values mode, second param is missing |
1158
|
|
|
* @return Response |
1159
|
|
|
*/ |
1160
|
|
|
public static function _xmlrpcs_listMethods($server, $req = null) |
|
|
|
|
1161
|
|
|
{ |
1162
|
|
|
$outAr = array(); |
1163
|
|
|
foreach ($server->dmap as $key => $val) { |
1164
|
43 |
|
$outAr[] = new Value($key, 'string'); |
1165
|
|
|
} |
1166
|
|
|
foreach ($server->getSystemDispatchMap() as $key => $val) { |
1167
|
43 |
|
$outAr[] = new Value($key, 'string'); |
1168
|
22 |
|
} |
1169
|
|
|
|
1170
|
|
|
return new static::$responseClass(new Value($outAr, 'array')); |
1171
|
|
|
} |
1172
|
22 |
|
|
1173
|
22 |
|
/** |
1174
|
|
|
* @internal handler of a system. method |
1175
|
|
|
* |
1176
|
|
|
* @param Server $server |
1177
|
22 |
|
* @param Request $req |
1178
|
|
|
* @return Response |
1179
|
|
|
*/ |
1180
|
22 |
|
public static function _xmlrpcs_methodSignature($server, $req) |
1181
|
22 |
|
{ |
1182
|
22 |
|
// let's accept as parameter either an xml-rpc value or string |
1183
|
|
|
if (is_object($req)) { |
1184
|
22 |
|
$methName = $req->getParam(0); |
1185
|
|
|
$methName = $methName->scalarVal(); |
1186
|
|
|
} else { |
1187
|
|
|
$methName = $req; |
1188
|
|
|
} |
1189
|
|
|
if ($server->isSyscall($methName)) { |
1190
|
|
|
$dmap = $server->getSystemDispatchMap(); |
1191
|
|
|
} else { |
1192
|
|
|
$dmap = $server->dmap; |
1193
|
|
|
} |
1194
|
|
|
if (isset($dmap[$methName])) { |
1195
|
|
|
if (isset($dmap[$methName]['signature'])) { |
1196
|
|
|
$sigs = array(); |
1197
|
|
|
foreach ($dmap[$methName]['signature'] as $inSig) { |
1198
|
22 |
|
$curSig = array(); |
1199
|
|
|
foreach ($inSig as $sig) { |
1200
|
|
|
$curSig[] = new Value($sig, 'string'); |
1201
|
|
|
} |
1202
|
|
|
$sigs[] = new Value($curSig, 'array'); |
1203
|
|
|
} |
1204
|
|
|
$r = new static::$responseClass(new Value($sigs, 'array')); |
1205
|
|
|
} else { |
1206
|
|
|
// NB: according to the official docs, we should be returning a |
1207
|
|
|
// "none-array" here, which means not-an-array |
1208
|
|
|
$r = new static::$responseClass(new Value('undef', 'string')); |
1209
|
|
|
} |
1210
|
|
|
} else { |
1211
|
|
|
$r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']); |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
return $r; |
1215
|
|
|
} |
1216
|
|
|
|
1217
|
|
|
/** |
1218
|
|
|
* @internal handler of a system. method |
1219
|
|
|
* |
1220
|
|
|
* @param Server $server |
1221
|
|
|
* @param Request $req |
1222
|
|
|
* @return Response |
1223
|
|
|
*/ |
1224
|
|
|
public static function _xmlrpcs_methodHelp($server, $req) |
1225
|
|
|
{ |
1226
|
|
|
// let's accept as parameter either an xml-rpc value or string |
1227
|
|
|
if (is_object($req)) { |
1228
|
|
|
$methName = $req->getParam(0); |
1229
|
|
|
$methName = $methName->scalarVal(); |
1230
|
|
|
} else { |
1231
|
|
|
$methName = $req; |
1232
|
|
|
} |
1233
|
|
|
if ($server->isSyscall($methName)) { |
1234
|
|
|
$dmap = $server->getSystemDispatchMap(); |
1235
|
|
|
} else { |
1236
|
|
|
$dmap = $server->dmap; |
1237
|
|
|
} |
1238
|
|
|
if (isset($dmap[$methName])) { |
1239
|
|
|
if (isset($dmap[$methName]['docstring'])) { |
1240
|
|
|
$r = new static::$responseClass(new Value($dmap[$methName]['docstring'], 'string')); |
1241
|
|
|
} else { |
1242
|
|
|
$r = new static::$responseClass(new Value('', 'string')); |
1243
|
|
|
} |
1244
|
|
|
} else { |
1245
|
|
|
$r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']); |
1246
|
|
|
} |
1247
|
|
|
|
1248
|
|
|
return $r; |
1249
|
|
|
} |
1250
|
|
|
|
1251
|
|
|
/** |
1252
|
|
|
* @internal this function will become protected in the future |
1253
|
|
|
* |
1254
|
|
|
* @param $err |
1255
|
|
|
* @return Value |
1256
|
|
|
*/ |
1257
|
|
|
public static function _xmlrpcs_multicall_error($err) |
1258
|
|
|
{ |
1259
|
|
|
if (is_string($err)) { |
1260
|
|
|
$str = PhpXmlRpc::$xmlrpcstr["multicall_{$err}"]; |
1261
|
|
|
$code = PhpXmlRpc::$xmlrpcerr["multicall_{$err}"]; |
1262
|
|
|
} else { |
1263
|
|
|
$code = $err->faultCode(); |
1264
|
|
|
$str = $err->faultString(); |
1265
|
|
|
} |
1266
|
|
|
$struct = array(); |
1267
|
|
|
$struct['faultCode'] = new Value($code, 'int'); |
1268
|
|
|
$struct['faultString'] = new Value($str, 'string'); |
1269
|
|
|
|
1270
|
|
|
return new Value($struct, 'struct'); |
1271
|
|
|
} |
1272
|
|
|
|
1273
|
|
|
/** |
1274
|
|
|
* @internal this function will become protected in the future |
1275
|
|
|
* |
1276
|
|
|
* @param Server $server |
1277
|
|
|
* @param Value $call |
1278
|
|
|
* @return Value |
1279
|
|
|
*/ |
1280
|
|
|
public static function _xmlrpcs_multicall_do_call($server, $call) |
1281
|
|
|
{ |
1282
|
|
|
if ($call->kindOf() != 'struct') { |
1283
|
|
|
return static::_xmlrpcs_multicall_error('notstruct'); |
1284
|
|
|
} |
1285
|
|
|
$methName = @$call['methodName']; |
1286
|
|
|
if (!$methName) { |
1287
|
|
|
return static::_xmlrpcs_multicall_error('nomethod'); |
1288
|
|
|
} |
1289
|
|
|
if ($methName->kindOf() != 'scalar' || $methName->scalarTyp() != 'string') { |
1290
|
|
|
return static::_xmlrpcs_multicall_error('notstring'); |
1291
|
|
|
} |
1292
|
|
|
if ($methName->scalarVal() == 'system.multicall') { |
1293
|
|
|
return static::_xmlrpcs_multicall_error('recursion'); |
1294
|
|
|
} |
1295
|
|
|
|
1296
|
|
|
$params = @$call['params']; |
1297
|
|
|
if (!$params) { |
1298
|
|
|
return static::_xmlrpcs_multicall_error('noparams'); |
1299
|
|
|
} |
1300
|
|
|
if ($params->kindOf() != 'array') { |
1301
|
|
|
return static::_xmlrpcs_multicall_error('notarray'); |
1302
|
|
|
} |
1303
|
|
|
|
1304
|
|
|
$req = new Request($methName->scalarVal()); |
1305
|
|
|
foreach ($params as $i => $param) { |
1306
|
|
|
if (!$req->addParam($param)) { |
1307
|
|
|
$i++; // for error message, we count params from 1 |
1308
|
|
|
return static::_xmlrpcs_multicall_error(new static::$responseClass(0, |
1309
|
|
|
PhpXmlRpc::$xmlrpcerr['incorrect_params'], |
1310
|
|
|
PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i)); |
1311
|
|
|
} |
1312
|
|
|
} |
1313
|
|
|
|
1314
|
|
|
$result = $server->execute($req); |
1315
|
|
|
|
1316
|
|
|
if ($result->faultCode() != 0) { |
1317
|
|
|
return static::_xmlrpcs_multicall_error($result); // Method returned fault. |
1318
|
|
|
} |
1319
|
|
|
|
1320
|
|
|
return new Value(array($result->value()), 'array'); |
1321
|
|
|
} |
1322
|
|
|
|
1323
|
|
|
/** |
1324
|
|
|
* @internal this function will become protected in the future |
1325
|
|
|
* |
1326
|
|
|
* @param Server $server |
1327
|
|
|
* @param Value $call |
1328
|
|
|
* @return Value |
1329
|
|
|
*/ |
1330
|
|
|
public static function _xmlrpcs_multicall_do_call_phpvals($server, $call) |
1331
|
|
|
{ |
1332
|
|
|
if (!is_array($call)) { |
|
|
|
|
1333
|
|
|
return static::_xmlrpcs_multicall_error('notstruct'); |
1334
|
|
|
} |
1335
|
|
|
if (!array_key_exists('methodName', $call)) { |
1336
|
|
|
return static::_xmlrpcs_multicall_error('nomethod'); |
1337
|
|
|
} |
1338
|
|
|
if (!is_string($call['methodName'])) { |
1339
|
|
|
return static::_xmlrpcs_multicall_error('notstring'); |
1340
|
|
|
} |
1341
|
|
|
if ($call['methodName'] == 'system.multicall') { |
1342
|
|
|
return static::_xmlrpcs_multicall_error('recursion'); |
1343
|
|
|
} |
1344
|
|
|
if (!array_key_exists('params', $call)) { |
1345
|
|
|
return static::_xmlrpcs_multicall_error('noparams'); |
1346
|
|
|
} |
1347
|
|
|
if (!is_array($call['params'])) { |
1348
|
|
|
return static::_xmlrpcs_multicall_error('notarray'); |
1349
|
|
|
} |
1350
|
|
|
|
1351
|
|
|
// this is a simplistic hack, since we might have received |
1352
|
|
|
// base64 or datetime values, but they will be listed as strings here... |
1353
|
|
|
$pt = array(); |
1354
|
|
|
$wrapper = new Wrapper(); |
1355
|
|
|
foreach ($call['params'] as $val) { |
1356
|
|
|
// support EPI-encoded base64 and datetime values |
1357
|
|
|
if ($val instanceof \stdClass && isset($val->xmlrpc_type)) { |
1358
|
|
|
$pt[] = $val->xmlrpc_type == 'datetime' ? Value::$xmlrpcDateTime : $val->xmlrpc_type; |
1359
|
|
|
} else { |
1360
|
|
|
$pt[] = $wrapper->php2XmlrpcType(gettype($val)); |
1361
|
|
|
} |
1362
|
|
|
} |
1363
|
|
|
|
1364
|
|
|
$result = $server->execute($call['methodName'], $call['params'], $pt); |
1365
|
|
|
|
1366
|
|
|
if ($result->faultCode() != 0) { |
1367
|
|
|
return static::_xmlrpcs_multicall_error($result); // Method returned fault. |
1368
|
|
|
} |
1369
|
|
|
|
1370
|
|
|
return new Value(array($result->value()), 'array'); |
1371
|
|
|
} |
1372
|
|
|
|
1373
|
|
|
/** |
1374
|
|
|
* @internal handler of a system. method |
1375
|
|
|
* |
1376
|
|
|
* @param Server $server |
1377
|
|
|
* @param Request|array $req |
1378
|
|
|
* @return Response |
1379
|
|
|
*/ |
1380
|
|
|
public static function _xmlrpcs_multicall($server, $req) |
1381
|
|
|
{ |
1382
|
|
|
$result = array(); |
1383
|
|
|
// let's accept a plain list of php parameters, beside a single xml-rpc msg object |
1384
|
|
|
if (is_object($req)) { |
1385
|
|
|
$calls = $req->getParam(0); |
1386
|
|
|
foreach ($calls as $call) { |
1387
|
|
|
$result[] = static::_xmlrpcs_multicall_do_call($server, $call); |
1388
|
|
|
} |
1389
|
|
|
} else { |
1390
|
|
|
$numCalls = count($req); |
1391
|
|
|
for ($i = 0; $i < $numCalls; $i++) { |
1392
|
|
|
$result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]); |
1393
|
|
|
} |
1394
|
|
|
} |
1395
|
|
|
|
1396
|
|
|
return new static::$responseClass(new Value($result, 'array')); |
1397
|
|
|
} |
1398
|
|
|
|
1399
|
|
|
/** |
1400
|
|
|
* Error handler used to track errors that occur during server-side execution of PHP code. |
1401
|
|
|
* This allows to report back to the client whether an internal error has occurred or not |
1402
|
|
|
* using an xml-rpc response object, instead of letting the client deal with the html junk |
1403
|
|
|
* that a PHP execution error on the server generally entails. |
1404
|
|
|
* |
1405
|
|
|
* NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors. |
1406
|
|
|
* |
1407
|
|
|
* @internal |
1408
|
|
|
*/ |
1409
|
|
|
public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null) |
1410
|
|
|
{ |
1411
|
|
|
// obey the @ protocol |
1412
|
|
|
if (error_reporting() == 0) { |
1413
|
|
|
return; |
1414
|
|
|
} |
1415
|
|
|
|
1416
|
|
|
//if ($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING) |
1417
|
|
|
if ($errCode != E_STRICT) { |
1418
|
|
|
static::error_occurred($errString); |
1419
|
|
|
} |
1420
|
|
|
|
1421
|
|
|
// Try to avoid as much as possible disruption to the previous error handling mechanism in place |
1422
|
|
|
if (self::$_xmlrpcs_prev_ehandler == '') { |
1423
|
|
|
// The previous error handler was the default: all we should do is log error to the default error log |
1424
|
|
|
// (if level high enough) |
1425
|
|
|
if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) { |
1426
|
|
|
// we can't use the functionality of LoggerAware, because this is a static method |
1427
|
|
|
if (self::$logger === null) { |
1428
|
|
|
self::$logger = Logger::instance(); |
1429
|
|
|
} |
1430
|
|
|
self::$logger->error($errString); |
1431
|
|
|
} |
1432
|
|
|
} else { |
1433
|
|
|
// Pass control on to previous error handler, trying to avoid loops... |
1434
|
|
|
if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) { |
|
|
|
|
1435
|
|
|
if (is_array(self::$_xmlrpcs_prev_ehandler)) { |
|
|
|
|
1436
|
|
|
// the following works both with static class methods and plain object methods as error handler |
1437
|
|
|
call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context)); |
1438
|
|
|
} else { |
1439
|
|
|
$method = self::$_xmlrpcs_prev_ehandler; |
1440
|
|
|
$method($errCode, $errString, $filename, $lineNo, $context); |
1441
|
|
|
} |
1442
|
|
|
} |
1443
|
|
|
} |
1444
|
|
|
} |
1445
|
|
|
|
1446
|
|
|
// *** BC layer *** |
1447
|
|
|
|
1448
|
|
|
/** |
1449
|
|
|
* @param string $charsetEncoding |
1450
|
|
|
* @return string |
1451
|
|
|
* |
1452
|
|
|
* @deprecated this method was moved to the Response class |
1453
|
|
|
*/ |
1454
|
|
|
protected function xml_header($charsetEncoding = '') |
1455
|
|
|
{ |
1456
|
|
|
$this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
1457
|
|
|
|
1458
|
|
|
if ($charsetEncoding != '') { |
1459
|
|
|
return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n"; |
1460
|
|
|
} else { |
1461
|
|
|
return "<?xml version=\"1.0\"?" . ">\n"; |
1462
|
|
|
} |
1463
|
|
|
} |
1464
|
|
|
|
1465
|
|
|
// we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];` |
1466
|
|
|
public function &__get($name) |
1467
|
|
|
{ |
1468
|
|
|
switch ($name) { |
1469
|
|
|
case self::OPT_ACCEPTED_COMPRESSION : |
1470
|
|
|
case self::OPT_ALLOW_SYSTEM_FUNCS: |
1471
|
|
|
case self::OPT_COMPRESS_RESPONSE: |
1472
|
|
|
case self::OPT_DEBUG: |
1473
|
|
|
case self::OPT_EXCEPTION_HANDLING: |
1474
|
|
|
case self::OPT_FUNCTIONS_PARAMETERS_TYPE: |
1475
|
|
|
case self::OPT_PHPVALS_ENCODING_OPTIONS: |
1476
|
|
|
case self::OPT_RESPONSE_CHARSET_ENCODING: |
1477
|
|
|
$this->logDeprecation('Getting property Request::' . $name . ' is deprecated'); |
1478
|
|
|
return $this->$name; |
1479
|
|
|
case 'accepted_charset_encodings': |
1480
|
|
|
// manually implement the 'protected property' behaviour |
1481
|
|
|
$canAccess = false; |
1482
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); |
1483
|
|
|
if (isset($trace[1]) && isset($trace[1]['class'])) { |
1484
|
|
|
if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) { |
1485
|
|
|
$canAccess = true; |
1486
|
|
|
} |
1487
|
|
|
} |
1488
|
|
|
if ($canAccess) { |
1489
|
|
|
$this->logDeprecation('Getting property Request::' . $name . ' is deprecated'); |
1490
|
|
|
return $this->accepted_compression; |
1491
|
|
|
} else { |
1492
|
|
|
trigger_error("Cannot access protected property Server::accepted_charset_encodings in " . __FILE__, E_USER_ERROR); |
1493
|
|
|
} |
1494
|
|
|
break; |
1495
|
|
|
default: |
1496
|
|
|
/// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... |
1497
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); |
1498
|
|
|
trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); |
1499
|
|
|
$result = null; |
1500
|
|
|
return $result; |
1501
|
|
|
} |
1502
|
|
|
} |
1503
|
|
|
|
1504
|
|
|
public function __set($name, $value) |
1505
|
|
|
{ |
1506
|
|
|
switch ($name) { |
1507
|
|
|
case self::OPT_ACCEPTED_COMPRESSION : |
1508
|
|
|
case self::OPT_ALLOW_SYSTEM_FUNCS: |
1509
|
|
|
case self::OPT_COMPRESS_RESPONSE: |
1510
|
|
|
case self::OPT_DEBUG: |
1511
|
|
|
case self::OPT_EXCEPTION_HANDLING: |
1512
|
|
|
case self::OPT_FUNCTIONS_PARAMETERS_TYPE: |
1513
|
|
|
case self::OPT_PHPVALS_ENCODING_OPTIONS: |
1514
|
|
|
case self::OPT_RESPONSE_CHARSET_ENCODING: |
1515
|
|
|
$this->logDeprecation('Setting property Request::' . $name . ' is deprecated'); |
1516
|
|
|
$this->$name = $value; |
1517
|
|
|
break; |
1518
|
|
|
case 'accepted_charset_encodings': |
1519
|
|
|
// manually implement the 'protected property' behaviour |
1520
|
|
|
$canAccess = false; |
1521
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); |
1522
|
|
|
if (isset($trace[1]) && isset($trace[1]['class'])) { |
1523
|
|
|
if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) { |
1524
|
|
|
$canAccess = true; |
1525
|
|
|
} |
1526
|
|
|
} |
1527
|
|
|
if ($canAccess) { |
1528
|
|
|
$this->logDeprecation('Setting property Request::' . $name . ' is deprecated'); |
1529
|
|
|
$this->accepted_compression = $value; |
1530
|
|
|
} else { |
1531
|
|
|
trigger_error("Cannot access protected property Server::accepted_charset_encodings in " . __FILE__, E_USER_ERROR); |
1532
|
|
|
} |
1533
|
|
|
break; |
1534
|
|
|
default: |
1535
|
|
|
/// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... |
1536
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); |
1537
|
|
|
trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); |
1538
|
|
|
} |
1539
|
|
|
} |
1540
|
|
|
|
1541
|
|
|
public function __isset($name) |
1542
|
|
|
{ |
1543
|
|
|
switch ($name) { |
1544
|
|
|
case self::OPT_ACCEPTED_COMPRESSION : |
1545
|
|
|
case self::OPT_ALLOW_SYSTEM_FUNCS: |
1546
|
|
|
case self::OPT_COMPRESS_RESPONSE: |
1547
|
|
|
case self::OPT_DEBUG: |
1548
|
|
|
case self::OPT_EXCEPTION_HANDLING: |
1549
|
|
|
case self::OPT_FUNCTIONS_PARAMETERS_TYPE: |
1550
|
|
|
case self::OPT_PHPVALS_ENCODING_OPTIONS: |
1551
|
|
|
case self::OPT_RESPONSE_CHARSET_ENCODING: |
1552
|
|
|
$this->logDeprecation('Checking property Request::' . $name . ' is deprecated'); |
1553
|
|
|
return isset($this->$name); |
1554
|
|
|
case 'accepted_charset_encodings': |
1555
|
|
|
// manually implement the 'protected property' behaviour |
1556
|
|
|
$canAccess = false; |
1557
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); |
1558
|
|
|
if (isset($trace[1]) && isset($trace[1]['class'])) { |
1559
|
|
|
if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) { |
1560
|
|
|
$canAccess = true; |
1561
|
|
|
} |
1562
|
|
|
} |
1563
|
|
|
if ($canAccess) { |
1564
|
|
|
$this->logDeprecation('Checking property Request::' . $name . ' is deprecated'); |
1565
|
|
|
return isset($this->accepted_compression); |
1566
|
|
|
} |
1567
|
|
|
// break through voluntarily |
1568
|
|
|
default: |
1569
|
|
|
return false; |
1570
|
|
|
} |
1571
|
|
|
} |
1572
|
|
|
|
1573
|
|
|
public function __unset($name) |
1574
|
|
|
{ |
1575
|
|
|
switch ($name) { |
1576
|
|
|
case self::OPT_ACCEPTED_COMPRESSION : |
1577
|
|
|
case self::OPT_ALLOW_SYSTEM_FUNCS: |
1578
|
|
|
case self::OPT_COMPRESS_RESPONSE: |
1579
|
|
|
case self::OPT_DEBUG: |
1580
|
|
|
case self::OPT_EXCEPTION_HANDLING: |
1581
|
|
|
case self::OPT_FUNCTIONS_PARAMETERS_TYPE: |
1582
|
|
|
case self::OPT_PHPVALS_ENCODING_OPTIONS: |
1583
|
|
|
case self::OPT_RESPONSE_CHARSET_ENCODING: |
1584
|
|
|
$this->logDeprecation('Unsetting property Request::' . $name . ' is deprecated'); |
1585
|
|
|
unset($this->$name); |
1586
|
|
|
break; |
1587
|
|
|
case 'accepted_charset_encodings': |
1588
|
|
|
// manually implement the 'protected property' behaviour |
1589
|
|
|
$canAccess = false; |
1590
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); |
1591
|
|
|
if (isset($trace[1]) && isset($trace[1]['class'])) { |
1592
|
|
|
if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) { |
1593
|
|
|
$canAccess = true; |
1594
|
|
|
} |
1595
|
|
|
} |
1596
|
|
|
if ($canAccess) { |
1597
|
|
|
$this->logDeprecation('Unsetting property Request::' . $name . ' is deprecated'); |
1598
|
|
|
unset($this->accepted_compression); |
1599
|
|
|
} else { |
1600
|
|
|
trigger_error("Cannot access protected property Server::accepted_charset_encodings in " . __FILE__, E_USER_ERROR); |
1601
|
|
|
} |
1602
|
|
|
break; |
1603
|
|
|
default: |
1604
|
|
|
/// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... |
1605
|
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); |
1606
|
|
|
trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); |
1607
|
|
|
} |
1608
|
|
|
} |
1609
|
|
|
} |
1610
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.