Completed
Push — master ( 6be6c0...cc60cf )
by Gaetano
03:16
created

Request::getNumParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Charset;
6
use PhpXmlRpc\Helper\Http;
7
use PhpXmlRpc\Helper\Logger;
8
use PhpXmlRpc\Helper\XMLParser;
9
10
/**
11
 * This class provides the representation of a request to an XML-RPC server.
12
 * A client sends a PhpXmlrpc\Request to a server, and receives back an PhpXmlrpc\Response.
13
 */
14
class Request
15
{
16
    /// @todo: do these need to be public?
17
    public $payload;
18
    public $methodname;
19
    public $params = array();
20
    public $debug = 0;
21
    public $content_type = 'text/xml';
22
23
    // holds data while parsing the response. NB: Not a full Response object
24
    protected $httpResponse = array();
25
26
    /**
27
     * @param string $methodName the name of the method to invoke
28
     * @param Value[] $params array of parameters to be passed to the method (NB: Value objects, not plain php values)
29
     */
30 602
    public function __construct($methodName, $params = array())
31
    {
32 602
        $this->methodname = $methodName;
33 602
        foreach ($params as $param) {
34 429
            $this->addParam($param);
35
        }
36 602
    }
37
38 543
    public function xml_header($charsetEncoding = '')
39
    {
40 543
        if ($charsetEncoding != '') {
41 56
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\" ?" . ">\n<methodCall>\n";
42
        } else {
43 487
            return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
44
        }
45
    }
46
47 543
    public function xml_footer()
48
    {
49 543
        return '</methodCall>';
50
    }
51
52 543
    public function createPayload($charsetEncoding = '')
53
    {
54 543
        if ($charsetEncoding != '') {
55 56
            $this->content_type = 'text/xml; charset=' . $charsetEncoding;
56
        } else {
57 487
            $this->content_type = 'text/xml';
58
        }
59 543
        $this->payload = $this->xml_header($charsetEncoding);
60 543
        $this->payload .= '<methodName>' . Charset::instance()->encodeEntities(
61 543
            $this->methodname, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</methodName>\n";
62 543
        $this->payload .= "<params>\n";
63 543
        foreach ($this->params as $p) {
64 486
            $this->payload .= "<param>\n" . $p->serialize($charsetEncoding) .
65 486
                "</param>\n";
66
        }
67 543
        $this->payload .= "</params>\n";
68 543
        $this->payload .= $this->xml_footer();
69 543
    }
70
71
    /**
72
     * Gets/sets the xmlrpc method to be invoked.
73
     *
74
     * @param string $methodName the method to be set (leave empty not to set it)
75
     *
76
     * @return string the method that will be invoked
77
     */
78 38
    public function method($methodName = '')
79
    {
80 38
        if ($methodName != '') {
81
            $this->methodname = $methodName;
82
        }
83
84 38
        return $this->methodname;
85
    }
86
87
    /**
88
     * Returns xml representation of the message. XML prologue included.
89
     *
90
     * @param string $charsetEncoding
91
     *
92
     * @return string the xml representation of the message, xml prologue included
93
     */
94 1
    public function serialize($charsetEncoding = '')
95
    {
96 1
        $this->createPayload($charsetEncoding);
97
98 1
        return $this->payload;
99
    }
100
101
    /**
102
     * Add a parameter to the list of parameters to be used upon method invocation.
103
     *
104
     * Checks that $params is actually a Value object and not a plain php value.
105
     *
106
     * @param Value $param
107
     *
108
     * @return boolean false on failure
109
     */
110 486
    public function addParam($param)
111
    {
112
        // check: do not add to self params which are not xmlrpc values
113 486
        if (is_object($param) && is_a($param, 'PhpXmlRpc\Value')) {
114 486
            $this->params[] = $param;
115
116 486
            return true;
117
        } else {
118
            return false;
119
        }
120
    }
121
122
    /**
123
     * Returns the nth parameter in the request. The index zero-based.
124
     *
125
     * @param integer $i the index of the parameter to fetch (zero based)
126
     *
127
     * @return Value the i-th parameter
128
     */
129 38
    public function getParam($i)
130
    {
131 38
        return $this->params[$i];
132
    }
133
134
    /**
135
     * Returns the number of parameters in the message.
136
     *
137
     * @return integer the number of parameters currently set
138
     */
139 38
    public function getNumParams()
140
    {
141 38
        return count($this->params);
142
    }
143
144
    /**
145
     * Given an open file handle, read all data available and parse it as an xmlrpc response.
146
     *
147
     * NB: the file handle is not closed by this function.
148
     * NNB: might have trouble in rare cases to work on network streams, as we check for a read of 0 bytes instead of
149
     *      feof($fp). But since checking for feof(null) returns false, we would risk an infinite loop in that case,
150
     *      because we cannot trust the caller to give us a valid pointer to an open file...
151
     *
152
     * @param resource $fp stream pointer
153
     *
154
     * @return Response
155
     *
156
     * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
157
     */
158
    public function parseResponseFile($fp)
159
    {
160
        $ipd = '';
161
        while ($data = fread($fp, 32768)) {
162
            $ipd .= $data;
163
        }
164
        return $this->parseResponse($ipd);
165
    }
166
167
    /**
168
     * Parse the xmlrpc response contained in the string $data and return a Response object.
169
     *
170
     * When $this->debug has been set to a value greater than 0, will echo debug messages to screen while decoding.
171
     *
172
     * @param string $data the xmlrpc response, possibly including http headers
173
     * @param bool $headersProcessed when true prevents parsing HTTP headers for interpretation of content-encoding and
174
     *                               consequent decoding
175
     * @param string $returnType decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or
176
     *                           'phpvals'
177
     *
178
     * @return Response
179
     */
180 510
    public function parseResponse($data = '', $headersProcessed = false, $returnType = 'xmlrpcvals')
181
    {
182 510
        if ($this->debug) {
183 462
            Logger::instance()->debugMessage("---GOT---\n$data\n---END---");
184
        }
185
186 510
        $this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array());
187
188 510 View Code Duplication
        if ($data == '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
            error_log('XML-RPC: ' . __METHOD__ . ': no response received from server.');
190
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_data'], PhpXmlRpc::$xmlrpcstr['no_data']);
191
        }
192
193
        // parse the HTTP headers of the response, if present, and separate them from data
194 510
        if (substr($data, 0, 4) == 'HTTP') {
195 498
            $httpParser = new Http();
196
            try {
197 498
                $this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug);
198 495
            } catch(\Exception $e) {
199 495
                $r = new Response(0, $e->getCode(), $e->getMessage());
200
                // failed processing of HTTP response headers
201
                // save into response obj the full payload received, for debugging
202 495
                $r->raw_data = $data;
203
204 495
                return $r;
205
            }
206
        }
207
208
        // be tolerant of extra whitespace in response body
209 15
        $data = trim($data);
210
211
        /// @todo return an error msg if $data=='' ?
212
213
        // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
214
        // idea from Luca Mariano <[email protected]> originally in PEARified version of the lib
215 15
        $pos = strrpos($data, '</methodResponse>');
216 15
        if ($pos !== false) {
217 15
            $data = substr($data, 0, $pos + 17);
218
        }
219
220
        // try to 'guestimate' the character encoding of the received response
221 15
        $respEncoding = XMLParser::guessEncoding(@$this->httpResponse['headers']['content-type'], $data);
222
223 15
        if ($this->debug) {
224 15
            $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
225 15
            if ($start) {
226
                $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
227
                $end = strpos($data, '-->', $start);
228
                $comments = substr($data, $start, $end - $start);
229
                Logger::instance()->debugMessage("---SERVER DEBUG INFO (DECODED) ---\n\t" .
230
                    str_replace("\n", "\n\t", base64_decode($comments)) . "\n---END---", $respEncoding);
231
            }
232
        }
233
234
        // if user wants back raw xml, give it to him
235 15
        if ($returnType == 'xml') {
236 1
            $r = new Response($data, 0, '', 'xml');
237 1
            $r->hdrs = $this->httpResponse['headers'];
238 1
            $r->_cookies = $this->httpResponse['cookies'];
239 1
            $r->raw_data = $this->httpResponse['raw_data'];
240
241 1
            return $r;
242
        }
243
244 14 View Code Duplication
        if ($respEncoding != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
246
            // Since parsing will fail if charset is not specified in the xml prologue,
247
            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
248
            // The following code might be better for mb_string enabled installs, but
249
            // makes the lib about 200% slower...
250
            //if (!is_valid_charset($respEncoding, array('UTF-8')))
251 14
            if (!in_array($respEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
252 1
                if ($respEncoding == 'ISO-8859-1') {
253 1
                    $data = utf8_encode($data);
254
                } else {
255
                    if (extension_loaded('mbstring')) {
256
                        $data = mb_convert_encoding($data, 'UTF-8', $respEncoding);
257
                    } else {
258
                        error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received response: ' . $respEncoding);
259
                    }
260
                }
261
            }
262
        }
263
264 14
        $parser = xml_parser_create();
265 14
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
266
        // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
267
        // the xml parser to give us back data in the expected charset.
268
        // What if internal encoding is not in one of the 3 allowed?
269
        // we use the broadest one, ie. utf8
270
        // This allows to send data which is native in various charset,
271
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
272 14 View Code Duplication
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
274
        } else {
275 14
            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
276
        }
277
278 14
        $xmlRpcParser = new XMLParser();
279 14
        xml_set_object($parser, $xmlRpcParser);
280
281 14
        if ($returnType == 'phpvals') {
282 3
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
283
        } else {
284 11
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
285
        }
286
287 14
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
288 14
        xml_set_default_handler($parser, 'xmlrpc_dh');
289
290
        // first error check: xml not well formed
291 14
        if (!xml_parse($parser, $data, 1)) {
292
            // thanks to Peter Kocks <[email protected]>
293 1
            if ((xml_get_current_line_number($parser)) == 1) {
294
                $errStr = 'XML error at line 1, check URL';
295
            } else {
296 1
                $errStr = sprintf('XML error: %s at line %d, column %d',
297 1
                    xml_error_string(xml_get_error_code($parser)),
298 1
                    xml_get_current_line_number($parser), xml_get_current_column_number($parser));
299
            }
300 1
            error_log($errStr);
301 1
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $errStr);
302 1
            xml_parser_free($parser);
303 1
            if ($this->debug) {
304 1
                print $errStr;
305
            }
306 1
            $r->hdrs = $this->httpResponse['headers'];
307 1
            $r->_cookies = $this->httpResponse['cookies'];
308 1
            $r->raw_data = $this->httpResponse['raw_data'];
309
310 1
            return $r;
311
        }
