Passed
Push — master ( 5d18c7...8ff19d )
by Gaetano
09:55
created

XMLParser::xmlrpc_se()   F

Complexity

Conditions 45
Paths 916

Size

Total Lines 174
Code Lines 103

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 63
CRAP Score 76.6406

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 45
eloc 103
c 2
b 0
f 0
nc 916
nop 4
dl 0
loc 174
ccs 63
cts 84
cp 0.75
crap 76.6406
rs 0.0932

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\Traits\LoggerAware;
7
use PhpXmlRpc\Value;
8
9
/**
10
 * Deals with parsing the XML.
11
 * @see http://xmlrpc.com/spec.md
12
 *
13
 * @todo implement an interface to allow for alternative implementations
14
 *       - make access to $_xh protected, return more high-level data structures
15
 *       - move the private parts of $_xh to the internal-use parsing-options config
16
 *       - add parseRequest, parseResponse, parseValue methods
17
 * @todo if iconv() or mb_string() are available, we could allow to convert the received xml to a custom charset encoding
18
 *       while parsing, which is faster than doing it later by going over the rebuilt data structure
19
 * @todo rename? This is an xml-rpc parser, not a generic xml parser...
20
 */
21
class XMLParser
22
{
23
    use LoggerAware;
24
25
    const RETURN_XMLRPCVALS = 'xmlrpcvals';
26
    const RETURN_EPIVALS = 'epivals';
27
    const RETURN_PHP = 'phpvals';
28
29
    const ACCEPT_REQUEST = 1;
30
    const ACCEPT_RESPONSE = 2;
31
    const ACCEPT_VALUE = 4;
32
    const ACCEPT_FAULT = 8;
33
34
    /**
35
     * @var int
36
     * The max length beyond which data will get truncated in error messages
37
     */
38
    protected $maxLogValueLength = 100;
39
40
    /**
41
     * @var array
42
     * Used to store state during parsing and to pass parsing results to callers.
43
     * Quick explanation of components:
44
     *  private:
45
     *    ac - used to accumulate values
46
     *    stack - array with genealogy of xml elements names, used to validate nesting of xml-rpc elements
47
     *    valuestack - array used for parsing arrays and structs
48
     *    lv - used to indicate "looking for a value": implements the logic to allow values with no types to be strings
49
     *         (values: 0=not looking, 1=looking, 3=found)
50
     *  public:
51
     *    isf - used to indicate an xml-rpc response fault (1), invalid xml-rpc fault (2), xml parsing fault (3)
52
     *    isf_reason - used for storing xml-rpc response fault string
53
     *    value - used to store the value in responses
54
     *    method - used to store method name in requests
55
     *    params - used to store parameters in requests
56
     *    pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values
57
     *    rt - 'methodcall', 'methodresponse', 'value' or 'fault' (the last one used only in EPI emulation mode)
58
     */
59
    public $_xh = array(
60
        'ac' => '',
61
        'stack' => array(),
62
        'valuestack' => array(),
63
        'lv' => 0,
64
        'isf' => 0,
65
        'isf_reason' => '',
66
        'value' => null,
67
        'method' => false,
68
        'params' => array(),
69
        'pt' => array(),
70
        'rt' => '',
71
    );
72
73
    /**
74
     * @var array[]
75
     * @internal
76
     */
77
    public $xmlrpc_valid_parents = array(
78
        'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
79
        'BOOLEAN' => array('VALUE'),
80
        'I4' => array('VALUE'),
81
        'I8' => array('VALUE'),
82
        'EX:I8' => array('VALUE'),
83
        'INT' => array('VALUE'),
84
        'STRING' => array('VALUE'),
85
        'DOUBLE' => array('VALUE'),
86
        'DATETIME.ISO8601' => array('VALUE'),
87
        'BASE64' => array('VALUE'),
88
        'MEMBER' => array('STRUCT'),
89
        'NAME' => array('MEMBER'),
90
        'DATA' => array('ARRAY'),
91 572
        'ARRAY' => array('VALUE'),
92
        'STRUCT' => array('VALUE'),
93 572
        'PARAM' => array('PARAMS'),
94 572
        'METHODNAME' => array('METHODCALL'),
95
        'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
96
        'FAULT' => array('METHODRESPONSE'),
97
        'NIL' => array('VALUE'), // only used when extension activated
98
        'EX:NIL' => array('VALUE'), // only used when extension activated
99
    );
100
101
    /** @var array $parsing_options */
102 712
    protected $parsing_options = array();
103
104 712
    /** @var int $accept self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE by default */
105
    //protected $accept = 3;
106
107
    /** @var int $maxChunkLength 4 MB by default. Any value below 10MB should be good */
108
    protected $maxChunkLength = 4194304;
109
    /** @var array
110
     * Used keys: accept, target_charset, methodname_callback, plus the ones set here.
111
     * We initialize it partially to help keep BC with subclasses which might have reimplemented `parse()` but not
112
     * the element handler methods
113
     */
114
    protected $current_parsing_options = array(
115
        'xmlrpc_null_extension' => false,
116
        'xmlrpc_return_datetimes' => false,
117 712
        'xmlrpc_reject_invalid_values' => false
118
    );
119
120 712
    /**
121 2
     * @param array $options integer keys: options passed to the inner xml parser
122 2
     *                       string keys:
123 2
     *                       - target_charset (string)
124
     *                       - methodname_callback (callable)
125
     *                       - xmlrpc_null_extension (bool)
126 710
     *                       - xmlrpc_return_datetimes (bool)
127
     *                       - xmlrpc_reject_invalid_values (bool)
128 710
     */
129
    public function __construct(array $options = array())
130
    {
131 710
        $this->parsing_options = $options;
132 709
    }
133
134
    /**
135 710
     * Parses an xml-rpc xml string. Results of the parsing are found in $this->['_xh'].
136
     * Logs to the error log any issues which do not cause the parsing to fail.
137 710
     *
138
     * @param string $data
139
     * @param string $returnType self::RETURN_XMLRPCVALS, self::RETURN_PHP, self::RETURN_EPIVALS
140 710
     * @param int $accept a bit-combination of self::ACCEPT_REQUEST, self::ACCEPT_RESPONSE, self::ACCEPT_VALUE
141 27
     * @param array $options integer-key options are passed to the xml parser, string-key options are used independently.
142 27
     *                       These options are added to options received in the constructor.
143 708
     *                       Note that if options xmlrpc_null_extension, xmlrpc_return_datetimes and xmlrpc_reject_invalid_values
144
     *                       are not set, the default settings from PhpXmlRpc\PhpXmlRpc are used
145
     * @return void the caller has to look into $this->_xh to find the results
146
     * @throws \Exception this can happen if a callback function is set and it does throw (i.e. we do not catch exceptions)
147 708
     *
148
     * @todo refactor? we could 1. return the parsed data structure, and 2. move $returnType and $accept into options
149
     */
150 710
    public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3, $options = array())
