Merge commit from fork

* Security Patch

* Minor Improvement to `unentity`

* More Unentity Tweaks
This commit is contained in:
oleibman
2026-09-15 19:03:30 -07:00
committed by GitHub
parent 9e6263ebe8
commit 89f455c133
6 changed files with 175 additions and 4 deletions
@@ -56,6 +56,11 @@ class XmlScanner
throw new Reader\Exception('UTF-7 encoding not permitted');
}
if (substr($xml, 0, Reader\Csv::UTF8_BOM_LEN) === Reader\Csv::UTF8_BOM) {
if (preg_match(self::ENCODING_PATTERN, $xml, $matches) === 1) {
if (strtolower($matches[2]) !== 'utf-8') {
throw new Reader\Exception("BOM says UTF-8 but encoding says {$matches[2]}");
}
}
$xml = substr($xml, Reader\Csv::UTF8_BOM_LEN);
}
+9 -3
View File
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader;
use Composer\Pcre\Preg;
use DateTime;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
@@ -53,9 +54,14 @@ class Xml extends BaseReader
public static function unentity(string $contents): string
{
$contents = preg_replace('/&(amp|lt|gt|quot|apos);/', "\u{fffe}\u{feff}\$1;", trim($contents)) ?? $contents;
// fffe is invalid, replace with replacement char
$contents = str_replace("\u{fffe}", "\u{fffd}", $contents);
// use positive lookahead to "protect" valid xml entities
$contents = Preg::replace('/&(?=(?:amp|lt|gt|quot|apos|#[0-9]+|#x[0-9a-fA-F]+);)/', "\u{fffe}", trim($contents));
// now decode remaining html entities
$contents = html_entity_decode($contents, ENT_NOQUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
$contents = str_replace("\u{fffe}\u{feff}", '&', $contents);
// Escape remaining ampersands, restore those which were replaced with fffe
$contents = str_replace(['&', "\u{fffe}"], ['&', '&'], $contents);
return $contents;
}
@@ -651,7 +657,7 @@ class Xml extends BaseReader
}
$rangeCalculated = false;
if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) {
if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
if (Preg::isMatch('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
$selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
. $selectionMatches[1]
. ':'
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PHPUnit\Framework\TestCase;
class Sec973cTest extends TestCase
{
public function test937c(): void
{
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('BOM says UTF-8 but encoding says ISO-2022-JP');
$reader = new XlsxReader();
$reader->load('tests/data/Reader/XLSX/sec937c.xlsx');
}
}
@@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
class HtmlEntitiesLoadTest extends TestCase
{
public static function testIssue2157(): void
public function testIssue2157(): void
{
$infile = 'tests/data/Reader/Xml/issue.2157.small.xml';
$contents = (string) file_get_contents($infile);
@@ -26,4 +26,12 @@ class HtmlEntitiesLoadTest extends TestCase
self::assertStringContainsString('</br>', $g2);
$spreadsheet->disconnectWorksheets();
}
public function testUnknownEntities(): void
{
$string = '&amp; &Amp; &Tau; &#30; &#x3f12; &#x3g12; & &*3 &lt;';
$expected = '&amp; &amp;Amp; Τ &#30; &#x3f12; &amp;#x3g12; &amp; &amp;*3 &lt;';
$result = XmlReader::unentity($string);
self::assertSame($expected, $result);
}
}
@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xml;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Reader\Xml as XmlReader;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PHPUnit\Framework\TestCase;
class Sec25mgTest extends TestCase
{
private string $filename = '';
private string $xmlns = XmlReader::NAMESPACES_SS;
private string $xmlnsss = XmlReader::NAMESPACES_SS;
protected function tearDown(): void
{
if ($this->filename !== '') {
unlink($this->filename);
}
}
public function testNumericEntities(): void
{
$this->filename = File::temporaryFilename();
$entity_value = str_repeat('A', 100000);
$refs = str_repeat('&big;', 200);
$xml = <<<EOF
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
&#38;#60;!DOCTYPE Workbook [
&#38;#60;!ENTITY big "$entity_value">
]>
<Workbook
xmlns="{$this->xmlns}"
xmlns:ss="{$this->xmlnsss}"
>
<Worksheet ss:Name="Sheet1">
<Table><Row><Cell>
<Data ss:Type="String">$refs</Data>
</Cell></Row></Table>
</Worksheet>
</Workbook>
EOF;
self::assertNotFalse(
file_put_contents($this->filename, $xml)
);
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('Cannot load invalid XML file');
$reader = new XmlReader();
$reader->load($this->filename);
}
public function testDetectDoctype(): void
{
$this->filename = File::temporaryFilename();
$entity_value = str_repeat('A', 100000);
$refs = str_repeat('&big;', 200);
$xml = <<<EOF
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<!DOCTYPE Workbook [
&#38;#60;!ENTITY big "$entity_value">
]>
<Workbook
xmlns="{$this->xmlns}"
xmlns:ss="{$this->xmlnsss}"
>
<Worksheet ss:Name="Sheet1">
<Table><Row><Cell>
<Data ss:Type="String">$refs</Data>
</Cell></Row></Table>
</Worksheet>
</Workbook>
EOF;
self::assertNotFalse(
file_put_contents($this->filename, $xml)
);
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('Detected use of ENTITY');
$reader = new XmlReader();
$reader->load($this->filename);
}
public function testNoDoctype(): void
{
$this->filename = File::temporaryFilename();
$refs = str_repeat('&amp;&pi;&#x03cF;&#48;', 200);
$refsOut = str_repeat('&πϏ0', 200);
$xml = <<<EOF
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook
xmlns="{$this->xmlns}"
xmlns:ss="{$this->xmlnsss}"
>
<Worksheet ss:Name="Sheet1">
<Table><Row><Cell>
<Data ss:Type="String">$refs</Data>
</Cell></Row></Table>
</Worksheet>
</Workbook>
EOF;
self::assertNotFalse(
file_put_contents($this->filename, $xml)
);
$reader = new XmlReader();
$spreadsheet = $reader->load($this->filename);
$sheet = $spreadsheet->getActiveSheet();
self::assertSame($refsOut, $sheet->getCell('A1')->getValue());
$spreadsheet->disconnectWorksheets();
}
public function testIsolated(): void
{
$scanner = new XmlScanner('<!DOCTYPE');
$scanner->setAdditionalCallback([XmlReader::class, 'unentity']);
$input = '&#38;#60;!DOCTYPE test';
$pass1 = $scanner->scan($input);
self::assertStringNotContainsString('<!DOCTYPE', $pass1);
$pass2 = $scanner->scan($pass1);
self::assertStringNotContainsString('<!DOCTYPE', $pass2);
}
}
Binary file not shown.