Passed
Push — master ( 89b8ba...1a4249 )
by Gaetano
09:50
created

XMLParser::xmlrpc_ee()   F

Complexity

Conditions 62
Paths 152

Size

Total Lines 293
Code Lines 195

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 82.2153

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 62
eloc 195
c 4
b 1
f 0
nc 152
nop 3
dl 0
loc 293
ccs 57
cts 69
cp 0.8261
crap 82.2153
rs 2.9866

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

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

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