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

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

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

525
    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...
526
    {
527
        // skip processing if xml fault already detected
528 607
        if ($this->_xh['isf'] < 2) {
529
            // "lookforvalue==3" means that we've found an entire value
530
            // and should discard any further character data
531 607
            if ($this->_xh['lv'] != 3) {
532 607
                $this->_xh['ac'] .= $data;
533
            }
534
        }
535 607
    }
536
537
    /**
538
     * xml parser handler function for 'other stuff', ie. not char data or
539
     * element start/end tag. In fact it only gets called on unknown entities...
540
     * @param $parser
541
     * @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...
542
     */
543 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

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