151 710
    {
152
        $this->_xh = array(
153 710
            'ac' => '',
154
            'stack' => array(),
155
            'valuestack' => array(),
156 710
            'lv' => 0,
157 710
            'isf' => 0,
158
            'isf_reason' => '',
159 710
            'value' => null,
160 3
            'method' => false, // so we can check later if we got a methodname or not
161 3
            'params' => array(),
162 3
            'pt' => array(),
163
            'rt' => '',
164 3
        );
165 3
166 3
        $len = strlen($data);
167
168
        // we test for empty documents here to save on resource allocation and simply the chunked-parsing loop below
169
        if ($len == 0) {
170 710
            $this->_xh['isf'] = 3;
171 710
            $this->_xh['isf_reason'] = 'XML error 5: empty document';
172
            return;
173
        }
174
175
        //$prevAccept = $this->accept;
176
        //$this->accept = $accept;
177
        $this->current_parsing_options = array('accept' => $accept);
178
179
        $mergedOptions = $this->parsing_options;
180
        foreach ($options as $key => $val) {
181 710
            $mergedOptions[$key] = $val;
182
        }
183
184 710
        foreach ($mergedOptions as $key => $val) {
185
            // q: can php be built without ctype? should we use a regexp?
186
            if (is_string($key) && !ctype_digit($key)) {
187 710
                /// @todo on invalid options, throw/error-out instead of logging an error message?
188
                switch($key) {
189
                    case 'target_charset':
190
                        if (function_exists('mb_convert_encoding')) {
191
                            $this->current_parsing_options['target_charset'] = $val;
192 710
                        } else {
193 710
                            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": 'target_charset' option is unsupported without mbstring");
194
                        }
195
                        break;
196
197 710
                    case 'methodname_callback':
198 708
                        if (is_callable($val)) {
199 3
                            $this->current_parsing_options['methodname_callback'] = $val;
200 703
                        } else {
201 710
                            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": Callback passed as 'methodname_callback' is not callable");
202
                        }
203 2
                        break;
204 2
205
                    case 'xmlrpc_null_extension':
206 703
                    case 'xmlrpc_return_datetimes':
207
                    case 'xmlrpc_reject_invalid_values':
208
                        $this->current_parsing_options[$key] = $val;
209
                        break;
210 710
211 710
                    default:
212 2
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": unsupported option: $key");
213 2
                }
214
                unset($mergedOptions[$key]);
215 2
            }
216
        }
217
218
        if (!isset($this->current_parsing_options['xmlrpc_null_extension'])) {
219 710
            $this->current_parsing_options['xmlrpc_null_extension'] = PhpXmlRpc::$xmlrpc_null_extension;
220
        }
221 710
        if (!isset($this->current_parsing_options['xmlrpc_return_datetimes'])) {
222
            $this->current_parsing_options['xmlrpc_return_datetimes'] = PhpXmlRpc::$xmlrpc_return_datetimes;
223 708
        }
224 708
        if (!isset($this->current_parsing_options['xmlrpc_reject_invalid_values'])) {
225 708
            $this->current_parsing_options['xmlrpc_reject_invalid_values'] = PhpXmlRpc::$xmlrpc_reject_invalid_values;
226 708
        }
227 708
228 710
        // NB: we use '' instead of null to force charset detection from the xml declaration
229 710
        $parser = xml_parser_create('');
230 1
231
        foreach ($mergedOptions as $key => $val) {
232
            xml_parser_set_option($parser, $key, $val);
233
        }
234
235
        // always set this, in case someone tries to disable it via options...
236
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
237
238 710
        xml_set_object($parser, $this);
239 710
240 710
        switch ($returnType) {
241 710
            case self::RETURN_PHP:
242 710
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
243 710
                break;
244 710
            case self::RETURN_EPIVALS:
245 685
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi');
246
                break;
247 1
            /// @todo log an error / throw / error-out on unsupported return type
248 1
            case XMLParser::RETURN_XMLRPCVALS:
249
            default:
250 1
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
251
        }
