From dbddcda001bb3e4d3bf8cb1ffa396c58019c1793 Mon Sep 17 00:00:00 2001 From: wapplay Date: Mon, 4 May 2020 14:13:39 +0300 Subject: [PATCH 1/3] #54 overwriting open zip archive fixed --- .php_cs | 32 ++++++++++++++++++++------------ src/ZipFile.php | 34 ++++++++++++++++++---------------- tests/ZipFileTest.php | 15 ++++----------- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/.php_cs b/.php_cs index 5f25716..9e263b9 100644 --- a/.php_cs +++ b/.php_cs @@ -1,7 +1,7 @@ [ 'ignored_tags' => [ @@ -283,7 +283,7 @@ $rules = [ * - Explicit syntax allows word concatenation inside strings, e.g. * `"${var}IsAVar"`, implicit doesn't * - Explicit syntax is easier to detect for IDE/editors and - * therefore has colors/hightlight with higher contrast, which is + * therefore has colors/highlight with higher contrast, which is * easier to read * Backtick operator is skipped because it is harder to handle; you * can use `backtick_to_shell_exec` fixer to normalize backticks to @@ -327,7 +327,7 @@ $rules = [ * want to override a method, use the Template method pattern. * * Risky! - * Risky when overriding `public` methods of `abstract` classes + * Risky when overriding `public` methods of `abstract` classes. */ 'final_public_method_for_abstract_class' => false, @@ -725,8 +725,8 @@ $rules = [ 'no_superfluous_elseif' => true, /* - * Removes `@param` and `@return` tags that don't provide any useful - * information. + * Removes `@param`, `@return` and `@var` tags that don't provide + * any useful information. */ 'no_superfluous_phpdoc_tags' => false, @@ -751,7 +751,13 @@ $rules = [ */ 'no_unneeded_curly_braces' => true, - // A final class must not have final methods. + /* + * A `final` class must not have `final` methods and `private` + * methods must not be `final`. + * + * Risky! + * Risky when child class overrides a `private` method. + */ 'no_unneeded_final_method' => true, /* @@ -1190,8 +1196,8 @@ $rules = [ 'phpdoc_var_annotation_correct_order' => true, /* - * `@var` and `@type` annotations should not contain the variable - * name. + * `@var` and `@type` annotations of classy properties should not + * contain the name. */ 'phpdoc_var_without_name' => false, @@ -1366,7 +1372,7 @@ $rules = [ * `static`. * * Risky! - * Risky when using "->bindTo" on lambdas without referencing to + * Risky when using `->bindTo` on lambdas without referencing to * `$this`. */ 'static_lambda' => true, @@ -1464,12 +1470,14 @@ $rules = [ if (\PHP_SAPI === 'cli' && !class_exists(\PhpCsFixer\Config::class)) { $binFixer = __DIR__ . '/vendor/bin/php-cs-fixer'; + if (!is_file($binFixer)) { $binFixer = 'php-cs-fixer'; } - $dryRun = !\in_array('--force', $_SERVER['argv'], true); + $dryRun = !in_array('--force', $_SERVER['argv'], true); + + $command = escapeshellarg($binFixer) . ' fix --config ' . escapeshellarg(__FILE__) . ' --diff-format udiff --ansi -vv'; - $command = escapeshellarg($binFixer) . ' fix --config ' . escapeshellarg(__FILE__) . ' --diff-format udiff --ansi'; if ($dryRun) { $command .= ' --dry-run'; } diff --git a/src/ZipFile.php b/src/ZipFile.php index 32560c6..8ec622f 100644 --- a/src/ZipFile.php +++ b/src/ZipFile.php @@ -1611,6 +1611,17 @@ public function saveAsFile($filename) } $this->saveAsStream($handle); + $reopen = false; + + if ($this->reader !== null) { + $meta = $this->reader->getStreamMetaData(); + + if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri']) && $meta['uri'] === $filename) { + $this->reader->close(); + $reopen = true; + } + } + if (!@rename($tempFilename, $filename)) { if (is_file($tempFilename)) { unlink($tempFilename); @@ -1619,6 +1630,10 @@ public function saveAsFile($filename) throw new ZipException('Can not move ' . $tempFilename . ' to ' . $filename); } + if ($reopen) { + return $this->openFile($filename); + } + return $this; } @@ -1822,24 +1837,11 @@ public function rewrite() $meta = $this->reader->getStreamMetaData(); - if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri'])) { - $this->saveAsFile($meta['uri']); - $this->close(); - - if (!($handle = @fopen($meta['uri'], 'rb'))) { - throw new ZipException("File {$meta['uri']} can't open."); - } - } else { - $handle = @fopen('php://temp', 'r+b'); - - if (!$handle) { - throw new ZipException('php://temp cannot open for write.'); - } - $this->writeZipToStream($handle); - $this->close(); + if ($meta['wrapper_type'] !== 'plainfile' || !isset($meta['uri'])) { + throw new ZipException('Overwrite is only supported for open local files.'); } - return $this->openFromStream($handle); + return $this->saveAsFile($meta['uri']); } /** diff --git a/tests/ZipFileTest.php b/tests/ZipFileTest.php index 4230f7b..91f630d 100644 --- a/tests/ZipFileTest.php +++ b/tests/ZipFileTest.php @@ -1943,23 +1943,16 @@ public function testRewriteFile() */ public function testRewriteString() { + $this->setExpectedException(ZipException::class, 'Overwrite is only supported for open local files'); + $zipFile = new ZipFile(); $zipFile['file'] = 'content'; - $zipFile['file2'] = 'content2'; $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); $zipFile->openFromString(file_get_contents($this->outputFilename)); - static::assertSame(\count($zipFile), 2); - static::assertTrue(isset($zipFile['file'])); - static::assertTrue(isset($zipFile['file2'])); - $zipFile['file3'] = 'content3'; - $zipFile = $zipFile->rewrite(); - static::assertSame(\count($zipFile), 3); - static::assertTrue(isset($zipFile['file'])); - static::assertTrue(isset($zipFile['file2'])); - static::assertTrue(isset($zipFile['file3'])); - $zipFile->close(); + $zipFile['file2'] = 'content 2'; + $zipFile->rewrite(); } /** From c10c425f7e22575ea6aba6ac2f9bdbcc611fb14d Mon Sep 17 00:00:00 2001 From: wapplay Date: Sat, 11 Jul 2020 23:04:33 +0300 Subject: [PATCH 2/3] Improved Windows compatibility, fix #54 --- .php_cs | 7 +- src/IO/ZipReader.php | 4 +- src/Model/Data/ZipNewData.php | 6 +- src/Util/FilesUtil.php | 8 +- src/ZipFile.php | 101 ++++++++------- tests/Internal/DummyFileSystemStream.php | 2 +- tests/SlowTests/Zip64Test.php | 2 +- tests/SymlinkTest.php | 20 +-- tests/ZipFileSetTestCase.php | 2 +- tests/ZipFileTest.php | 131 ++++++++++++++++++-- tests/ZipPasswordTest.php | 5 +- tests/ZipStreamOpenTest.php | 4 +- tests/ZipTestCase.php | 151 +++++++++++++++-------- 13 files changed, 297 insertions(+), 146 deletions(-) diff --git a/.php_cs b/.php_cs index 9e263b9..2c7f3dd 100644 --- a/.php_cs +++ b/.php_cs @@ -1,7 +1,7 @@ = 7.0. * * Risky! - * [1] This rule is EXPERIMENTAL and is not covered with backward + * This rule is EXPERIMENTAL and [1] is not covered with backward * compatibility promise. [2] `@param` annotation is mandatory for * the fixer to make changes, signatures of methods without it (no * docblock, inheritdocs) will not be fixed. [3] Manual actions are @@ -1159,7 +1160,7 @@ $rules = [ * adjusts accordingly the function signature. Requires PHP >= 7.0. * * Risky! - * [1] This rule is EXPERIMENTAL and is not covered with backward + * This rule is EXPERIMENTAL and [1] is not covered with backward * compatibility promise. [2] `@return` annotation is mandatory for * the fixer to make changes, signatures of methods without it (no * docblock, inheritdocs) will not be fixed. [3] Manual actions are diff --git a/src/IO/ZipReader.php b/src/IO/ZipReader.php index 7dfc97e..f445bf8 100644 --- a/src/IO/ZipReader.php +++ b/src/IO/ZipReader.php @@ -354,7 +354,9 @@ protected function readCentralDirectory(EndOfCentralDirectory $endCD) fseek($this->inStream, $cdOffset); if (!($cdStream = fopen('php://temp', 'w+b'))) { - throw new ZipException('Temp resource can not open from write'); + // @codeCoverageIgnoreStart + throw new ZipException('A temporary resource cannot be opened for writing.'); + // @codeCoverageIgnoreEnd } stream_copy_to_stream($this->inStream, $cdStream, $endCD->getCdSize()); rewind($cdStream); diff --git a/src/Model/Data/ZipNewData.php b/src/Model/Data/ZipNewData.php index 68f76f6..e149638 100644 --- a/src/Model/Data/ZipNewData.php +++ b/src/Model/Data/ZipNewData.php @@ -39,7 +39,9 @@ public function __construct(ZipEntry $zipEntry, $data) $zipEntry->setUncompressedSize(\strlen($data)); if (!($handle = fopen('php://temp', 'w+b'))) { - throw new \RuntimeException('Temp resource can not open from write.'); + // @codeCoverageIgnoreStart + throw new \RuntimeException('A temporary resource cannot be opened for writing.'); + // @codeCoverageIgnoreEnd } fwrite($handle, $data); rewind($handle); @@ -61,7 +63,7 @@ public function __construct(ZipEntry $zipEntry, $data) public function getDataAsStream() { if (!\is_resource($this->stream)) { - throw new \LogicException(sprintf('Resource was closed (entry=%s).', $this->zipEntry->getName())); + throw new \LogicException(sprintf('Resource has been closed (entry=%s).', $this->zipEntry->getName())); } return $this->stream; diff --git a/src/Util/FilesUtil.php b/src/Util/FilesUtil.php index c29c471..3e64614 100644 --- a/src/Util/FilesUtil.php +++ b/src/Util/FilesUtil.php @@ -48,7 +48,7 @@ public static function removeDir($dir) $function = ($fileInfo->isDir() ? 'rmdir' : 'unlink'); $function($fileInfo->getPathname()); } - rmdir($dir); + @rmdir($dir); } /** @@ -198,10 +198,10 @@ public static function globFileSearch($globPattern, $flags = 0, $recursive = tru return $files; } - foreach (glob(\dirname($globPattern) . '/*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) { + foreach (glob(\dirname($globPattern) . \DIRECTORY_SEPARATOR . '*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) { // Unpacking the argument via ... is supported starting from php 5.6 only /** @noinspection SlowArrayOperationsInLoopInspection */ - $files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive)); + $files = array_merge($files, self::globFileSearch($dir . \DIRECTORY_SEPARATOR . basename($globPattern), $flags, $recursive)); } return $files; @@ -273,7 +273,7 @@ public static function humanSize($size, $unit = null) public static function normalizeZipPath($path) { return implode( - '/', + \DIRECTORY_SEPARATOR, array_filter( explode('/', (string) $path), static function ($part) { diff --git a/src/ZipFile.php b/src/ZipFile.php index 8ec622f..384c03f 100644 --- a/src/ZipFile.php +++ b/src/ZipFile.php @@ -1,9 +1,5 @@ false, ]; + /** @noinspection AdditionOperationOnArraysInspection */ $options += $defaultOptions; $zipEntries = $this->zipContainer->getEntries(); @@ -443,9 +440,6 @@ public function extractTo($destDir, $entries = null, array $options = [], &$extr $entryName = FilesUtil::normalizeZipPath($entryName); $file = $destDir . \DIRECTORY_SEPARATOR . $entryName; - if (\DIRECTORY_SEPARATOR === '\\') { - $file = str_replace('/', '\\', $file); - } $extractedEntries[$file] = $entry; $modifyTimestamp = $entry->getMTime()->getTimestamp(); $atime = $entry->getATime(); @@ -568,19 +562,12 @@ public function extractTo($destDir, $entries = null, array $options = [], &$extr */ public function addFromString($entryName, $contents, $compressionMethod = null) { - if ($entryName === null) { - throw new InvalidArgumentException('Entry name is null'); - } + $entryName = $this->normalizeEntryName($entryName); if ($contents === null) { throw new InvalidArgumentException('Contents is null'); } - $entryName = ltrim((string) $entryName, '\\/'); - - if ($entryName === '') { - throw new InvalidArgumentException('Empty entry name'); - } $contents = (string) $contents; $length = \strlen($contents); @@ -609,6 +596,30 @@ public function addFromString($entryName, $contents, $compressionMethod = null) return $this; } + /** + * @param string $entryName + * + * @return string + */ + protected function normalizeEntryName($entryName) + { + if ($entryName === null) { + throw new InvalidArgumentException('Entry name is null'); + } + + $entryName = ltrim((string) $entryName, '\\/'); + + if (\DIRECTORY_SEPARATOR === '\\') { + $entryName = str_replace('\\', '/', $entryName); + } + + if ($entryName === '') { + throw new InvalidArgumentException('Empty entry name'); + } + + return $entryName; + } + /** * @param Finder $finder * @param array $options @@ -624,6 +635,7 @@ public function addFromFinder(Finder $finder, array $options = []) ZipOptions::COMPRESSION_METHOD => null, ZipOptions::MODIFIED_TIME => null, ]; + /** @noinspection AdditionOperationOnArraysInspection */ $options += $defaultOptions; if ($options[ZipOptions::STORE_ONLY_FILES]) { @@ -660,6 +672,7 @@ public function addSplFile(\SplFileInfo $file, $entryName = null, array $options ZipOptions::COMPRESSION_METHOD => null, ZipOptions::MODIFIED_TIME => null, ]; + /** @noinspection AdditionOperationOnArraysInspection */ $options += $defaultOptions; if (!$file->isReadable()) { @@ -674,12 +687,7 @@ public function addSplFile(\SplFileInfo $file, $entryName = null, array $options } } - $entryName = ltrim((string) $entryName, '\\/'); - - if ($entryName === '') { - throw new InvalidArgumentException('Empty entry name'); - } - + $entryName = $this->normalizeEntryName($entryName); $entryName = $file->isDir() ? rtrim($entryName, '/\\') . '/' : $entryName; $zipEntry = new ZipEntry($entryName); @@ -814,17 +822,9 @@ public function addFromStream($stream, $entryName, $compressionMethod = null) throw new InvalidArgumentException('Stream is not resource'); } - if ($entryName === null) { - throw new InvalidArgumentException('Entry name is null'); - } - $entryName = ltrim((string) $entryName, '\\/'); - - if ($entryName === '') { - throw new InvalidArgumentException('Empty entry name'); - } - $fstat = fstat($stream); - + $entryName = $this->normalizeEntryName($entryName); $zipEntry = new ZipEntry($entryName); + $fstat = fstat($stream); if ($fstat !== false) { $unixMode = $fstat['mode']; @@ -875,14 +875,7 @@ public function addFromStream($stream, $entryName, $compressionMethod = null) */ public function addEmptyDir($dirName) { - if ($dirName === null) { - throw new InvalidArgumentException('Dir name is null'); - } - $dirName = ltrim((string) $dirName, '\\/'); - - if ($dirName === '') { - throw new InvalidArgumentException('Empty dir name'); - } + $dirName = $this->normalizeEntryName($dirName); $dirName = rtrim($dirName, '\\/') . '/'; $zipEntry = new ZipEntry($dirName); @@ -1077,6 +1070,7 @@ public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $com * @throws ZipException * * @return ZipFile + * * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ private function addGlob( @@ -1604,10 +1598,10 @@ public function saveAsFile($filename) { $filename = (string) $filename; - $tempFilename = $filename . '.temp' . uniqid('', true); + $tempFilename = $filename . '.temp' . uniqid('', false); if (!($handle = @fopen($tempFilename, 'w+b'))) { - throw new InvalidArgumentException('File ' . $tempFilename . ' can not open from write.'); + throw new InvalidArgumentException(sprintf('Cannot open "%s" for writing.', $tempFilename)); } $this->saveAsStream($handle); @@ -1616,9 +1610,14 @@ public function saveAsFile($filename) if ($this->reader !== null) { $meta = $this->reader->getStreamMetaData(); - if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri']) && $meta['uri'] === $filename) { - $this->reader->close(); - $reopen = true; + if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri'])) { + $readFilePath = realpath($meta['uri']); + $writeFilePath = realpath($filename); + + if ($readFilePath !== false && $writeFilePath !== false && $readFilePath === $writeFilePath) { + $this->reader->close(); + $reopen = true; + } } } @@ -1627,7 +1626,7 @@ public function saveAsFile($filename) unlink($tempFilename); } - throw new ZipException('Can not move ' . $tempFilename . ' to ' . $filename); + throw new ZipException(sprintf('Cannot move %s to %s', $tempFilename, $filename)); } if ($reopen) { diff --git a/tests/Internal/DummyFileSystemStream.php b/tests/Internal/DummyFileSystemStream.php index 1f4817d..d843543 100644 --- a/tests/Internal/DummyFileSystemStream.php +++ b/tests/Internal/DummyFileSystemStream.php @@ -40,7 +40,7 @@ class DummyFileSystemStream public function stream_open($path, $mode, $options, &$opened_path) { $parsedUrl = parse_url($path); - $uri = str_replace('//', '/', $parsedUrl['path']); + $uri = substr($parsedUrl['path'], 1); $this->fp = @fopen($uri, $mode); return $this->fp !== false; diff --git a/tests/SlowTests/Zip64Test.php b/tests/SlowTests/Zip64Test.php index 608137f..f3a2c54 100644 --- a/tests/SlowTests/Zip64Test.php +++ b/tests/SlowTests/Zip64Test.php @@ -76,7 +76,7 @@ public function testCreateLargeZip64File() self::assertCorrectZipArchive($this->outputFilename); if (!is_dir($this->outputDirname)) { - mkdir($this->outputDirname, 0755, true); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); } $zipFile->openFile($this->outputFilename); diff --git a/tests/SymlinkTest.php b/tests/SymlinkTest.php index 2731607..f7d3b7a 100644 --- a/tests/SymlinkTest.php +++ b/tests/SymlinkTest.php @@ -12,22 +12,8 @@ * * @small */ -final class SymlinkTest extends ZipFileTest +final class SymlinkTest extends ZipTestCase { - /** - * This method is called before the first test of this test class is run. - */ - public static function setUpBeforeClass() - { - parent::setUpBeforeClass(); - - if (\DIRECTORY_SEPARATOR === '\\') { - self::markTestSkipped('only linux test'); - - return; - } - } - /** * @dataProvider provideAllowSymlink * @@ -37,6 +23,10 @@ public static function setUpBeforeClass() */ public function testSymlink($allowSymlink) { + if (self::skipTestForWindows()) { + return; + } + if (!is_dir($this->outputDirname)) { self::assertTrue(mkdir($this->outputDirname, 0755, true)); } diff --git a/tests/ZipFileSetTestCase.php b/tests/ZipFileSetTestCase.php index 6872840..f77a15f 100644 --- a/tests/ZipFileSetTestCase.php +++ b/tests/ZipFileSetTestCase.php @@ -75,7 +75,7 @@ protected static function assertFilesResult( $zipEntryName = $localPath . $file; if (isset($actualResultFiles[$file])) { - static::assertTrue(isset($zipFile[$zipEntryName])); + static::assertTrue(isset($zipFile[$zipEntryName]), 'Not found entry name ' . $zipEntryName); static::assertSame( $zipFile[$zipEntryName], $content, diff --git a/tests/ZipFileTest.php b/tests/ZipFileTest.php index 91f630d..a22a524 100644 --- a/tests/ZipFileTest.php +++ b/tests/ZipFileTest.php @@ -34,7 +34,7 @@ public function testOpenFileCantExists() $this->setExpectedException(ZipException::class, 'does not exist'); $zipFile = new ZipFile(); - $zipFile->openFile(uniqid('', true)); + $zipFile->openFile(uniqid('', false)); } /** @@ -42,12 +42,16 @@ public function testOpenFileCantExists() */ public function testOpenFileCantOpen() { - $this->setExpectedException(ZipException::class, 'can\'t open'); + if (static::skipTestForWindows()) { + return; + } if (static::skipTestForRootUser()) { return; } + $this->setExpectedException(ZipException::class, 'can\'t open'); + static::assertNotFalse(file_put_contents($this->outputFilename, 'content')); static::assertTrue(chmod($this->outputFilename, 0222)); @@ -1031,7 +1035,12 @@ public function testExtract() $zipFile->extractTo($this->outputDirname, null, [], $extractedEntries); foreach ($entries as $entryName => $contents) { - $fullExtractedFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $entryName; + $name = $entryName; + + if (\DIRECTORY_SEPARATOR === '\\') { + $name = str_replace('/', '\\', $name); + } + $fullExtractedFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $name; static::assertTrue( isset($extractedEntries[$fullExtractedFilename]), @@ -1393,14 +1402,18 @@ public function testAddFileUnsupportedMethod() /** * @throws ZipException */ - public function testAddFileCantOpen() + public function testAddFileCannotOpen() { - $this->setExpectedException(InvalidArgumentException::class, 'is not readable'); + if (static::skipTestForWindows()) { + return; + } if (static::skipTestForRootUser()) { return; } + $this->setExpectedException(InvalidArgumentException::class, 'is not readable'); + static::assertNotFalse(file_put_contents($this->outputFilename, '')); static::assertTrue(chmod($this->outputFilename, 0244)); @@ -1438,7 +1451,7 @@ public function testAddDirCantExists() $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); $zipFile = new ZipFile(); - $zipFile->addDir(uniqid('', true)); + $zipFile->addDir(uniqid('', false)); } /** @@ -1471,7 +1484,7 @@ public function testAddDirRecursiveCantExists() $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); $zipFile = new ZipFile(); - $zipFile->addDirRecursive(uniqid('', true)); + $zipFile->addDirRecursive(uniqid('', false)); } /** @@ -1711,7 +1724,9 @@ public function testSaveAsStreamBadStream() */ public function testSaveAsFileNotWritable() { - $this->setExpectedException(InvalidArgumentException::class, 'can not open from write'); + if (static::skipTestForWindows()) { + return; + } if (static::skipTestForRootUser()) { return; @@ -1722,6 +1737,8 @@ public function testSaveAsFileNotWritable() $this->outputFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . basename($this->outputFilename); + $this->setExpectedExceptionRegExp(InvalidArgumentException::class, '~Cannot open ".*?" for writing.~'); + $zipFile = new ZipFile(); $zipFile->saveAsFile($this->outputFilename); } @@ -1877,7 +1894,7 @@ public function testUnknownCompressionMethod() */ public function testAddEmptyDirNullName() { - $this->setExpectedException(InvalidArgumentException::class, 'Dir name is null'); + $this->setExpectedException(InvalidArgumentException::class, 'Entry name is null'); $zipFile = new ZipFile(); $zipFile->addEmptyDir(null); @@ -1888,7 +1905,7 @@ public function testAddEmptyDirNullName() */ public function testAddEmptyDirEmptyName() { - $this->setExpectedException(InvalidArgumentException::class, 'Empty dir name'); + $this->setExpectedException(InvalidArgumentException::class, 'Empty entry name'); $zipFile = new ZipFile(); $zipFile->addEmptyDir(''); @@ -1947,12 +1964,21 @@ public function testRewriteString() $zipFile = new ZipFile(); $zipFile['file'] = 'content'; + $zipFile['file2'] = 'content2'; $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); $zipFile->openFromString(file_get_contents($this->outputFilename)); - $zipFile['file2'] = 'content 2'; - $zipFile->rewrite(); + static::assertSame(\count($zipFile), 2); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); + $zipFile['file3'] = 'content3'; + $zipFile = $zipFile->rewrite(); + static::assertSame(\count($zipFile), 3); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); + static::assertTrue(isset($zipFile['file3'])); + $zipFile->close(); } /** @@ -1966,6 +1992,87 @@ public function testRewriteNullStream() $zipFile->rewrite(); } + /** + * Checks the ability to overwrite an open zip file with a relative path. + * + * @throws ZipException + */ + public function testRewriteRelativeFile() + { + $zipFile = new ZipFile(); + $zipFile['entry.txt'] = 'test'; + $zipFile->saveAsFile($this->outputFilename); + $zipFile->close(); + + $outputDirname = \dirname($this->outputFilename); + static::assertTrue(chdir($outputDirname)); + + $relativeFilename = basename($this->outputFilename); + + $zipFile->openFile($this->outputFilename); + $zipFile['entry2.txt'] = 'test'; + $zipFile->saveAsFile($relativeFilename); + $zipFile->close(); + + self::assertCorrectZipArchive($this->outputFilename); + } + + /** + * Checks the ability to overwrite an open zip file with a relative path. + * + * @throws ZipException + */ + public function testRewriteDifferentWinDirectorySeparator() + { + if (\DIRECTORY_SEPARATOR !== '\\') { + static::markTestSkipped('Windows test only'); + + return; + } + + $zipFile = new ZipFile(); + $zipFile['entry.txt'] = 'test'; + $zipFile->saveAsFile($this->outputFilename); + $zipFile->close(); + + $alternativeOutputFilename = str_replace('\\', '/', $this->outputFilename); + self::assertCorrectZipArchive($alternativeOutputFilename); + + $zipFile->openFile($this->outputFilename); + $zipFile['entry2.txt'] = 'test'; + $zipFile->saveAsFile($alternativeOutputFilename); + $zipFile->close(); + + self::assertCorrectZipArchive($alternativeOutputFilename); + + $zipFile->openFile($this->outputFilename); + static::assertCount(2, $zipFile); + $zipFile->close(); + } + + /** + * @throws ZipException + */ + public function testRewriteRelativeFile2() + { + $this->outputFilename = basename($this->outputFilename); + + $zipFile = new ZipFile(); + $zipFile['entry.txt'] = 'test'; + $zipFile->saveAsFile($this->outputFilename); + $zipFile->close(); + + $absoluteOutputFilename = getcwd() . \DIRECTORY_SEPARATOR . $this->outputFilename; + self::assertCorrectZipArchive($absoluteOutputFilename); + + $zipFile->openFile($this->outputFilename); + $zipFile['entry2.txt'] = 'test'; + $zipFile->saveAsFile($absoluteOutputFilename); + $zipFile->close(); + + self::assertCorrectZipArchive($absoluteOutputFilename); + } + /** * @throws ZipException */ diff --git a/tests/ZipPasswordTest.php b/tests/ZipPasswordTest.php index 3690496..88a93b0 100644 --- a/tests/ZipPasswordTest.php +++ b/tests/ZipPasswordTest.php @@ -77,7 +77,8 @@ public function testSetPassword() $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - static::assertCorrectZipArchive($this->outputFilename, $password); + /** @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ WinZip 99-character limit */ + static::assertCorrectZipArchive($this->outputFilename, substr($password, 0, 99)); // check from WinZip AES encryption $zipFile->openFile($this->outputFilename); @@ -137,7 +138,7 @@ public function testTraditionalEncryption() ); } - $password = base64_encode(random_bytes(50)); + $password = md5(random_bytes(50)); $zip = new ZipFile(); $zip->addDirRecursive($this->outputDirname); diff --git a/tests/ZipStreamOpenTest.php b/tests/ZipStreamOpenTest.php index 7ce11a0..42e98e0 100644 --- a/tests/ZipStreamOpenTest.php +++ b/tests/ZipStreamOpenTest.php @@ -29,8 +29,8 @@ class ZipStreamOpenTest extends TestCase */ public function testOpenStream($resource, $exceptionClass = null, $exceptionMessage = null) { - if ($resource === null) { - static::markTestSkipped('skip null resource'); + if ($resource === null || $resource === false) { + static::markTestSkipped('skip resource'); return; } diff --git a/tests/ZipTestCase.php b/tests/ZipTestCase.php index d900208..e195c43 100644 --- a/tests/ZipTestCase.php +++ b/tests/ZipTestCase.php @@ -24,14 +24,14 @@ abstract class ZipTestCase extends TestCase */ protected function setUp() { - $id = uniqid('phpzip', true); - $tempDir = sys_get_temp_dir() . '/phpunit-phpzip'; + $id = uniqid('phpzip', false); + $tempDir = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'phpunit-phpzip'; if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) { - throw new \RuntimeException('Dir ' . $tempDir . " can't created"); + throw new \RuntimeException(sprintf('Directory "%s" was not created', $tempDir)); } - $this->outputFilename = $tempDir . '/' . $id . '.zip'; - $this->outputDirname = $tempDir . '/' . $id; + $this->outputFilename = $tempDir . \DIRECTORY_SEPARATOR . $id . '.zip'; + $this->outputDirname = $tempDir . \DIRECTORY_SEPARATOR . $id; } /** @@ -58,64 +58,91 @@ protected function tearDown() */ public static function assertCorrectZipArchive($filename, $password = null) { - if (self::existsProgram('unzip')) { - $command = 'unzip'; + if (self::existsProgram('7z')) { + self::assertCorrectZipArchiveFrom7z($filename, $password); + } elseif (self::existsProgram('unzip')) { + self::assertCorrectZipArchiveFromUnzip($filename, $password); + } else { + fwrite(\STDERR, 'Skipped testing the zip archive for errors using third-party utilities.' . \PHP_EOL); + fwrite(\STDERR, 'To fix this, install 7-zip or unzip.' . \PHP_EOL); + fwrite(\STDERR, \PHP_EOL); + fwrite(\STDERR, 'Install on Ubuntu: sudo apt-get install p7zip-full unzip' . \PHP_EOL); + fwrite(\STDERR, \PHP_EOL); + fwrite(\STDERR, 'Install on Windows:' . \PHP_EOL); + fwrite(\STDERR, ' * 7-zip - https://www.7-zip.org/download.html' . \PHP_EOL); + fwrite(\STDERR, ' * unzip - http://gnuwin32.sourceforge.net/packages/unzip.htm' . \PHP_EOL); + fwrite(\STDERR, \PHP_EOL); + } + } - if ($password !== null) { - $command .= ' -P ' . escapeshellarg($password); - } - $command .= ' -t ' . escapeshellarg($filename); - $command .= ' 2>&1'; - exec($command, $output, $returnCode); + /** + * @param string $filename + * @param string|null $password + */ + private static function assertCorrectZipArchiveFrom7z($filename, $password = null) + { + $command = '7z t'; - $output = implode(\PHP_EOL, $output); + if ($password !== null) { + $command .= ' -p' . escapeshellarg($password); + } + $command .= ' ' . escapeshellarg($filename) . ' 2>&1'; - if ($password !== null && $returnCode === 81) { - if (self::existsProgram('7z')) { - /** - * WinZip 99-character limit. - * - * @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ - */ - $password = substr($password, 0, 99); - - $command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename); - exec($command, $output, $returnCode); - /** - * @var array $output - */ - $output = implode(\PHP_EOL, $output); - - static::assertSame($returnCode, 0); - static::assertNotContains(' Errors', $output); - static::assertContains(' Ok', $output); - } else { - fwrite(\STDERR, 'Program unzip cannot support this function.' . \PHP_EOL); - fwrite(\STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . \PHP_EOL); - } - } else { - static::assertSame($returnCode, 0, $output); - static::assertNotContains('incorrect password', $output); - static::assertContains(' OK', $output); - static::assertContains('No errors', $output); - } + exec($command, $output, $returnCode); + $output = implode(\PHP_EOL, $output); + + static::assertSame($returnCode, 0); + static::assertNotContains(' Errors', $output); + static::assertContains(' Ok', $output); + } + + /** + * @param string $filename + * @param string|null $password + */ + private static function assertCorrectZipArchiveFromUnzip($filename, $password = null) + { + $command = 'unzip'; + + if ($password !== null) { + $command .= ' -P ' . escapeshellarg($password); } + $command .= ' -t ' . escapeshellarg($filename) . ' 2>&1'; + + exec($command, $output, $returnCode); + $output = implode(\PHP_EOL, $output); + + if ($password !== null && $returnCode === 81) { + fwrite(\STDERR, 'Program unzip cannot support this function.' . \PHP_EOL); + fwrite(\STDERR, 'You have to install 7-zip to complete this test.' . \PHP_EOL); + fwrite(\STDERR, 'Install 7-Zip on Ubuntu: sudo apt-get install p7zip-full' . \PHP_EOL); + fwrite(\STDERR, 'Install 7-Zip on Windows: https://www.7-zip.org/download.html' . \PHP_EOL); + + return; + } + + static::assertSame($returnCode, 0, $output); + static::assertNotContains('incorrect password', $output); + static::assertContains(' OK', $output); + static::assertContains('No errors', $output); } /** * @param string $program + * @param array $successCodes * * @return bool */ - protected static function existsProgram($program) + protected static function existsProgram($program, array $successCodes = [0]) { - if (\DIRECTORY_SEPARATOR !== '\\') { - exec('which ' . escapeshellarg($program), $output, $returnCode); + $command = \DIRECTORY_SEPARATOR === '\\' ? + escapeshellarg($program) : + 'which ' . escapeshellarg($program); + $command .= ' 2>&1'; - return $returnCode === 0; - } - // false for Windows - return false; + exec($command, $output, $returnCode); + + return \in_array($returnCode, $successCodes, true); } /** @@ -144,7 +171,7 @@ public static function assertCorrectEmptyZip($filename) */ public static function assertVerifyZipAlign($filename, $showErrors = false) { - if (self::existsProgram('zipalign')) { + if (self::existsProgram('zipalign', [0, 2])) { exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode); if ($showErrors && $returnCode !== 0) { @@ -155,6 +182,14 @@ public static function assertVerifyZipAlign($filename, $showErrors = false) } fwrite(\STDERR, "Cannot find the program 'zipalign' for the test" . \PHP_EOL); + fwrite(\STDERR, 'To fix this, install zipalign.' . \PHP_EOL); + fwrite(\STDERR, \PHP_EOL); + fwrite(\STDERR, 'Install on Ubuntu: sudo apt-get install zipalign' . \PHP_EOL); + fwrite(\STDERR, \PHP_EOL); + fwrite(\STDERR, 'Install on Windows:' . \PHP_EOL); + fwrite(\STDERR, ' 1. Install Android Studio' . \PHP_EOL); + fwrite(\STDERR, ' 2. Install Android Sdk' . \PHP_EOL); + fwrite(\STDERR, ' 3. Add zipalign path to \$Path' . \PHP_EOL); return null; } @@ -173,4 +208,18 @@ public static function skipTestForRootUser() return false; } + + /** + * @return bool + */ + public static function skipTestForWindows() + { + if (\DIRECTORY_SEPARATOR === '\\') { + static::markTestSkipped('Skip on Windows'); + + return true; + } + + return false; + } } From d9022e80c529b0569b73496f4208c964678465a0 Mon Sep 17 00:00:00 2001 From: wapplay Date: Sat, 11 Jul 2020 23:48:23 +0300 Subject: [PATCH 3/3] LICENSE file added --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b38ec97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2020 Ne-Lexa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.