Completed
Push — master ( 94203c...087b47 )
by Christopher
08:35 queued 04:46
created

XML2Array::createArray()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 27
rs 5.3846
cc 8
eloc 20
nc 8
nop 1
1
<?php
2
namespace POData\UriProcessor;
3
4
class XML2Array
5
{
6
    /**
7
     * @var string
8
     */
9
    private static $encoding = 'UTF-8';
10
    /**
11
     * @var \DOMDocument
12
     */
13
    private static $xml = null;
14
    /**
15
     * Convert an XML to Array.
16
     *
17
     * @param string $input_xml
18
     *
19
     * @return array
20
     *
21
     * @throws \Exception
22
     */
23
    public static function createArray($input_xml)
24
    {
25
        if (empty($input_xml)) {
26
            return [];
27
        }
28
        $xml = self::getXMLRoot();
29
        if (is_string($input_xml)) {
30
            try {
31
                $xml->loadXML($input_xml);
0 ignored issues
show
Security XML Injection introduced by
$input_xml can contain request data and is used in xml context(s) leading to a potential security vulnerability.

8 paths for user data to reach this point

  1. Path: $this->parameters['HTTP_AUTHORIZATION'] seems to return tainted data, and $authorizationHeader is assigned in ServerBag.php on line 62
  1. $this->parameters['HTTP_AUTHORIZATION'] seems to return tainted data, and $authorizationHeader is assigned
    in vendor/ServerBag.php on line 62
  2. ParameterBag::$parameters is assigned
    in vendor/ServerBag.php on line 77
  3. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  4. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  5. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  6. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  7. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  8. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  9. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  2. Path: Read from $_POST, and $_POST is passed to Request::createRequestFromFactory() in Request.php on line 281
  1. Read from $_POST, and $_POST is passed to Request::createRequestFromFactory()
    in vendor/Request.php on line 281
  2. $request is passed to Request::__construct()
    in vendor/Request.php on line 1945
  3. $request is passed to Request::initialize()
    in vendor/Request.php on line 222
  4. $request is passed to ParameterBag::__construct()
    in vendor/Request.php on line 240
  5. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 35
  6. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  7. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  8. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  9. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  10. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  11. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  12. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  3. Path: Read from $_SERVER, and $server is assigned in Request.php on line 271
  1. Read from $_SERVER, and $server is assigned
    in vendor/Request.php on line 271
  2. $server is passed to Request::createRequestFromFactory()
    in vendor/Request.php on line 281
  3. $server is passed to Request::__construct()
    in vendor/Request.php on line 1945
  4. $server is passed to Request::initialize()
    in vendor/Request.php on line 222
  5. $server is passed to ParameterBag::__construct()
    in vendor/Request.php on line 245
  6. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 35
  7. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  8. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  9. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  10. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  11. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  12. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  13. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  4. Path: Fetching key HTTP_CONTENT_LENGTH from $_SERVER, and $server is assigned in Request.php on line 274
  1. Fetching key HTTP_CONTENT_LENGTH from $_SERVER, and $server is assigned
    in vendor/Request.php on line 274
  2. $server is passed to Request::createRequestFromFactory()
    in vendor/Request.php on line 281
  3. $server is passed to Request::__construct()
    in vendor/Request.php on line 1945
  4. $server is passed to Request::initialize()
    in vendor/Request.php on line 222
  5. $server is passed to ParameterBag::__construct()
    in vendor/Request.php on line 245
  6. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 35
  7. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  8. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  9. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  10. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  11. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  12. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  13. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  5. Path: Fetching key HTTP_CONTENT_TYPE from $_SERVER, and $server is assigned in Request.php on line 277
  1. Fetching key HTTP_CONTENT_TYPE from $_SERVER, and $server is assigned
    in vendor/Request.php on line 277
  2. $server is passed to Request::createRequestFromFactory()
    in vendor/Request.php on line 281
  3. $server is passed to Request::__construct()
    in vendor/Request.php on line 1945
  4. $server is passed to Request::initialize()
    in vendor/Request.php on line 222
  5. $server is passed to ParameterBag::__construct()
    in vendor/Request.php on line 245
  6. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 35
  7. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  8. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  9. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  10. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  11. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  12. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  13. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  6. Path: $server['HTTP_HOST'] seems to return tainted data, and $server is assigned in Request.php on line 347
  1. $server['HTTP_HOST'] seems to return tainted data, and $server is assigned
    in vendor/Request.php on line 347
  2. $server is assigned
    in vendor/Request.php on line 395
  3. $server is assigned
    in vendor/Request.php on line 396
  4. $server is passed to Request::createRequestFromFactory()
    in vendor/Request.php on line 398
  5. $server is passed to Request::__construct()
    in vendor/Request.php on line 1945
  6. $server is passed to Request::initialize()
    in vendor/Request.php on line 222
  7. $server is passed to ParameterBag::__construct()
    in vendor/Request.php on line 245
  8. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 35
  9. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  10. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  11. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  12. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  13. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  14. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  15. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  7. Path: $this->parameters['PHP_AUTH_USER'] seems to return tainted data, and $headers is assigned in ServerBag.php on line 43
  1. $this->parameters['PHP_AUTH_USER'] seems to return tainted data, and $headers is assigned
    in vendor/ServerBag.php on line 43
  2. $headers is assigned
    in vendor/ServerBag.php on line 44
  3. ServerBag::getHeaders() returns tainted data, and $this->server->getHeaders() is passed to HeaderBag::__construct()
    in vendor/Request.php on line 246
  4. $values is assigned
    in vendor/HeaderBag.php on line 31
  5. $values is passed to HeaderBag::set()
    in vendor/HeaderBag.php on line 32
  6. (array) $values is passed through array_values(), and $values is assigned
    in vendor/HeaderBag.php on line 142
  7. HeaderBag::$headers is assigned
    in vendor/HeaderBag.php on line 145
  8. Tainted property HeaderBag::$headers is read
    in vendor/HeaderBag.php on line 125
  9. HeaderBag::get() returns tainted data, and $requestUri is assigned
    in vendor/Request.php on line 1715
  10. $requestUri is passed to ParameterBag::set()
    in vendor/Request.php on line 1746
  11. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 99
  12. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  13. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  14. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  15. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  16. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  17. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  18. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302
  8. Path: $this->parameters['PHP_AUTH_PW'] seems to return tainted data, and $headers is assigned in ServerBag.php on line 44
  1. $this->parameters['PHP_AUTH_PW'] seems to return tainted data, and $headers is assigned
    in vendor/ServerBag.php on line 44
  2. ServerBag::getHeaders() returns tainted data, and $this->server->getHeaders() is passed to HeaderBag::__construct()
    in vendor/Request.php on line 246
  3. $values is assigned
    in vendor/HeaderBag.php on line 31
  4. $values is passed to HeaderBag::set()
    in vendor/HeaderBag.php on line 32
  5. (array) $values is passed through array_values(), and $values is assigned
    in vendor/HeaderBag.php on line 142
  6. HeaderBag::$headers is assigned
    in vendor/HeaderBag.php on line 145
  7. Tainted property HeaderBag::$headers is read
    in vendor/HeaderBag.php on line 125
  8. HeaderBag::get() returns tainted data, and $requestUri is assigned
    in vendor/Request.php on line 1715
  9. $requestUri is passed to ParameterBag::set()
    in vendor/Request.php on line 1746
  10. ParameterBag::$parameters is assigned
    in vendor/ParameterBag.php on line 99
  11. Tainted property ParameterBag::$parameters is read
    in vendor/ParameterBag.php on line 45
  12. ParameterBag::all() returns tainted data, and $input is assigned
    in vendor/Request.php on line 335
  13. Request::input() returns tainted data, and $this->input() is passed through array_replace_recursive()
    in vendor/Request.php on line 323
  14. Request::all() returns tainted data
    in src/POData/OperationContext/Web/Illuminate/IncomingIlluminateRequest.php on line 132
  15. IncomingIlluminateRequest::getAllInput() returns tainted data, and RequestDescription::$_data is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 282
  16. Tainted property RequestDescription::$_data is read, and $string is assigned
    in src/POData/UriProcessor/RequestDescription.php on line 297
  17. $string is passed to XML2Array::createArray()
    in src/POData/UriProcessor/RequestDescription.php on line 302

Preventing XML Injection Attacks

If you pass user-data to XML parsing functions like simplexml_load_string(), this can be abused to inject external entities to gain access to the contents of any file in your filesystem, or it can be used to freeze your PHP process with an entity expansion attack.

In order to prevent that, make sure to disable external entity loading and disallow custom doc-types:

libxml_disable_entity_loader(true);

$dom = new DOMDocument;
$dom->loadXML($taintedXml);
foreach ($dom->childNodes as $child) {
    if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
        throw new InvalidArgumentException(
            'Invalid XML: Detected use of illegal DOCTYPE'
        );
    }
}

// It is now safe to use $taintedXml
$xml = simplexml_load_string($taintedXml);

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
32
                if (!is_object($xml) || empty($xml->documentElement)) {
33
                    throw new \Exception();
34
                }
35
            } catch (\Exception $ex) {
36
                throw new \Exception('[XML2Array] Error parsing the XML string.' . PHP_EOL . $ex->getMessage());
37
            }
38
        } elseif (is_object($input_xml)) {
39
            if (get_class($input_xml) != 'DOMDocument') {
40
                throw new \Exception('[XML2Array] The input XML object should be of type: DOMDocument.');
41
            }
42
            $xml = self::$xml = $input_xml;
43
        } else {
44
            throw new \Exception('[XML2Array] Invalid input');
45
        }
46
        $array[$xml->documentElement->tagName] = self::convert($xml->documentElement);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$array was never initialized. Although not strictly required by PHP, it is generally a good practice to add $array = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
47
        self::$xml = null; // clear the xml node in the class for 2nd time use.
48
        return $array;
49
    }