252 685
253 685
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
254 710
        xml_set_default_handler($parser, 'xmlrpc_dh');
255 710
256 400
        try {
257
            // @see ticket #70 - we have to parse big xml docs in chunks to avoid errors
258 1
            for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
259 1
                $chunk = substr($data, $offset, $this->maxChunkLength);
260
                // error handling: xml not well formed
261 1
                if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) {
262
                    $errCode = xml_get_error_code($parser);
263
                    $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode),
264 399
                        xml_get_current_line_number($parser), xml_get_current_column_number($parser));
265 399
266 399
                    $this->_xh['isf'] = 3;
267
                    $this->_xh['isf_reason'] = $errStr;
268
                    break;
269 399
                }
270 22
                // no need to parse further if we already have a fatal error
271
                if ($this->_xh['isf'] >= 2) {
272 399
                    break;
273 399
                }
274 399
            }
275 710
        } catch (\Exception $e) {
276 239
            xml_parser_free($parser);
277
            $this->current_parsing_options = array();
278 1
            //$this->accept = $prevAccept;
279 1
            /// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ?
280
            throw $e;
281 1
        }
282
283 710
        xml_parser_free($parser);
284 710
        $this->current_parsing_options = array();
285 710
        //$this->accept = $prevAccept;
286
    }
287 710
288 710
    /**
289 710
     * xml parser handler function for opening element tags.
290
     * @internal
291 637
     *
292 637
     * @param resource $parser
293 710
     * @param string $name
294 109
     * @param $attrs
295 109
     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
296 710
     * @return void
297
     *
298 289
     * @todo optimization creep: throw when setting $this->_xh['isf'] > 1, to completely avoid further xml parsing
299
     *       and remove the checking for $this->_xh['isf'] >= 2 everywhere
300
     */
301 688
    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

301
    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...
302
    {
303 710
        // if invalid xml-rpc already detected, skip all processing
304 710
        if ($this->_xh['isf'] >= 2) {
305 23
            return;
306 23
        }
307 23
308 23
        // check for correct element nesting
309
        if (count($this->_xh['stack']) == 0) {
310
            // top level element can only be of 2 types
311
            /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
312
            ///       there is only a single top level element in xml anyway
313
314
            // BC
315 23
            if ($acceptSingleVals === false) {
316 23
                $accept = $this->current_parsing_options['accept'];
317
            } else {
318
                //trigger_error('using argument $acceptSingleVals is deprecated', E_USER_DEPRECATED);
319
                $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE;
320
            }
321
            if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) ||
322 1
                ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) ||
323 1
                ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE)) ||
324 1
                ($name == 'FAULT' && ($accept & self::ACCEPT_FAULT))) {
325
                $this->_xh['rt'] = strtolower($name);
326
            } else {
327
                $this->_xh['isf'] = 2;
328 710
                $this->_xh['isf_reason'] = 'missing top level xmlrpc element. Found: ' . $name;
329
330
                return;
331 710
            }
332 710
        } else {
333
            // not top level element: see if parent is OK
334
            $parent = end($this->_xh['stack']);
335 710
            if (!array_key_exists($name, $this->xmlrpc_valid_parents) || !in_array($parent, $this->xmlrpc_valid_parents[$name])) {
336
                $this->_xh['isf'] = 2;
337
                $this->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
338
339
                return;
340
            }
341
        }
