1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpXmlRpc; |
4
|
|
|
|
5
|
|
|
use PhpXmlRpc\Exception\HttpException; |
6
|
|
|
use PhpXmlRpc\Helper\Charset; |
7
|
|
|
use PhpXmlRpc\Helper\Http; |
8
|
|
|
use PhpXmlRpc\Helper\Logger; |
9
|
|
|
use PhpXmlRpc\Helper\XMLParser; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* This class provides the representation of a request to an XML-RPC server. |
13
|
|
|
* A client sends a PhpXmlrpc\Request to a server, and receives back an PhpXmlrpc\Response. |
14
|
|
|
*/ |
15
|
|
|
class Request |
16
|
|
|
{ |
17
|
|
|
protected static $logger; |
18
|
|
|
protected static $parser; |
19
|
|
|
protected static $charsetEncoder; |
20
|
|
|
|
21
|
|
|
/// @todo: do these need to be public? |
22
|
|
|
public $payload; |
23
|
|
|
/** @internal */ |
24
|
|
|
public $methodname; |
25
|
|
|
/** @internal */ |
26
|
|
|
public $params = array(); |
27
|
|
|
public $debug = 0; |
28
|
|
|
public $content_type = 'text/xml'; |
29
|
|
|
|
30
|
|
|
// holds data while parsing the response. NB: Not a full Response object |
31
|
|
|
/** @deprecated will be removed in a future release */ |
32
|
|
|
protected $httpResponse = array(); |
33
|
|
|
|
34
|
3 |
|
public function getLogger() |
35
|
|
|
{ |
36
|
3 |
|
if (self::$logger === null) { |
37
|
3 |
|
self::$logger = Logger::instance(); |
38
|
|
|
} |
39
|
3 |
|
return self::$logger; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
public static function setLogger($logger) |
43
|
|
|
{ |
44
|
|
|
self::$logger = $logger; |
45
|
|
|
} |
46
|
|
|
|
47
|
642 |
|
public function getParser() |
48
|
|
|
{ |
49
|
642 |
|
if (self::$parser === null) { |
50
|
7 |
|
self::$parser = new XMLParser(); |
51
|
|
|
} |
52
|
642 |
|
return self::$parser; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
public static function setParser($parser) |
56
|
|
|
{ |
57
|
|
|
self::$parser = $parser; |
58
|
|
|
} |
59
|
|
|
|
60
|
591 |
|
public function getCharsetEncoder() |
61
|
|
|
{ |
62
|
591 |
|
if (self::$charsetEncoder === null) { |
63
|
8 |
|
self::$charsetEncoder = Charset::instance(); |
64
|
|
|
} |
65
|
591 |
|
return self::$charsetEncoder; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
public function setCharsetEncoder($charsetEncoder) |
69
|
|
|
{ |
70
|
|
|
self::$charsetEncoder = $charsetEncoder; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param string $methodName the name of the method to invoke |
75
|
|
|
* @param Value[] $params array of parameters to be passed to the method (NB: Value objects, not plain php values) |
76
|
|
|
*/ |
77
|
650 |
|
public function __construct($methodName, $params = array()) |
78
|
|
|
{ |
79
|
650 |
|
$this->methodname = $methodName; |
80
|
650 |
|
foreach ($params as $param) { |
81
|
453 |
|
$this->addParam($param); |
82
|
|
|
} |
83
|
650 |
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @internal this function will become protected in the future |
87
|
|
|
* @param string $charsetEncoding |
88
|
|
|
* @return string |
89
|
|
|
*/ |
90
|
591 |
|
public function xml_header($charsetEncoding = '') |
91
|
|
|
{ |
92
|
591 |
|
if ($charsetEncoding != '') { |
93
|
60 |
|
return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\" ?" . ">\n<methodCall>\n"; |
94
|
|
|
} else { |
95
|
531 |
|
return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n"; |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @internal this function will become protected in the future |
101
|
|
|
* @return string |
102
|
|
|
*/ |
103
|
591 |
|
public function xml_footer() |
104
|
|
|
{ |
105
|
591 |
|
return '</methodCall>'; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @internal this function will become protected in the future |
110
|
|
|
* @param string $charsetEncoding |
111
|
|
|
*/ |
112
|
591 |
|
public function createPayload($charsetEncoding = '') |
113
|
|
|
{ |
114
|
591 |
|
if ($charsetEncoding != '') { |
115
|
60 |
|
$this->content_type = 'text/xml; charset=' . $charsetEncoding; |
116
|
|
|
} else { |
117
|
531 |
|
$this->content_type = 'text/xml'; |
118
|
|
|
} |
119
|
591 |
|
$this->payload = $this->xml_header($charsetEncoding); |
120
|
591 |
|
$this->payload .= '<methodName>' . $this->getCharsetEncoder()->encodeEntities( |
121
|
591 |
|
$this->methodname, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</methodName>\n"; |
122
|
591 |
|
$this->payload .= "<params>\n"; |
123
|
591 |
|
foreach ($this->params as $p) { |
124
|
552 |
|
$this->payload .= "<param>\n" . $p->serialize($charsetEncoding) . |
125
|
552 |
|
"</param>\n"; |
126
|
|
|
} |
127
|
591 |
|
$this->payload .= "</params>\n"; |
128
|
591 |
|
$this->payload .= $this->xml_footer(); |
129
|
591 |
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Gets/sets the xmlrpc method to be invoked. |
133
|
|
|
* |
134
|
|
|
* @param string $methodName the method to be set (leave empty not to set it) |
135
|
|
|
* |
136
|
|
|
* @return string the method that will be invoked |
137
|
|
|
*/ |
138
|
509 |
|
public function method($methodName = '') |
139
|
|
|
{ |
140
|
509 |
|
if ($methodName != '') { |
141
|
|
|
$this->methodname = $methodName; |
142
|
|
|
} |
143
|
|
|
|
144
|
509 |
|
return $this->methodname; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Returns xml representation of the message. XML prologue included. |
149
|
|
|
* |
150
|
|
|
* @param string $charsetEncoding |
151
|
|
|
* |
152
|
|
|
* @return string the xml representation of the message, xml prologue included |
153
|
|
|
*/ |
154
|
591 |
|
public function serialize($charsetEncoding = '') |
155
|
|
|
{ |
156
|
591 |
|
$this->createPayload($charsetEncoding); |
157
|
|
|
|
158
|
591 |
|
return $this->payload; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Add a parameter to the list of parameters to be used upon method invocation. |
163
|
|
|
* |
164
|
|
|
* Checks that $params is actually a Value object and not a plain php value. |
165
|
|
|
* |
166
|
|
|
* @param Value $param |
167
|
|
|
* |
168
|
|
|
* @return boolean false on failure |
169
|
|
|
*/ |
170
|
596 |
|
public function addParam($param) |
171
|
|
|
{ |
172
|
|
|
// check: do not add to self params which are not xmlrpc values |
173
|
596 |
|
if (is_object($param) && is_a($param, 'PhpXmlRpc\Value')) { |
174
|
596 |
|
$this->params[] = $param; |
175
|
|
|
|
176
|
596 |
|
return true; |
177
|
|
|
} else { |
178
|
|
|
return false; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Returns the nth parameter in the request. The index zero-based. |
184
|
|
|
* |
185
|
|
|
* @param integer $i the index of the parameter to fetch (zero based) |
186
|
|
|
* |
187
|
|
|
* @return Value the i-th parameter |
188
|
|
|
*/ |
189
|
468 |
|
public function getParam($i) |
190
|
|
|
{ |
191
|
468 |
|
return $this->params[$i]; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Returns the number of parameters in the message. |
196
|
|
|
* |
197
|
|
|
* @return integer the number of parameters currently set |
198
|
|
|
*/ |
199
|
487 |
|
public function getNumParams() |
200
|
|
|
{ |
201
|
487 |
|
return count($this->params); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Given an open file handle, read all data available and parse it as an xmlrpc response. |
206
|
|
|
* |
207
|
|
|
* NB: the file handle is not closed by this function. |
208
|
|
|
* NNB: might have trouble in rare cases to work on network streams, as we check for a read of 0 bytes instead of |
209
|
|
|
* feof($fp). But since checking for feof(null) returns false, we would risk an infinite loop in that case, |
210
|
|
|
* because we cannot trust the caller to give us a valid pointer to an open file... |
211
|
|
|
* |
212
|
|
|
* @param resource $fp stream pointer |
213
|
|
|
* @param bool $headersProcessed |
214
|
|
|
* @param string $returnType |
215
|
|
|
* |
216
|
|
|
* @return Response |
217
|
|
|
*/ |
218
|
|
|
public function parseResponseFile($fp, $headersProcessed = false, $returnType = 'xmlrpcvals') |
219
|
|
|
{ |
220
|
|
|
$ipd = ''; |
221
|
|
|
while ($data = fread($fp, 32768)) { |
222
|
|
|
$ipd .= $data; |
223
|
|
|
} |
224
|
|
|
return $this->parseResponse($ipd, $headersProcessed, $returnType); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Parse the xmlrpc response contained in the string $data and return a Response object. |
229
|
|
|
* |
230
|
|
|
* When $this->debug has been set to a value greater than 0, will echo debug messages to screen while decoding. |
231
|
|
|
* |
232
|
|
|
* @param string $data the xmlrpc response, possibly including http headers |
233
|
|
|
* @param bool $headersProcessed when true prevents parsing HTTP headers for interpretation of content-encoding and |
234
|
|
|
* consequent decoding |
235
|
|
|
* @param string $returnType decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or |
236
|
|
|
* 'phpvals' |
237
|
|
|
* |
238
|
|
|
* @return Response |
239
|
|
|
* |
240
|
|
|
* @todo parsing Responses is not really the responsibility of the Request class. Maybe of the Client... |
241
|
|
|
*/ |
242
|
646 |
|
public function parseResponse($data = '', $headersProcessed = false, $returnType = XMLParser::RETURN_XMLRPCVALS) |
243
|
|
|
{ |
244
|
646 |
|
if ($this->debug) { |
245
|
3 |
|
$this->getLogger()->debugMessage("---GOT---\n$data\n---END---"); |
246
|
|
|
} |
247
|
|
|
|
248
|
646 |
|
$this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array()); |
|
|
|
|
249
|
|
|
|
250
|
646 |
|
if ($data == '') { |
251
|
|
|
$this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': no response received from server.'); |
252
|
|
|
return new Response(0, PhpXmlRpc::$xmlrpcerr['no_data'], PhpXmlRpc::$xmlrpcstr['no_data']); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
// parse the HTTP headers of the response, if present, and separate them from data |
256
|
646 |
|
if (substr($data, 0, 4) == 'HTTP') { |
257
|
634 |
|
$httpParser = new Http(); |
258
|
|
|
try { |
259
|
634 |
|
$this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug); |
|
|
|
|
260
|
3 |
|
} catch (HttpException $e) { |
261
|
|
|
// failed processing of HTTP response headers |
262
|
|
|
// save into response obj the full payload received, for debugging |
263
|
3 |
|
return new Response(0, $e->getCode(), $e->getMessage(), '', array('raw_data' => $data, 'status_code', $e->statusCode())); |
264
|
|
|
} catch(\Exception $e) { |
265
|
|
|
return new Response(0, $e->getCode(), $e->getMessage(), '', array('raw_data' => $data)); |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
// be tolerant of extra whitespace in response body |
270
|
643 |
|
$data = trim($data); |
271
|
|
|
|
272
|
|
|
/// @todo return an error msg if $data == '' ? |
273
|
|
|
|
274
|
|
|
// be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) |
275
|
|
|
// idea from Luca Mariano <[email protected]> originally in PEARified version of the lib |
276
|
643 |
|
$pos = strrpos($data, '</methodResponse>'); |
277
|
643 |
|
if ($pos !== false) { |
278
|
643 |
|
$data = substr($data, 0, $pos + 17); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
// try to 'guestimate' the character encoding of the received response |
282
|
643 |
|
$respEncoding = XMLParser::guessEncoding(@$this->httpResponse['headers']['content-type'], $data); |
|
|
|
|
283
|
|
|
|
284
|
643 |
|
if ($this->debug) { |
285
|
3 |
|
$start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):'); |
286
|
3 |
|
if ($start) { |
287
|
3 |
|
$start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):'); |
288
|
3 |
|
$end = strpos($data, '-->', $start); |
289
|
3 |
|
$comments = substr($data, $start, $end - $start); |
290
|
3 |
|
$this->getLogger()->debugMessage("---SERVER DEBUG INFO (DECODED) ---\n\t" . |
291
|
3 |
|
str_replace("\n", "\n\t", base64_decode($comments)) . "\n---END---", $respEncoding); |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// if user wants back raw xml, give it to her |
296
|
643 |
|
if ($returnType == 'xml') { |
297
|
1 |
|
return new Response($data, 0, '', 'xml', $this->httpResponse); |
|
|
|
|
298
|
|
|
} |
299
|
|
|
|
300
|
642 |
|
if ($respEncoding != '') { |
301
|
|
|
|
302
|
|
|
// Since parsing will fail if charset is not specified in the xml prologue, |
303
|
|
|
// the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that... |
304
|
|
|
// The following code might be better for mb_string enabled installs, but makes the lib about 200% slower... |
305
|
|
|
//if (!is_valid_charset($respEncoding, array('UTF-8'))) |
306
|
642 |
|
if (!in_array($respEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) { |
307
|
1 |
|
if ($respEncoding == 'ISO-8859-1') { |
308
|
1 |
|
$data = utf8_encode($data); |
309
|
|
|
} else { |
310
|
|
|
|
311
|
|
|
if (extension_loaded('mbstring')) { |
312
|
|
|
$data = mb_convert_encoding($data, 'UTF-8', $respEncoding); |
313
|
|
|
} else { |
314
|
|
|
$this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received response: ' . $respEncoding); |
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
// PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset. |
321
|
|
|
// What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8 |
322
|
|
|
// This allows to send data which is native in various charset, by extending xmlrpc_encode_entities() and |
323
|
|
|
// setting xmlrpc_internalencoding |
324
|
642 |
|
if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { |
325
|
|
|
/// @todo emit a warning |
326
|
|
|
$options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8'); |
327
|
|
|
} else { |
328
|
642 |
|
$options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); |
329
|
|
|
} |
330
|
|
|
|
331
|
642 |
|
$xmlRpcParser = $this->getParser(); |
332
|
642 |
|
$xmlRpcParser->parse($data, $returnType, XMLParser::ACCEPT_RESPONSE, $options); |
333
|
|
|
|
334
|
|
|
// first error check: xml not well formed |
335
|
642 |
|
if ($xmlRpcParser->_xh['isf'] > 2) { |
336
|
|
|
|
337
|
|
|
// BC break: in the past for some cases we used the error message: 'XML error at line 1, check URL' |
338
|
|
|
|
339
|
3 |
|
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], |
340
|
3 |
|
PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason'], '', |
341
|
3 |
|
$this->httpResponse |
|
|
|
|
342
|
|
|
); |
343
|
|
|
|
344
|
3 |
|
if ($this->debug) { |
345
|
3 |
|
print $xmlRpcParser->_xh['isf_reason']; |
346
|
|
|
} |
347
|
|
|
} |
348
|
|
|
// second error check: xml well formed but not xml-rpc compliant |
349
|
642 |
|
elseif ($xmlRpcParser->_xh['isf'] == 2) { |
350
|
4 |
|
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], |
351
|
4 |
|
PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason'], '', |
352
|
4 |
|
$this->httpResponse |
|
|
|
|
353
|
|
|
); |
354
|
|
|
|
355
|
4 |
|
if ($this->debug) { |
356
|
|
|
/// @todo echo something for user? |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
// third error check: parsing of the response has somehow gone boink. |
360
|
|
|
/// @todo shall we omit this check, since we trust the parsing code? |
361
|
639 |
|
elseif ($returnType == XMLParser::RETURN_XMLRPCVALS && !is_object($xmlRpcParser->_xh['value'])) { |
362
|
|
|
// something odd has happened |
363
|
|
|
// and it's time to generate a client side error |
364
|
|
|
// indicating something odd went on |
365
|
|
|
$r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'], |
366
|
|
|
'', $this->httpResponse |
|
|
|
|
367
|
|
|
); |
368
|
|
|
} else { |
369
|
639 |
|
if ($this->debug > 1) { |
370
|
2 |
|
$this->getLogger()->debugMessage( |
371
|
2 |
|
"---PARSED---\n".var_export($xmlRpcParser->_xh['value'], true)."\n---END---" |
372
|
|
|
); |
373
|
|
|
} |
374
|
|
|
|
375
|
639 |
|
$v = $xmlRpcParser->_xh['value']; |
376
|
|
|
|
377
|
639 |
|
if ($xmlRpcParser->_xh['isf']) { |
378
|
|
|
/// @todo we should test here if server sent an int and a string, and/or coerce them into such... |
379
|
99 |
|
if ($returnType == XMLParser::RETURN_XMLRPCVALS) { |
380
|
99 |
|
$errNo_v = $v['faultCode']; |
381
|
99 |
|
$errStr_v = $v['faultString']; |
382
|
99 |
|
$errNo = $errNo_v->scalarval(); |
383
|
99 |
|
$errStr = $errStr_v->scalarval(); |
384
|
|
|
} else { |
385
|
|
|
$errNo = $v['faultCode']; |
386
|
|
|
$errStr = $v['faultString']; |
387
|
|
|
} |
388
|
|
|
|
389
|
99 |
|
if ($errNo == 0) { |
390
|
|
|
// FAULT returned, errno needs to reflect that |
391
|
|
|
$errNo = -1; |
392
|
|
|
} |
393
|
|
|
|
394
|
99 |
|
$r = new Response(0, $errNo, $errStr, '', $this->httpResponse); |
|
|
|
|
395
|
|
|
} else { |
396
|
617 |
|
$r = new Response($v, 0, '', $returnType, $this->httpResponse); |
|
|
|
|
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
|
400
|
642 |
|
return $r; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Kept the old name even if Request class was renamed, for compatibility. |
405
|
|
|
* |
406
|
|
|
* @return string |
407
|
|
|
*/ |
408
|
136 |
|
public function kindOf() |
409
|
|
|
{ |
410
|
136 |
|
return 'msg'; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Enables/disables the echoing to screen of the xmlrpc responses received. |
415
|
|
|
* |
416
|
|
|
* @param integer $level values 0, 1, 2 are supported |
417
|
|
|
*/ |
418
|
647 |
|
public function setDebug($level) |
419
|
|
|
{ |
420
|
647 |
|
$this->debug = $level; |
421
|
647 |
|
} |
422
|
|
|
} |
423
|
|
|
|
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.