mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-16 13:06:31 +00:00
Security Patches
This commit is contained in:
@@ -7,6 +7,7 @@ use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Helper\Dimension as HelperDimension;
|
||||
@@ -182,7 +183,11 @@ class Ods extends BaseReader
|
||||
$xml->read();
|
||||
if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$rowspan = $xml->getAttribute('table:number-rows-repeated');
|
||||
$rowspan = empty($rowspan) ? 1 : $rowspan;
|
||||
$rowspan = empty($rowspan) ? 1 : (int) $rowspan;
|
||||
self::checkRowsRepeated(
|
||||
$tmpInfo['totalRows'],
|
||||
$rowspan
|
||||
);
|
||||
$tmpInfo['totalRows'] += $rowspan;
|
||||
$tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
|
||||
$currCells = 0;
|
||||
@@ -191,13 +196,24 @@ class Ods extends BaseReader
|
||||
do {
|
||||
$doread = true;
|
||||
if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$mergeSize = $xml->getAttribute('table:number-columns-repeated');
|
||||
$mergeSize = empty($mergeSize) ? 1 : (int) $mergeSize;
|
||||
self::checkColumnsRepeatedInt(
|
||||
$currCells,
|
||||
$mergeSize
|
||||
);
|
||||
$currCells += (int) $mergeSize;
|
||||
if (!$xml->isEmptyElement) {
|
||||
++$currCells;
|
||||
$xml->next();
|
||||
$doread = false;
|
||||
}
|
||||
} elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$mergeSize = $xml->getAttribute('table:number-columns-repeated');
|
||||
$mergeSize = empty($mergeSize) ? 1 : (int) $mergeSize;
|
||||
self::checkColumnsRepeatedInt(
|
||||
$currCells,
|
||||
$mergeSize
|
||||
);
|
||||
$currCells += (int) $mergeSize;
|
||||
}
|
||||
if ($doread) {
|
||||
@@ -380,15 +396,19 @@ class Ods extends BaseReader
|
||||
break;
|
||||
case 'table-column':
|
||||
if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) {
|
||||
$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
|
||||
$colRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
|
||||
} else {
|
||||
$rowRepeats = 1;
|
||||
$colRepeats = 1;
|
||||
}
|
||||
self::checkColumnsRepeatedInt(
|
||||
$tableColumnIndex - 1,
|
||||
$colRepeats
|
||||
);
|
||||
$tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name');
|
||||
if (isset($columnWidths[$tableStyleName])) {
|
||||
$columnWidth = new HelperDimension($columnWidths[$tableStyleName]);
|
||||
$tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex);
|
||||
for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0; --$rowRepeats2) {
|
||||
for ($colRepeats2 = $colRepeats; $colRepeats2 > 0; --$colRepeats2) {
|
||||
$spreadsheet->getActiveSheet()
|
||||
->getColumnDimension($tableColumnString)
|
||||
->setWidth($columnWidth->toUnit('cm'), 'cm');
|
||||
@@ -397,7 +417,7 @@ class Ods extends BaseReader
|
||||
);
|
||||
}
|
||||
}
|
||||
$tableColumnIndex += $rowRepeats;
|
||||
$tableColumnIndex += $colRepeats;
|
||||
|
||||
break;
|
||||
case 'table-row':
|
||||
@@ -406,6 +426,7 @@ class Ods extends BaseReader
|
||||
} else {
|
||||
$rowRepeats = 1;
|
||||
}
|
||||
self::checkRowsRepeated($rowID, $rowRepeats);
|
||||
|
||||
$columnID = 'A';
|
||||
/** @var DOMElement|DOMText $cellData */
|
||||
@@ -420,6 +441,10 @@ class Ods extends BaseReader
|
||||
} else {
|
||||
$colRepeats = 1;
|
||||
}
|
||||
self::checkColumnsRepeated(
|
||||
$columnID,
|
||||
$colRepeats
|
||||
);
|
||||
|
||||
for ($i = 0; $i < $colRepeats; ++$i) {
|
||||
StringHelper::stringIncrement(
|
||||
@@ -594,6 +619,10 @@ class Ods extends BaseReader
|
||||
} else {
|
||||
$colRepeats = 1;
|
||||
}
|
||||
self::checkColumnsRepeated(
|
||||
$columnID,
|
||||
$colRepeats
|
||||
);
|
||||
|
||||
if ($type !== null) {
|
||||
for ($i = 0; $i < $colRepeats; ++$i) {
|
||||
@@ -831,4 +860,28 @@ class Ods extends BaseReader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function checkRowsRepeated(int $rowID, int $rowRepeats): void
|
||||
{
|
||||
if ($rowRepeats < 1 || $rowID + $rowRepeats - 1 > AddressRange::MAX_ROW) {
|
||||
throw new Exception("Invalid number-rows-repeated $rowRepeats following row $rowID");
|
||||
}
|
||||
}
|
||||
|
||||
private static function checkColumnsRepeated(string $colID, int $colRepeats): void
|
||||
{
|
||||
$colIndex = Coordinate::columnIndexFromString($colID);
|
||||
if ($colRepeats < 1 || $colIndex + $colRepeats - 1 > AddressRange::MAX_COLUMN_INT) {
|
||||
throw new Exception("Invalid number-columns-repeated $colRepeats following column $colID");
|
||||
}
|
||||
}
|
||||
|
||||
private static function checkColumnsRepeatedInt(int $colIndex, int $colRepeats): void
|
||||
{
|
||||
// We don't have column string at this point,
|
||||
// and colIndex is actually 1 less than it should be.
|
||||
if ($colRepeats < 1 || $colIndex + $colRepeats > AddressRange::MAX_COLUMN_INT) {
|
||||
throw new Exception("Invalid number-columns-repeated $colRepeats following column index $colIndex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ class Drawing extends BaseDrawing
|
||||
if (str_starts_with($path, 'https:') || str_starts_with($path, 'http:')) {
|
||||
$ctxArray = [
|
||||
'http' => [
|
||||
'follow_location' => 0,
|
||||
'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
'header' => [
|
||||
//'Connection: keep-alive', // unacceptable performance
|
||||
|
||||
@@ -17,6 +17,9 @@ class ServerTest extends TestCase
|
||||
private const REDIRECT = 'http://' . self::SERVER . '/redirect.php';
|
||||
private const OLDVALUE = 'LOCAL_REDIRECT_SECRET2';
|
||||
private const NEWVALUE = 'LOCAL_REDIRECT_SECRET3';
|
||||
private const IMAGE_DIRECT = __DIR__ . '/blue_square.png.xlsx';
|
||||
private const IMAGE_REDIRECT = __DIR__ . '/blue_square.php.xlsx';
|
||||
private const IMAGE_START = 'http://' . self::SERVER . '/blue_square';
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
@@ -77,10 +80,51 @@ class ServerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
private static function allowLocalhost(string $path): bool
|
||||
{
|
||||
return str_starts_with($path, self::IMAGE_START);
|
||||
}
|
||||
|
||||
public function xtestImageNoRedirect(): void
|
||||
{
|
||||
$file = 'zip://' . self::IMAGE_DIRECT . '#xl/drawings/_rels/drawing1.xml.rels';
|
||||
$contents = (string) file_get_contents($file);
|
||||
self::assertStringContainsString(
|
||||
'Target="' . self::IMAGE_START . '.png"',
|
||||
$contents
|
||||
);
|
||||
$reader = new XlsxReader();
|
||||
$reader->setAllowExternalImages(true)
|
||||
->setIsWhiteListed(self::allowLocalhost(...));
|
||||
$spreadsheet = $reader->load(self::IMAGE_DIRECT);
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
self::assertCount(1, $sheet->getDrawingCollection());
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}
|
||||
|
||||
public function xtestImageRedirect(): void
|
||||
{
|
||||
$file = 'zip://' . self::IMAGE_REDIRECT . '#xl/drawings/_rels/drawing1.xml.rels';
|
||||
$contents = (string) file_get_contents($file);
|
||||
self::assertStringContainsString(
|
||||
'Target="' . self::IMAGE_START . '.php"',
|
||||
$contents
|
||||
);
|
||||
$reader = new XlsxReader();
|
||||
$reader->setAllowExternalImages(true)
|
||||
->setIsWhiteListed(self::allowLocalhost(...));
|
||||
$spreadsheet = $reader->load(self::IMAGE_REDIRECT);
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
self::assertCount(0, $sheet->getDrawingCollection());
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}
|
||||
|
||||
public function testAll(): void
|
||||
{
|
||||
$this->xtestServer();
|
||||
$this->xtestReadFile();
|
||||
$this->xtestNew();
|
||||
$this->xtestImageNoRedirect();
|
||||
$this->xtestImageRedirect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
header('Location: http://localhost:8080/blue_square.png', true, 302);
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Reader\Ods;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BadRepeatsTest extends TestCase
|
||||
{
|
||||
public function testBadRepeatedColsRead(): void
|
||||
{
|
||||
$this->expectException(ReaderException::class);
|
||||
$this->expectExceptionMessage('Invalid number-columns-repeated');
|
||||
$reader = new Ods();
|
||||
$infile = 'tests/data/Reader/Ods/BadRepeatCol.ods';
|
||||
$reader->load($infile);
|
||||
}
|
||||
|
||||
public function testBadRepeatedColsWorksheetInfo(): void
|
||||
{
|
||||
$this->expectException(ReaderException::class);
|
||||
$this->expectExceptionMessage('Invalid number-columns-repeated');
|
||||
$reader = new Ods();
|
||||
$infile = 'tests/data/Reader/Ods/BadRepeatCol.ods';
|
||||
$reader->listWorksheetInfo($infile);
|
||||
}
|
||||
|
||||
public function testBadRepeatedRowsRead(): void
|
||||
{
|
||||
$this->expectException(ReaderException::class);
|
||||
$this->expectExceptionMessage('Invalid number-rows-repeated');
|
||||
$reader = new Ods();
|
||||
$infile = 'tests/data/Reader/Ods/BadRepeatRow.ods';
|
||||
$reader->load($infile);
|
||||
}
|
||||
|
||||
public function testBadRepeatedRowsWorksheetInfo(): void
|
||||
{
|
||||
$this->expectException(ReaderException::class);
|
||||
$this->expectExceptionMessage('Invalid number-rows-repeated');
|
||||
$reader = new Ods();
|
||||
$infile = 'tests/data/Reader/Ods/BadRepeatRow.ods';
|
||||
$reader->listWorksheetInfo($infile);
|
||||
}
|
||||
}
|
||||
@@ -73,10 +73,10 @@ class OdsInfoTest extends TestCase
|
||||
],
|
||||
[
|
||||
'worksheetName' => 'Second Sheet',
|
||||
'lastColumnLetter' => 'A',
|
||||
'lastColumnIndex' => 0,
|
||||
'lastColumnLetter' => 'B',
|
||||
'lastColumnIndex' => 1,
|
||||
'totalRows' => 2,
|
||||
'totalColumns' => 1,
|
||||
'totalColumns' => 2,
|
||||
],
|
||||
], $wsinfo);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xml;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
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"?>
|
||||
&#60;!DOCTYPE Workbook [
|
||||
&#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 [
|
||||
&#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('&πϏ0', 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)
|
||||
);
|
||||
// no 'unentity' function in this branch
|
||||
$this->expectException(ReaderException::class);
|
||||
$this->expectExceptionMessage('Cannot load invalid XML file');
|
||||
$reader = new XmlReader();
|
||||
$spreadsheet = $reader->load($this->filename);
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
self::assertSame($refsOut, $sheet->getCell('A1')->getValue());
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user