Completed
Push — master ( 252344...1eeaee )
by Gaetano
04:02 queued 11s
created

XMLParser::xmlrpc_se()   F

Complexity

Conditions 43
Paths 220

Size

Total Lines 154

Duplication

Lines 22
Ratio 14.29 %

Code Coverage

Tests 88
CRAP Score 43.7401

Importance

Changes 0
Metric Value
cc 43
nc 220
nop 4
dl 22
loc 154
ccs 88
cts 95
cp 0.9263
crap 43.7401
rs 2.4666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpXmlRpc\Helper;
4
5
use PhpXmlRpc\PhpXmlRpc;
6
use PhpXmlRpc\Value;
7
8
/**
9
 * Deals with parsing the XML.
10
 */
11
class XMLParser
12
{
13
    const RETURN_XMLRPCVALS = 'xmlrpcvals';
14
    const RETURN_PHP = 'phpvals';
15
16
    const ACCEPT_REQUEST = 1;
17
    const ACCEPT_RESPONSE = 2;
18
    const ACCEPT_VALUE = 4;
19
20
    // Used to store state during parsing.
21
    // Quick explanation of components:
22
    //  private:
23
    //   ac - used to accumulate values
24
    //   stack - array with genealogy of xml elements names used to validate nesting of xmlrpc elements
25
    //   valuestack - array used for parsing arrays and structs
26
    //   lv - used to indicate "looking for a value": implements the logic to allow values with no types to be strings
27
    //  public:
28
    //   isf - used to indicate an xml parsing fault (3), invalid xmlrpc fault (2) or xmlrpc response fault (1)
29
    //   isf_reason - used for storing xmlrpc response fault string
30
    //   method - used to store method name
31
    //   params - used to store parameters in method calls
32
    //   pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values
33
    //   rt  - 'methodcall', 'methodresponse' or 'value'
34
    public $_xh = array(
35
        'ac' => '',
36
        'stack' => array(),
37
        'valuestack' => array(),
38
        'isf' => 0,
39
        'isf_reason' => '',
40
        'method' => false,
41
        'params' => array(),
42
        'pt' => array(),
43
        'rt' => '',
44
    );
45
46
    public $xmlrpc_valid_parents = array(
47
        'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
48
        'BOOLEAN' => array('VALUE'),
49
        'I4' => array('VALUE'),
50
        'I8' => array('VALUE'),
51
        'EX:I8' => array('VALUE'),
52
        'INT' => array('VALUE'),
53
        'STRING' => array('VALUE'),
54
        'DOUBLE' => array('VALUE'),
55
        'DATETIME.ISO8601' => array('VALUE'),
56
        'BASE64' => array('VALUE'),
57
        'MEMBER' => array('STRUCT'),
58
        'NAME' => array('MEMBER'),
59
        'DATA' => array('ARRAY'),
60
        'ARRAY' => array('VALUE'),
61
        'STRUCT' => array('VALUE'),
62
        'PARAM' => array('PARAMS'),
63
        'METHODNAME' => array('METHODCALL'),
64
        'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
65
        'FAULT' => array('METHODRESPONSE'),
66
        'NIL' => array('VALUE'), // only used when extension activated
67
        'EX:NIL' => array('VALUE'), // only used when extension activated
68
    );
69
70
    /** @var array $parsing_options */
71
    protected $parsing_options = array();
72
    /** @var int $accept */
73
    protected $accept = 3; // self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE;
74
    /** @var int $maxChunkLength */
75
    protected $maxChunkLength = 4194304; // 4 MB
76
77
    /**
78
     * @param array $options passed to the xml parser
79
     */
80 600
    public function __construct(array $options = array())
81
    {
82 600
        $this->parsing_options = $options;
83 600
    }
84
85
    /**
86
     * @param array $options passed to the xml parser
87
     */
88
    public function setParsingOptions(array $options)
89
    {
90
        $this->parsing_options = $options;
91
    }
92
93
    /**
94
     * @param string $data
95
     * @param string $returnType
96
     * @param int $accept a bit-combination of self::ACCEPT_REQUEST, self::ACCEPT_RESPONSE, self::ACCEPT_VALUE
97
     * @return string
98
     */
99 600
    public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3)
