Completed
Push — master ( 252344...1eeaee )
by Gaetano
04:02 queued 11s
created

Request::xml_footer()   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 505
            $this->payload .= "<param>\n" . $p->serialize($charsetEncoding) .
65 505
                "</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 488
    public function method($methodName = '')
79
    {
80 488
        if ($methodName != '') {
81
            $this->methodname = $methodName;
82
        }
83
84 488
        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 549
    public function addParam($param)
111
    {
112
        // check: do not add to self params which are not xmlrpc values
113 549
        if (is_object($param) && is_a($param, 'PhpXmlRpc\Value')) {
114 549
            $this->params[] = $param;
115
116 549
            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 448
    public function getParam($i)
130
    {
131 448
        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 467
    public function getNumParams()
140
    {
141 467
        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
     * @param bool $headersProcessed
154
     * @param string $returnType
155
     *
156
     * @return Response
157
     */
158
    public function parseResponseFile($fp, $headersProcessed = false, $returnType = 'xmlrpcvals')
159
    {
160
        $ipd = '';
161
        while ($data = fread($fp, 32768)) {
162
            $ipd .= $data;
163
        }
164
        return $this->parseResponse($ipd, $headersProcessed, $returnType);
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 600
    public function parseResponse($data = '', $headersProcessed = false, $returnType = 'xmlrpcvals')
181
    {
182 600
        if ($this->debug) {
183
            Logger::instance()->debugMessage("---GOT---\n$data\n---END---");
184
        }
185
186 600
        $this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array());
187
188 600 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 600
        if (substr($data, 0, 4) == 'HTTP') {
195 588
            $httpParser = new Http();
196
            try {
197 588
                $this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug);
198 1
            } catch(\Exception $e) {
199 1
                $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 1
                $r->raw_data = $data;
203
204 1
                return $r;
205
            }
206
        }
207
208
        // be tolerant of extra whitespace in response body
209 599
        $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 599
        $pos = strrpos($data, '</methodResponse>');
216 599
        if ($pos !== false) {
217 599
            $data = substr($data, 0, $pos + 17);
218
        }
219
220
        // try to 'guestimate' the character encoding of the received response
221 599
        $respEncoding = XMLParser::guessEncoding(@$this->httpResponse['headers']['content-type'], $data);
222
223 599
        if ($this->debug) {
224
            $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
225
            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 599
        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 598 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 598
            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
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
265
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
266
        // This allows to send data which is native in various charset,
267
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
268 598 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...
269
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
270
        } else {
271 598
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
272
        }
273
274 598
        $xmlRpcParser = new XMLParser($options);
275 598
        $xmlRpcParser->parse($data, $returnType, XMLParser::ACCEPT_RESPONSE);
276
277
        // first error check: xml not well formed
278 598
        if ($xmlRpcParser->_xh['isf'] > 2) {
279
280
            // BC break: in the past for some cases we used the error message: 'XML error at line 1, check URL'
281
282 3
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
283 3
                PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
284
285 3
            if ($this->debug) {
286 3
                print $xmlRpcParser->_xh['isf_reason'];
287
            }
288
        }
289
        // second error check: xml well formed but not xml-rpc compliant
290 598 View Code Duplication
        elseif ($xmlRpcParser->_xh['isf'] == 2) {
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...
291 4
            if ($this->debug) {
292
                /// @todo echo something for user?
293
            }
294
295 4
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
296 4
                PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
297
        }
298
        // third error check: parsing of the response has somehow gone boink.
299
        // NB: shall we omit this check, since we trust the parsing code?
300 595
        elseif ($returnType == 'xmlrpcvals' && !is_object($xmlRpcParser->_xh['value'])) {
301
            // something odd has happened
302
            // and it's time to generate a client side error
303
            // indicating something odd went on
304
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
305
                PhpXmlRpc::$xmlrpcstr['invalid_return']);
306
        } else {
307 595
            if ($this->debug > 1) {
308
                Logger::instance()->debugMessage(
309
                    "---PARSED---\n".var_export($xmlRpcParser->_xh['value'], true)."\n---END---"
310
                );
311
            }
312
313 595
            $v = $xmlRpcParser->_xh['value'];
314
315 595
            if ($xmlRpcParser->_xh['isf']) {
316
                /// @todo we should test here if server sent an int and a string, and/or coerce them into such...
317 80
                if ($returnType == 'xmlrpcvals') {
318 80
                    $errNo_v = $v['faultCode'];
319 80
                    $errStr_v = $v['faultString'];
320 80
                    $errNo = $errNo_v->scalarval();
321 80
                    $errStr = $errStr_v->scalarval();
322
                } else {
323
                    $errNo = $v['faultCode'];
324
                    $errStr = $v['faultString'];
325
                }
326
327 80
                if ($errNo == 0) {
328
                    // FAULT returned, errno needs to reflect that
329
                    $errNo = -1;
330
                }
331
332 80
                $r = new Response(0, $errNo, $errStr);
333
            } else {
334 592
                $r = new Response($v, 0, '', $returnType);
335
            }
336
        }
337
338 598
        $r->hdrs = $this->httpResponse['headers'];
339 598
        $r->_cookies = $this->httpResponse['cookies'];
340 598
        $r->raw_data = $this->httpResponse['raw_data'];
341
342 598
        return $r;
343
    }
344
345
    /**
346
     * Kept the old name even if Request class was renamed, for compatibility.
347
     *
348
     * @return string
349
     */
350 117
    public function kindOf()
351
    {
352 117
        return 'msg';
353
    }
354
355
    /**
356
     * Enables/disables the echoing to screen of the xmlrpc responses received.
357
     *
358
     * @param integer $level values 0, 1, 2 are supported
359
     */
360 601
    public function setDebug($level)
361
    {
362 601
        $this->debug = $level;
363 601
    }
364
}
365