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

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

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

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

552
    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...
553
    {
554
        // skip processing if xml fault already detected
555 607
        if ($this->_xh['isf'] < 2) {
556
            // "lookforvalue==3" means that we've found an entire value
557
            // and should discard any further character data
558 607
            if ($this->_xh['lv'] != 3) {
559 607
                $this->_xh['ac'] .= $data;
560
            }
561
        }
562 607
    }
563
564
    /**
565
     * xml parser handler function for 'other stuff', ie. not char data or
566
     * element start/end tag. In fact it only gets called on unknown entities...
567
     * @internal
568
     * @param $parser
569
     * @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...
570
     */
571 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

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