312 14
        xml_parser_free($parser);
313
        // second error check: xml well formed but not xml-rpc compliant
314 14
        if ($xmlRpcParser->_xh['isf'] > 1) {
315 4
            if ($this->debug) {
316
                /// @todo echo something for user?
317
            }
318
319 4
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
320 4
                PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
321
        }
322
        // third error check: parsing of the response has somehow gone boink.
323
        // NB: shall we omit this check, since we trust the parsing code?
324 11
        elseif ($returnType == 'xmlrpcvals' && !is_object($xmlRpcParser->_xh['value'])) {
325
            // something odd has happened
326
            // and it's time to generate a client side error
327
            // indicating something odd went on
328
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
329
                PhpXmlRpc::$xmlrpcstr['invalid_return']);
330
        } else {
331 11
            if ($this->debug > 1) {
332
                Logger::instance()->debugMessage(
333
                    "---PARSED---\n".var_export($xmlRpcParser->_xh['value'], true)."\n---END---"
334
                );
335
            }
336
337
            // note that using =& will raise an error if $xmlRpcParser->_xh['st'] does not generate an object.
338 11
            $v = &$xmlRpcParser->_xh['value'];
339
340 11
            if ($xmlRpcParser->_xh['isf']) {
341
                /// @todo we should test here if server sent an int and a string, and/or coerce them into such...
342 1
                if ($returnType == 'xmlrpcvals') {
343 1
                    $errNo_v = $v['faultCode'];
344 1
                    $errStr_v = $v['faultString'];
345 1
                    $errNo = $errNo_v->scalarval();
346 1
                    $errStr = $errStr_v->scalarval();
347
                } else {
348
                    $errNo = $v['faultCode'];
349
                    $errStr = $v['faultString'];
350
                }
351
352 1
                if ($errNo == 0) {
353
                    // FAULT returned, errno needs to reflect that
354
                    $errNo = -1;
355
                }
356
357 1
                $r = new Response(0, $errNo, $errStr);
358
            } else {
359 10
                $r = new Response($v, 0, '', $returnType);
360
            }
361
        }
362
363 14
        $r->hdrs = $this->httpResponse['headers'];
364 14
        $r->_cookies = $this->httpResponse['cookies'];
365 14
        $r->raw_data = $this->httpResponse['raw_data'];
366
367 14
        return $r;
368
    }
369
370
    /**
371
     * Kept the old name even if Request class was renamed, for compatibility.
372
     *
373
     * @return string
374
     */
375
    public function kindOf()
376
    {
377
        return 'msg';
378
    }
379
380
    /**
381
     * Enables/disables the echoing to screen of the xmlrpc responses received.
382
     *
383
     * @param integer $level values 0, 1, 2 are supported
384
     */
385 601
    public function setDebug($level)
386
    {
387 601
        $this->debug = $level;
388 601
    }
389
}
390