342
343
        switch ($name) {
344
            // optimize for speed switch cases: most common cases first
345
            case 'VALUE':
346
                /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
347
                $this->_xh['vt'] = 'value'; // indicator: no value found yet
348
                $this->_xh['ac'] = '';
349
                $this->_xh['lv'] = 1;
350
                $this->_xh['php_class'] = null;
351
                break;
352
353
            case 'I8':
354
            case 'EX:I8':
355
                if (PHP_INT_SIZE === 4) {
356
                    // INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
357 710
                    $this->_xh['isf'] = 2;
358
                    $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode";
359 710
360
                    return;
361
                }
362
                // fall through voluntarily
363
364 709
            case 'I4':
365
            case 'INT':
366 709
            case 'STRING':
367 709
            case 'BOOLEAN':
368
            case 'DOUBLE':
369 707
            case 'DATETIME.ISO8601':
370 30
            case 'BASE64':
371 30
                if ($this->_xh['vt'] != 'value') {
372
                    // two data elements inside a value: an error occurred!
373
                    $this->_xh['isf'] = 2;
374 707
                    $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
375
376 705
                    return;
377
                }
378
                $this->_xh['ac'] = ''; // reset the accumulator
379 705
                break;
380 22
381
            case 'STRUCT':
382 705
            case 'ARRAY':
383 27
                if ($this->_xh['vt'] != 'value') {
384
                    // two data elements inside a value: an error occurred!
385
                    $this->_xh['isf'] = 2;
386
                    $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
387
388
                    return;
389
                }
390
                // create an empty array to hold child values, and push it onto appropriate stack
391
                $curVal = array(
392
                    'values' => array(),
393
                    'type' => $name,
394
                );
395
                // check for out-of-band information to rebuild php objs and, in case it is found, save it
396
                if (@isset($attrs['PHP_CLASS'])) {
397
                    $curVal['php_class'] = $attrs['PHP_CLASS'];
398
                }
399
                $this->_xh['valuestack'][] = $curVal;
400
                $this->_xh['vt'] = 'data'; // be prepared for a data element next
401
                break;
402
403
            case 'DATA':
404
                if ($this->_xh['vt'] != 'data') {
405
                    // two data elements inside a value: an error occurred!
406 707
                    $this->_xh['isf'] = 2;
407 707
                    $this->_xh['isf_reason'] = "found two data elements inside an array element";
408 239
409
                    return;
410 707
                }
411 709
412 709
            case 'METHODCALL':
413 709
            case 'METHODRESPONSE':
414 709
            case 'PARAMS':
415 709
                // valid elements that add little to processing
416 709
                break;
417 708
418 708
            case 'METHODNAME':
419 708
            case 'NAME':
420 685
                /// @todo we could check for 2 NAME elements inside a MEMBER element
421
                $this->_xh['ac'] = '';
422
                break;
423 685
424 594
            case 'FAULT':
425 477
                $this->_xh['isf'] = 1;
426 7
                break;
427
428
            case 'MEMBER':
429 7
                // set member name to null, in case we do not find in the xml later on
430 7
                $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = null;
431 472
                //$this->_xh['ac']='';
432
                // Drop trough intentionally
433 22
434 451
            case 'PARAM':
435
                // clear value type, so we can check later if no value has been passed for this param/member
436
                $this->_xh['vt'] = null;
437
                break;
438
439
            case 'NIL':
440
            case 'EX:NIL':
441 46
                if ($this->current_parsing_options['xmlrpc_null_extension']) {
442 46
                    if ($this->_xh['vt'] != 'value') {
443
                        // two data elements inside a value: an error occurred!
444
                        $this->_xh['isf'] = 2;
445 24
                        $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
446
447
                        return;
448 46
                    }
449
                    // reset the accumulator - q: is this necessary at all here? we don't use it on _ee anyway for NILs
450 408
                    $this->_xh['ac'] = '';
451
452
                } else {
453
                    $this->_xh['isf'] = 2;
454 25
                    $this->_xh['isf_reason'] = 'Invalid NIL value received. Support for NIL can be enabled via \\PhpXmlRpc\\PhpXmlRpc::$xmlrpc_null_extension';
455
456
                    return;
457
                }
458
                break;
459
460 25
            default:
461
                // INVALID ELEMENT: RAISE ISF so that it is later recognized
462
                /// @todo feature creep = allow a callback instead
463
                $this->_xh['isf'] = 2;
464
                $this->_xh['isf_reason'] = "found not-xmlrpc xml element $name";
465 387
466
                return;
467
        }
468
469
        // Save current element name to stack, to validate nesting
470
        $this->_xh['stack'][] = $name;
471 387
472
        /// @todo optimization creep: move this inside the big switch() above
473
        if ($name != 'VALUE') {
474 685
            $this->_xh['lv'] = 0;
475 685
        }
476 708
    }
477 289
478 289
    /**
479 708
     * xml parser handler function for opening element tags.
480
     * Used in decoding xml chunks that might represent single xml-rpc values as well as requests, responses.
481
     * @deprecated
482 289
     *
483 268
     * @param resource $parser
484 268
     * @param $name
485
     * @param $attrs
486 22
     * @return void
487
     */
488 289
    public function xmlrpc_se_any($parser, $name, $attrs)
489 708
    {
490 239
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
491 239
492 707
        $this->xmlrpc_se($parser, $name, $attrs, true);
493 707
    }
494
495 398
    /**
496 398
     * xml parser handler function for close element tags.
497 398
     * @internal
498 398
     *
499 22
     * @param resource $parser
500
     * @param string $name
501 398
     * @param int $rebuildXmlrpcvals >1 for rebuilding xmlrpcvals, 0 for rebuilding php values, -1 for xmlrpc-extension compatibility
502 707
     * @return void
503
     * @throws \Exception this can happen if a callback function is set and it does throw (i.e. we do not catch exceptions)
504
     *
505 684
     * @todo optimization creep: throw when setting $this->_xh['isf'] > 1, to completely avoid further xml parsing
506 684
     *       and remove the checking for $this->_xh['isf'] >= 2 everywhere
507 684
     */
508
    public function xmlrpc_ee($parser, $name, $rebuildXmlrpcvals = 1)
