Passed
Push — master ( 7f4693...cd6053 )
by Mark
42:18 queued 34:20
created

XmlScanner::setAdditionalCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader\Security;
4
5
use PhpOffice\PhpSpreadsheet\Reader;
6
7
class XmlScanner
8
{
9
    /**
10
     * Identifies whether the thread-safe libxmlDisableEntityLoader() function is available.
11
     *
12
     * @var bool
13
     */
14
    private $libxmlDisableEntityLoader = false;
15
16
    /**
17
     * Store the initial setting of libxmlDisableEntityLoader so that we can resore t later.
18
     *
19
     * @var bool
20
     */
21
    private $previousLibxmlDisableEntityLoaderValue;
22
23
    /**
24
     * String used to identify risky xml elements.
25
     *
26
     * @var string
27
     */
28
    private $pattern;
29
30
    private $callback;
31
32 98
    private function __construct($pattern = '<!DOCTYPE')
33
    {
34 98
        $this->pattern = $pattern;
35 98
        $this->libxmlDisableEntityLoader = $this->identifyLibxmlDisableEntityLoaderAvailability();
36
37 98
        if ($this->libxmlDisableEntityLoader) {
38 98
            $this->previousLibxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true);
39
        }
40 98
    }
41
42 98
    public function __destruct()
43
    {
44 98
        if ($this->libxmlDisableEntityLoader) {
45 98
            libxml_disable_entity_loader($this->previousLibxmlDisableEntityLoaderValue);
46
        }
47 98
    }
48
49 98
    public static function getInstance(Reader\IReader $reader)
50
    {
51
        switch (true) {
52 98
            case $reader instanceof Reader\Html:
53 18
                return new self('<!ENTITY');
54 80
            case $reader instanceof Reader\Xlsx:
55 36
            case $reader instanceof Reader\Xml:
56 20
            case $reader instanceof Reader\Ods:
57 4
            case $reader instanceof Reader\Gnumeric:
58 80
                return new self('<!DOCTYPE');
59
            default:
60
                return new self('<!DOCTYPE');
61
        }
62
    }
63
64 98
    private function identifyLibxmlDisableEntityLoaderAvailability()
65
    {
66 98
        if (PHP_MAJOR_VERSION == 7) {
67 98
            switch (PHP_MINOR_VERSION) {
68 98
                case 2:
69 98
                    return PHP_RELEASE_VERSION >= 1;
70
                case 1:
71
                    return PHP_RELEASE_VERSION >= 13;
72
                case 0:
73
                    return PHP_RELEASE_VERSION >= 27;
74
            }
75
76
            return true;
77
        }
78
79
        return false;
80
    }
81
82 1
    public function setAdditionalCallback(callable $callback)
83
    {
84 1
        $this->callback = $callback;
85 1
    }
86
87
    /**
88
     * Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks.
89
     *
90
     * @param mixed $xml
91
     *
92
     * @throws Reader\Exception
93
     *
94
     * @return string
95
     */
96 78
    public function scan($xml)
97
    {
98 78
        $pattern = '/encoding="(.*?)"/';
99 78
        $result = preg_match($pattern, $xml, $matches);
100 78
        $charset = $result ? $matches[1] : 'UTF-8';
101
102 78
        if ($charset !== 'UTF-8') {
103 1
            $xml = mb_convert_encoding($xml, 'UTF-8', $charset);
104
        }
105
106
        // Don't rely purely on libxml_disable_entity_loader()
107 78
        $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/';
108 78
        if (preg_match($pattern, $xml)) {
109 5
            throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
110
        }
111
112 73
        if ($this->callback !== null && is_callable($this->callback)) {
113 1
            $xml = call_user_func($this->callback, $xml);
114
        }
115
116 73
        return $xml;
117
    }
118
119
    /**
120
     * Scan theXML for use of <!ENTITY to prevent XXE/XEE attacks.
121
     *
122
     * @param string $filestream
123
     *
124
     * @throws Reader\Exception
125
     *
126
     * @return string
127
     */
128 21
    public function scanFile($filestream)
129
    {
130 21
        return $this->scan(file_get_contents($filestream));
131
    }
132
}
133