Passed
Push — master ( 67ed62...5da465 )
by Gaetano
02:56
created

XMLParser::xmlrpc_cd()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 3
c 2
b 0
f 0
nc 3
nop 2
dl 0
loc 8
rs 10
ccs 4
cts 4
cp 1
crap 3
1
<?php
2
3
namespace PhpXmlRpc\Helper;
4
5
use PhpXmlRpc\PhpXmlRpc;
6
use PhpXmlRpc\Value;
7
8
/**
9
 * Deals with parsing the XML.
10
 */
11
class XMLParser
12
{
13
    const RETURN_XMLRPCVALS = 'xmlrpcvals';
14
    const RETURN_PHP = 'phpvals';
15
16
    const ACCEPT_REQUEST = 1;
17
    const ACCEPT_RESPONSE = 2;
18
    const ACCEPT_VALUE = 4;
19
20
    // Used to store state during parsing.
21
    // Quick explanation of components:
22
    //  private:
23
    //   ac - used to accumulate values
24
    //   stack - array with genealogy of xml elements names used to validate nesting of xmlrpc elements
25
    //   valuestack - array used for parsing arrays and structs
26
    //   lv - used to indicate "looking for a value": implements the logic to allow values with no types to be strings
27
    //  public:
28
    //   isf - used to indicate an xml parsing fault (3), invalid xmlrpc fault (2) or xmlrpc response fault (1)
29
    //   isf_reason - used for storing xmlrpc response fault string
30
    //   method - used to store method name
31
    //   params - used to store parameters in method calls
32
    //   pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values
33
    //   rt  - 'methodcall', 'methodresponse' or 'value'
34
    public $_xh = array(
35
        'ac' => '',
36
        'stack' => array(),
37
        'valuestack' => array(),
38
        'isf' => 0,
39
        'isf_reason' => '',
40
        'method' => false,
41
        'params' => array(),
42
        'pt' => array(),
43
        'rt' => '',
44
    );
45
46
    public $xmlrpc_valid_parents = array(
47
        'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
48
        'BOOLEAN' => array('VALUE'),
49
        'I4' => array('VALUE'),
50
        'I8' => array('VALUE'),
51
        'EX:I8' => array('VALUE'),
52
        'INT' => array('VALUE'),
53
        'STRING' => array('VALUE'),
54
        'DOUBLE' => array('VALUE'),
55
        'DATETIME.ISO8601' => array('VALUE'),
56
        'BASE64' => array('VALUE'),
57
        'MEMBER' => array('STRUCT'),
58
        'NAME' => array('MEMBER'),
59
        'DATA' => array('ARRAY'),
60
        'ARRAY' => array('VALUE'),
61
        'STRUCT' => array('VALUE'),
62
        'PARAM' => array('PARAMS'),
63
        'METHODNAME' => array('METHODCALL'),
64
        'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
65
        'FAULT' => array('METHODRESPONSE'),
66
        'NIL' => array('VALUE'), // only used when extension activated
67
        'EX:NIL' => array('VALUE'), // only used when extension activated
68
    );
69
70
    /** @var array $parsing_options */
71
    protected $parsing_options = array();
72
    /** @var int $accept self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE by default */
73
    protected $accept = 3;
74
    /** @var int $maxChunkLength 4 MB by default. Any value below 10MB should be good */
75
    protected $maxChunkLength = 4194304;
76
77
    /**
78
     * @param array $options passed to the xml parser
79
     */
80 609
    public function __construct(array $options = array())
81
    {
82 609
        $this->parsing_options = $options;
83 609
    }
84
85
    /**
86
     * @param string $data
87
     * @param string $returnType
88
     * @param int $accept a bit-combination of self::ACCEPT_REQUEST, self::ACCEPT_RESPONSE, self::ACCEPT_VALUE
89
     * @return string
90
     */
91 609
    public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3)