509
    {
510
        if ($this->_xh['isf'] >= 2) {
511 684
            return;
512 707
        }
513 562
514 562
        // push this element name from stack
515 706
        // NB: if XML validates, correct opening/closing is guaranteed and we do not have to check for $name == $currElem.
516 706
        // we also checked for proper nesting at start of elements...
517 23
        $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...
518 23
519 23
        switch ($name) {
520 23
            case 'VALUE':
521 23
                // If no scalar was inside <VALUE></VALUE>, it was a string value
522
                if ($this->_xh['vt'] == 'value') {
523
                    $this->_xh['value'] = $this->_xh['ac'];
524 706
                    $this->_xh['vt'] = Value::$xmlrpcString;
525 706
                }
526 706
527 705
                // in case there is charset conversion required, do it here, to catch both cases of string values
528 706
                if (isset($this->current_parsing_options['target_charset']) && $this->_xh['vt'] === Value::$xmlrpcString) {
529
                    $this->_xh['value'] = mb_convert_encoding($this->_xh['value'], $this->current_parsing_options['target_charset'], 'UTF-8');
530
                }
531
532 705
                if ($rebuildXmlrpcvals > 0) {
533
                    // build the xml-rpc val out of the data received, and substitute it
534
                    $temp = new Value($this->_xh['value'], $this->_xh['vt']);
535 710
                    // in case we got info about underlying php class, save it in the object we're rebuilding
536
                    if (isset($this->_xh['php_class'])) {
537
                        $temp->_php_class = $this->_xh['php_class'];
538
                    }
539
                    $this->_xh['value'] = $temp;
540
                } elseif ($rebuildXmlrpcvals < 0) {
541
                    if ($this->_xh['vt'] == Value::$xmlrpcDateTime) {
542
                        $this->_xh['value'] = (object)array(
543 27
                            'xmlrpc_type' => 'datetime',
544
                            'scalar' => $this->_xh['value'],
545 27
                            'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($this->_xh['value'])
546 27
                        );
547
                    } elseif ($this->_xh['vt'] == Value::$xmlrpcBase64) {
548
                        $this->_xh['value'] = (object)array(
549
                            'xmlrpc_type' => 'base64',
550
                            'scalar' => $this->_xh['value']
551
                        );
552
                    }
553
                } else {
554
                    /// @todo this should handle php-serialized objects, since std deserializing is done
555
                    ///       by php_xmlrpc_decode, which we will not be calling...
556
                    //if (isset($this->_xh['php_class'])) {
557
                    //}
558
                }
559
560
                // check if we are inside an array or struct:
561
                // if value just built is inside an array, let's move it into array on the stack
562
                $vscount = count($this->_xh['valuestack']);
563
                if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
564
                    $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value'];
565 710
                }
566
                break;
567
568 710
            case 'STRING':
569
                $this->_xh['vt'] = Value::$xmlrpcString;
570
                $this->_xh['lv'] = 3; // indicate we've found a value
571 710
                $this->_xh['value'] = $this->_xh['ac'];
572 710
                break;
573
574
            case 'BOOLEAN':
575 710
                $this->_xh['vt'] = Value::$xmlrpcBoolean;
576
                $this->_xh['lv'] = 3; // indicate we've found a value
577
                // We translate boolean 1 or 0 into PHP constants true or false. Strings 'true' and 'false' are accepted,
578
                // even though the spec never mentions them (see e.g. Blogger api docs)
579
                // NB: this simple checks helps a lot sanitizing input, i.e. no security problems around here
580
                // Note the non-strict type check: it will allow ' 1 '
581
                /// @todo feature-creep: use a flexible regexp, the same as we do with int, double and datetime.
582
                ///       Note that using a regexp would also make this test less sensitive to phpunit shenanigans, and
583
                ///       to changes in the way php compares strings (since 8.0, leading and trailing newlines are
584 696
                ///       accepted when deciding if a string numeric...)
585
                if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') === 0) {
586
                    $this->_xh['value'] = true;
587 696
                } else {
588 696
                    // log if receiving something strange, even though we set the value to false anyway
589
                    /// @todo to be consistent with the other types, we should return a value outside the good-value domain, e.g. NULL
590
                    if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') !== 0) {
591
                        if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
592
                            $this->_xh['isf'] = 2;
593
                            $this->_xh['isf_reason'] = 'Invalid data received in BOOLEAN value: ' . $this->truncateValueForLog($this->_xh['ac']);
594 696
                            return;
595
                        } else {
596
                            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': invalid data received in BOOLEAN value: ' .
597
                                $this->truncateValueForLog($this->_xh['ac']));
598
                        }
599
                    }
600
                    $this->_xh['value'] = false;
601
                }
602
                break;
603
604
            case 'EX:I8':
605
                $name = 'i8';
606
                // fall through voluntarily
607
            case 'I4':
608
            case 'I8':
609
            case 'INT':
610
                // NB: we build the Value object with the original xml element name found, except for ex:i8. The
611
                // `Value::scalartyp()` function will do some normalization of the data
612
                $this->_xh['vt'] = strtolower($name);
613
                $this->_xh['lv'] = 3; // indicate we've found a value
614
                if (!preg_match(PhpXmlRpc::$xmlrpc_int_format, $this->_xh['ac'])) {
615
                    if ($this->current_parsing_options['xmlrpc_reject_invalid_values'])
616
                    {
617 711
                        $this->_xh['isf'] = 2;
618
                        $this->_xh['isf_reason'] = 'Non numeric data received in INT value: ' . $this->truncateValueForLog($this->_xh['ac']);
619
                        return;
620
                    } else {
621
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': non numeric data received in INT: ' .
622
                            $this->truncateValueForLog($this->_xh['ac']));
623
                    }
624
                    /// @todo: find a better way of reporting an error value than this! Use NaN?
625
                    $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
626
                } else {
627
                    // it's ok, add it on
628
                    $this->_xh['value'] = (int)$this->_xh['ac'];
629
                }
630
                break;
631
632
            case 'DOUBLE':
633
                $this->_xh['vt'] = Value::$xmlrpcDouble;
634 711
                $this->_xh['lv'] = 3; // indicate we've found a value
