Completed
Push — master ( 87b7a4...6ce28d )
by Gaetano
11:11 queued 06:38
created

XMLParser::guessEncoding()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 70
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 11.6363

Importance

Changes 0
Metric Value
cc 11
eloc 25
c 0
b 0
f 0
nc 14
nop 3
dl 0
loc 70
rs 7.3166
ccs 19
cts 23
cp 0.8261
crap 11.6363

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 601
    public function __construct(array $options = array())
81
    {
82 601
        $this->parsing_options = $options;
83 601
    }
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 601
    public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3)
92
    {
93 601
        $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 601
        $len = strlen($data);
106
107
        // we test for empty documents here to save on resource allocation and simply the chunked-parsing loop below
108 601
        if ($len == 0) {
109
            $this->_xh['isf'] = 3;
110
            $this->_xh['isf_reason'] = 'XML error 5: empty document';
111
            return;
112
        }
113
114 601
        $parser = xml_parser_create();
115
116 601
        foreach ($this->parsing_options as $key => $val) {
117 600
            xml_parser_set_option($parser, $key, $val);
118
        }
119
        // always set this, in case someone tries to disable it via options...
120 601
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
121
122 601
        xml_set_object($parser, $this);
123
124 601
        if ($returnType == self::RETURN_PHP) {
125 23
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
126
        } else {
127 600
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
128
        }
129
130 601
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
131 601
        xml_set_default_handler($parser, 'xmlrpc_dh');
132
133 601
        $this->accept = $accept;
134
135
        // @see ticket #70 - we have to parse big xml docks in chunks to avoid errors
136 601
        for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
137 601
            $chunk = substr($data, $offset, $this->maxChunkLength);
138
            // error handling: xml not well formed
139 601
            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 601
        xml_parser_free($parser);
151 601
    }
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 601
    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 601
        if ($this->_xh['isf'] < 2) {
164
165
            // check for correct element nesting
166 601
            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 601
                if ($acceptSingleVals === false) {
172 601
                    $accept = $this->accept;
173
                } else {
174
                    $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE;
175
                }
176 601
                if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) ||
177 599
                    ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) ||
178 601
                    ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE))) {
179 601
                    $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 601
                $parent = end($this->_xh['stack']);
189 601
                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 601
            switch ($name) {
198
                // optimize for speed switch cases: most common cases first
199 601
                case 'VALUE':
200
                    /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
201 599
                    $this->_xh['vt'] = 'value'; // indicator: no value found yet
202 599
                    $this->_xh['ac'] = '';
203 599
                    $this->_xh['lv'] = 1;
204 599
                    $this->_xh['php_class'] = null;
205 599
                    break;
206 601
                case 'I8':
207 601
                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 601
                case 'I4':
217 601
                case 'INT':
218 601
                case 'STRING':
219 601
                case 'BOOLEAN':
220 601
                case 'DOUBLE':
221 601
                case 'DATETIME.ISO8601':
222 601
                case 'BASE64':
223 578
                    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 578
                    $this->_xh['ac'] = ''; // reset the accumulator
231 578
                    break;
232 601
                case 'STRUCT':
233 601
                case 'ARRAY':
234 340
                    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 339
                    $curVal = array();
243 339
                    $curVal['values'] = array();
244 339
                    $curVal['type'] = $name;
245
                    // check for out-of-band information to rebuild php objs
246
                    // and in case it is found, save it
247 339
                    if (@isset($attrs['PHP_CLASS'])) {
248 20
                        $curVal['php_class'] = $attrs['PHP_CLASS'];
249
                    }
250 339
                    $this->_xh['valuestack'][] = $curVal;
251 339
                    $this->_xh['vt'] = 'data'; // be prepared for a data element next
252 339
                    break;
253 601
                case 'DATA':
254 213
                    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 601
                case 'METHODCALL':
262 601
                case 'METHODRESPONSE':
263 601
                case 'PARAMS':
264
                    // valid elements that add little to processing
265 601
                    break;
266 601
                case 'METHODNAME':
267 601
                case 'NAME':
268
                    /// @todo we could check for 2 NAME elements inside a MEMBER element
269 539
                    $this->_xh['ac'] = '';
270 539
                    break;
271 601
                case 'FAULT':
272 80
                    $this->_xh['isf'] = 1;
273 80
                    break;
274 601
                case 'MEMBER':
275
                    // set member name to null, in case we do not find in the xml later on
276 242
                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = '';
277
                    //$this->_xh['ac']='';
278
                // Drop trough intentionally
279 600
                case 'PARAM':
280
                    // clear value type, so we can check later if no value has been passed for this param/member
281 601
                    $this->_xh['vt'] = null;
282 601
                    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 601
            $this->_xh['stack'][] = $name;
307
308
            /// @todo optimization creep: move this inside the big switch() above
309 601
            if ($name != 'VALUE') {
310 601
                $this->_xh['lv'] = 0;
311
            }
312
        }
313 601
    }
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 601
    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 601
        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 600
            $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 600
            switch ($name) {
344 600
                case 'VALUE':
345
                    // This if() detects if no scalar was inside <VALUE></VALUE>
346 598
                    if ($this->_xh['vt'] == 'value') {
347 28
                        $this->_xh['value'] = $this->_xh['ac'];
348 28
                        $this->_xh['vt'] = Value::$xmlrpcString;
349
                    }
350
351 598
                    if ($rebuildXmlrpcvals) {
352
                        // build the xmlrpc val out of the data received, and substitute it
353 597
                        $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 597
                        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 597
                        $vscount = count($this->_xh['valuestack']);
362 597
                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
363 213
                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $temp;
364
                        } else {
365 597
                            $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 23
                        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 23
                        $vscount = count($this->_xh['valuestack']);
377 23
                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
378 20
                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value'];
379
                        }
380
                    }
