Passed
Push — master ( 02b7f4...c1cbf5 )
by Gaetano
07:18
created

XMLParser::xmlrpc_ee()   F

Complexity

Conditions 63
Paths 156

Size

Total Lines 269
Code Lines 163

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 79.8118

Importance

Changes 7
Bugs 1 Features 1
Metric Value
cc 63
eloc 163
c 7
b 1
f 1
nc 156
nop 3
dl 0
loc 269
ccs 57
cts 68
cp 0.8382
crap 79.8118
rs 2.9599

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 simplify 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
        $this->current_parsing_options = array('accept' => $accept);
176
177
        $mergedOptions = $this->parsing_options;
178
        foreach ($options as $key => $val) {
179
            $mergedOptions[$key] = $val;
180
        }
181 710
182
        foreach ($mergedOptions as $key => $val) {
183
            // q: can php be built without ctype? should we use a regexp?
184 710
            if (is_string($key) && !ctype_digit($key)) {
185
                /// @todo on invalid options, throw/error-out instead of logging an error message?
186
                switch($key) {
187 710
                    case 'target_charset':
188
                        if (function_exists('mb_convert_encoding')) {
189
                            $this->current_parsing_options['target_charset'] = $val;
190
                        } else {
191
                            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": 'target_charset' option is unsupported without mbstring");
192 710
                        }
193 710
                        break;
194
195
                    case 'methodname_callback':
196
                        if (is_callable($val)) {
197 710
                            $this->current_parsing_options['methodname_callback'] = $val;
198 708
                        } else {
199 3
                            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": Callback passed as 'methodname_callback' is not callable");
200 703
                        }
201 710
                        break;
202
203 2
                    case 'xmlrpc_null_extension':
204 2
                    case 'xmlrpc_return_datetimes':
205
                    case 'xmlrpc_reject_invalid_values':
206 703
                        $this->current_parsing_options[$key] = $val;
207
                        break;
208
209
                    default:
210 710
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": unsupported option: $key");
211 710
                }
212 2
                unset($mergedOptions[$key]);
213 2
            }
214
        }
215 2
216
        if (!isset($this->current_parsing_options['xmlrpc_null_extension'])) {
217
            $this->current_parsing_options['xmlrpc_null_extension'] = PhpXmlRpc::$xmlrpc_null_extension;
218
        }
219 710
        if (!isset($this->current_parsing_options['xmlrpc_return_datetimes'])) {
220
            $this->current_parsing_options['xmlrpc_return_datetimes'] = PhpXmlRpc::$xmlrpc_return_datetimes;
221 710
        }
222
        if (!isset($this->current_parsing_options['xmlrpc_reject_invalid_values'])) {
223 708
            $this->current_parsing_options['xmlrpc_reject_invalid_values'] = PhpXmlRpc::$xmlrpc_reject_invalid_values;
224 708
        }
225 708
226 708
        // NB: we use '' instead of null to force charset detection from the xml declaration
227 708
        $parser = xml_parser_create('');
228 710
229 710
        foreach ($mergedOptions as $key => $val) {
230 1
            xml_parser_set_option($parser, $key, $val);
231
        }
232
233
        // always set this, in case someone tries to disable it via options...
234
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
235
236
        xml_set_object($parser, $this);
237
238 710
        switch ($returnType) {
239 710
            case self::RETURN_PHP:
240 710
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
241 710
                break;
242 710
            case self::RETURN_EPIVALS:
243 710
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi');
244 710
                break;
245 685
            /// @todo log an error / throw / error-out on unsupported return type
246
            case XMLParser::RETURN_XMLRPCVALS:
247 1
            default:
248 1
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
249
        }
250 1
251
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
252 685
        xml_set_default_handler($parser, 'xmlrpc_dh');
253 685
254 710
        try {
255 710
            // @see ticket #70 - we have to parse big xml docs in chunks to avoid errors
256 400
            for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
257
                $chunk = substr($data, $offset, $this->maxChunkLength);
258 1
                // error handling: xml not well formed
259 1
                if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) {
260
                    $errCode = xml_get_error_code($parser);
261 1
                    $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode),
262
                        xml_get_current_line_number($parser), xml_get_current_column_number($parser));
263
264 399
                    $this->_xh['isf'] = 3;
265 399
                    $this->_xh['isf_reason'] = $errStr;
266 399
                }
267
                // no need to parse further if we already have a fatal error
268
                if ($this->_xh['isf'] >= 2) {
269 399
                    break;
270 22
                }
271
            }
272 399
        /// @todo bump minimum php version to 5.5 and use a finally clause instead of doing cleanup 3 times
273 399
        } catch (\Exception $e) {
274 399
            xml_parser_free($parser);
275 710
            $this->current_parsing_options = array();
276 239
            /// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ?
277
            throw $e;
278 1
        } catch (\Error $e) {
279 1
            xml_parser_free($parser);
280
            $this->current_parsing_options = array();
281 1
                //$this->accept = $prevAccept;
282
                /// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ?
283 710
            throw $e;
284 710
        }
285 710
286
        xml_parser_free($parser);
287 710
        $this->current_parsing_options = array();
288 710
    }
289 710
290
    /**
291 637
     * xml parser handler function for opening element tags.
292 637
     * @internal
293 710
     *
294 109
     * @param resource $parser
295 109
     * @param string $name
296 710
     * @param $attrs
297
     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
298 289
     * @return void
299
     *
300
     * @todo optimization creep: throw when setting $this->_xh['isf'] > 1, to completely avoid further xml parsing
301 688
     *       and remove the checking for $this->_xh['isf'] >= 2 everywhere
302
     */
303 710
    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

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

816
    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...
817
    {
818
        // skip processing if xml fault already detected
819
        if ($this->_xh['isf'] >= 2) {
820
            return;
821
        }
822
823
        // "lookforvalue == 3" means that we've found an entire value and should discard any further character data
824
        if ($this->_xh['lv'] != 3) {
825
            $this->_xh['ac'] .= $data;
826
        }
827
    }
828
829
    /**
830
     * xml parser handler function for 'other stuff', i.e. not char data or element start/end tag.
831
     * In fact it only gets called on unknown entities...
832
     * @internal
833
     *
834
     * @param $parser
835
     * @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...
836
     * @return void
837
     */
838
    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

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