50
    /**
51
     * Initialize the root XML node [optional].
52
     *
53
     * @param string $version
54
     * @param string $encoding
55
     * @param bool   $standalone
56
     * @param bool   $format_output
57
     */
58
    public static function init($version = '1.0', $encoding = 'utf-8', $standalone = false, $format_output = true)
59
    {
60
        self::$xml = new \DomDocument($version, $encoding);
61
        self::$xml->xmlStandalone = $standalone;
62
        self::$xml->formatOutput = $format_output;
63
        self::$encoding = $encoding;
64
    }
65
    /**
66
     * Convert an Array to XML.
67
     *
68
     * @param \DOMNode $node - XML as a string or as an object of DOMDocument
69
     *
70
     * @return array
71
     */
72
    private static function convert(\DOMNode $node)
73
    {
74
        $output = [];
75
        switch ($node->nodeType) {
76
            case XML_CDATA_SECTION_NODE:
77
                $output['@cdata'] = trim($node->textContent);
78
                break;
79
            case XML_TEXT_NODE:
80
                $output = trim($node->textContent);
81
                break;
82
            case XML_ELEMENT_NODE:
83
                // for each child node, call the covert function recursively
84
                for ($i = 0, $m = $node->childNodes->length; $i < $m; ++$i) {
85
                    $child = $node->childNodes->item($i);
86
                    $v = self::convert($child);
87
                    if (isset($child->tagName)) {
88
                        $t = $child->tagName;
89
                        // assume more nodes of same kind are coming
90
                        if (!array_key_exists($t, $output)) {
91
                            $output[$t] = [];
92
                        }
93
                        $output[$t][] = $v;
94
                    } else {
95
                        //check if it is not an empty node
96
                        if (!empty($v)) {
97
                            $output = $v;
98
                        }
99
                    }
100
                }
101
                if (is_array($output)) {
102
                    // if only one node of its kind, assign it directly instead if array($value);
103
                    foreach ($output as $t => $v) {
104
                        if (is_array($v) && count($v) == 1) {
105
                            $output[$t] = $v[0];
106
                        }
107
                    }
108
                    if (empty($output)) {
109
                        //for empty nodes
110
                        $output = '';
111
                    }
112
                }
113
                // loop through the attributes and collect them
114
                if ($node->attributes->length) {
1 ignored issue
show
Bug introduced by
The property length does not seem to exist in DOMNamedNodeMap.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
115
                    $a = [];
116
                    foreach ($node->attributes as $attrName => $attrNode) {
117
                        $a[$attrName] = $attrNode->value;
118
                    }
119
                    // if its an leaf node, store the value in @value instead of directly storing it.
120
                    if (!is_array($output)) {
121
                        $output = ['@value' => $output];
122
                    }
123
                    $output['@attributes'] = $a;
124
                }
125
                break;
126
        }
127
        return $output;
128
    }
129
    /**
130
     * Get the root XML node, if there isn't one, create it.
131
     *
132
     * @return \DOMDocument
133
     */
134
    private static function getXMLRoot()
135
    {
136
        if (empty(self::$xml)) {
137
            self::init();
138
        }
139
        return self::$xml;
140
    }
141
}
142