Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Reader\Security; 4 5 use PhpOffice\PhpSpreadsheet\Reader; 6 use PhpOffice\PhpSpreadsheet\Settings; 7 8 class XmlScanner 9 { 10 /** 11 * String used to identify risky xml elements. 12 * 13 * @var string 14 */ 15 private $pattern; 16 17 private $callback; 18 19 private static $libxmlDisableEntityLoaderValue; 20 21 public function __construct($pattern = '<!DOCTYPE') 22 { 23 $this->pattern = $pattern; 24 25 $this->disableEntityLoaderCheck(); 26 27 // A fatal error will bypass the destructor, so we register a shutdown here 28 register_shutdown_function([__CLASS__, 'shutdown']); 29 } 30 31 public static function getInstance(Reader\IReader $reader) 32 { 33 switch (true) { 34 case $reader instanceof Reader\Html: 35 return new self('<!ENTITY'); 36 case $reader instanceof Reader\Xlsx: 37 case $reader instanceof Reader\Xml: 38 case $reader instanceof Reader\Ods: 39 case $reader instanceof Reader\Gnumeric: 40 return new self('<!DOCTYPE'); 41 default: 42 return new self('<!DOCTYPE'); 43 } 44 } 45 46 public static function threadSafeLibxmlDisableEntityLoaderAvailability() 47 { 48 if (PHP_MAJOR_VERSION == 7) { 49 switch (PHP_MINOR_VERSION) { 50 case 2: 51 return PHP_RELEASE_VERSION >= 1; 52 case 1: 53 return PHP_RELEASE_VERSION >= 13; 54 case 0: 55 return PHP_RELEASE_VERSION >= 27; 56 } 57 58 return true; 59 } 60 61 return false; 62 } 63 64 private function disableEntityLoaderCheck(): void 65 { 66 if (Settings::getLibXmlDisableEntityLoader() && \PHP_VERSION_ID < 80000) { 67 $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true); 68 69 if (self::$libxmlDisableEntityLoaderValue === null) { 70 self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue; 71 } 72 } 73 } 74 75 public static function shutdown(): void 76 { 77 if (self::$libxmlDisableEntityLoaderValue !== null && \PHP_VERSION_ID < 80000) { 78 libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue); 79 self::$libxmlDisableEntityLoaderValue = null; 80 } 81 } 82 83 public function __destruct() 84 { 85 self::shutdown(); 86 } 87 88 public function setAdditionalCallback(callable $callback): void 89 { 90 $this->callback = $callback; 91 } 92 93 private function toUtf8($xml) 94 { 95 $pattern = '/encoding="(.*?)"/'; 96 $result = preg_match($pattern, $xml, $matches); 97 $charset = strtoupper($result ? $matches[1] : 'UTF-8'); 98 99 if ($charset !== 'UTF-8') { 100 $xml = mb_convert_encoding($xml, 'UTF-8', $charset); 101 102 $result = preg_match($pattern, $xml, $matches); 103 $charset = strtoupper($result ? $matches[1] : 'UTF-8'); 104 if ($charset !== 'UTF-8') { 105 throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); 106 } 107 } 108 109 return $xml; 110 } 111 112 /** 113 * Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks. 114 * 115 * @param mixed $xml 116 * 117 * @return string 118 */ 119 public function scan($xml) 120 { 121 $this->disableEntityLoaderCheck(); 122 123 $xml = $this->toUtf8($xml); 124 125 // Don't rely purely on libxml_disable_entity_loader() 126 $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/'; 127 128 if (preg_match($pattern, $xml)) { 129 throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); 130 } 131 132 if ($this->callback !== null && is_callable($this->callback)) { 133 $xml = call_user_func($this->callback, $xml); 134 } 135 136 return $xml; 137 } 138 139 /** 140 * Scan theXML for use of <!ENTITY to prevent XXE/XEE attacks. 141 * 142 * @param string $filestream 143 * 144 * @return string 145 */ 146 public function scanFile($filestream) 147 { 148 return $this->scan(file_get_contents($filestream)); 149 } 150 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body