diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7566e774..b970b1f3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3490,11 +3490,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Settings.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:\\$pageArray has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/CodePage.php - - message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:timestampToExcel\\(\\) expects int, float\\|int\\|string given\\.$#" count: 1 @@ -4025,11 +4020,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Shared/OLERead.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/PasswordHasher.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:sanitizeUTF8\\(\\) should return string but returns string\\|false\\.$#" count: 1 @@ -6515,16 +6505,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php - - - message: "#^Cannot call method getFormattedValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsTest.php - - - - message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Reader/XlsTest.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getWorksheetInstance\\(\\) has no return typehint specified\\.$#" count: 1 @@ -6640,11 +6620,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php - - - message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheetTests\\\\Writer\\\\Csv\\\\CsvEnclosureTest\\:\\:\\$cellValues has no typehint specified\\.$#" count: 1 @@ -6685,11 +6660,6 @@ parameters: count: 3 path: tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php - - - message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php - - message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" count: 1 @@ -6700,16 +6670,6 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php - - - message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" - count: 2 - path: tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php - - - - message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" - count: 2 - path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php - - message: "#^Cannot call method getDrawingCollection\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" count: 4 @@ -6720,11 +6680,6 @@ parameters: count: 4 path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php - - - message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php - - message: "#^Parameter \\#1 \\$data of function simplexml_load_string expects string, string\\|false given\\.$#" count: 2 diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index d94a8852..d78b227e 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -336,6 +336,29 @@ class Xlsx extends BaseReader } } + /** + * @param string $fileName + */ + private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool + { + // Root-relative paths + if (strpos($fileName, '//') !== false) { + $fileName = substr($fileName, strpos($fileName, '//') + 1); + } + $fileName = File::realpath($fileName); + + // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming + // so we need to load case-insensitively from the zip file + + // Apache POI fixes + $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE); + if ($contents === false) { + $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE); + } + + return $contents !== false; + } + /** * @param string $fileName * @@ -821,8 +844,8 @@ class Xlsx extends BaseReader $this->readSheetProtection($docSheet, $xmlSheet); } - if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) { - (new AutoFilter($docSheet, $xmlSheet))->load(); + if ($this->readDataOnly === false) { + $this->readAutoFilterTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip); } if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) { @@ -1977,4 +2000,52 @@ class Xlsx extends BaseReader } } } + + private function readAutoFilterTables( + SimpleXMLElement $xmlSheet, + Worksheet $docSheet, + string $dir, + string $fileWorksheet, + ZipArchive $zip + ): void { + if ($xmlSheet && $xmlSheet->autoFilter) { + // In older files, autofilter structure is defined in the worksheet file + (new AutoFilter($docSheet, $xmlSheet))->load(); + } elseif ($xmlSheet && $xmlSheet->tableParts && $xmlSheet->tableParts['count'] > 0) { + // But for Office365, MS decided to make it all just a bit more complicated + $this->readAutoFilterTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet); + } + } + + private function readAutoFilterTablesInTablesFile( + SimpleXMLElement $xmlSheet, + string $dir, + string $fileWorksheet, + ZipArchive $zip, + Worksheet $docSheet + ): void { + foreach ($xmlSheet->tableParts->tablePart as $tablePart) { + $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT); + $tablePartRel = (string) $relation['id']; + $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + + if ($zip->locateName($relationsFileName)) { + $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); + foreach ($relsTableReferences->Relationship as $relationship) { + $relationshipAttributes = self::getAttributes($relationship, ''); + + if ((string) $relationshipAttributes['Id'] === $tablePartRel) { + $relationshipFileName = (string) $relationshipAttributes['Target']; + $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName; + $relationshipFilePath = File::realpath($relationshipFilePath); + + if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) { + $autoFilter = $this->loadZip($relationshipFilePath); + (new AutoFilter($docSheet, $autoFilter))->load(); + } + } + } + } + } + } } diff --git a/src/PhpSpreadsheet/Shared/CodePage.php b/src/PhpSpreadsheet/Shared/CodePage.php index 1d5d8933..8718a613 100644 --- a/src/PhpSpreadsheet/Shared/CodePage.php +++ b/src/PhpSpreadsheet/Shared/CodePage.php @@ -8,6 +8,7 @@ class CodePage { public const DEFAULT_CODE_PAGE = 'CP1252'; + /** @var array */ private static $pageArray = [ 0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program 367 => 'ASCII', // ASCII @@ -56,7 +57,7 @@ class CodePage 10010 => 'MACROMANIA', // Macintosh Romania 10017 => 'MACUKRAINE', // Macintosh Ukraine 10021 => 'MACTHAI', // Macintosh Thai - 10029 => 'MACCENTRALEUROPE', // Macintosh Central Europe + 10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe 10079 => 'MACICELAND', // Macintosh Icelandic 10081 => 'MACTURKISH', // Macintosh Turkish 10082 => 'MACCROATIAN', // Macintosh Croatian @@ -65,6 +66,7 @@ class CodePage //32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3) 65000 => 'UTF-7', // Unicode (UTF-7) 65001 => 'UTF-8', // Unicode (UTF-8) + 99999 => ['unsupported'], // Unicode (UTF-8) ]; public static function validate(string $codePage): bool @@ -83,7 +85,20 @@ class CodePage public static function numberToName(int $codePage): string { if (array_key_exists($codePage, self::$pageArray)) { - return self::$pageArray[$codePage]; + $value = self::$pageArray[$codePage]; + if (is_array($value)) { + foreach ($value as $encoding) { + if (@iconv('UTF-8', $encoding, ' ') !== false) { + self::$pageArray[$codePage] = $encoding; + + return $encoding; + } + } + + throw new PhpSpreadsheetException("Code page $codePage not implemented on this system."); + } else { + return $value; + } } if ($codePage == 720 || $codePage == 32769) { throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic diff --git a/src/PhpSpreadsheet/Shared/PasswordHasher.php b/src/PhpSpreadsheet/Shared/PasswordHasher.php index 9fefe88f..ba30fc31 100644 --- a/src/PhpSpreadsheet/Shared/PasswordHasher.php +++ b/src/PhpSpreadsheet/Shared/PasswordHasher.php @@ -2,11 +2,13 @@ namespace PhpOffice\PhpSpreadsheet\Shared; -use PhpOffice\PhpSpreadsheet\Exception; +use PhpOffice\PhpSpreadsheet\Exception as SpException; use PhpOffice\PhpSpreadsheet\Worksheet\Protection; class PasswordHasher { + const MAX_PASSWORD_LENGTH = 255; + /** * Get algorithm name for PHP. */ @@ -34,36 +36,40 @@ class PasswordHasher return $mapping[$algorithmName]; } - throw new Exception('Unsupported password algorithm: ' . $algorithmName); + throw new SpException('Unsupported password algorithm: ' . $algorithmName); } /** * Create a password hash from a given string. * - * This method is based on the algorithm provided by + * This method is based on the spec at: + * https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf + * 2.3.7.1 Binary Document Password Verifier Derivation Method 1 + * + * It replaces a method based on the algorithm provided by * Daniel Rentz of OpenOffice and the PEAR package * Spreadsheet_Excel_Writer by Xavier Noguer . * + * Scrutinizer will squawk at the use of bitwise operations here, + * but it should ultimately pass. + * * @param string $pPassword Password to hash */ private static function defaultHashPassword(string $pPassword): string { - $password = 0x0000; - $charPos = 1; // char position - - // split the plain text password in its component characters - $chars = preg_split('//', $pPassword, -1, PREG_SPLIT_NO_EMPTY); - foreach ($chars as $char) { - $value = ord($char) << $charPos++; // shifted ASCII value - $rotated_bits = $value >> 15; // rotated bits beyond bit 15 - $value &= 0x7fff; // first 15 bits - $password ^= ($value | $rotated_bits); + $verifier = 0; + $pwlen = strlen($pPassword); + $passwordArray = pack('c', $pwlen) . $pPassword; + for ($i = $pwlen; $i >= 0; --$i) { + $intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1; + $intermediate2 = 2 * $verifier; + $intermediate2 = $intermediate2 & 0x7fff; + $intermediate3 = $intermediate1 | $intermediate2; + $verifier = $intermediate3 ^ ord($passwordArray[$i]); } + $verifier ^= 0xCE4B; - $password ^= strlen($pPassword); - $password ^= 0xCE4B; - - return strtoupper(dechex($password)); + return strtoupper(dechex($verifier)); } /** @@ -82,6 +88,9 @@ class PasswordHasher */ public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string { + if (strlen($password) > self::MAX_PASSWORD_LENGTH) { + throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters'); + } $phpAlgorithm = self::getAlgorithm($algorithm); if (!$phpAlgorithm) { return self::defaultHashPassword($password); diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php index 6bedceca..3c4bee47 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php @@ -267,6 +267,11 @@ class Column return null; } + public function ruleCount(): int + { + return count($this->ruleset); + } + /** * Get all AutoFilter Column Rules. * diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 6b5fa6fd..e8377ee9 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -2138,7 +2138,7 @@ class Worksheet extends BIFFwriter private function writePassword(): void { // Exit unless sheet protection and password have been specified - if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword()) { + if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword() || $this->phpSheet->getProtection()->getAlgorithm() !== '') { return; } diff --git a/tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php b/tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php new file mode 100644 index 00000000..c36e6741 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php @@ -0,0 +1,74 @@ +load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame('A1:D57', $worksheet->getAutoFilter()->getRange()); + self::assertSame(2, $worksheet->getAutoFilter()->getColumn('C')->ruleCount()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getOperator() + ); + self::assertSame('UK', $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getValue()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getOperator() + ); + self::assertSame('United States', $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getValue()); + self::assertSame(2, $worksheet->getAutoFilter()->getColumn('D')->ruleCount()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN, + $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getOperator() + ); + self::assertSame('650', $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getValue()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, + $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getOperator() + ); + self::assertSame('800', $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getValue()); + } + + public function testLoadOffice365AutoFilter(): void + { + $filename = 'tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame('A1:D57', $worksheet->getAutoFilter()->getRange()); + self::assertSame(2, $worksheet->getAutoFilter()->getColumn('C')->ruleCount()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getOperator() + ); + self::assertSame('UK', $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getValue()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getOperator() + ); + self::assertSame('United States', $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getValue()); + self::assertSame(2, $worksheet->getAutoFilter()->getColumn('D')->ruleCount()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN, + $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getOperator() + ); + self::assertSame('650', $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getValue()); + self::assertSame( + Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, + $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getOperator() + ); + self::assertSame('800', $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/XlsTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/XlsTest.php similarity index 61% rename from tests/PhpSpreadsheetTests/Reader/XlsTest.php rename to tests/PhpSpreadsheetTests/Reader/Xls/XlsTest.php index 130374b3..94257694 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/XlsTest.php @@ -1,8 +1,10 @@ load($filename); self::assertEquals('Title', $spreadsheet->getSheet(0)->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); } /** @@ -42,6 +45,8 @@ class XlsTest extends AbstractFunctional self::assertEquals($row, $newrow); self::assertEquals($sheet->getCell('A1')->getFormattedValue(), $newsheet->getCell('A1')->getFormattedValue()); self::assertEquals($sheet->getCell("$col$row")->getFormattedValue(), $newsheet->getCell("$col$row")->getFormattedValue()); + $spreadsheet->disconnectWorksheets(); + $newspreadsheet->disconnectWorksheets(); } /** @@ -71,11 +76,49 @@ class XlsTest extends AbstractFunctional $rowIterator = $sheet->getRowIterator(); foreach ($rowIterator as $row) { - foreach ($row->getCellIterator() as $cell) { + foreach ($row->getCellIterator() as $cellx) { + /** @var Cell */ + $cell = $cellx; $valOld = $cell->getFormattedValue(); $valNew = $newsheet->getCell($cell->getCoordinate())->getFormattedValue(); self::assertEquals($valOld, $valNew); } } + $spreadsheet->disconnectWorksheets(); + $newspreadsheet->disconnectWorksheets(); + } + + /** + * Test load Xls file with MACCENTRALEUROPE encoding, which is implemented + * as MAC-CENTRALEUROPE on some systems. Issue #549. + */ + public function testLoadMacCentralEurope(): void + { + $codePages = CodePage::getEncodings(); + self::assertIsArray($codePages[10029]); + $filename = 'tests/data/Reader/XLS/maccentraleurope.xls'; + $reader = new Xls(); + // When no fix applied, spreadsheet fails to load on some systems + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Ładowność', $sheet->getCell('I1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } + + /** + * First test changes array entry in CodePage. + * This test confirms new that new entry is okay. + */ + public function testLoadMacCentralEurope2(): void + { + $codePages = CodePage::getEncodings(); + self::assertIsString($codePages[10029]); + $filename = 'tests/data/Reader/XLS/maccentraleurope.xls'; + $reader = new Xls(); + // When no fix applied, spreadsheet fails to load on some systems + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('Ładowność', $sheet->getCell('I1')->getValue()); + $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/PhpSpreadsheetTests/SettingsTest.php b/tests/PhpSpreadsheetTests/SettingsTest.php index 11b93ae6..80dc07ab 100644 --- a/tests/PhpSpreadsheetTests/SettingsTest.php +++ b/tests/PhpSpreadsheetTests/SettingsTest.php @@ -41,6 +41,7 @@ class SettingsTest extends TestCase public function testSetXMLSettings(): void { + $original = Settings::getLibXmlLoaderOptions(); Settings::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_DTDVALID); $result = Settings::getLibXmlLoaderOptions(); self::assertTrue((bool) ((LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_DTDVALID) & $result)); @@ -48,5 +49,6 @@ class SettingsTest extends TestCase if (\PHP_VERSION_ID < 80000) { self::assertFalse(libxml_disable_entity_loader()); } + Settings::setLibXmlLoaderOptions($original); } } diff --git a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php index e7dc7469..af036f4e 100644 --- a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php +++ b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php @@ -16,8 +16,15 @@ class CodePageTest extends TestCase */ public function testCodePageNumberToName($expectedResult, $codePageIndex): void { + if ($expectedResult === 'exception') { + $this->expectException(Exception::class); + } $result = CodePage::numberToName($codePageIndex); - self::assertEquals($expectedResult, $result); + if (is_array($expectedResult)) { + self::assertContains($result, $expectedResult); + } else { + self::assertEquals($expectedResult, $result); + } } public function providerCodePage(): array diff --git a/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php b/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php index e85b9fa3..4b7923d8 100644 --- a/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php +++ b/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; +use PhpOffice\PhpSpreadsheet\Exception as SpException; use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher; use PHPUnit\Framework\TestCase; @@ -14,6 +15,9 @@ class PasswordHasherTest extends TestCase */ public function testHashPassword($expectedResult, ...$args): void { + if ($expectedResult === 'exception') { + $this->expectException(SpException::class); + } $result = PasswordHasher::hashPassword(...$args); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Shared/PasswordReloadTest.php b/tests/PhpSpreadsheetTests/Shared/PasswordReloadTest.php new file mode 100644 index 00000000..b6c28b17 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Shared/PasswordReloadTest.php @@ -0,0 +1,51 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $protection = $sheet->getProtection(); + $protection->setAlgorithm($algorithm); + $protection->setPassword($password); + $protection->setSheet(true); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $format); + $resheet = $reloadedSpreadsheet->getActiveSheet(); + $reprot = $resheet->getProtection(); + $repassword = $reprot->getPassword(); + $hash = ''; + if ($supported) { + $readAlgorithm = $reprot->getAlgorithm(); + self::assertSame($algorithm, $readAlgorithm); + $salt = $reprot->getSalt(); + $spin = $reprot->getSpinCount(); + $hash = PasswordHasher::hashPassword($password, $readAlgorithm, $salt, $spin); + } + self::assertSame($repassword, $hash); + $spreadsheet->disconnectWorksheets(); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function providerPasswords(): array + { + return [ + 'Xls basic algorithm' => ['Xls', ''], + 'Xls cannot use SHA512' => ['Xls', 'SHA-512', false], + 'Xlsx basic algorithm' => ['Xlsx', ''], + 'Xlsx can use SHA512' => ['Xlsx', 'SHA-512'], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php b/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php index 1560f1ed..b367583b 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php @@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; @@ -17,8 +16,6 @@ class WorksheetNamedRangesTest extends TestCase protected function setUp(): void { - Settings::setLibXmlLoaderOptions(null); // reset to default options - $reader = new Xlsx(); $this->spreadsheet = $reader->load('tests/data/Worksheet/namedRangeTest.xlsx'); } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalTest.php index 21df6ed6..21659dad 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalTest.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Conditional; use PhpOffice\PhpSpreadsheet\Style\Fill; @@ -11,24 +10,6 @@ use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class ConditionalTest extends AbstractFunctional { - /** - * @var int - */ - private $prevValue; - - protected function setUp(): void - { - $this->prevValue = Settings::getLibXmlLoaderOptions(); - - // Disable validating XML with the DTD - Settings::setLibXmlLoaderOptions($this->prevValue & ~LIBXML_DTDVALID & ~LIBXML_DTDATTR & ~LIBXML_DTDLOAD); - } - - protected function tearDown(): void - { - Settings::setLibXmlLoaderOptions($this->prevValue); - } - /** * Test check if conditional style with type 'notContainsText' works on xlsx. */ diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index 88c63306..012cdbcd 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -4,30 +4,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class DrawingsTest extends AbstractFunctional { - /** - * @var int - */ - private $prevValue; - - protected function setUp(): void - { - $this->prevValue = Settings::getLibXmlLoaderOptions(); - - // Disable validating XML with the DTD - Settings::setLibXmlLoaderOptions($this->prevValue & ~LIBXML_DTDVALID & ~LIBXML_DTDATTR & ~LIBXML_DTDLOAD); - } - - protected function tearDown(): void - { - Settings::setLibXmlLoaderOptions($this->prevValue); - } - /** * Test save and load XLSX file with drawing on 2nd worksheet. */ diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php index 22f3284b..6ba8316b 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php @@ -3,7 +3,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as Reader; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx as Writer; @@ -19,7 +18,6 @@ class FloatsRetainedTest extends TestCase public function testIntyFloatsRetainedByWriter($value): void { $outputFilename = File::temporaryFilename(); - Settings::setLibXmlLoaderOptions(null); $sheet = new Spreadsheet(); $sheet->getActiveSheet()->getCell('A1')->setValue($value); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php index 826c482d..ebc3c92f 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php @@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as Reader; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx as Writer; @@ -16,7 +15,6 @@ class StartsWithHashTest extends TestCase public function testStartWithHash(): void { $outputFilename = File::temporaryFilename(); - Settings::setLibXmlLoaderOptions(null); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setCellValueExplicit('A1', '#define M', DataType::TYPE_STRING); @@ -41,7 +39,6 @@ class StartsWithHashTest extends TestCase { // Make sure raw data indicates A3 is an error, but A2 isn't. $outputFilename = File::temporaryFilename(); - Settings::setLibXmlLoaderOptions(null); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setCellValueExplicit('A1', '#define M', DataType::TYPE_STRING); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php index def6f70e..9a801015 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; -use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\File; use PHPUnit\Framework\TestCase; use ZipArchive; @@ -16,7 +15,6 @@ class UnparsedDataCloneTest extends TestCase { $sampleFilename = 'tests/data/Writer/XLSX/drawing_on_2nd_page.xlsx'; $resultFilename = File::temporaryFilename(); - Settings::setLibXmlLoaderOptions(null); // reset to default options $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet = $reader->load($sampleFilename); $spreadsheet->setActiveSheetIndex(1); @@ -63,7 +61,6 @@ class UnparsedDataCloneTest extends TestCase $resultFilename1 = File::temporaryFilename(); $resultFilename2 = File::temporaryFilename(); self::assertNotEquals($resultFilename1, $resultFilename2); - Settings::setLibXmlLoaderOptions(null); // reset to default options $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet = $reader->load($sampleFilename); $sheet = $spreadsheet->setActiveSheetIndex(1); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php index a2f21aef..56193419 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php @@ -17,7 +17,6 @@ class UnparsedDataTest extends TestCase { $sampleFilename = 'tests/data/Writer/XLSX/form_pass_print.xlsm'; $resultFilename = File::temporaryFilename(); - Settings::setLibXmlLoaderOptions(null); // reset to default options $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $excel = $reader->load($sampleFilename); diff --git a/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx new file mode 100644 index 00000000..030f8db2 Binary files /dev/null and b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx differ diff --git a/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx new file mode 100644 index 00000000..24893e74 Binary files /dev/null and b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx differ diff --git a/tests/data/Reader/XLS/maccentraleurope.xls b/tests/data/Reader/XLS/maccentraleurope.xls new file mode 100644 index 00000000..98607556 Binary files /dev/null and b/tests/data/Reader/XLS/maccentraleurope.xls differ diff --git a/tests/data/Shared/CodePage.php b/tests/data/Shared/CodePage.php index 82bb23e4..ccb59f24 100644 --- a/tests/data/Shared/CodePage.php +++ b/tests/data/Shared/CodePage.php @@ -233,7 +233,7 @@ return [ ], // Macintosh Central Europe [ - 'MACCENTRALEUROPE', + ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], 10029, ], // Macintosh Icelandic @@ -271,4 +271,9 @@ return [ 'UTF-8', 65001, ], + // invalid + [ + 'exception', + 99999, + ], ]; diff --git a/tests/data/Shared/PasswordHashes.php b/tests/data/Shared/PasswordHashes.php index 34c25cef..c46adf14 100644 --- a/tests/data/Shared/PasswordHashes.php +++ b/tests/data/Shared/PasswordHashes.php @@ -51,4 +51,9 @@ return [ 'Symbols_salt', 100000, ], + // Additional tests suggested by Issue #1897 + ['DCDF', 'ABCDEFGHIJKLMNOPQRSTUVW'], + ['ECD1', 'ABCDEFGHIJKLMNOPQRSTUVWX'], + ['88D2', 'ABCDEFGHIJKLMNOPQRSTUVWXY'], + 'password too long' => ['exception', str_repeat('x', 256)], ];