92
    {
93 609
        $this->_xh = array(
94
            'ac' => '',
95
            'stack' => array(),
96
            'valuestack' => array(),
97
            'isf' => 0,
98
            'isf_reason' => '',
99
            'method' => false, // so we can check later if we got a methodname or not
100
            'params' => array(),
101
            'pt' => array(),
102
            'rt' => '',
103
        );
104
105 609
        $len = strlen($data);
106
107
        // we test for empty documents here to save on resource allocation and simply the chunked-parsing loop below
108 609
        if ($len == 0) {
109 2
            $this->_xh['isf'] = 3;
110 2
            $this->_xh['isf_reason'] = 'XML error 5: empty document';
111 2
            return;
112
        }
113
114 607
        $parser = xml_parser_create();
115
116 607
        foreach ($this->parsing_options as $key => $val) {
117 606
            xml_parser_set_option($parser, $key, $val);
118
        }
119
        // always set this, in case someone tries to disable it via options...
120 607
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
121
122 607
        xml_set_object($parser, $this);
123
124 607
        if ($returnType == self::RETURN_PHP) {
125 25
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
126
        } else {
127 605
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
128
        }
129
130 607
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
131 607
        xml_set_default_handler($parser, 'xmlrpc_dh');
132
133 607
        $this->accept = $accept;
134
135
        // @see ticket #70 - we have to parse big xml docks in chunks to avoid errors
136 607
        for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
137 607
            $chunk = substr($data, $offset, $this->maxChunkLength);
138
            // error handling: xml not well formed
139 607
            if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) {
140 3
                $errCode = xml_get_error_code($parser);
141 3
                $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode),
142 3
                    xml_get_current_line_number($parser), xml_get_current_column_number($parser));
143
144 3
                $this->_xh['isf'] = 3;
145 3
                $this->_xh['isf_reason'] = $errStr;
146 3
                break;
147
            }
148
        }
149
150 607
        xml_parser_free($parser);
151 607
    }
152
153
    /**
154
     * xml parser handler function for opening element tags.
155
     * @param resource $parser
156
     * @param string $name
157
     * @param $attrs
158
     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
159
     */
160 607
    public function xmlrpc_se($parser, $name, $attrs, $acceptSingleVals = false)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed. ( Ignorable by Annotation )

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

160
    public function xmlrpc_se(/** @scrutinizer ignore-unused */ $parser, $name, $attrs, $acceptSingleVals = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
161
    {
162
        // if invalid xmlrpc already detected, skip all processing
163 607
        if ($this->_xh['isf'] < 2) {
164
165
            // check for correct element nesting
166 607
            if (count($this->_xh['stack']) == 0) {
167
                // top level element can only be of 2 types
168
                /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
169
                ///       there is only a single top level element in xml anyway
170
                // BC
171 607
                if ($acceptSingleVals === false) {
172 607
                    $accept = $this->accept;
173
                } else {
174
                    $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE;
175
                }
176 607
                if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) ||
177 605
                    ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) ||
178 601
                    ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE))) {
179 607
                    $this->_xh['rt'] = strtolower($name);
180
                } else {
181 2
                    $this->_xh['isf'] = 2;
182 2
                    $this->_xh['isf_reason'] = 'missing top level xmlrpc element. Found: ' . $name;
183
184 601
                    return;
185
                }
186
            } else {
187
                // not top level element: see if parent is OK
188 607
                $parent = end($this->_xh['stack']);
189 607
                if (!array_key_exists($name, $this->xmlrpc_valid_parents) || !in_array($parent, $this->xmlrpc_valid_parents[$name])) {
190 2
                    $this->_xh['isf'] = 2;
191 2
                    $this->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
192
193 2
                    return;
194
                }
195
            }