635 711
                if (!preg_match(PhpXmlRpc::$xmlrpc_double_format, $this->_xh['ac'])) {
636 695
                    if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
637
                        $this->_xh['isf'] = 2;
638
                        $this->_xh['isf_reason'] = 'Non numeric data received in DOUBLE value: ' .
639
                            $this->truncateValueForLog($this->_xh['ac']);
640
                        return;
641
                    } else {
642
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': non numeric data received in DOUBLE value: ' .
643
                            $this->truncateValueForLog($this->_xh['ac']));
644
                    }
645
646 529
                    $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
647
                } else {
648 529
                    // it's ok, add it on
649
                    $this->_xh['value'] = (double)$this->_xh['ac'];
650 529
                }
651
                break;
652
653
            case 'DATETIME.ISO8601':
654
                $this->_xh['vt'] = Value::$xmlrpcDateTime;
655
                $this->_xh['lv'] = 3; // indicate we've found a value
656
                if (!preg_match(PhpXmlRpc::$xmlrpc_datetime_format, $this->_xh['ac'])) {
657
                    if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
658 529
                        $this->_xh['isf'] = 2;
659 529
                        $this->_xh['isf_reason'] = 'Invalid data received in DATETIME value: ' . $this->truncateValueForLog($this->_xh['ac']);
660
                        return;
661 24
                    } else {
662
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': invalid data received in DATETIME value: ' .
663
                            $this->truncateValueForLog($this->_xh['ac']));
664
                    }
665 506
                }
666 506
                if ($this->current_parsing_options['xmlrpc_return_datetimes']) {
667 4
                    try {
668
                        $this->_xh['value'] = new \DateTime($this->_xh['ac']);
669 506
                    } catch(\Exception $e) {
670 4
                        // q: what to do? we can not guarantee that a valid date can be created. Return null or throw?
671
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': ' . $e->getMessage());
672 502
                        $this->_xh['value'] = null;
673
                    }
674
                } else {
675
                    $this->_xh['value'] = $this->_xh['ac'];
676 506
                }
677 499
                break;
678
679
            case 'BASE64':
680 506
                $this->_xh['vt'] = Value::$xmlrpcBase64;
681
                $this->_xh['lv'] = 3; // indicate we've found a value
682
                if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
683
                    $v = base64_decode($this->_xh['ac'], true);
684
                    if ($v === false) {
685
                        $this->_xh['isf'] = 2;
686
                        $this->_xh['isf_reason'] = 'Invalid data received in BASE64 value: '. $this->truncateValueForLog($this->_xh['ac']);
687
                        return;
688
                    }
689
                } else {
690
                    $v = base64_decode($this->_xh['ac']);
691
                    if ($v === '' && $this->_xh['ac'] !== '') {
692
                        // only the empty string should decode to the empty string
693
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': invalid data received in BASE64 value: ' .
694
                            $this->truncateValueForLog($this->_xh['ac']));
695
                    }
696 82
                }
697
                $this->_xh['value'] = $v;
698
                break;
699
700 82
            case 'NAME':
701
                $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = $this->_xh['ac'];
702 82
                break;
703
704 82
            case 'MEMBER':
705
                // add to array in the stack the last element built, unless no VALUE or no NAME were found
706
                if ($this->_xh['vt']) {
707
                    $vscount = count($this->_xh['valuestack']);
708
                    if ($this->_xh['valuestack'][$vscount - 1]['name'] === null) {
709
                        if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
710
                            $this->_xh['isf'] = 2;
711
                            $this->_xh['isf_reason'] = 'Missing NAME inside STRUCT in received xml';
712 82
                            return;
713 82
                        } else {
714
                            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': missing NAME inside STRUCT in received xml');
715 78
                        }
716
                        $this->_xh['valuestack'][$vscount - 1]['name'] = '';
717
                    }
718 5
                    $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value'];
719
                } else {
720
                    if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
721
                        $this->_xh['isf'] = 2;
722
                        $this->_xh['isf_reason'] = 'Missing VALUE inside STRUCT in received xml';
723
                        return;
724
                    } else {
725
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml');
726
                    }
727
                }
728
                break;
729
730
            case 'DATA':
731
                $this->_xh['vt'] = null; // reset this to check for 2 data elements in a row - even if they're empty
732
                break;
733
734
            case 'STRUCT':
735
            case 'ARRAY':
736
                // fetch out of stack array of values, and promote it to current value
737
                $currVal = array_pop($this->_xh['valuestack']);
738
                $this->_xh['value'] = $currVal['values'];
739
                $this->_xh['vt'] = strtolower($name);
740
                if (isset($currVal['php_class'])) {
741
                    $this->_xh['php_class'] = $currVal['php_class'];
742
                }
743
                break;
744
745
            case 'PARAM':
746
                // add to array of params the current value, unless no VALUE was found
747
                /// @todo should we also check if there were two VALUE inside the PARAM?
748
                if ($this->_xh['vt']) {
749
                    $this->_xh['params'][] = $this->_xh['value'];
750
                    $this->_xh['pt'][] = $this->_xh['vt'];
751
                } else {
752
                    if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
753
                        $this->_xh['isf'] = 2;
754
                        $this->_xh['isf_reason'] = 'Missing VALUE inside PARAM in received xml';
755
                        return;
756
                    } else {
757
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml');
758
                    }
759
                }
760
                break;
761
762
            case 'METHODNAME':
