Passed
Push — master ( b6cd05...9f5262 )
by Gaetano
05:39
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 68
CRAP Score 74.9735

Importance

Changes 5
Bugs 1 Features 1
Metric Value
cc 62
eloc 195
c 5
b 1
f 1
nc 152
nop 3
dl 0
loc 293
ccs 68
cts 80
cp 0.85
crap 74.9735
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\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 allow to parse data from a stream, to avoid having to copy first the whole xml to memory
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
    /** @var int $maxChunkLength 4 MB by default. Any value below 10MB should be good */
107
    protected $maxChunkLength = 4194304;
108
    /** @var array supported keys: accept, target_charset, methodname_callback, xmlrpc_null_extension, xmlrpc_return_datetimes */
109
    protected $current_parsing_options = array();
110
111
    /**
112
     * @param array $options integer keys: options passed to the xml parser
113
     *                       string keys:
114
     *                       - target_charset (string)
115
     *                       - methodname_callback (callable)
116
     *                       - xmlrpc_null_extension (bool)
117 712
     *                       - xmlrpc_return_datetimes (bool)
118
     *                       - xmlrpc_reject_invalid_values (bool)
119
     */
120 712
    public function __construct(array $options = array())
121 2
    {
122 2
        $this->parsing_options = $options;
123 2
    }
124
125
    /**
126 710
     * Parses an xml-rpc xml string. Results of the parsing are found in $this->['_xh'].
127
     * Logs to the error log any issues which do not cause the parsing to fail.
128 710
     *
129
     * @param string $data
130
     * @param string $returnType self::RETURN_XMLRPCVALS, self::RETURN_PHP, self::RETURN_EPIVALS
131 710
     * @param int $accept a bit-combination of self::ACCEPT_REQUEST, self::ACCEPT_RESPONSE, self::ACCEPT_VALUE
132 709
     * @param array $options integer-key options are passed to the xml parser, string-key options are used independently.
133
     *                       These options are added to options received in the constructor.
134
     *                       Note that if options xmlrpc_null_extension, xmlrpc_return_datetimes and xmlrpc_reject_invalid_values
135 710
     *                       are not set, the default settings from PhpXmlRpc\PhpXmlRpc are used
136
     * @return void the caller has to look into $this->_xh to find the results
137 710
     * @throws \Exception this can happen if a callback function is set and it does throw (i.e. we do not catch exceptions)
138
     *
139
     * @todo refactor? we could 1. return the parsed data structure, and 2. move $returnType and $accept into options
140 710
     */
141 27
    public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3, $options = array())
142 27
    {
143 708
        $this->_xh = array(
144
            'ac' => '',
145
            'stack' => array(),
146
            'valuestack' => array(),
147 708
            'lv' => 0,
148
            'isf' => 0,
149
            'isf_reason' => '',
150 710
            'value' => null,
151 710
            'method' => false, // so we can check later if we got a methodname or not
152
            'params' => array(),
153 710
            'pt' => array(),
154
            'rt' => '',
155
        );
156 710
157 710
        $len = strlen($data);
158
159 710
        // we test for empty documents here to save on resource allocation and simply the chunked-parsing loop below
160 3
        if ($len == 0) {
161 3
            $this->_xh['isf'] = 3;
162 3
            $this->_xh['isf_reason'] = 'XML error 5: empty document';
163
            return;
164 3
        }
165 3
166 3
        //$prevAccept = $this->accept;
167
        //$this->accept = $accept;
168
        $this->current_parsing_options = array('accept' => $accept);
169
170 710
        $mergedOptions = $this->parsing_options;
171 710
        foreach ($options as $key => $val) {
172
            $mergedOptions[$key] = $val;
173
        }
174
175
        foreach ($mergedOptions as $key => $val) {
176
            // q: can php be built without ctype? should we use a regexp?
177
            if (is_string($key) && !ctype_digit($key)) {
178
                switch($key) {
179
                    case 'target_charset':
180
                        if (function_exists('mb_convert_encoding')) {
181 710
                            $this->current_parsing_options['target_charset'] = $val;
182
                        } else {
183
                            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ": 'target_charset' option is unsupported without mbstring");
184 710
                        }
185
                        break;
186
187 710
                    case 'methodname_callback':
188
                        if (is_callable($val)) {
189
                            $this->current_parsing_options['methodname_callback'] = $val;
190
                        } else {
191
                            //$this->_xh['isf'] = 4;
192 710
                            //$this->_xh['isf_reason'] = "Callback passed as 'methodname_callback' is not callable";
193 710
                            //return;
194
                            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ": Callback passed as 'methodname_callback' is not callable");
195
                        }
196
                        break;
197 710
198 708
                    case 'xmlrpc_null_extension':
199 3
                    case 'xmlrpc_return_datetimes':
200 703
                    case 'xmlrpc_reject_invalid_values':
201 710
                        $this->current_parsing_options[$key] = $val;
202
                        break;
203 2
204 2
                    default:
205
                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ": unsupported option: $key");
206 703
                }