381 598
                    break;
382 600
                case 'BOOLEAN':
383 600
                case 'I4':
384 600
                case 'I8':
385 600
                case 'EX:I8':
386 600
                case 'INT':
387 600
                case 'STRING':
388 599
                case 'DOUBLE':
389 599
                case 'DATETIME.ISO8601':
390 599
                case 'BASE64':
391 578
                    $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 578
                    if ($name == 'STRING') {
395 495
                        $this->_xh['value'] = $this->_xh['ac'];
396 393
                    } 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 388
                    } elseif ($name == 'BASE64') {
403
                        /// @todo check for failure of base64 decoding / catch warnings
404 20
                        $this->_xh['value'] = base64_decode($this->_xh['ac']);
405 369
                    } 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 330
                    } 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 311
                        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 311
                            $this->_xh['value'] = (int)$this->_xh['ac'];
443
                        }
444
                    }
445 578
                    $this->_xh['lv'] = 3; // indicate we've found a value
446 578
                    break;
447 599
                case 'NAME':
448 242
                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = $this->_xh['ac'];
449 242
                    break;
450 599
                case 'MEMBER':
451
                    // add to array in the stack the last element built,
452
                    // unless no VALUE was found
453 242
                    if ($this->_xh['vt']) {
454 223
                        $vscount = count($this->_xh['valuestack']);
455 223
                        $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 242
                    break;
460 599
                case 'DATA':
461 213
                    $this->_xh['vt'] = null; // reset this to check for 2 data elements in a row - even if they're empty
462 213
                    break;
463 598
                case 'STRUCT':
464 598
                case 'ARRAY':
465
                    // fetch out of stack array of values, and promote it to current value
466 338
                    $currVal = array_pop($this->_xh['valuestack']);
467 338
                    $this->_xh['value'] = $currVal['values'];
468 338
                    $this->_xh['vt'] = strtolower($name);
469 338
                    if (isset($currVal['php_class'])) {
470 20
                        $this->_xh['php_class'] = $currVal['php_class'];
471
                    }
472 338
                    break;
473 598
                case 'PARAM':
474
                    // add to array of params the current value,
475
                    // unless no VALUE was found
476 596
                    if ($this->_xh['vt']) {
477 596
                        $this->_xh['params'][] = $this->_xh['value'];
478 596
                        $this->_xh['pt'][] = $this->_xh['vt'];
479
                    } else {
480
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml');
481
                    }
482 596
                    break;
483 598
                case 'METHODNAME':
484 491
                    $this->_xh['method'] = preg_replace('/^[\n\r\t ]+/', '', $this->_xh['ac']);
485 491
                    break;
486 597
                case 'NIL':
487 597
                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 597
                case 'PARAMS':
496 597
                case 'FAULT':
497 597
                case 'METHODCALL':
498 596
                case 'METHORESPONSE':
499 597
                    break;
500
                default:
501
                    // End of INVALID ELEMENT!
502
                    // shall we add an assert here for unreachable code???
503 596
                    break;
504
            }
505
        }
506 601
    }
507
508
    /**
509
     * Used in decoding xmlrpc requests/responses without rebuilding xmlrpc Values.
510
     * @param resource $parser
511
     * @param string $name
512
     */
513 23
    public function xmlrpc_ee_fast($parser, $name)
514
    {
515 23
        $this->xmlrpc_ee($parser, $name, false);
516 23
    }
517
518
    /**
519
     * xml parser handler function for character data.
520
     * @param resource $parser
521
     * @param string $data
522
     */
523 601
    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 601
        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 601
            if ($this->_xh['lv'] != 3) {
530 601
                $this->_xh['ac'] .= $data;
531
            }
532
        }
533 601
    }
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 587
    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 587
        if ($this->_xh['isf'] < 2) {
545 587
            if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') {
546
                $this->_xh['ac'] .= $data;
547
            }
548
        }
549
550
        //return true;
551 587
    }
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 600
    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 600
        $matches = array();
592 600
        if (preg_match('/;\s*charset\s*=([^;]+)/i', $httpHeader, $matches)) {
593 586
            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 458
        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 458
        } elseif (preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlChunk)) {
606
            return 'UTF-16';
607 458
        } 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 458
        if (preg_match('/^<\?xml\s+version\s*=\s*' . "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))" .
616 458
            '\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 437
        if (extension_loaded('mbstring')) {
623 437
            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 437
            if ($encodingPrefs) {
627 4
                $enc = mb_detect_encoding($xmlChunk, $encodingPrefs);
628
            } else {
629 433
                $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 437
            if ($enc == 'ASCII') {
634 430
                $enc = 'US-' . $enc;
635
            }
636
637 437
            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