100
    {
101 600
        $parser = xml_parser_create();
102
103 600
        foreach ($this->parsing_options as $key => $val) {
104 600
            xml_parser_set_option($parser, $key, $val);
105
        }
106
107 600
        xml_set_object($parser, $this);
108
109 600
        if ($returnType == self::RETURN_PHP) {
110 23
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
111
        } else {
112 599
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
113
        }
114
115 600
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
116 600
        xml_set_default_handler($parser, 'xmlrpc_dh');
117
118 600
        $this->accept = $accept;
119
120 600
        $this->_xh = array(
121
            'ac' => '',
122
            'stack' => array(),
123
            'valuestack' => array(),
124
            'isf' => 0,
125
            'isf_reason' => '',
126
            'method' => false, // so we can check later if we got a methodname or not
127
            'params' => array(),
128
            'pt' => array(),
129
            'rt' => '',
130
        );
131
132 600
        $len = strlen($data);
133 600
        for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
134 600
            $chunk = substr($data, $offset, $this->maxChunkLength);
135
            // error handling: xml not well formed
136 600
            if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) {
137 3
                $errCode = xml_get_error_code($parser);
138 3
                $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode),
139 3
                    xml_get_current_line_number($parser), xml_get_current_column_number($parser));
140
141 3
                $this->_xh['isf'] = 3;
142 3
                $this->_xh['isf_reason'] = $errStr;
143 3
                break;
144
            }
145
        }
146
147 600
        xml_parser_free($parser);
148 600
    }
149
150
    /**
151
     * xml parser handler function for opening element tags.
152
     * @param resource $parser
153
     * @param string $name
154
     * @param $attrs
155
     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
156
     */
157 600
    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.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
158
    {
159
        // if invalid xmlrpc already detected, skip all processing
160 600
        if ($this->_xh['isf'] < 2) {
161
162
            // check for correct element nesting
163 600
            if (count($this->_xh['stack']) == 0) {
164
                // top level element can only be of 2 types
165
                /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
166
                ///       there is only a single top level element in xml anyway
167
                // BC
168 600
                if ($acceptSingleVals === false) {
169 600
                    $accept = $this->accept;
170
                } else {
171
                    $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE;
172
                }
173 600
                if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) ||
174 599
                    ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) ||
175 600
                    ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE))) {
176 600
                    $this->_xh['rt'] = strtolower($name);
177
                } else {
178 2
                    $this->_xh['isf'] = 2;
179 2
                    $this->_xh['isf_reason'] = 'missing top level xmlrpc element. Found: ' . $name;
180
181 600
                    return;
182
                }
183
            } else {
184
                // not top level element: see if parent is OK
185 600
                $parent = end($this->_xh['stack']);
186 600
                if (!array_key_exists($name, $this->xmlrpc_valid_parents) || !in_array($parent, $this->xmlrpc_valid_parents[$name])) {
187 2
                    $this->_xh['isf'] = 2;
188 2
                    $this->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
189
190 2
                    return;
191
                }
192
            }
