Passed
Push — master ( 442034...57812a )
by Gaetano
02:50
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
     * @param resource $parser
163
     * @param string $name
164
     * @param $attrs
165
     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
166
     */
167 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

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

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

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

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