196
197 607
            switch ($name) {
198
                // optimize for speed switch cases: most common cases first
199 607
                case 'VALUE':
200
                    /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
201 605
                    $this->_xh['vt'] = 'value'; // indicator: no value found yet
202 605
                    $this->_xh['ac'] = '';
203 605
                    $this->_xh['lv'] = 1;
204 605
                    $this->_xh['php_class'] = null;
205 605
                    break;
206 607
                case 'I8':
207 607
                case 'EX:I8':
208 1
                    if (PHP_INT_SIZE === 4) {
209
                        // INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
210
                        $this->_xh['isf'] = 2;
211
                        $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode";
212
213
                        return;
214
                    }
215
                // fall through voluntarily
216 607
                case 'I4':
217 607
                case 'INT':
218 607
                case 'STRING':
219 607
                case 'BOOLEAN':
220 607
                case 'DOUBLE':
221 607
                case 'DATETIME.ISO8601':
222 607
                case 'BASE64':
223 584
                    if ($this->_xh['vt'] != 'value') {
224
                        // two data elements inside a value: an error occurred!
225 1
                        $this->_xh['isf'] = 2;
226 1
                        $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
227
228 1
                        return;
229
                    }
230 584
                    $this->_xh['ac'] = ''; // reset the accumulator
231 584
                    break;
232 607
                case 'STRUCT':
233 607
                case 'ARRAY':
234 344
                    if ($this->_xh['vt'] != 'value') {
235
                        // two data elements inside a value: an error occurred!
236 1
                        $this->_xh['isf'] = 2;
237 1
                        $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
238
239 1
                        return;
240
                    }
241
                    // create an empty array to hold child values, and push it onto appropriate stack
242 343
                    $curVal = array();
243 343
                    $curVal['values'] = array();
244 343
                    $curVal['type'] = $name;
245
                    // check for out-of-band information to rebuild php objs
246
                    // and in case it is found, save it
247 343
                    if (@isset($attrs['PHP_CLASS'])) {
248 20
                        $curVal['php_class'] = $attrs['PHP_CLASS'];
249
                    }
250 343
                    $this->_xh['valuestack'][] = $curVal;
251 343
                    $this->_xh['vt'] = 'data'; // be prepared for a data element next
252 343
                    break;
253 607
                case 'DATA':
254 216
                    if ($this->_xh['vt'] != 'data') {
255
                        // two data elements inside a value: an error occurred!
256 1
                        $this->_xh['isf'] = 2;
257 1
                        $this->_xh['isf_reason'] = "found two data elements inside an array element";
258
259 1
                        return;
260
                    }
261 607
                case 'METHODCALL':
262 607
                case 'METHODRESPONSE':
263 607
                case 'PARAMS':
264
                    // valid elements that add little to processing
265 607
                    break;
266 607
                case 'METHODNAME':
267 607
                case 'NAME':
268
                    /// @todo we could check for 2 NAME elements inside a MEMBER element
269 541
                    $this->_xh['ac'] = '';
270 541
                    break;
271 607
                case 'FAULT':
272 80
                    $this->_xh['isf'] = 1;
273 80
                    break;
274 607
                case 'MEMBER':
275
                    // set member name to null, in case we do not find in the xml later on
276 244
                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = '';
277
                    //$this->_xh['ac']='';
278
                // Drop trough intentionally
279 606
                case 'PARAM':
280
                    // clear value type, so we can check later if no value has been passed for this param/member
281 607
                    $this->_xh['vt'] = null;
282 607
                    break;
283 21
                case 'NIL':
284 21
                case 'EX:NIL':
285 21
                    if (PhpXmlRpc::$xmlrpc_null_extension) {
286 21
                        if ($this->_xh['vt'] != 'value') {
287
                            // two data elements inside a value: an error occurred!
288
                            $this->_xh['isf'] = 2;
289
                            $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
290
291
                            return;
292
                        }
293 21
                        $this->_xh['ac'] = ''; // reset the accumulator
294 21
                        break;
295
                    }
296
                // we do not support the <NIL/> extension, so
297
                // drop through intentionally
298
                default:
299
                    // INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
300 1
                    $this->_xh['isf'] = 2;
301 1
                    $this->_xh['isf_reason'] = "found not-xmlrpc xml element $name";
302 1
                    break;
303
            }
304
305
            // Save current element name to stack, to validate nesting
306 607
            $this->_xh['stack'][] = $name;