193
194 600
            switch ($name) {
195
                // optimize for speed switch cases: most common cases first
196 600
                case 'VALUE':
197
                    /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
198 598
                    $this->_xh['vt'] = 'value'; // indicator: no value found yet
199 598
                    $this->_xh['ac'] = '';
200 598
                    $this->_xh['lv'] = 1;
201 598
                    $this->_xh['php_class'] = null;
202 598
                    break;
203 600
                case 'I8':
204 600
                case 'EX:I8':
205 1
                    if (PHP_INT_SIZE === 4) {
206
                        // INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
207
                        $this->_xh['isf'] = 2;
208
                        $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode";
209
210
                        return;
211
                    }
212
                // fall through voluntarily
213 600
                case 'I4':
214 600
                case 'INT':
215 600
                case 'STRING':
216 600
                case 'BOOLEAN':
217 600
                case 'DOUBLE':
218 600
                case 'DATETIME.ISO8601':
219 600 View Code Duplication
                case 'BASE64':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220 577
                    if ($this->_xh['vt'] != 'value') {
221
                        // two data elements inside a value: an error occurred!
222 1
                        $this->_xh['isf'] = 2;
223 1
                        $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
224
225 1
                        return;
226
                    }
227 577
                    $this->_xh['ac'] = ''; // reset the accumulator
228 577
                    break;
229 600
                case 'STRUCT':
230 600
                case 'ARRAY':
231 339
                    if ($this->_xh['vt'] != 'value') {
232
                        // two data elements inside a value: an error occurred!
233 1
                        $this->_xh['isf'] = 2;
234 1
                        $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
235
236 1
                        return;
237
                    }
238
                    // create an empty array to hold child values, and push it onto appropriate stack
239 338
                    $curVal = array();
240 338
                    $curVal['values'] = array();
241 338
                    $curVal['type'] = $name;
242
                    // check for out-of-band information to rebuild php objs
243
                    // and in case it is found, save it
244 338
                    if (@isset($attrs['PHP_CLASS'])) {
245 20
                        $curVal['php_class'] = $attrs['PHP_CLASS'];
246
                    }
247 338
                    $this->_xh['valuestack'][] = $curVal;
248 338
                    $this->_xh['vt'] = 'data'; // be prepared for a data element next
249 338
                    break;
250 600
                case 'DATA':
251 212
                    if ($this->_xh['vt'] != 'data') {
252
                        // two data elements inside a value: an error occurred!
253 1
                        $this->_xh['isf'] = 2;
254 1
                        $this->_xh['isf_reason'] = "found two data elements inside an array element";
255
256 1
                        return;
257
                    }
258 600
                case 'METHODCALL':
259 600
                case 'METHODRESPONSE':
260 600
                case 'PARAMS':
261
                    // valid elements that add little to processing
262 600
                    break;
263 600
                case 'METHODNAME':
264 600
                case 'NAME':
265
                    /// @todo we could check for 2 NAME elements inside a MEMBER element
266 538
                    $this->_xh['ac'] = '';
267 538
                    break;
268 600
                case 'FAULT':
269 80
                    $this->_xh['isf'] = 1;
270 80
                    break;
271 600
                case 'MEMBER':
272
                    // set member name to null, in case we do not find in the xml later on
273 242
                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = '';
274
                    //$this->_xh['ac']='';
275
                // Drop trough intentionally
276 599
                case 'PARAM':
277
                    // clear value type, so we can check later if no value has been passed for this param/member
278 600
                    $this->_xh['vt'] = null;
279 600
                    break;
280 21
                case 'NIL':
281 21 View Code Duplication
                case 'EX:NIL':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282 21
                    if (PhpXmlRpc::$xmlrpc_null_extension) {
283 21
                        if ($this->_xh['vt'] != 'value') {
284
                            // two data elements inside a value: an error occurred!
285
                            $this->_xh['isf'] = 2;
286
                            $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
287
288
                            return;
289
                        }
290 21
                        $this->_xh['ac'] = ''; // reset the accumulator
291 21
                        break;
292
                    }
293
                // we do not support the <NIL/> extension, so
294
                // drop through intentionally
295
                default:
296
                    /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
297 1
                    $this->_xh['isf'] = 2;
298 1
                    $this->_xh['isf_reason'] = "found not-xmlrpc xml element $name";
299 1
                    break;
300
            }
301
302
            // Save current element name to stack, to validate nesting
303 600
            $this->_xh['stack'][] = $name;
304
305
            /// @todo optimization creep: move this inside the big switch() above
306 600
            if ($name != 'VALUE') {
307 600
                $this->_xh['lv'] = 0;
308
            }
309
        }
310 600
    }
311
312
    /**
313
     * xml parser handler function for opening element tags.
314
     * Used in decoding xml chunks that might represent single xmlrpc values as well as requests, responses.
315
     * @deprecated
316
     * @param resource $parser
317
     * @param $name
318
     * @param $attrs
319
     */
320
    public function xmlrpc_se_any($parser, $name, $attrs)
321
    {
322
        $this->xmlrpc_se($parser, $name, $attrs, true);
323
    }
324
325
    /**
326
     * xml parser handler function for close element tags.
327
     * @param resource $parser
328
     * @param string $name
329
     * @param bool $rebuildXmlrpcvals
330
     */
331 600
    public function xmlrpc_ee($parser, $name, $rebuildXmlrpcvals = true)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