207
                unset($mergedOptions[$key]);
208
            }
209
        }
210 710
211 710
        if (!isset($this->current_parsing_options['xmlrpc_null_extension'])) {
212 2
            $this->current_parsing_options['xmlrpc_null_extension'] = PhpXmlRpc::$xmlrpc_null_extension;
213 2
        }
214
        if (!isset($this->current_parsing_options['xmlrpc_return_datetimes'])) {
215 2
            $this->current_parsing_options['xmlrpc_return_datetimes'] = PhpXmlRpc::$xmlrpc_return_datetimes;
216
        }
217
        if (!isset($this->current_parsing_options['xmlrpc_reject_invalid_values'])) {
218
            $this->current_parsing_options['xmlrpc_reject_invalid_values'] = PhpXmlRpc::$xmlrpc_reject_invalid_values;
219 710
        }
220
221 710
        // NB: we use '' instead of null to force charset detection from the xml declaration
222
        $parser = xml_parser_create('');
223 708
224 708
        foreach ($mergedOptions as $key => $val) {
225 708
            xml_parser_set_option($parser, $key, $val);
226 708
        }
227 708
228 710
        // always set this, in case someone tries to disable it via options...
229 710
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
230 1
231
        xml_set_object($parser, $this);
232
233
        switch ($returnType) {
234
            case self::RETURN_PHP:
235
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
236
                break;
237
            case self::RETURN_EPIVALS:
238 710
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi');
239 710
                break;
240 710
            /// @todo log a warning on unsupported return type
241 710
            case XMLParser::RETURN_XMLRPCVALS:
242 710
            default:
243 710
                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
244 710
        }
245 685
246
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
247 1
        xml_set_default_handler($parser, 'xmlrpc_dh');
248 1
249
        try {
250 1
            // @see ticket #70 - we have to parse big xml docs in chunks to avoid errors
251
            for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
252 685
                $chunk = substr($data, $offset, $this->maxChunkLength);
253 685
                // error handling: xml not well formed
254 710
                if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) {
255 710
                    $errCode = xml_get_error_code($parser);
256 400
                    $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode),
257
                        xml_get_current_line_number($parser), xml_get_current_column_number($parser));
258 1
259 1
                    $this->_xh['isf'] = 3;
260
                    $this->_xh['isf_reason'] = $errStr;
261 1
                    break;
262
                }
263
                // no need to parse further if we already have a fatal error
264 399
                if ($this->_xh['isf'] >= 2) {
265 399
                    break;
266 399
                }
267
            }
268
        } catch (\Exception $e) {
269 399
            xml_parser_free($parser);
270 22
            $this->current_parsing_options = array();
271
            //$this->accept = $prevAccept;
272 399
            /// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ?
273 399
            throw $e;
274 399
        }
275 710
276 239
        xml_parser_free($parser);
277
        $this->current_parsing_options = array();
278 1
        //$this->accept = $prevAccept;
279 1
    }
280
281 1
    /**
282
     * xml parser handler function for opening element tags.
283 710
     * @internal
284 710
     *
285 710
     * @param resource $parser
286
     * @param string $name
287 710
     * @param $attrs
288 710
     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
289 710
     * @return void
290
     *
291 637
     * @todo optimization: throw when setting $this->_xh['isf'] > 1, to completely avoid further xml parsing
292 637
     */
293 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

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

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

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