307
308
            /// @todo optimization creep: move this inside the big switch() above
309 607
            if ($name != 'VALUE') {
310 607
                $this->_xh['lv'] = 0;
311
            }
312
        }
313 607
    }
314
315
    /**
316
     * xml parser handler function for opening element tags.
317
     * Used in decoding xml chunks that might represent single xmlrpc values as well as requests, responses.
318
     * @deprecated
319
     * @param resource $parser
320
     * @param $name
321
     * @param $attrs
322
     */
323
    public function xmlrpc_se_any($parser, $name, $attrs)
324
    {
325
        $this->xmlrpc_se($parser, $name, $attrs, true);
326
    }
327
328
    /**
329
     * xml parser handler function for close element tags.
330
     * @param resource $parser
331
     * @param string $name
332
     * @param bool $rebuildXmlrpcvals
333
     */
334 607
    public function xmlrpc_ee($parser, $name, $rebuildXmlrpcvals = true)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed. ( Ignorable by Annotation )

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

334
    public function xmlrpc_ee(/** @scrutinizer ignore-unused */ $parser, $name, $rebuildXmlrpcvals = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
335
    {
336 607
        if ($this->_xh['isf'] < 2) {
337
            // push this element name from stack
338
            // NB: if XML validates, correct opening/closing is guaranteed and
339
            // we do not have to check for $name == $currElem.
340
            // we also checked for proper nesting at start of elements...
341 606
            $currElem = array_pop($this->_xh['stack']);
0 ignored issues
show
Unused Code introduced by
The assignment to $currElem is dead and can be removed.
Loading history...
342
343 606
            switch ($name) {
344 606
                case 'VALUE':
345
                    // This if() detects if no scalar was inside <VALUE></VALUE>
346 604
                    if ($this->_xh['vt'] == 'value') {
347 28
                        $this->_xh['value'] = $this->_xh['ac'];
348 28
                        $this->_xh['vt'] = Value::$xmlrpcString;
349
                    }
350
351 604
                    if ($rebuildXmlrpcvals) {
352
                        // build the xmlrpc val out of the data received, and substitute it
353 602
                        $temp = new Value($this->_xh['value'], $this->_xh['vt']);
354
                        // in case we got info about underlying php class, save it
355
                        // in the object we're rebuilding
356 602
                        if (isset($this->_xh['php_class'])) {
357 20
                            $temp->_php_class = $this->_xh['php_class'];
358
                        }
359
                        // check if we are inside an array or struct:
360
                        // if value just built is inside an array, let's move it into array on the stack
361 602
                        $vscount = count($this->_xh['valuestack']);
362 602
                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
363 215
                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $temp;
364
                        } else {
365 602
                            $this->_xh['value'] = $temp;
366
                        }
367
                    } else {
368
                        /// @todo this needs to treat correctly php-serialized objects,
369
                        /// since std deserializing is done by php_xmlrpc_decode,
370
                        /// which we will not be calling...
371 25
                        if (isset($this->_xh['php_class'])) {
372
                        }
373
374
                        // check if we are inside an array or struct:
375
                        // if value just built is inside an array, let's move it into array on the stack
376 25
                        $vscount = count($this->_xh['valuestack']);
377 25
                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
378 21
                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value'];
379
                        }
380
                    }
381 604
                    break;
382 606
                case 'BOOLEAN':
383 606
                case 'I4':
384 606
                case 'I8':
385 606
                case 'EX:I8':
386 606
                case 'INT':
387 606
                case 'STRING':
388 605
                case 'DOUBLE':
389 605
                case 'DATETIME.ISO8601':
390 605
                case 'BASE64':
391 584
                    $this->_xh['vt'] = strtolower($name);
392
                    /// @todo: optimization creep - remove the if/elseif cycle below
393
                    /// since the case() in which we are already did that
394 584
                    if ($name == 'STRING') {
395 501
                        $this->_xh['value'] = $this->_xh['ac'];
396 394
                    } elseif ($name == 'DATETIME.ISO8601') {
397 6
                        if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $this->_xh['ac'])) {
398
                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in DATETIME: ' . $this->_xh['ac']);
399
                        }
400 6
                        $this->_xh['vt'] = Value::$xmlrpcDateTime;
401 6
                        $this->_xh['value'] = $this->_xh['ac'];
402 389
                    } elseif ($name == 'BASE64') {
403
                        /// @todo check for failure of base64 decoding / catch warnings
404 20
                        $this->_xh['value'] = base64_decode($this->_xh['ac']);
405 370
                    } elseif ($name == 'BOOLEAN') {
406
                        // special case here: we translate boolean 1 or 0 into PHP
407
                        // constants true or false.
408
                        // Strings 'true' and 'false' are accepted, even though the
409
                        // spec never mentions them (see eg. Blogger api docs)
410
                        // NB: this simple checks helps a lot sanitizing input, ie no
411
                        // security problems around here
412 41
                        if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') == 0) {
413 41
                            $this->_xh['value'] = true;
414
                        } else {
415
                            // log if receiving something strange, even though we set the value to false anyway
416 21
                            if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') != 0) {
417
                                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in BOOLEAN: ' . $this->_xh['ac']);
418
                            }
