Passed
Push — master ( b9401f...4e5288 )
by Gaetano
06:45
created

Request::getCharsetEncoder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
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());
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

248
        /** @scrutinizer ignore-deprecated */ $this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array());

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

259
                /** @scrutinizer ignore-deprecated */ $this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug);

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

282
        $respEncoding = XMLParser::guessEncoding(@/** @scrutinizer ignore-deprecated */ $this->httpResponse['headers']['content-type'], $data);

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

297
            return new Response($data, 0, '', 'xml', /** @scrutinizer ignore-deprecated */ $this->httpResponse);

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.

Loading history...
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
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

341
                /** @scrutinizer ignore-deprecated */ $this->httpResponse

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.

Loading history...
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
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

352
                /** @scrutinizer ignore-deprecated */ $this->httpResponse

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.

Loading history...
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
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

366
                '', /** @scrutinizer ignore-deprecated */ $this->httpResponse

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

394
                $r = new Response(0, $errNo, $errStr, '', /** @scrutinizer ignore-deprecated */ $this->httpResponse);

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.

Loading history...
395
            } else {
396 617
                $r = new Response($v, 0, '', $returnType, $this->httpResponse);
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Request::$httpResponse has been deprecated: will be removed in a future release ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

396
                $r = new Response($v, 0, '', $returnType, /** @scrutinizer ignore-deprecated */ $this->httpResponse);

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.

Loading history...
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