332
    {
333 600
        if ($this->_xh['isf'] < 2) {
334
            // push this element name from stack
335
            // NB: if XML validates, correct opening/closing is guaranteed and
336
            // we do not have to check for $name == $currElem.
337
            // we also checked for proper nesting at start of elements...
338 599
            $currElem = array_pop($this->_xh['stack']);
0 ignored issues
show
Unused Code introduced by
$currElem is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
339
340 599
            switch ($name) {
341 599
                case 'VALUE':
342
                    // This if() detects if no scalar was inside <VALUE></VALUE>
343 597
                    if ($this->_xh['vt'] == 'value') {
344 28
                        $this->_xh['value'] = $this->_xh['ac'];
345 28
                        $this->_xh['vt'] = Value::$xmlrpcString;
346
                    }
347
348 597
                    if ($rebuildXmlrpcvals) {
349
                        // build the xmlrpc val out of the data received, and substitute it
350 596
                        $temp = new Value($this->_xh['value'], $this->_xh['vt']);
351
                        // in case we got info about underlying php class, save it
352
                        // in the object we're rebuilding
353 596
                        if (isset($this->_xh['php_class'])) {
354 20
                            $temp->_php_class = $this->_xh['php_class'];
355
                        }
356
                        // check if we are inside an array or struct:
357
                        // if value just built is inside an array, let's move it into array on the stack
358 596
                        $vscount = count($this->_xh['valuestack']);
359 596 View Code Duplication
                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360 212
                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $temp;
361
                        } else {
362 596
                            $this->_xh['value'] = $temp;
363
                        }
364
                    } else {
365
                        /// @todo this needs to treat correctly php-serialized objects,
366
                        /// since std deserializing is done by php_xmlrpc_decode,
367
                        /// which we will not be calling...
368 23
                        if (isset($this->_xh['php_class'])) {
369
                        }
370
371
                        // check if we are inside an array or struct:
372
                        // if value just built is inside an array, let's move it into array on the stack
373 23
                        $vscount = count($this->_xh['valuestack']);
374 23 View Code Duplication
                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375 20
                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value'];
376
                        }
377
                    }
378 597
                    break;
379 599
                case 'BOOLEAN':
380 599
                case 'I4':
381 599
                case 'I8':
382 599
                case 'EX:I8':
383 599
                case 'INT':
384 599
                case 'STRING':
385 598
                case 'DOUBLE':
386 598
                case 'DATETIME.ISO8601':
387 598
                case 'BASE64':
388 577
                    $this->_xh['vt'] = strtolower($name);
389
                    /// @todo: optimization creep - remove the if/elseif cycle below
390
                    /// since the case() in which we are already did that
391 577
                    if ($name == 'STRING') {
392 494
                        $this->_xh['value'] = $this->_xh['ac'];
393 393 View Code Duplication
                    } elseif ($name == 'DATETIME.ISO8601') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394 6
                        if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $this->_xh['ac'])) {
395
                            error_log('XML-RPC: ' . __METHOD__ . ': invalid value received in DATETIME: ' . $this->_xh['ac']);
396
                        }
397 6
                        $this->_xh['vt'] = Value::$xmlrpcDateTime;
398 6
                        $this->_xh['value'] = $this->_xh['ac'];
399 388
                    } elseif ($name == 'BASE64') {
400
                        /// @todo check for failure of base64 decoding / catch warnings
401 20
                        $this->_xh['value'] = base64_decode($this->_xh['ac']);
402 369
                    } elseif ($name == 'BOOLEAN') {
403
                        // special case here: we translate boolean 1 or 0 into PHP
404
                        // constants true or false.
405
                        // Strings 'true' and 'false' are accepted, even though the
406
                        // spec never mentions them (see eg. Blogger api docs)
407
                        // NB: this simple checks helps a lot sanitizing input, ie no
408
                        // security problems around here
409 41
                        if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') == 0) {
410 41
                            $this->_xh['value'] = true;
411
                        } else {
412
                            // log if receiving something strange, even though we set the value to false anyway
413 21
                            if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') != 0) {
414
                                error_log('XML-RPC: ' . __METHOD__ . ': invalid value received in BOOLEAN: ' . $this->_xh['ac']);
415
                            }
416 41
                            $this->_xh['value'] = false;
417
                        }
418 330 View Code Duplication
                    } elseif ($name == 'DOUBLE') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
                        // we have a DOUBLE
420
                        // we must check that only 0123456789-.<space> are characters here
421
                        // NOTE: regexp could be much stricter than this...
422 22
                        if (!preg_match('/^[+-eE0123456789 \t.]+$/', $this->_xh['ac'])) {
423
                            /// @todo: find a better way of throwing an error than this!
424
                            error_log('XML-RPC: ' . __METHOD__ . ': non numeric value received in DOUBLE: ' . $this->_xh['ac']);
425
                            $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
426
                        } else {
427
                            // it's ok, add it on
428 22
                            $this->_xh['value'] = (double)$this->_xh['ac'];
429
                        }