419 41
                            $this->_xh['value'] = false;
420
                        }
421 331
                    } elseif ($name == 'DOUBLE') {
422
                        // we have a DOUBLE
423
                        // we must check that only 0123456789-.<space> are characters here
424
                        // NOTE: regexp could be much stricter than this...
425 22
                        if (!preg_match('/^[+-eE0123456789 \t.]+$/', $this->_xh['ac'])) {
426
                            /// @todo: find a better way of throwing an error than this!
427
                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in DOUBLE: ' . $this->_xh['ac']);
428
                            $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
429
                        } else {
430
                            // it's ok, add it on
431 22
                            $this->_xh['value'] = (double)$this->_xh['ac'];
432
                        }
433
                    } else {
434
                        // we have an I4/I8/INT
435
                        // we must check that only 0123456789-<space> are characters here
436 312
                        if (!preg_match('/^[+-]?[0123456789 \t]+$/', $this->_xh['ac'])) {
437
                            /// @todo find a better way of throwing an error than this!
438
                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in INT: ' . $this->_xh['ac']);
439
                            $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
440
                        } else {
441
                            // it's ok, add it on
442 312
                            $this->_xh['value'] = (int)$this->_xh['ac'];
443
                        }
444
                    }
445 584
                    $this->_xh['lv'] = 3; // indicate we've found a value
446 584
                    break;
447 605
                case 'NAME':
448 244
                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = $this->_xh['ac'];
449 244
                    break;
450 605
                case 'MEMBER':
451
                    // add to array in the stack the last element built,
452
                    // unless no VALUE was found
453 244
                    if ($this->_xh['vt']) {
454 225
                        $vscount = count($this->_xh['valuestack']);
455 225
                        $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value'];
456
                    } else {
457 20
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml');
458
                    }
459 244
                    break;
460 605
                case 'DATA':
461 216
                    $this->_xh['vt'] = null; // reset this to check for 2 data elements in a row - even if they're empty
462 216
                    break;
463 604
                case 'STRUCT':
464 604
                case 'ARRAY':
465
                    // fetch out of stack array of values, and promote it to current value
466 342
                    $currVal = array_pop($this->_xh['valuestack']);
467 342
                    $this->_xh['value'] = $currVal['values'];
468 342
                    $this->_xh['vt'] = strtolower($name);
469 342
                    if (isset($currVal['php_class'])) {
470 20
                        $this->_xh['php_class'] = $currVal['php_class'];
471
                    }
472 342
                    break;
473 604
                case 'PARAM':
474
                    // add to array of params the current value,
475
                    // unless no VALUE was found
476 602
                    if ($this->_xh['vt']) {
477 602
                        $this->_xh['params'][] = $this->_xh['value'];
478 602
                        $this->_xh['pt'][] = $this->_xh['vt'];
479
                    } else {
480
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml');
481
                    }