763
                if (!preg_match(PhpXmlRpc::$xmlrpc_methodname_format, $this->_xh['ac'])) {
764
                    if ($this->current_parsing_options['xmlrpc_reject_invalid_values']) {
765
                        $this->_xh['isf'] = 2;
766
                        $this->_xh['isf_reason'] = 'Invalid data received in METHODNAME: '. $this->truncateValueForLog($this->_xh['ac']);
767
                        return;
768
                    } else {
769
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': invalid data received in METHODNAME: '.
770
                            $this->truncateValueForLog($this->_xh['ac']));
771
                    }
772
                }
773
                $methodName = trim($this->_xh['ac']);
774
                $this->_xh['method'] = $methodName;
775
                // we allow the callback to f.e. give us back a mangled method name by manipulating $this
776
                if (isset($this->current_parsing_options['methodname_callback'])) {
777
                    call_user_func($this->current_parsing_options['methodname_callback'], $methodName, $this, $parser);
778
                }
779
                break;
780
781
            case 'NIL':
782
            case 'EX:NIL':
783
                // NB: if NIL support is not enabled, parsing stops at element start. So this If is redundant
784
                //if ($this->current_parsing_options['xmlrpc_null_extension']) {
785
                    $this->_xh['vt'] = 'null';
786
                    $this->_xh['value'] = null;
787
                    $this->_xh['lv'] = 3;
788
                //}
789
                break;
790
791
            /// @todo add extra checking:
792
            ///       - METHODRESPONSE should contain either a PARAMS with a single PARAM, or a FAULT
793
            ///       - FAULT should contain a single struct with the 2 expected members (check their name and type)
794
            ///       - METHODCALL should contain a methodname
795
            case 'PARAMS':
796
            case 'FAULT':
797
            case 'METHODCALL':
798
            case 'METHODRESPONSE':
799
                break;
800
801
            default:
802
                // End of INVALID ELEMENT
803
                // Should we add an assert here for unreachable code? When an invalid element is found in xmlrpc_se,
804
                // $this->_xh['isf'] is set to 2...
805
                break;
806
        }
807
    }
808
809
    /**
810
     * Used in decoding xml-rpc requests/responses without rebuilding xml-rpc Values.
811
     * @internal
812
     *
813
     * @param resource $parser
814
     * @param string $name
815
     * @return void
816
     */
817
    public function xmlrpc_ee_fast($parser, $name)
818
    {
819
        $this->xmlrpc_ee($parser, $name, 0);
820
    }
821
822
    /**
823
     * Used in decoding xml-rpc requests/responses while building xmlrpc-extension Values (plain php for all but base64 and datetime).
824
     * @internal
825
     *
826
     * @param resource $parser
827
     * @param string $name
828
     * @return void
829
     */
830
    public function xmlrpc_ee_epi($parser, $name)
831
    {
832
        $this->xmlrpc_ee($parser, $name, -1);
833
    }
834
835
    /**
836
     * xml parser handler function for character data.
837
     * @internal
838
     *
839
     * @param resource $parser
840
     * @param string $data
841
     * @return void
842
     */
843
    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

843
    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...
844
    {
845
        // skip processing if xml fault already detected
846
        if ($this->_xh['isf'] >= 2) {
847
            return;
848
        }
849
850
        // "lookforvalue == 3" means that we've found an entire value and should discard any further character data
851
        if ($this->_xh['lv'] != 3) {
852
            $this->_xh['ac'] .= $data;
853
        }
854
    }
855
856
    /**
857
     * xml parser handler function for 'other stuff', i.e. not char data or element start/end tag.
858
     * In fact it only gets called on unknown entities...
859
     * @internal
860
     *
861
     * @param $parser
862
     * @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...
863
     * @return void
864
     */
865
    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

865
    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...
866
    {
867
        // skip processing if xml fault already detected
868
        if ($this->_xh['isf'] >= 2) {
869
            return;
870
        }
871
872
        if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') {
873
            $this->_xh['ac'] .= $data;
874
        }
875
    }
876
877
    /**
878
     * xml charset encoding guessing helper function.
879
     * Tries to determine the charset encoding of an XML chunk received over HTTP.
880
     * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
881
     * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of non-conforming (legacy?) clients/servers,
882
     * which will be most probably using UTF-8 anyway...
883
     * In order of importance checks:
884
     * 1. http headers
885
     * 2. BOM
886
     * 3. XML declaration
887
     * 4. guesses using mb_detect_encoding()
888
     *
889
     * @param string $httpHeader the http Content-type header
890
     * @param string $xmlChunk xml content buffer
891
     * @param string $encodingPrefs comma separated list of character encodings to be used as default (when mb extension is enabled).
892
     *                              This can also be set globally using PhpXmlRpc::$xmlrpc_detectencodings
893
     * @return string the encoding determined. Null if it can't be determined and mbstring is enabled,
894
     *                PhpXmlRpc::$xmlrpc_defencoding if it can't be determined and mbstring is not enabled
895
     *
896
     * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
897
     */
898
    public static function guessEncoding($httpHeader = '', $xmlChunk = '', $encodingPrefs = null)