430
                    } else {
431
                        // we have an I4/I8/INT
432
                        // we must check that only 0123456789-<space> are characters here
433 311 View Code Duplication
                        if (!preg_match('/^[+-]?[0123456789 \t]+$/', $this->_xh['ac'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
434
                            /// @todo find a better way of throwing an error than this!
435
                            error_log('XML-RPC: ' . __METHOD__ . ': non numeric value received in INT: ' . $this->_xh['ac']);
436
                            $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
437
                        } else {
438
                            // it's ok, add it on
439 311
                            $this->_xh['value'] = (int)$this->_xh['ac'];
440
                        }
441
                    }
442 577
                    $this->_xh['lv'] = 3; // indicate we've found a value
443 577
                    break;
444 598
                case 'NAME':
445 242
                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = $this->_xh['ac'];
446 242
                    break;
447 598
                case 'MEMBER':
448
                    // add to array in the stack the last element built,
449
                    // unless no VALUE was found
450 242
                    if ($this->_xh['vt']) {
451 223
                        $vscount = count($this->_xh['valuestack']);
452 223
                        $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value'];
453
                    } else {
454 20
                        error_log('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml');
455
                    }
456 242
                    break;
457 598
                case 'DATA':
458 212
                    $this->_xh['vt'] = null; // reset this to check for 2 data elements in a row - even if they're empty
459 212
                    break;
460 597
                case 'STRUCT':
461 597
                case 'ARRAY':
462
                    // fetch out of stack array of values, and promote it to current value
463 337
                    $currVal = array_pop($this->_xh['valuestack']);
464 337
                    $this->_xh['value'] = $currVal['values'];
465 337
                    $this->_xh['vt'] = strtolower($name);
466 337
                    if (isset($currVal['php_class'])) {
467 20
                        $this->_xh['php_class'] = $currVal['php_class'];
468
                    }
469 337
                    break;
470 597
                case 'PARAM':
471
                    // add to array of params the current value,
472
                    // unless no VALUE was found
473 595
                    if ($this->_xh['vt']) {
474 595
                        $this->_xh['params'][] = $this->_xh['value'];
475 595
                        $this->_xh['pt'][] = $this->_xh['vt'];
476
                    } else {
477
                        error_log('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml');
478
                    }
479 595
                    break;
480 597
                case 'METHODNAME':
481 490
                    $this->_xh['method'] = preg_replace('/^[\n\r\t ]+/', '', $this->_xh['ac']);
482 490
                    break;
483 596
                case 'NIL':
484 596
                case 'EX:NIL':
485 21
                    if (PhpXmlRpc::$xmlrpc_null_extension) {
486 21
                        $this->_xh['vt'] = 'null';
487 21
                        $this->_xh['value'] = null;
488 21
                        $this->_xh['lv'] = 3;
489 21
                        break;
490
                    }
491
                // drop through intentionally if nil extension not enabled
492 596
                case 'PARAMS':
493 596
                case 'FAULT':
494 596
                case 'METHODCALL':
495 596
                case 'METHORESPONSE':
496 596
                    break;
497
                default:
498
                    // End of INVALID ELEMENT!
499
                    // shall we add an assert here for unreachable code???
500 596
                    break;
501
            }
502
        }
503 600
    }
504
505
    /**
506
     * Used in decoding xmlrpc requests/responses without rebuilding xmlrpc Values.
507
     * @param resource $parser
508
     * @param string $name
509
     */
510 23
    public function xmlrpc_ee_fast($parser, $name)
511
    {
512 23
        $this->xmlrpc_ee($parser, $name, false);
513 23
    }
514
515
    /**
516
     * xml parser handler function for character data.
517
     * @param resource $parser
518
     * @param string $data
519
     */
520 600
    public function xmlrpc_cd($parser, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
521
    {
522
        // skip processing if xml fault already detected
523 600
        if ($this->_xh['isf'] < 2) {
524
            // "lookforvalue==3" means that we've found an entire value
525
            // and should discard any further character data
526 600
            if ($this->_xh['lv'] != 3) {
527 600
                $this->_xh['ac'] .= $data;
528
            }
529
        }
530 600
    }
531
532
    /**
533
     * xml parser handler function for 'other stuff', ie. not char data or
534
     * element start/end tag. In fact it only gets called on unknown entities...
535
     * @param $parser
536
     * @param string data
537
     */
538 587
    public function xmlrpc_dh($parser, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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