482 602
                    break;
483 604
                case 'METHODNAME':
484 491
                    $this->_xh['method'] = preg_replace('/^[\n\r\t ]+/', '', $this->_xh['ac']);
485 491
                    break;
486 603
                case 'NIL':
487 603
                case 'EX:NIL':
488 21
                    if (PhpXmlRpc::$xmlrpc_null_extension) {
489 21
                        $this->_xh['vt'] = 'null';
490 21
                        $this->_xh['value'] = null;
491 21
                        $this->_xh['lv'] = 3;
492 21
                        break;
493
                    }
494
                // drop through intentionally if nil extension not enabled
495 603
                case 'PARAMS':
496 603
                case 'FAULT':
497 603
                case 'METHODCALL':
498 602
                case 'METHORESPONSE':
499 603
                    break;
500
                default:
501
                    // End of INVALID ELEMENT!
502
                    // shall we add an assert here for unreachable code???
503 602
                    break;
504
            }
505
        }
506 607
    }
507
508
    /**
509
     * Used in decoding xmlrpc requests/responses without rebuilding xmlrpc Values.
510
     * @param resource $parser
511
     * @param string $name
512
     */
513 25
    public function xmlrpc_ee_fast($parser, $name)
514
    {
515 25
        $this->xmlrpc_ee($parser, $name, false);
516 25
    }
517
518
    /**
519
     * xml parser handler function for character data.
520
     * @param resource $parser
521
     * @param string $data
522
     */