899
    {
900
        // discussion: see http://www.yale.edu/pclt/encoding/
901
        // 1 - test if encoding is specified in HTTP HEADERS
902
903
        // Details:
904
        // LWS:           (\13\10)?( |\t)+
905
        // token:         (any char but excluded stuff)+
906
        // quoted string: " (any char but double quotes and control chars)* "
907
        // header:        Content-type = ...; charset=value(; ...)*
908
        //   where value is of type token, no LWS allowed between 'charset' and value
909
        // Note: we do not check for invalid chars in VALUE:
910
        //   this had better be done using pure ereg as below
911
        // Note 2: we might be removing whitespace/tabs that ought to be left in if
912
        //   the received charset is a quoted string. But nobody uses such charset names...
913
914
        /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
915
        $matches = array();
916
        if (preg_match('/;\s*charset\s*=([^;]+)/i', $httpHeader, $matches)) {
917
            return strtoupper(trim($matches[1], " \t\""));
918
        }
919
920
        // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
921
        //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
922
        //     NOTE: actually, according to the spec, even if we find the BOM and determine
923
        //     an encoding, we should check if there is an encoding specified
924
        //     in the xml declaration, and verify if they match.
925
        /// @todo implement check as described above?
926
        /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
927
        if (preg_match('/^(?:\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlChunk)) {
928
            return 'UCS-4';
929
        } elseif (preg_match('/^(?:\xFE\xFF|\xFF\xFE)/', $xmlChunk)) {
930
            return 'UTF-16';
931
        } elseif (preg_match('/^(?:\xEF\xBB\xBF)/', $xmlChunk)) {
932
            return 'UTF-8';
933
        }
934
935
        // 3 - test if encoding is specified in the xml declaration
936
        /// @todo this regexp will fail if $xmlChunk uses UTF-32/UCS-4, and most likely UTF-16/UCS-2 as well. In that
937
        ///       case we leave the guesswork up to mbstring - which seems to be able to detect it, starting with php 5.6.
938
        ///       For lower versions, we could attempt usage of mb_ereg...
939
        // Details:
940
        // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
941
        // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
942
        if (preg_match('/^<\?xml\s+version\s*=\s*' . "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))" .
943
            '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
944
            $xmlChunk, $matches)) {
945
            return strtoupper(substr($matches[2], 1, -1));
946
        }
947
948
        // 4 - if mbstring is available, let it do the guesswork
949
        if (function_exists('mb_detect_encoding')) {
950
            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...
951
                $encodingPrefs = PhpXmlRpc::$xmlrpc_detectencodings;
952
            }
953
            if ($encodingPrefs) {
954
                $enc = mb_detect_encoding($xmlChunk, $encodingPrefs);
955
            } else {
956
                $enc = mb_detect_encoding($xmlChunk);
957
            }
958
            // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
959
            // IANA also likes better US-ASCII, so go with it
960
            if ($enc == 'ASCII') {
961
                $enc = 'US-' . $enc;
962
            }
963
964
            return $enc;
965
        } else {
966
            // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
967
            // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
968
            // this should be the standard. And we should be getting text/xml as request and response.
969
            // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
970
            return PhpXmlRpc::$xmlrpc_defencoding;
971
        }
972
    }
973
974
    /**
975
     * Helper function: checks if an xml chunk has a charset declaration (BOM or in the xml declaration).
976
     *
977
     * @param string $xmlChunk
978
     * @return bool
979
     *
980
     * @todo rename to hasEncodingDeclaration
981
     */
982
    public static function hasEncoding($xmlChunk)
983
    {
984
        // scan the first bytes of the data for a UTF-16 (or other) BOM pattern
985
        //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
986
        if (preg_match('/^(?:\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlChunk)) {
987
            return true;
988
        } elseif (preg_match('/^(?:\xFE\xFF|\xFF\xFE)/', $xmlChunk)) {
989
            return true;
990
        } elseif (preg_match('/^(?:\xEF\xBB\xBF)/', $xmlChunk)) {
991
            return true;
992
        }
993
994
        // test if encoding is specified in the xml declaration
995
        // Details:
996
        // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
997
        // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
998
        if (preg_match('/^<\?xml\s+version\s*=\s*' . "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))" .
999
            '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
1000
            $xmlChunk)) {
1001
            return true;
1002
        }
1003
1004
        return false;
1005
    }
1006
1007
    /**
1008
     * Truncates unsafe data
1009
     * @param string $data
1010
     * @return string
1011
     */
1012
    protected function truncateValueForLog($data)
1013
    {
1014
        if (strlen($data) > $this->maxLogValueLength) {
1015
            return substr($data, 0, $this->maxLogValueLength - 3) . '...';
1016
        }
1017
1018
        return $data;
1019
    }
1020
1021
    // BC layer
1022
1023
    public function __set($name, $value)
1024
    {
1025
        //trigger_error('setting property Response::' . $name . ' is deprecated', E_USER_DEPRECATED);
1026
1027
        switch ($name) {
1028
            // this should only ever be called by subclasses which overtook `parse()`
1029
            case 'accept':
1030
                $this->current_parsing_options['accept'] = $value;
1031
                break;
1032
            default:
1033
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
1034
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1035
        }
1036
    }
1037
1038
    public function __isset($name)
1039
    {
1040
        //trigger_error('checking property Response::' . $name . ' is deprecated', E_USER_DEPRECATED);
1041
1042
        switch ($name) {
1043
            case 'accept':
1044
                return isset($this->current_parsing_options['accept']);
1045
            default:
1046
                return false;
1047
        }
1048
    }
1049
1050
    public function __unset($name)
1051
    {
1052
        switch ($name) {
1053
            // q: does this make sense at all?
1054
            case 'accept':
1055
                unset($this->current_parsing_options['accept']);
1056
                break;
1057
            default:
1058
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
1059
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1060
        }
1061
    }
1062
}
1063