523 607
    public function xmlrpc_cd($parser, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed. ( Ignorable by Annotation )

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

523
    public function xmlrpc_cd(/** @scrutinizer ignore-unused */ $parser, $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
524
    {
525
        // skip processing if xml fault already detected
526 607
        if ($this->_xh['isf'] < 2) {
527
            // "lookforvalue==3" means that we've found an entire value
528
            // and should discard any further character data
529 607
            if ($this->_xh['lv'] != 3) {
530 607
                $this->_xh['ac'] .= $data;
531
            }
532
        }
533 607
    }
534
535
    /**
536
     * xml parser handler function for 'other stuff', ie. not char data or
537
     * element start/end tag. In fact it only gets called on unknown entities...
538
     * @param $parser
539
     * @param string data
0 ignored issues
show
Bug introduced by
The type PhpXmlRpc\Helper\data was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
540
     */
541 593
    public function xmlrpc_dh($parser, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed. ( Ignorable by Annotation )

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

541
    public function xmlrpc_dh(/** @scrutinizer ignore-unused */ $parser, $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
542
    {
543
        // skip processing if xml fault already detected
544 593
        if ($this->_xh['isf'] < 2) {
545 593
            if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') {
546
                $this->_xh['ac'] .= $data;
547
            }
548
        }
549
550
        //return true;
551 593
    }
552
553
    /**
554
     * xml charset encoding guessing helper function.
555
     * Tries to determine the charset encoding of an XML chunk received over HTTP.
556
     * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
557
     * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of non conforming (legacy?) clients/servers,
558
     * which will be most probably using UTF-8 anyway...
559
     * In order of importance checks:
560
     * 1. http headers
561
     * 2. BOM
562
     * 3. XML declaration
563
     * 4. guesses using mb_detect_encoding()
564
     *
565
     * @param string $httpHeader the http Content-type header
566
     * @param string $xmlChunk xml content buffer
567
     * @param string $encodingPrefs comma separated list of character encodings to be used as default (when mb extension is enabled).
568
     *                              This can also be set globally using PhpXmlRpc::$xmlrpc_detectencodings
569
     * @return string the encoding determined. Null if it can't be determined and mbstring is enabled,
570
     *                PhpXmlRpc::$xmlrpc_defencoding if it can't be determined and mbstring is not enabled
571
     *
572
     * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
573
     */
574 608
    public static function guessEncoding($httpHeader = '', $xmlChunk = '', $encodingPrefs = null)
575
    {
576
        // discussion: see http://www.yale.edu/pclt/encoding/
577
        // 1 - test if encoding is specified in HTTP HEADERS
578
579
        // Details:
580
        // LWS:           (\13\10)?( |\t)+
581
        // token:         (any char but excluded stuff)+
582
        // quoted string: " (any char but double quotes and control chars)* "
583
        // header:        Content-type = ...; charset=value(; ...)*
584
        //   where value is of type token, no LWS allowed between 'charset' and value
585
        // Note: we do not check for invalid chars in VALUE:
586
        //   this had better be done using pure ereg as below
587
        // Note 2: we might be removing whitespace/tabs that ought to be left in if
588
        //   the received charset is a quoted string. But nobody uses such charset names...
589
590
        /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
591 608
        $matches = array();
592 608
        if (preg_match('/;\s*charset\s*=([^;]+)/i', $httpHeader, $matches)) {
593 592
            return strtoupper(trim($matches[1], " \t\""));
594
        }
595
596
        // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
597
        //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
598
        //     NOTE: actually, according to the spec, even if we find the BOM and determine
599
        //     an encoding, we should check if there is an encoding specified
600
        //     in the xml declaration, and verify if they match.
601
        /// @todo implement check as described above?
602
        /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
603 460
        if (preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlChunk)) {
604
            return 'UCS-4';
605 460
        } elseif (preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlChunk)) {
606
            return 'UTF-16';
607 460
        } elseif (preg_match('/^(\xEF\xBB\xBF)/', $xmlChunk)) {
608
            return 'UTF-8';
609
        }
610
611
        // 3 - test if encoding is specified in the xml declaration
612
        // Details:
613
        // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
614
        // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
615 460
        if (preg_match('/^<\?xml\s+version\s*=\s*' . "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))" .
616 460
            '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
617
            $xmlChunk, $matches)) {
618 22
            return strtoupper(substr($matches[2], 1, -1));
619
        }
620
621
        // 4 - if mbstring is available, let it do the guesswork
622 439
        if (extension_loaded('mbstring')) {
623 439
            if ($encodingPrefs == null && PhpXmlRpc::$xmlrpc_detectencodings != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $encodingPrefs of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
624 4
                $encodingPrefs = PhpXmlRpc::$xmlrpc_detectencodings;
625
            }
626 439
            if ($encodingPrefs) {
627 4
                $enc = mb_detect_encoding($xmlChunk, $encodingPrefs);
628
            } else {
629 435
                $enc = mb_detect_encoding($xmlChunk);
630
            }
631
            // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
632
            // IANA also likes better US-ASCII, so go with it
633 439
            if ($enc == 'ASCII') {
634 432
                $enc = 'US-' . $enc;
635
            }
636
637 439
            return $enc;
638
        } else {
639
            // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
640
            // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
641
            // this should be the standard. And we should be getting text/xml as request and response.
642
            // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
643
            return PhpXmlRpc::$xmlrpc_defencoding;
644
        }
645
    }
646
647
    /**
648
     * Helper function: checks if an xml chunk as a charset declaration (BOM or in the xml declaration)
649
     *
650
     * @param string $xmlChunk
651
     * @return bool
652
     */
653 77
    public static function hasEncoding($xmlChunk)
654
    {
655
        // scan the first bytes of the data for a UTF-16 (or other) BOM pattern
656
        //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
657 77
        if (preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlChunk)) {
658
            return true;
659 77
        } elseif (preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlChunk)) {
660
            return true;
661 77
        } elseif (preg_match('/^(\xEF\xBB\xBF)/', $xmlChunk)) {
662
            return true;
663
        }
664
665
        // test if encoding is specified in the xml declaration
666
        // Details:
667
        // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
668
        // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
669 77
        if (preg_match('/^<\?xml\s+version\s*=\s*' . "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))" .
670 77
            '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
671
            $xmlChunk, $matches)) {
672 73
            return true;
673
        }
674
675 5
        return false;
676
    }
677
}
678