diff --git a/.travis.yml b/.travis.yml
index 3ebec66..7256300 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,15 +2,15 @@ language: php
env:
global:
- - ZIPALIGN_INSTALL=false
- COVERAGE=false
- - PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests"
+ - PHPUNIT_FLAGS="-v -c phpunit.xml"
matrix:
include:
- php: 5.5
os: linux
dist: trusty
+ env: ZIPALIGN_INSTALL=false
- php: 5.6
os: linux
@@ -40,7 +40,7 @@ matrix:
- php: 7.4
os: linux
dist: xenial
- env: COVERAGE=true ZIPALIGN_INSTALL=true PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests --coverage-clover=coverage.clover"
+ env: COVERAGE=true ZIPALIGN_INSTALL=true PHPUNIT_FLAGS="-v -c phpunit.xml --coverage-clover=coverage.clover"
before_install:
- if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
diff --git a/composer.json b/composer.json
index d360777..7e75f36 100644
--- a/composer.json
+++ b/composer.json
@@ -31,6 +31,7 @@
"ext-bz2": "*",
"ext-openssl": "*",
"ext-fileinfo": "*",
+ "ext-xml": "*",
"guzzlehttp/psr7": "^1.6",
"phpunit/phpunit": "^4.8|^5.7",
"symfony/var-dumper": "^3.0|^4.0|^5.0"
diff --git a/phpunit.xml b/phpunit.xml
index 046df3b..cbb13f5 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -22,6 +22,12 @@
+
+
+ large
+
+
+
src
diff --git a/src/Constants/ZipOptions.php b/src/Constants/ZipOptions.php
index 25c88a7..c5d9671 100644
--- a/src/Constants/ZipOptions.php
+++ b/src/Constants/ZipOptions.php
@@ -2,6 +2,9 @@
namespace PhpZip\Constants;
+use PhpZip\IO\ZipReader;
+use PhpZip\ZipFile;
+
/**
* Interface ZipOptions.
*/
@@ -10,20 +13,50 @@ interface ZipOptions
/**
* Boolean option for store just file names (skip directory names).
*
- * @var string
+ * @see ZipFile::addFromFinder()
*/
const STORE_ONLY_FILES = 'only_files';
- /** @var string */
+ /**
+ * Uses the specified compression method.
+ *
+ * @see ZipFile::addFromFinder()
+ * @see ZipFile::addSplFile()
+ */
const COMPRESSION_METHOD = 'compression_method';
- /** @var string */
+ /**
+ * Set the specified record modification time.
+ * The value can be {@see \DateTimeInterface}, integer timestamp
+ * or a string of any format.
+ *
+ * @see ZipFile::addFromFinder()
+ * @see ZipFile::addSplFile()
+ */
const MODIFIED_TIME = 'mtime';
/**
- * @var string
+ * Specifies the encoding of the record name for cases when the UTF-8
+ * usage flag is not set.
*
+ * The most commonly used encodings are compiled into the constants
+ * of the {@see DosCodePage} class.
+ *
+ * @see ZipFile::openFile()
+ * @see ZipFile::openFromString()
+ * @see ZipFile::openFromStream()
+ * @see ZipReader::getDefaultOptions()
* @see DosCodePage::getCodePages()
*/
const CHARSET = 'charset';
+
+ /**
+ * Allows ({@see true}) or denies ({@see false}) unpacking unix symlinks.
+ *
+ * This is a potentially dangerous operation for uncontrolled zip files.
+ * By default is ({@see false}).
+ *
+ * @see https://josipfranjkovic.blogspot.com/2014/12/reading-local-files-from-facebooks.html
+ */
+ const EXTRACT_SYMLINKS = 'extract_symlinks';
}
diff --git a/src/IO/ZipWriter.php b/src/IO/ZipWriter.php
index 5b0a332..b5cc8a8 100644
--- a/src/IO/ZipWriter.php
+++ b/src/IO/ZipWriter.php
@@ -39,7 +39,9 @@ class ZipWriter
*/
public function __construct(ZipContainer $container)
{
- $this->zipContainer = $container;
+ // we clone the container so that the changes made to
+ // it do not affect the data in the ZipFile class
+ $this->zipContainer = clone $container;
}
/**
diff --git a/src/Model/Data/ZipNewData.php b/src/Model/Data/ZipNewData.php
index 4b1dd0e..68f76f6 100644
--- a/src/Model/Data/ZipNewData.php
+++ b/src/Model/Data/ZipNewData.php
@@ -4,18 +4,27 @@ namespace PhpZip\Model\Data;
use PhpZip\Model\ZipData;
use PhpZip\Model\ZipEntry;
+use PhpZip\ZipFile;
/**
- * Class ZipNewData.
+ * The class contains a streaming resource with new content added to the ZIP archive.
*/
class ZipNewData implements ZipData
{
- /** @var resource */
- private $stream;
+ /**
+ * A static variable allows closing the stream in the destructor
+ * only if it is its sole holder.
+ *
+ * @var array array of resource ids and the number of class clones
+ */
+ private static $guardClonedStream = [];
/** @var ZipEntry */
private $zipEntry;
+ /** @var resource */
+ private $stream;
+
/**
* ZipStringData constructor.
*
@@ -38,6 +47,12 @@ class ZipNewData implements ZipData
} elseif (\is_resource($data)) {
$this->stream = $data;
}
+
+ $resourceId = (int) $this->stream;
+ self::$guardClonedStream[$resourceId] =
+ isset(self::$guardClonedStream[$resourceId]) ?
+ self::$guardClonedStream[$resourceId] + 1 :
+ 0;
}
/**
@@ -79,8 +94,35 @@ class ZipNewData implements ZipData
stream_copy_to_stream($stream, $outStream);
}
+ /**
+ * @see https://php.net/manual/en/language.oop5.cloning.php
+ */
+ public function __clone()
+ {
+ $resourceId = (int) $this->stream;
+ self::$guardClonedStream[$resourceId] =
+ isset(self::$guardClonedStream[$resourceId]) ?
+ self::$guardClonedStream[$resourceId] + 1 :
+ 1;
+ }
+
+ /**
+ * The stream will be closed when closing the zip archive.
+ *
+ * The method implements protection against closing the stream of the cloned object.
+ *
+ * @see ZipFile::close()
+ */
public function __destruct()
{
+ $resourceId = (int) $this->stream;
+
+ if (isset(self::$guardClonedStream[$resourceId]) && self::$guardClonedStream[$resourceId] > 0) {
+ self::$guardClonedStream[$resourceId]--;
+
+ return;
+ }
+
if (\is_resource($this->stream)) {
fclose($this->stream);
}
diff --git a/src/Model/ImmutableZipContainer.php b/src/Model/ImmutableZipContainer.php
index 72e0d33..69722a0 100644
--- a/src/Model/ImmutableZipContainer.php
+++ b/src/Model/ImmutableZipContainer.php
@@ -53,4 +53,20 @@ class ImmutableZipContainer implements \Countable
{
return \count($this->entries);
}
+
+ /**
+ * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
+ * Any properties that are references to other variables, will remain references.
+ * Once the cloning is complete, if a __clone() method is defined,
+ * then the newly created object's __clone() method will be called, to allow any necessary properties that need to
+ * be changed. NOT CALLABLE DIRECTLY.
+ *
+ * @see https://php.net/manual/en/language.oop5.cloning.php
+ */
+ public function __clone()
+ {
+ foreach ($this->entries as $key => $value) {
+ $this->entries[$key] = clone $value;
+ }
+ }
}
diff --git a/src/Model/ZipContainer.php b/src/Model/ZipContainer.php
index f3c2d9c..6cfe87e 100644
--- a/src/Model/ZipContainer.php
+++ b/src/Model/ZipContainer.php
@@ -12,7 +12,12 @@ use PhpZip\Exception\ZipException;
*/
class ZipContainer extends ImmutableZipContainer
{
- /** @var ImmutableZipContainer|null */
+ /**
+ * @var ImmutableZipContainer|null The source container contains zip entries from
+ * an open zip archive. The source container makes
+ * it possible to undo changes in the archive.
+ * When cloning, this container is not cloned.
+ */
private $sourceContainer;
/**
diff --git a/src/Util/FilesUtil.php b/src/Util/FilesUtil.php
index 6b016e8..c29c471 100644
--- a/src/Util/FilesUtil.php
+++ b/src/Util/FilesUtil.php
@@ -43,9 +43,10 @@ final class FilesUtil
\RecursiveIteratorIterator::CHILD_FIRST
);
+ /** @var \SplFileInfo $fileInfo */
foreach ($files as $fileInfo) {
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
- $function($fileInfo->getRealPath());
+ $function($fileInfo->getPathname());
}
rmdir($dir);
}
@@ -303,36 +304,19 @@ final class FilesUtil
}
/**
- * @param string $linkPath
* @param string $target
+ * @param string $path
+ * @param bool $allowSymlink
*
* @return bool
*/
- public static function symlink($target, $linkPath)
+ public static function symlink($target, $path, $allowSymlink)
{
- if (\DIRECTORY_SEPARATOR === '\\') {
- $linkPath = str_replace('/', '\\', $linkPath);
- $target = str_replace('/', '\\', $target);
- $abs = null;
-
- if (!self::isAbsolutePath($target)) {
- $abs = realpath(\dirname($linkPath) . \DIRECTORY_SEPARATOR . $target);
-
- if (\is_string($abs)) {
- $target = $abs;
- }
- }
+ if (\DIRECTORY_SEPARATOR === '\\' || !$allowSymlink) {
+ return file_put_contents($path, $target) !== false;
}
- if (!symlink($target, $linkPath)) {
- if (\DIRECTORY_SEPARATOR === '\\' && is_file($target)) {
- return copy($target, $linkPath);
- }
-
- return false;
- }
-
- return true;
+ return symlink($target, $path);
}
/**
diff --git a/src/ZipFile.php b/src/ZipFile.php
index a0ea78f..32560c6 100644
--- a/src/ZipFile.php
+++ b/src/ZipFile.php
@@ -6,6 +6,7 @@
namespace PhpZip;
+use PhpZip\Constants\UnixStat;
use PhpZip\Constants\ZipCompressionLevel;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipEncryptionMethod;
@@ -20,6 +21,7 @@ use PhpZip\IO\ZipReader;
use PhpZip\IO\ZipWriter;
use PhpZip\Model\Data\ZipFileData;
use PhpZip\Model\Data\ZipNewData;
+use PhpZip\Model\ImmutableZipContainer;
use PhpZip\Model\ZipContainer;
use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipEntryMatcher;
@@ -68,7 +70,7 @@ class ZipFile implements ZipFileInterface
*/
public function __construct()
{
- $this->zipContainer = new ZipContainer();
+ $this->zipContainer = $this->createZipContainer(null);
}
/**
@@ -90,6 +92,16 @@ class ZipFile implements ZipFileInterface
return new ZipWriter($this->zipContainer);
}
+ /**
+ * @param ImmutableZipContainer|null $sourceContainer
+ *
+ * @return ZipContainer
+ */
+ protected function createZipContainer(ImmutableZipContainer $sourceContainer = null)
+ {
+ return new ZipContainer($sourceContainer);
+ }
+
/**
* Open zip archive from file.
*
@@ -130,7 +142,9 @@ class ZipFile implements ZipFileInterface
}
if (!($handle = fopen('php://temp', 'r+b'))) {
+ // @codeCoverageIgnoreStart
throw new ZipException("Can't open temp stream.");
+ // @codeCoverageIgnoreEnd
}
fwrite($handle, $data);
rewind($handle);
@@ -151,7 +165,7 @@ class ZipFile implements ZipFileInterface
public function openFromStream($handle, array $options = [])
{
$this->reader = $this->createZipReader($handle, $options);
- $this->zipContainer = new ZipContainer($this->reader->read());
+ $this->zipContainer = $this->createZipContainer($this->reader->read());
return $this;
}
@@ -363,15 +377,20 @@ class ZipFile implements ZipFileInterface
*
* Extract the complete archive or the given files to the specified destination.
*
- * @param string $destDir location where to extract the files
- * @param array|string|null $entries The entries to extract. It accepts either
- * a single entry name or an array of names.
+ * @param string $destDir location where to extract the files
+ * @param array|string|null $entries entries to extract
+ * @param array $options extract options
+ * @param array $extractedEntries if the extractedEntries argument
+ * is present, then the specified
+ * array will be filled with
+ * information about the
+ * extracted entries
*
* @throws ZipException
*
* @return ZipFile
*/
- public function extractTo($destDir, $entries = null)
+ public function extractTo($destDir, $entries = null, array $options = [], &$extractedEntries = [])
{
if (!file_exists($destDir)) {
throw new ZipException(sprintf('Destination %s not found', $destDir));
@@ -385,7 +404,14 @@ class ZipFile implements ZipFileInterface
throw new ZipException('Destination is not writable directory');
}
- $extractedEntries = [];
+ if ($extractedEntries === null) {
+ $extractedEntries = [];
+ }
+
+ $defaultOptions = [
+ ZipOptions::EXTRACT_SYMLINKS => false,
+ ];
+ $options += $defaultOptions;
$zipEntries = $this->zipContainer->getEntries();
@@ -435,7 +461,9 @@ class ZipFile implements ZipFileInterface
}
if (!mkdir($dir, $dirMode, true) && !is_dir($dir)) {
+ // @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
+ // @codeCoverageIgnoreEnd
}
chmod($dir, $dirMode);
}
@@ -471,6 +499,7 @@ class ZipFile implements ZipFileInterface
/** @noinspection PhpUsageOfSilenceOperatorInspection */
if (!($handle = @fopen($file, 'w+b'))) {
+ // @codeCoverageIgnoreStart
throw new ZipException(
sprintf(
'Cannot extract zip entry %s. File %s cannot open for write.',
@@ -478,6 +507,7 @@ class ZipFile implements ZipFileInterface
$file
)
);
+ // @codeCoverageIgnoreEnd
}
try {
@@ -486,9 +516,8 @@ class ZipFile implements ZipFileInterface
unlink($file);
throw $e;
- } finally {
- fclose($handle);
}
+ fclose($handle);
if ($unixMode === 0) {
$unixMode = 0644;
@@ -503,8 +532,10 @@ class ZipFile implements ZipFileInterface
}
}
+ $allowSymlink = (bool) $options[ZipOptions::EXTRACT_SYMLINKS];
+
foreach ($symlinks as $linkPath => $target) {
- if (!FilesUtil::symlink($target, $linkPath)) {
+ if (!FilesUtil::symlink($target, $linkPath, $allowSymlink)) {
unset($extractedEntries[$linkPath]);
}
}
@@ -515,7 +546,7 @@ class ZipFile implements ZipFileInterface
touch($dir, $lastMod);
}
-// ksort($extractedEntries);
+ ksort($extractedEntries);
return $this;
}
@@ -652,9 +683,24 @@ class ZipFile implements ZipFileInterface
$entryName = $file->isDir() ? rtrim($entryName, '/\\') . '/' : $entryName;
$zipEntry = new ZipEntry($entryName);
- $zipData = null;
+ $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX);
+ $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
- if ($file->isFile()) {
+ $zipData = null;
+ $filePerms = $file->getPerms();
+
+ if ($file->isLink()) {
+ $linkTarget = $file->getLinkTarget();
+ $lengthLinkTarget = \strlen($linkTarget);
+
+ $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
+ $zipEntry->setUncompressedSize($lengthLinkTarget);
+ $zipEntry->setCompressedSize($lengthLinkTarget);
+ $zipEntry->setCrc(crc32($linkTarget));
+ $filePerms |= UnixStat::UNX_IFLNK;
+
+ $zipData = new ZipNewData($zipEntry, $linkTarget);
+ } elseif ($file->isFile()) {
if (isset($options[ZipOptions::COMPRESSION_METHOD])) {
$compressionMethod = $options[ZipOptions::COMPRESSION_METHOD];
} elseif ($file->getSize() < 512) {
@@ -673,21 +719,9 @@ class ZipFile implements ZipFileInterface
$zipEntry->setUncompressedSize(0);
$zipEntry->setCompressedSize(0);
$zipEntry->setCrc(0);
- } elseif ($file->isLink()) {
- $linkTarget = $file->getLinkTarget();
- $lengthLinkTarget = \strlen($linkTarget);
-
- $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
- $zipEntry->setUncompressedSize($lengthLinkTarget);
- $zipEntry->setCompressedSize($lengthLinkTarget);
- $zipEntry->setCrc(crc32($linkTarget));
-
- $zipData = new ZipNewData($zipEntry, $linkTarget);
}
- $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
- $zipEntry->setUnixMode($file->getPerms());
+ $zipEntry->setUnixMode($filePerms);
$timestamp = null;
@@ -1768,8 +1802,9 @@ class ZipFile implements ZipFileInterface
if ($this->reader !== null) {
$this->reader->close();
$this->reader = null;
- $this->zipContainer = new ZipContainer();
}
+ $this->zipContainer = $this->createZipContainer(null);
+ gc_collect_cycles();
}
/**
diff --git a/src/ZipFileInterface.php b/src/ZipFileInterface.php
index 1ba1850..07108d1 100644
--- a/src/ZipFileInterface.php
+++ b/src/ZipFileInterface.php
@@ -292,15 +292,20 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
*
* Extract the complete archive or the given files to the specified destination.
*
- * @param string $destDir location where to extract the files
- * @param array|string|null $entries The entries to extract. It accepts either
- * a single entry name or an array of names.
+ * @param string $destDir location where to extract the files
+ * @param array|string|null $entries entries to extract
+ * @param array $options extract options
+ * @param array $extractedEntries if the extractedEntries argument
+ * is present, then the specified
+ * array will be filled with
+ * information about the
+ * extracted entries
*
* @throws ZipException
*
* @return ZipFile
*/
- public function extractTo($destDir, $entries = null);
+ public function extractTo($destDir, $entries = null, array $options = [], &$extractedEntries = []);
/**
* Add entry from the string.
diff --git a/tests/CustomZipFormatTest.php b/tests/CustomZipFormatTest.php
new file mode 100644
index 0000000..020162f
--- /dev/null
+++ b/tests/CustomZipFormatTest.php
@@ -0,0 +1,126 @@
+openFile(__DIR__ . '/resources/Advanced-v1.0.0.epub');
+ self::assertSame($epubFile->getRootFile(), 'EPUB/package.opf');
+ self::assertSame($epubFile->getMimeType(), 'application/epub+zip');
+ $epubInfo = $epubFile->getEpubInfo();
+ self::assertSame($epubInfo->toArray(), [
+ 'title' => 'Advanced Accessibility Tests: Extended Descriptions',
+ 'creator' => 'DAISY Consortium Transition to EPUB 3 and DIAGRAM Standards WG',
+ 'language' => 'en-US',
+ 'publisher' => 'DAISY Consortium and DIAGRAM Center',
+ 'description' => 'Tests for accessible extended descriptions of images in EPUBs',
+ 'rights' => 'This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike (CC BY-NC-SA) license.',
+ 'date' => '2019-01-03',
+ 'subject' => 'extended-descriptions',
+ ]);
+ $epubFile->deleteFromName('mimetype');
+ self::assertFalse($epubFile->hasEntry('mimetype'));
+
+ try {
+ $epubFile->getMimeType();
+ self::fail('deleted mimetype');
+ } catch (ZipEntryNotFoundException $e) {
+ self::assertSame('Zip Entry "mimetype" was not found in the archive.', $e->getMessage());
+ }
+ $epubFile->saveAsFile($this->outputFilename);
+ self::assertFalse($epubFile->hasEntry('mimetype'));
+ $epubFile->close();
+
+ self::assertCorrectZipArchive($this->outputFilename);
+
+ $epubFile->openFile($this->outputFilename);
+ // file appended in EpubWriter before write
+ self::assertTrue($epubFile->hasEntry('mimetype'));
+ $epubFile->close();
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function testBeforeSaveInZipWriter()
+ {
+ $zipFile = new ZipFileCustomWriter();
+ for ($i = 0; $i < 10; $i++) {
+ $zipFile['file ' . $i] = 'contents file ' . $i;
+ }
+ $this->existsExtraFields($zipFile, false);
+ $zipFile->saveAsFile($this->outputFilename);
+ $this->existsExtraFields($zipFile, false);
+ $zipFile->close();
+
+ self::assertCorrectZipArchive($this->outputFilename);
+
+ $zipFile->openFile($this->outputFilename);
+ $this->existsExtraFields($zipFile, true);
+ $zipFile->close();
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function testBeforeSaveInZipFile()
+ {
+ $zipFile = new ZipFileWithBeforeSave();
+ for ($i = 0; $i < 10; $i++) {
+ $zipFile['file ' . $i] = 'contents file ' . $i;
+ }
+ $this->existsExtraFields($zipFile, false);
+ $zipFile->saveAsFile($this->outputFilename);
+ $this->existsExtraFields($zipFile, true);
+ $zipFile->close();
+
+ self::assertCorrectZipArchive($this->outputFilename);
+
+ $zipFile->openFile($this->outputFilename);
+ $this->existsExtraFields($zipFile, true);
+ $zipFile->close();
+ }
+
+ /**
+ * @param ZipFile $zipFile
+ * @param bool $exists
+ */
+ private function existsExtraFields(ZipFile $zipFile, $exists)
+ {
+ foreach ($zipFile->getEntries() as $entry) {
+ $localExtras = $entry->getLocalExtraFields();
+ $cdExtras = $entry->getCdExtraFields();
+
+ self::assertSame(isset($localExtras[NtfsExtraField::HEADER_ID]), $exists);
+ self::assertSame(isset($cdExtras[NtfsExtraField::HEADER_ID]), $exists);
+
+ self::assertSame(isset($localExtras[NewUnixExtraField::HEADER_ID]), $exists);
+ self::assertSame(isset($cdExtras[NewUnixExtraField::HEADER_ID]), $exists);
+ }
+ }
+}
diff --git a/tests/Extra/Fields/AbstractUnicodeExtraFieldTest.php b/tests/Extra/Fields/AbstractUnicodeExtraFieldTest.php
new file mode 100644
index 0000000..2dd5ee4
--- /dev/null
+++ b/tests/Extra/Fields/AbstractUnicodeExtraFieldTest.php
@@ -0,0 +1,101 @@
+
+ */
+ abstract protected function getUnicodeExtraFieldClassName();
+
+ /**
+ * @dataProvider provideExtraField
+ *
+ * @param int $crc32
+ * @param string $unicodePath
+ * @param string $originalPath
+ * @param string $binaryData
+ *
+ * @throws ZipException
+ */
+ public function testExtraField($crc32, $unicodePath, $originalPath, $binaryData)
+ {
+ $crc32 = (int) $crc32; // for php 32-bit
+
+ $className = $this->getUnicodeExtraFieldClassName();
+
+ /** @var AbstractUnicodeExtraField $extraField */
+ $extraField = new $className($crc32, $unicodePath);
+ static::assertSame($extraField->getCrc32(), $crc32);
+ static::assertSame($extraField->getUnicodeValue(), $unicodePath);
+ static::assertSame(crc32($originalPath), $crc32);
+
+ static::assertSame($binaryData, $extraField->packLocalFileData());
+ static::assertSame($binaryData, $extraField->packCentralDirData());
+ static::assertEquals($className::unpackLocalFileData($binaryData), $extraField);
+ static::assertEquals($className::unpackCentralDirData($binaryData), $extraField);
+ }
+
+ /**
+ * @return array
+ */
+ abstract public function provideExtraField();
+
+ public function testSetter()
+ {
+ $className = $this->getUnicodeExtraFieldClassName();
+ $entryName = '11111';
+
+ /** @var AbstractUnicodeExtraField $extraField */
+ $extraField = new $className(crc32($entryName), '22222');
+ static::assertSame($extraField->getHeaderId(), $className::HEADER_ID);
+ static::assertSame($extraField->getCrc32(), crc32($entryName));
+ static::assertSame($extraField->getUnicodeValue(), '22222');
+
+ $crc32 = 1234567;
+ $extraField->setCrc32($crc32);
+ static::assertSame($extraField->getCrc32(), $crc32);
+ $extraField->setUnicodeValue('44444');
+ static::assertSame($extraField->getUnicodeValue(), '44444');
+ }
+
+ /**
+ * @throws ZipException
+ */
+ public function testUnicodeErrorParse()
+ {
+ $this->setExpectedException(
+ ZipException::class,
+ 'Unicode path extra data must have at least 5 bytes.'
+ );
+
+ $className = $this->getUnicodeExtraFieldClassName();
+ $className::unpackLocalFileData('');
+ }
+
+ /**
+ * @throws ZipException
+ */
+ public function testUnknownVersionParse()
+ {
+ $this->setExpectedException(
+ ZipException::class,
+ 'Unsupported version [2] for Unicode path extra data.'
+ );
+
+ $className = $this->getUnicodeExtraFieldClassName();
+ $className::unpackLocalFileData("\x02\x04a\xD28\xC3\xA4\\\xC3\xBC.txt");
+ }
+}
diff --git a/tests/Extra/Fields/ApkAlignmentExtraFieldTest.php b/tests/Extra/Fields/ApkAlignmentExtraFieldTest.php
new file mode 100644
index 0000000..ddc68f5
--- /dev/null
+++ b/tests/Extra/Fields/ApkAlignmentExtraFieldTest.php
@@ -0,0 +1,106 @@
+getHeaderId(), ApkAlignmentExtraField::HEADER_ID);
+ self::assertSame($extraField->getMultiple(), $multiple);
+ self::assertSame($extraField->getPadding(), $padding);
+
+ self::assertSame($extraField->packLocalFileData(), $binaryData);
+ self::assertSame($extraField->packCentralDirData(), $binaryData);
+
+ self::assertEquals(ApkAlignmentExtraField::unpackLocalFileData($binaryData), $extraField);
+ self::assertEquals(ApkAlignmentExtraField::unpackCentralDirData($binaryData), $extraField);
+
+ self::assertSame((string) $extraField, $toString);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ ApkAlignmentExtraField::ALIGNMENT_BYTES,
+ 0,
+ "\x04\x00",
+ '0xd935 APK Alignment: Multiple=4 Padding=0',
+ ],
+ [
+ ApkAlignmentExtraField::ALIGNMENT_BYTES,
+ 1,
+ "\x04\x00\x00",
+ '0xd935 APK Alignment: Multiple=4 Padding=1',
+ ],
+ [
+ ApkAlignmentExtraField::ALIGNMENT_BYTES,
+ 2,
+ "\x04\x00\x00\x00",
+ '0xd935 APK Alignment: Multiple=4 Padding=2',
+ ],
+ [
+ ApkAlignmentExtraField::ALIGNMENT_BYTES,
+ 3,
+ "\x04\x00\x00\x00\x00",
+ '0xd935 APK Alignment: Multiple=4 Padding=3',
+ ],
+ [
+ ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES,
+ 4023,
+ "\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ '0xd935 APK Alignment: Multiple=4096 Padding=4023',
+ ],
+ ];
+ }
+
+ public function testSetter()
+ {
+ $extraField = new ApkAlignmentExtraField(ApkAlignmentExtraField::ALIGNMENT_BYTES, 3);
+ $extraField->setMultiple(ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES);
+ self::assertSame($extraField->getMultiple(), ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES);
+ $extraField->setPadding(1);
+ self::assertSame(1, $extraField->getPadding());
+ }
+
+ /**
+ * @throws ZipException
+ */
+ public function testInvalidParse()
+ {
+ $this->setExpectedException(
+ ZipException::class,
+ 'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.'
+ );
+
+ ApkAlignmentExtraField::unpackLocalFileData("\x04");
+ }
+}
diff --git a/tests/Extra/Fields/AsiExtraFieldTest.php b/tests/Extra/Fields/AsiExtraFieldTest.php
new file mode 100644
index 0000000..5360e87
--- /dev/null
+++ b/tests/Extra/Fields/AsiExtraFieldTest.php
@@ -0,0 +1,101 @@
+getHeaderId(), AsiExtraField::HEADER_ID);
+
+ self::assertSame($asiExtraField->getMode(), $mode);
+ self::assertSame($asiExtraField->getUserId(), $uid);
+ self::assertSame($asiExtraField->getGroupId(), $uid);
+ self::assertSame($asiExtraField->getLink(), $link);
+
+ self::assertSame($asiExtraField->packLocalFileData(), $binaryData);
+ self::assertSame($asiExtraField->packCentralDirData(), $binaryData);
+
+ self::assertEquals(AsiExtraField::unpackLocalFileData($binaryData), $asiExtraField);
+ self::assertEquals(AsiExtraField::unpackCentralDirData($binaryData), $asiExtraField);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ 040755,
+ AsiExtraField::USER_GID_PID,
+ AsiExtraField::USER_GID_PID,
+ '',
+ "#\x06\\\xF6\xEDA\x00\x00\x00\x00\xE8\x03\xE8\x03",
+ ],
+ [
+ 0100644,
+ 0,
+ 0,
+ 'sites-enabled/example.conf',
+ "_\xB8\xC7b\xA4\x81\x1A\x00\x00\x00\x00\x00\x00\x00sites-enabled/example.conf",
+ ],
+ ];
+ }
+
+ public function testSetter()
+ {
+ $extraField = new AsiExtraField(0777);
+ $extraField->setMode(0100666);
+ self::assertSame(0100666, $extraField->getMode());
+ $extraField->setUserId(700);
+ self::assertSame(700, $extraField->getUserId());
+ $extraField->setGroupId(500);
+ self::assertSame(500, $extraField->getGroupId());
+ $extraField->setLink('link.txt');
+ self::assertSame($extraField->getLink(), 'link.txt');
+ self::assertSame(0120666, $extraField->getMode());
+
+ // dir mode
+ $extraField->setMode(0755);
+ self::assertSame(0120755, $extraField->getMode());
+ $extraField->setLink('');
+ self::assertSame($extraField->getLink(), '');
+ self::assertSame(0100755, $extraField->getMode());
+ }
+
+ /**
+ * @throws Crc32Exception
+ */
+ public function testInvalidParse()
+ {
+ $this->setExpectedException(
+ Crc32Exception::class,
+ 'Asi Unix Extra Filed Data (expected CRC32 value'
+ );
+
+ AsiExtraField::unpackLocalFileData("\x01\x06\\\xF6\xEDA\x00\x00\x00\x00\xE8\x03\xE8\x03");
+ }
+}
diff --git a/tests/Extra/Fields/ExtendedTimestampExtraFieldTest.php b/tests/Extra/Fields/ExtendedTimestampExtraFieldTest.php
new file mode 100644
index 0000000..0da3af5
--- /dev/null
+++ b/tests/Extra/Fields/ExtendedTimestampExtraFieldTest.php
@@ -0,0 +1,158 @@
+getHeaderId(), ExtendedTimestampExtraField::HEADER_ID);
+ self::assertSame($localExtraField->getFlags(), $flags);
+ self::assertSame($localExtraField->getModifyTime(), $modifyTime);
+ self::assertSame($localExtraField->getAccessTime(), $accessTime);
+ self::assertSame($localExtraField->getCreateTime(), $createTime);
+ self::assertSame($localExtraField->packLocalFileData(), $localData);
+ self::assertEquals(ExtendedTimestampExtraField::unpackLocalFileData($localData), $localExtraField);
+
+ $extTimeField = ExtendedTimestampExtraField::create($modifyTime, $accessTime, $createTime);
+ self::assertEquals($extTimeField, $localExtraField);
+
+ $accessTime = null;
+ $createTime = null;
+ $cdExtraField = new ExtendedTimestampExtraField($flags, $modifyTime, $accessTime, $createTime);
+ self::assertSame($cdExtraField->getHeaderId(), ExtendedTimestampExtraField::HEADER_ID);
+ self::assertSame($cdExtraField->getFlags(), $flags);
+ self::assertSame($cdExtraField->getModifyTime(), $modifyTime);
+ self::assertSame($cdExtraField->getAccessTime(), $accessTime);
+ self::assertSame($cdExtraField->getCreateTime(), $createTime);
+ self::assertSame($cdExtraField->packCentralDirData(), $cdData);
+ self::assertEquals(ExtendedTimestampExtraField::unpackCentralDirData($cdData), $cdExtraField);
+ self::assertSame($localExtraField->packCentralDirData(), $cdData);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ ExtendedTimestampExtraField::MODIFY_TIME_BIT |
+ ExtendedTimestampExtraField::ACCESS_TIME_BIT |
+ ExtendedTimestampExtraField::CREATE_TIME_BIT,
+ 911512006,
+ 911430000,
+ 893709400,
+ "\x07\xC6\x91T6pQS6X\xECD5",
+ "\x07\xC6\x91T6",
+ ],
+ [
+ ExtendedTimestampExtraField::MODIFY_TIME_BIT |
+ ExtendedTimestampExtraField::ACCESS_TIME_BIT,
+ 1492955702,
+ 1492955638,
+ null,
+ "\x036\xB2\xFCX\xF6\xB1\xFCX",
+ "\x036\xB2\xFCX",
+ ],
+ [
+ ExtendedTimestampExtraField::MODIFY_TIME_BIT,
+ 1470494391,
+ null,
+ null,
+ "\x01\xB7\xF6\xA5W",
+ "\x01\xB7\xF6\xA5W",
+ ],
+ ];
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function testSetter()
+ {
+ $mtime = time();
+ $atime = null;
+ $ctime = null;
+
+ $field = ExtendedTimestampExtraField::create($mtime, $atime, $ctime);
+ self::assertSame($field->getFlags(), ExtendedTimestampExtraField::MODIFY_TIME_BIT);
+ self::assertSame($field->getModifyTime(), $mtime);
+ self::assertEquals($field->getModifyDateTime(), new \DateTimeImmutable('@' . $mtime));
+ self::assertSame($field->getAccessTime(), $atime);
+ self::assertSame($field->getCreateTime(), $ctime);
+
+ $atime = strtotime('-1 min');
+ $field->setAccessTime($atime);
+ self::assertSame(
+ $field->getFlags(),
+ ExtendedTimestampExtraField::MODIFY_TIME_BIT |
+ ExtendedTimestampExtraField::ACCESS_TIME_BIT
+ );
+ self::assertSame($field->getModifyTime(), $mtime);
+ self::assertSame($field->getAccessTime(), $atime);
+ self::assertEquals($field->getAccessDateTime(), new \DateTimeImmutable('@' . $atime));
+ self::assertSame($field->getCreateTime(), $ctime);
+
+ $ctime = strtotime('-1 hour');
+ $field->setCreateTime($ctime);
+ self::assertSame(
+ $field->getFlags(),
+ ExtendedTimestampExtraField::MODIFY_TIME_BIT |
+ ExtendedTimestampExtraField::ACCESS_TIME_BIT |
+ ExtendedTimestampExtraField::CREATE_TIME_BIT
+ );
+ self::assertSame($field->getModifyTime(), $mtime);
+ self::assertSame($field->getAccessTime(), $atime);
+ self::assertSame($field->getCreateTime(), $ctime);
+ self::assertEquals($field->getCreateDateTime(), new \DateTimeImmutable('@' . $ctime));
+
+ $field->setCreateTime(null);
+ self::assertNull($field->getCreateTime());
+ self::assertNull($field->getCreateDateTime());
+ self::assertSame(
+ $field->getFlags(),
+ ExtendedTimestampExtraField::MODIFY_TIME_BIT |
+ ExtendedTimestampExtraField::ACCESS_TIME_BIT
+ );
+
+ $field->setAccessTime(null);
+ self::assertNull($field->getAccessTime());
+ self::assertNull($field->getAccessDateTime());
+ self::assertSame($field->getFlags(), ExtendedTimestampExtraField::MODIFY_TIME_BIT);
+
+ $field->setModifyTime(null);
+ self::assertNull($field->getModifyTime());
+ self::assertNull($field->getModifyDateTime());
+ self::assertSame($field->getFlags(), 0);
+ }
+}
diff --git a/tests/Extra/Fields/JarMarkerExtraFieldTest.php b/tests/Extra/Fields/JarMarkerExtraFieldTest.php
new file mode 100644
index 0000000..2067215
--- /dev/null
+++ b/tests/Extra/Fields/JarMarkerExtraFieldTest.php
@@ -0,0 +1,55 @@
+packLocalFileData());
+ self::assertSame('', $jarField->packCentralDirData());
+ self::assertEquals(JarMarkerExtraField::unpackLocalFileData(''), $jarField);
+ self::assertEquals(JarMarkerExtraField::unpackCentralDirData(''), $jarField);
+ }
+
+ /**
+ * @throws ZipException
+ */
+ public function testInvalidUnpackLocalData()
+ {
+ $this->setExpectedException(
+ ZipException::class,
+ "JarMarker doesn't expect any data"
+ );
+
+ JarMarkerExtraField::unpackLocalFileData("\x02\x00\00");
+ }
+
+ /**
+ * @throws ZipException
+ */
+ public function testInvalidUnpackCdData()
+ {
+ $this->setExpectedException(
+ ZipException::class,
+ "JarMarker doesn't expect any data"
+ );
+
+ JarMarkerExtraField::unpackCentralDirData("\x02\x00\00");
+ }
+}
diff --git a/tests/Extra/Fields/NewUnixExtraFieldTest.php b/tests/Extra/Fields/NewUnixExtraFieldTest.php
new file mode 100644
index 0000000..02cf159
--- /dev/null
+++ b/tests/Extra/Fields/NewUnixExtraFieldTest.php
@@ -0,0 +1,97 @@
+getHeaderId(), NewUnixExtraField::HEADER_ID);
+ self::assertSame($extraField->getVersion(), $version);
+ self::assertSame($extraField->getGid(), $gid);
+ self::assertSame($extraField->getUid(), $uid);
+
+ self::assertEquals(NewUnixExtraField::unpackLocalFileData($binaryData), $extraField);
+ self::assertEquals(NewUnixExtraField::unpackCentralDirData($binaryData), $extraField);
+
+ self::assertSame($extraField->packLocalFileData(), $binaryData);
+ self::assertSame($extraField->packCentralDirData(), $binaryData);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ 1,
+ NewUnixExtraField::USER_GID_PID,
+ NewUnixExtraField::USER_GID_PID,
+ "\x01\x04\xE8\x03\x00\x00\x04\xE8\x03\x00\x00",
+ ],
+ [
+ 1,
+ 501,
+ 20,
+ "\x01\x04\xF5\x01\x00\x00\x04\x14\x00\x00\x00",
+ ],
+ [
+ 1,
+ 500,
+ 495,
+ "\x01\x04\xF4\x01\x00\x00\x04\xEF\x01\x00\x00",
+ ],
+ [
+ 1,
+ 11252,
+ 10545,
+ "\x01\x04\xF4+\x00\x00\x041)\x00\x00",
+ ],
+ [
+ 1,
+ 1721,
+ 1721,
+ "\x01\x04\xB9\x06\x00\x00\x04\xB9\x06\x00\x00",
+ ],
+ ];
+ }
+
+ public function testSetter()
+ {
+ $extraField = new NewUnixExtraField(1, 1000, 1000);
+ self::assertSame(1, $extraField->getVersion());
+ self::assertSame(1000, $extraField->getUid());
+ self::assertSame(1000, $extraField->getGid());
+
+ $extraField->setUid(0);
+ self::assertSame(0, $extraField->getUid());
+ self::assertSame(1000, $extraField->getGid());
+
+ $extraField->setGid(0);
+ self::assertSame(0, $extraField->getUid());
+ self::assertSame(0, $extraField->getGid());
+ }
+}
diff --git a/tests/Extra/Fields/NtfsExtraFieldTest.php b/tests/Extra/Fields/NtfsExtraFieldTest.php
new file mode 100644
index 0000000..822a51f
--- /dev/null
+++ b/tests/Extra/Fields/NtfsExtraFieldTest.php
@@ -0,0 +1,245 @@
+getHeaderId(), NtfsExtraField::HEADER_ID);
+
+ self::assertEquals($extraField->getModifyDateTime()->getTimestamp(), (int) $modifyTimestamp);
+ self::assertEquals($extraField->getAccessDateTime()->getTimestamp(), (int) $accessTimestamp);
+ self::assertEquals($extraField->getCreateDateTime()->getTimestamp(), (int) $createTimestamp);
+
+ self::assertEquals(NtfsExtraField::unpackLocalFileData($binaryData), $extraField);
+ self::assertEquals(NtfsExtraField::unpackCentralDirData($binaryData), $extraField);
+
+ self::assertSame($extraField->packLocalFileData(), $binaryData);
+ self::assertSame($extraField->packCentralDirData(), $binaryData);
+
+ $extraFieldFromDateTime = NtfsExtraField::create(
+ $extraField->getModifyDateTime(),
+ $extraField->getAccessDateTime(),
+ $extraField->getCreateDateTime()
+ );
+
+ self::assertEqualsIntegerWithDelta($extraFieldFromDateTime->getModifyNtfsTime(), $extraField->getModifyNtfsTime(), 100);
+ self::assertEqualsIntegerWithDelta($extraFieldFromDateTime->getAccessNtfsTime(), $extraField->getAccessNtfsTime(), 100);
+ self::assertEqualsIntegerWithDelta($extraFieldFromDateTime->getCreateNtfsTime(), $extraField->getCreateNtfsTime(), 100);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ 129853553114795379,
+ 129853553114795379,
+ 129853552641022547,
+ 1340881711.4795379,
+ 1340881711.4795379,
+ 1340881664.1022547,
+ "\x00\x00\x00\x00\x01\x00\x18\x00s\xCD:Z\x1EU\xCD\x01s\xCD:Z\x1EU\xCD\x01S\x9A\xFD=\x1EU\xCD\x01",
+ ],
+ [
+ 131301570250000000,
+ 131865940850000000,
+ 131840940680000000,
+ 1485683425.000000,
+ 1542120485.000000,
+ 1539620468.000000,
+ "\x00\x00\x00\x00\x01\x00\x18\x00\x80\xC63\x1D\x15z\xD2\x01\x80@V\xE2_{\xD4\x01\x00\xB2\x15\x14\xA3d\xD4\x01",
+ ],
+ [
+ 132181086710000000,
+ 132181086710000000,
+ 132181086710000000,
+ 1573635071.000000,
+ 1573635071.000000,
+ 1573635071.000000,
+ "\x00\x00\x00\x00\x01\x00\x18\x00\x80\xE9_\x7F\xFF\x99\xD5\x01\x80\xE9_\x7F\xFF\x99\xD5\x01\x80\xE9_\x7F\xFF\x99\xD5\x01",
+ ],
+ ];
+ }
+
+ /**
+ * @param int $expected
+ * @param int $actual
+ * @param int $delta
+ * @param string $message
+ */
+ private static function assertEqualsIntegerWithDelta(
+ $expected,
+ $actual,
+ $delta,
+ $message = ''
+ ) {
+ self::assertSame(
+ self::roundInt($expected, $delta),
+ self::roundInt($actual, $delta),
+ $message
+ );
+ }
+
+ /**
+ * @param int $number
+ * @param int $delta
+ *
+ * @return int
+ */
+ private static function roundInt($number, $delta)
+ {
+ return (int) (floor($number / $delta) * $delta);
+ }
+
+ /**
+ * @dataProvider provideExtraField
+ *
+ * @param int $mtimeNtfs
+ * @param int $atimeNtfs
+ * @param int $ctimeNtfs
+ * @param float $mtimeTimestamp
+ * @param float $atimeTimestamp
+ * @param float $ctimeTimestamp
+ *
+ * @noinspection PhpTooManyParametersInspection
+ * @noinspection PhpUnitDeprecationsInspection
+ */
+ public function testConverter(
+ $mtimeNtfs,
+ $atimeNtfs,
+ $ctimeNtfs,
+ $mtimeTimestamp,
+ $atimeTimestamp,
+ $ctimeTimestamp
+ ) {
+ self::assertEquals(NtfsExtraField::ntfsTimeToTimestamp($mtimeNtfs), $mtimeTimestamp, '', 0.00001);
+ self::assertEquals(NtfsExtraField::ntfsTimeToTimestamp($atimeNtfs), $atimeTimestamp, '', 0.00001);
+ self::assertEquals(NtfsExtraField::ntfsTimeToTimestamp($ctimeNtfs), $ctimeTimestamp, '', 0.00001);
+
+ self::assertEqualsIntegerWithDelta(NtfsExtraField::timestampToNtfsTime($mtimeTimestamp), $mtimeNtfs, 10);
+ self::assertEqualsIntegerWithDelta(NtfsExtraField::timestampToNtfsTime($atimeTimestamp), $atimeNtfs, 10);
+ self::assertEqualsIntegerWithDelta(NtfsExtraField::timestampToNtfsTime($ctimeTimestamp), $ctimeNtfs, 10);
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function testSetter()
+ {
+ $timeZone = new \DateTimeZone('UTC');
+ $initDateTime = new \DateTimeImmutable('-1 min', $timeZone);
+ $mtimeDateTime = new \DateTimeImmutable('-1 hour', $timeZone);
+ $atimeDateTime = new \DateTimeImmutable('-1 day', $timeZone);
+ $ctimeDateTime = new \DateTimeImmutable('-1 year', $timeZone);
+
+ $extraField = NtfsExtraField::create($initDateTime, $initDateTime, $initDateTime);
+ self::assertEquals(
+ $extraField->getModifyDateTime()->getTimestamp(),
+ $initDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getAccessDateTime()->getTimestamp(),
+ $initDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getCreateDateTime()->getTimestamp(),
+ $initDateTime->getTimestamp()
+ );
+
+ $extraField->setModifyDateTime($mtimeDateTime);
+ self::assertEquals(
+ $extraField->getModifyDateTime()->getTimestamp(),
+ $mtimeDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getAccessDateTime()->getTimestamp(),
+ $initDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getCreateDateTime()->getTimestamp(),
+ $initDateTime->getTimestamp()
+ );
+
+ $extraField->setAccessDateTime($atimeDateTime);
+ self::assertEquals(
+ $extraField->getModifyDateTime()->getTimestamp(),
+ $mtimeDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getAccessDateTime()->getTimestamp(),
+ $atimeDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getCreateDateTime()->getTimestamp(),
+ $initDateTime->getTimestamp()
+ );
+
+ $extraField->setCreateDateTime($ctimeDateTime);
+ self::assertEquals(
+ $extraField->getModifyDateTime()->getTimestamp(),
+ $mtimeDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getAccessDateTime()->getTimestamp(),
+ $atimeDateTime->getTimestamp()
+ );
+ self::assertEquals(
+ $extraField->getCreateDateTime()->getTimestamp(),
+ $ctimeDateTime->getTimestamp()
+ );
+
+ $newModifyNtfsTime = $extraField->getCreateNtfsTime();
+ $newAccessNtfsTime = $extraField->getModifyNtfsTime();
+ $newCreateNtfsTime = $extraField->getAccessNtfsTime();
+ $extraField->setModifyNtfsTime($newModifyNtfsTime);
+ $extraField->setAccessNtfsTime($newAccessNtfsTime);
+ $extraField->setCreateNtfsTime($newCreateNtfsTime);
+ self::assertSame($extraField->getModifyNtfsTime(), $newModifyNtfsTime);
+ self::assertSame($extraField->getAccessNtfsTime(), $newAccessNtfsTime);
+ self::assertSame($extraField->getCreateNtfsTime(), $newCreateNtfsTime);
+ }
+}
diff --git a/tests/Extra/Fields/OldUnixExtraFieldTest.php b/tests/Extra/Fields/OldUnixExtraFieldTest.php
new file mode 100644
index 0000000..bf0d78a
--- /dev/null
+++ b/tests/Extra/Fields/OldUnixExtraFieldTest.php
@@ -0,0 +1,156 @@
+getHeaderId(), OldUnixExtraField::HEADER_ID);
+
+ self::assertSame($extraField->getAccessTime(), $accessTime);
+ self::assertSame($extraField->getModifyTime(), $modifyTime);
+ self::assertSame($extraField->getUid(), $uid);
+ self::assertSame($extraField->getGid(), $gid);
+
+ if ($extraField->getModifyTime() !== null) {
+ self::assertEquals(
+ new \DateTimeImmutable('@' . $extraField->getModifyTime()),
+ $extraField->getModifyDateTime()
+ );
+ }
+
+ if ($extraField->getAccessTime() !== null) {
+ self::assertEquals(
+ new \DateTimeImmutable('@' . $extraField->getAccessTime()),
+ $extraField->getAccessDateTime()
+ );
+ }
+
+ self::assertEquals(OldUnixExtraField::unpackLocalFileData($localBinaryData), $extraField);
+ self::assertSame($extraField->packLocalFileData(), $localBinaryData);
+
+ $uid = null;
+ $gid = null;
+ $extraField = new OldUnixExtraField($accessTime, $modifyTime, $uid, $gid);
+ self::assertSame($extraField->getHeaderId(), OldUnixExtraField::HEADER_ID);
+
+ self::assertSame($extraField->getAccessTime(), $accessTime);
+ self::assertSame($extraField->getModifyTime(), $modifyTime);
+ self::assertNull($extraField->getUid());
+ self::assertNull($extraField->getGid());
+
+ if ($extraField->getModifyTime() !== null) {
+ self::assertEquals(
+ new \DateTimeImmutable('@' . $extraField->getModifyTime()),
+ $extraField->getModifyDateTime()
+ );
+ }
+
+ if ($extraField->getAccessTime() !== null) {
+ self::assertEquals(
+ new \DateTimeImmutable('@' . $extraField->getAccessTime()),
+ $extraField->getAccessDateTime()
+ );
+ }
+
+ self::assertEquals(OldUnixExtraField::unpackCentralDirData($cdBinaryData), $extraField);
+ self::assertSame($extraField->packCentralDirData(), $cdBinaryData);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ 1213373265,
+ 1213365834,
+ 502,
+ 502,
+ "Q\x9BRHJ~RH\xF6\x01\xF6\x01",
+ "Q\x9BRHJ~RH",
+ ],
+ [
+ 935520420,
+ 935520401,
+ 501,
+ 100,
+ "\xA4\xE8\xC27\x91\xE8\xC27\xF5\x01d\x00",
+ "\xA4\xE8\xC27\x91\xE8\xC27",
+ ],
+ [
+ 1402666135,
+ 1402666135,
+ 501,
+ 20,
+ "\x97\xFC\x9AS\x97\xFC\x9AS\xF5\x01\x14\x00",
+ "\x97\xFC\x9AS\x97\xFC\x9AS",
+ ],
+ [
+ null,
+ null,
+ null,
+ null,
+ '',
+ '',
+ ],
+ ];
+ }
+
+ public function testSetter()
+ {
+ $extraField = new OldUnixExtraField(null, null, null, null);
+
+ self::assertNull($extraField->getAccessTime());
+ self::assertNull($extraField->getAccessDateTime());
+ self::assertNull($extraField->getModifyTime());
+ self::assertNull($extraField->getModifyDateTime());
+ self::assertNull($extraField->getUid());
+ self::assertNull($extraField->getGid());
+
+ $extraField->setModifyTime(1402666135);
+ self::assertSame($extraField->getModifyTime(), 1402666135);
+
+ $extraField->setAccessTime(1213365834);
+ self::assertSame($extraField->getAccessTime(), 1213365834);
+
+ $extraField->setUid(500);
+ self::assertSame($extraField->getUid(), 500);
+
+ $extraField->setGid(100);
+ self::assertSame($extraField->getGid(), 100);
+ }
+}
diff --git a/tests/Extra/Fields/UnicodeCommentExtraFieldTest.php b/tests/Extra/Fields/UnicodeCommentExtraFieldTest.php
new file mode 100644
index 0000000..a637111
--- /dev/null
+++ b/tests/Extra/Fields/UnicodeCommentExtraFieldTest.php
@@ -0,0 +1,42 @@
+getHeaderId(), $headerId);
+ self::assertSame($unrecognizedExtraField->getData(), $binaryData);
+
+ $newHeaderId = 0xDADA;
+ $newBinaryData = "\x05\x00";
+ $unrecognizedExtraField->setHeaderId($newHeaderId);
+ self::assertSame($unrecognizedExtraField->getHeaderId(), $newHeaderId);
+ $unrecognizedExtraField->setData($newBinaryData);
+ self::assertSame($unrecognizedExtraField->getData(), $newBinaryData);
+
+ self::assertSame($unrecognizedExtraField->packLocalFileData(), $newBinaryData);
+ self::assertSame($unrecognizedExtraField->packCentralDirData(), $newBinaryData);
+ }
+
+ public function testUnpackLocalData()
+ {
+ $this->setExpectedException(
+ RuntimeException::class,
+ 'Unsupport parse'
+ );
+
+ UnrecognizedExtraField::unpackLocalFileData("\x01\x02");
+ }
+
+ public function testUnpackCentralDirData()
+ {
+ $this->setExpectedException(
+ RuntimeException::class,
+ 'Unsupport parse'
+ );
+
+ UnrecognizedExtraField::unpackCentralDirData("\x01\x02");
+ }
+}
diff --git a/tests/Extra/Fields/WinZipAesExtraFieldTest.php b/tests/Extra/Fields/WinZipAesExtraFieldTest.php
new file mode 100644
index 0000000..70e7a69
--- /dev/null
+++ b/tests/Extra/Fields/WinZipAesExtraFieldTest.php
@@ -0,0 +1,240 @@
+getHeaderId(), WinZipAesExtraField::HEADER_ID);
+ self::assertSame($extraField->getVendorVersion(), $vendorVersion);
+ self::assertSame($extraField->getKeyStrength(), $keyStrength);
+ self::assertSame($extraField->getCompressionMethod(), $compressionMethod);
+ self::assertSame($extraField->getVendorId(), WinZipAesExtraField::VENDOR_ID);
+ self::assertSame($extraField->getSaltSize(), $saltSize);
+
+ self::assertSame($binaryData, $extraField->packLocalFileData());
+ self::assertSame($binaryData, $extraField->packCentralDirData());
+ self::assertEquals(WinZipAesExtraField::unpackLocalFileData($binaryData), $extraField);
+ self::assertEquals(WinZipAesExtraField::unpackCentralDirData($binaryData), $extraField);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ WinZipAesExtraField::VERSION_AE1,
+ WinZipAesExtraField::KEY_STRENGTH_128BIT,
+ ZipCompressionMethod::STORED,
+ 8,
+ "\x01\x00AE\x01\x00\x00",
+ ],
+ [
+ WinZipAesExtraField::VERSION_AE1,
+ WinZipAesExtraField::KEY_STRENGTH_192BIT,
+ ZipCompressionMethod::DEFLATED,
+ 12,
+ "\x01\x00AE\x02\x08\x00",
+ ],
+ [
+ WinZipAesExtraField::VERSION_AE2,
+ WinZipAesExtraField::KEY_STRENGTH_128BIT,
+ ZipCompressionMethod::DEFLATED,
+ 8,
+ "\x02\x00AE\x01\x08\x00",
+ ],
+ [
+ WinZipAesExtraField::VERSION_AE2,
+ WinZipAesExtraField::KEY_STRENGTH_256BIT,
+ ZipCompressionMethod::STORED,
+ 16,
+ "\x02\x00AE\x03\x00\x00",
+ ],
+ [
+ WinZipAesExtraField::VERSION_AE2,
+ WinZipAesExtraField::KEY_STRENGTH_192BIT,
+ ZipCompressionMethod::DEFLATED,
+ 12,
+ "\x02\x00AE\x02\x08\x00",
+ ],
+ [
+ WinZipAesExtraField::VERSION_AE2,
+ WinZipAesExtraField::KEY_STRENGTH_256BIT,
+ ZipCompressionMethod::STORED,
+ 16,
+ "\x02\x00AE\x03\x00\x00",
+ ],
+ ];
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testSetter()
+ {
+ $extraField = new WinZipAesExtraField(
+ WinZipAesExtraField::VERSION_AE1,
+ WinZipAesExtraField::KEY_STRENGTH_256BIT,
+ ZipCompressionMethod::DEFLATED
+ );
+
+ self::assertSame($extraField->getVendorVersion(), WinZipAesExtraField::VERSION_AE1);
+ self::assertSame($extraField->getKeyStrength(), WinZipAesExtraField::KEY_STRENGTH_256BIT);
+ self::assertSame($extraField->getCompressionMethod(), ZipCompressionMethod::DEFLATED);
+ self::assertSame($extraField->getSaltSize(), 16);
+ self::assertSame($extraField->getEncryptionStrength(), 256);
+ self::assertSame($extraField->getEncryptionMethod(), ZipEncryptionMethod::WINZIP_AES_256);
+
+ $extraField->setVendorVersion(WinZipAesExtraField::VERSION_AE2);
+ self::assertSame($extraField->getVendorVersion(), WinZipAesExtraField::VERSION_AE2);
+ self::assertSame($extraField->getKeyStrength(), WinZipAesExtraField::KEY_STRENGTH_256BIT);
+ self::assertSame($extraField->getCompressionMethod(), ZipCompressionMethod::DEFLATED);
+ self::assertSame($extraField->getSaltSize(), 16);
+ self::assertSame($extraField->getEncryptionStrength(), 256);
+ self::assertSame($extraField->getEncryptionMethod(), ZipEncryptionMethod::WINZIP_AES_256);
+
+ $extraField->setKeyStrength(WinZipAesExtraField::KEY_STRENGTH_128BIT);
+ self::assertSame($extraField->getVendorVersion(), WinZipAesExtraField::VERSION_AE2);
+ self::assertSame($extraField->getKeyStrength(), WinZipAesExtraField::KEY_STRENGTH_128BIT);
+ self::assertSame($extraField->getCompressionMethod(), ZipCompressionMethod::DEFLATED);
+ self::assertSame($extraField->getSaltSize(), 8);
+ self::assertSame($extraField->getEncryptionStrength(), 128);
+ self::assertSame($extraField->getEncryptionMethod(), ZipEncryptionMethod::WINZIP_AES_128);
+
+ $extraField->setKeyStrength(WinZipAesExtraField::KEY_STRENGTH_192BIT);
+ self::assertSame($extraField->getVendorVersion(), WinZipAesExtraField::VERSION_AE2);
+ self::assertSame($extraField->getKeyStrength(), WinZipAesExtraField::KEY_STRENGTH_192BIT);
+ self::assertSame($extraField->getCompressionMethod(), ZipCompressionMethod::DEFLATED);
+ self::assertSame($extraField->getSaltSize(), 12);
+ self::assertSame($extraField->getEncryptionStrength(), 192);
+ self::assertSame($extraField->getEncryptionMethod(), ZipEncryptionMethod::WINZIP_AES_192);
+
+ $extraField->setCompressionMethod(ZipCompressionMethod::STORED);
+ self::assertSame($extraField->getVendorVersion(), WinZipAesExtraField::VERSION_AE2);
+ self::assertSame($extraField->getKeyStrength(), WinZipAesExtraField::KEY_STRENGTH_192BIT);
+ self::assertSame($extraField->getCompressionMethod(), ZipCompressionMethod::STORED);
+ self::assertSame($extraField->getSaltSize(), 12);
+ self::assertSame($extraField->getEncryptionStrength(), 192);
+ self::assertSame($extraField->getEncryptionMethod(), ZipEncryptionMethod::WINZIP_AES_192);
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testConstructUnsupportVendorVersion()
+ {
+ $this->setExpectedException(InvalidArgumentException::class, 'Unsupport WinZip AES vendor version: 3');
+
+ new WinZipAesExtraField(
+ 3,
+ WinZipAesExtraField::KEY_STRENGTH_192BIT,
+ ZipCompressionMethod::STORED
+ );
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testSetterUnsupportVendorVersion()
+ {
+ $this->setExpectedException(InvalidArgumentException::class, 'Unsupport WinZip AES vendor version: 3');
+
+ $extraField = new WinZipAesExtraField(
+ WinZipAesExtraField::VERSION_AE1,
+ WinZipAesExtraField::KEY_STRENGTH_192BIT,
+ ZipCompressionMethod::STORED
+ );
+ $extraField->setVendorVersion(3);
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testConstructUnsupportCompressionMethod()
+ {
+ $this->setExpectedException(ZipUnsupportMethodException::class, 'Compression method 3 (Reduced compression factor 2) is not supported.');
+
+ new WinZipAesExtraField(
+ WinZipAesExtraField::VERSION_AE1,
+ WinZipAesExtraField::KEY_STRENGTH_192BIT,
+ 3
+ );
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testSetterUnsupportCompressionMethod()
+ {
+ $this->setExpectedException(ZipUnsupportMethodException::class, 'Compression method 3 (Reduced compression factor 2) is not supported.');
+
+ $extraField = new WinZipAesExtraField(
+ WinZipAesExtraField::VERSION_AE1,
+ WinZipAesExtraField::KEY_STRENGTH_192BIT,
+ ZipCompressionMethod::STORED
+ );
+ $extraField->setCompressionMethod(3);
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testConstructUnsupportKeyStrength()
+ {
+ $this->setExpectedException(InvalidArgumentException::class, 'Key strength 16 not support value. Allow values: 1, 2, 3');
+
+ new WinZipAesExtraField(
+ WinZipAesExtraField::VERSION_AE1,
+ 0x10,
+ ZipCompressionMethod::STORED
+ );
+ }
+
+ /**
+ * @throws ZipUnsupportMethodException
+ */
+ public function testSetterUnsupportKeyStrength()
+ {
+ $this->setExpectedException(InvalidArgumentException::class, 'Key strength 16 not support value. Allow values: 1, 2, 3');
+
+ new WinZipAesExtraField(
+ WinZipAesExtraField::VERSION_AE1,
+ 0x10,
+ ZipCompressionMethod::STORED
+ );
+ }
+}
diff --git a/tests/Extra/Fields/Zip64ExtraFieldTest.php b/tests/Extra/Fields/Zip64ExtraFieldTest.php
new file mode 100644
index 0000000..815128d
--- /dev/null
+++ b/tests/Extra/Fields/Zip64ExtraFieldTest.php
@@ -0,0 +1,132 @@
+getHeaderId(), Zip64ExtraField::HEADER_ID);
+ self::assertSame($extraField->getUncompressedSize(), $uncompressedSize);
+ self::assertSame($extraField->getCompressedSize(), $compressedSize);
+ self::assertSame($extraField->getLocalHeaderOffset(), $localHeaderOffset);
+ self::assertSame($extraField->getDiskStart(), $diskStart);
+
+ $zipEntry = new ZipEntry('entry');
+ $zipEntry->setUncompressedSize($uncompressedSize !== null ? ZipConstants::ZIP64_MAGIC : 0xfffff);
+ $zipEntry->setCompressedSize($compressedSize !== null ? ZipConstants::ZIP64_MAGIC : 0xffff);
+ $zipEntry->setLocalHeaderOffset($localHeaderOffset !== null ? ZipConstants::ZIP64_MAGIC : 0xfff);
+
+ if ($localBinaryData !== null) {
+ self::assertSame($localBinaryData, $extraField->packLocalFileData());
+ self::assertEquals(Zip64ExtraField::unpackLocalFileData($localBinaryData, $zipEntry), $extraField);
+ }
+
+ if ($cdBinaryData !== null) {
+ self::assertSame($cdBinaryData, $extraField->packCentralDirData());
+ self::assertEquals(Zip64ExtraField::unpackCentralDirData($cdBinaryData, $zipEntry), $extraField);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function provideExtraField()
+ {
+ return [
+ [
+ 0,
+ 2,
+ null,
+ null,
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00",
+ null,
+ ],
+ [
+ 5368709120,
+ 5369580144,
+ null,
+ null,
+ null,
+ "\x00\x00\x00@\x01\x00\x00\x00pJ\x0D@\x01\x00\x00\x00",
+ ],
+ [
+ null,
+ null,
+ 4945378839,
+ null,
+ null,
+ "\x17~\xC4&\x01\x00\x00\x00",
+ ],
+ ];
+ }
+
+ public function testSetter()
+ {
+ $extraField = new Zip64ExtraField();
+ self::assertNull($extraField->getUncompressedSize());
+ self::assertNull($extraField->getCompressedSize());
+ self::assertNull($extraField->getLocalHeaderOffset());
+ self::assertNull($extraField->getDiskStart());
+
+ $uncompressedSize = 12222;
+ $extraField->setUncompressedSize($uncompressedSize);
+ self::assertSame($extraField->getUncompressedSize(), $uncompressedSize);
+
+ $compressedSize = 12222;
+ $extraField->setCompressedSize($uncompressedSize);
+ self::assertSame($extraField->getCompressedSize(), $compressedSize);
+
+ $localHeaderOffset = 12222;
+ $extraField->setLocalHeaderOffset($localHeaderOffset);
+ self::assertSame($extraField->getLocalHeaderOffset(), $localHeaderOffset);
+
+ $diskStart = 2;
+ $extraField->setDiskStart($diskStart);
+ self::assertSame($extraField->getDiskStart(), $diskStart);
+ }
+}
diff --git a/tests/Internal/CustomZip/CustomZipWriter.php b/tests/Internal/CustomZip/CustomZipWriter.php
new file mode 100644
index 0000000..4d91ee5
--- /dev/null
+++ b/tests/Internal/CustomZip/CustomZipWriter.php
@@ -0,0 +1,39 @@
+zipContainer);
+ }
+
+ protected function beforeWrite()
+ {
+ parent::beforeWrite();
+ $now = new \DateTimeImmutable();
+ $ntfsTimeExtra = NtfsExtraField::create($now, $now->modify('-1 day'), $now->modify('-10 day'));
+ $unixExtra = new NewUnixExtraField();
+
+ foreach ($this->zipContainer->getEntries() as $entry) {
+ $entry->addExtraField($ntfsTimeExtra);
+ $entry->addExtraField($unixExtra);
+ }
+ }
+}
diff --git a/tests/Internal/CustomZip/ZipFileCustomWriter.php b/tests/Internal/CustomZip/ZipFileCustomWriter.php
new file mode 100644
index 0000000..6c143b0
--- /dev/null
+++ b/tests/Internal/CustomZip/ZipFileCustomWriter.php
@@ -0,0 +1,20 @@
+zipContainer);
+ }
+}
diff --git a/tests/Internal/CustomZip/ZipFileWithBeforeSave.php b/tests/Internal/CustomZip/ZipFileWithBeforeSave.php
new file mode 100644
index 0000000..98d470f
--- /dev/null
+++ b/tests/Internal/CustomZip/ZipFileWithBeforeSave.php
@@ -0,0 +1,28 @@
+modify('-1 day'), $now->modify('-10 day'));
+ $unixExtra = new NewUnixExtraField();
+
+ foreach ($this->zipContainer->getEntries() as $entry) {
+ $entry->addExtraField($ntfsTimeExtra);
+ $entry->addExtraField($unixExtra);
+ }
+ }
+}
diff --git a/tests/Internal/Epub/EpubFile.php b/tests/Internal/Epub/EpubFile.php
new file mode 100644
index 0000000..a30cb76
--- /dev/null
+++ b/tests/Internal/Epub/EpubFile.php
@@ -0,0 +1,98 @@
+zipContainer);
+ }
+
+ /**
+ * @param resource $inputStream
+ * @param array $options
+ *
+ * @return ZipReader
+ */
+ protected function createZipReader($inputStream, array $options = [])
+ {
+ return new EpubReader($inputStream, $options);
+ }
+
+ /**
+ * @param ImmutableZipContainer|null $sourceContainer
+ *
+ * @return ZipContainer
+ */
+ protected function createZipContainer(ImmutableZipContainer $sourceContainer = null)
+ {
+ return new EpubZipContainer($sourceContainer);
+ }
+
+ /**
+ * @param ZipEntry $zipEntry
+ */
+ protected function addZipEntry(ZipEntry $zipEntry)
+ {
+ $zipEntry->setCreatedOS(ZipPlatform::OS_DOS);
+ $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX);
+ parent::addZipEntry($zipEntry);
+ }
+
+ /**
+ * @throws ZipEntryNotFoundException
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return $this->zipContainer->getMimeType();
+ }
+
+ public function getEpubInfo()
+ {
+ return new EpubInfo($this->getEntryContents($this->getRootFile()));
+ }
+
+ /**
+ * @throws ZipException
+ *
+ * @return string
+ */
+ public function getRootFile()
+ {
+ $entryName = 'META-INF/container.xml';
+ $contents = $this->getEntryContents($entryName);
+ $doc = new \DOMDocument();
+ $doc->loadXML($contents);
+ $xpath = new \DOMXPath($doc);
+ $rootFile = $xpath->evaluate('string(//@full-path)');
+
+ if ($rootFile === '') {
+ throw new ZipException('Incorrect ' . $entryName . ' file format');
+ }
+
+ return $rootFile;
+ }
+}
diff --git a/tests/Internal/Epub/EpubInfo.php b/tests/Internal/Epub/EpubInfo.php
new file mode 100644
index 0000000..e1e1b78
--- /dev/null
+++ b/tests/Internal/Epub/EpubInfo.php
@@ -0,0 +1,159 @@
+loadXML($xmlContents);
+ $xpath = new \DOMXpath($doc);
+ $xpath->registerNamespace('root', 'http://www.idpf.org/2007/opf');
+ $metaDataNodeList = $xpath->query('//root:metadata');
+
+ if (\count($metaDataNodeList) !== 1) {
+ throw new ZipException('Invalid .opf file format');
+ }
+ $metaDataNode = $metaDataNodeList->item(0);
+
+ $title = $xpath->evaluate('string(//dc:title)', $metaDataNode);
+ $creator = $xpath->evaluate('string(//dc:creator)', $metaDataNode);
+ $language = $xpath->evaluate('string(//dc:language)', $metaDataNode);
+ $publisher = $xpath->evaluate('string(//dc:publisher)', $metaDataNode);
+ $description = $xpath->evaluate('string(//dc:description)', $metaDataNode);
+ $rights = $xpath->evaluate('string(//dc:rights)', $metaDataNode);
+ $date = $xpath->evaluate('string(//dc:date)', $metaDataNode);
+ $subject = $xpath->evaluate('string(//dc:subject)', $metaDataNode);
+
+ $this->title = empty($title) ? null : $title;
+ $this->creator = empty($creator) ? null : $creator;
+ $this->language = empty($language) ? null : $language;
+ $this->publisher = empty($publisher) ? null : $publisher;
+ $this->description = empty($description) ? null : $description;
+ $this->rights = empty($rights) ? null : $rights;
+ $this->date = empty($date) ? null : $date;
+ $this->subject = empty($subject) ? null : $subject;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCreator()
+ {
+ return $this->creator;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLanguage()
+ {
+ return $this->language;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPublisher()
+ {
+ return $this->publisher;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRights()
+ {
+ return $this->rights;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDate()
+ {
+ return $this->date;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ return [
+ 'title' => $this->title,
+ 'creator' => $this->creator,
+ 'language' => $this->language,
+ 'publisher' => $this->publisher,
+ 'description' => $this->description,
+ 'rights' => $this->rights,
+ 'date' => $this->date,
+ 'subject' => $this->subject,
+ ];
+ }
+}
diff --git a/tests/Internal/Epub/EpubReader.php b/tests/Internal/Epub/EpubReader.php
new file mode 100644
index 0000000..ba909bb
--- /dev/null
+++ b/tests/Internal/Epub/EpubReader.php
@@ -0,0 +1,21 @@
+zipContainer->hasEntry('mimetype')) {
+ $zipEntry = new ZipEntry('mimetype');
+ $zipEntry->setCreatedOS(ZipPlatform::OS_DOS);
+ $zipEntry->setExtractedOS(ZipPlatform::OS_DOS);
+ $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
+ $zipEntry->setData(new ZipNewData($zipEntry, 'application/epub+zip'));
+ $this->zipContainer->addEntry($zipEntry);
+ }
+
+ $this->sortEntries();
+ }
+
+ private function sortEntries()
+ {
+ $this->zipContainer->sortByEntry(
+ static function (ZipEntry $a, ZipEntry $b) {
+ if (strcasecmp($a->getName(), 'mimetype') === 0) {
+ return -1;
+ }
+
+ if (strcasecmp($b->getName(), 'mimetype') === 0) {
+ return 1;
+ }
+
+ if ($a->isDirectory() && $b->isDirectory()) {
+ return strcmp($a->getName(), $b->getName());
+ }
+
+ if ($a->isDirectory()) {
+ return -1;
+ }
+
+ if ($b->isDirectory()) {
+ return 1;
+ }
+
+ return strcmp($a->getName(), $b->getName());
+ }
+ );
+ }
+}
diff --git a/tests/Internal/Epub/EpubZipContainer.php b/tests/Internal/Epub/EpubZipContainer.php
new file mode 100644
index 0000000..29fff15
--- /dev/null
+++ b/tests/Internal/Epub/EpubZipContainer.php
@@ -0,0 +1,22 @@
+getEntry('mimetype')->getData()->getDataAsString();
+ }
+}
diff --git a/tests/SymlinkTest.php b/tests/SymlinkTest.php
new file mode 100644
index 0000000..2731607
--- /dev/null
+++ b/tests/SymlinkTest.php
@@ -0,0 +1,88 @@
+outputDirname)) {
+ self::assertTrue(mkdir($this->outputDirname, 0755, true));
+ }
+
+ $contentsFile = random_bytes(100);
+ $filePath = $this->outputDirname . '/file.bin';
+ $symlinkPath = $this->outputDirname . '/symlink.bin';
+ $symlinkTarget = basename($filePath);
+ self::assertNotFalse(file_put_contents($filePath, $contentsFile));
+ self::assertTrue(symlink($symlinkTarget, $symlinkPath));
+
+ $finder = (new Finder())->in($this->outputDirname);
+ $zipFile = new ZipFile();
+ $zipFile->addFromFinder($finder);
+ $zipFile->saveAsFile($this->outputFilename);
+ $zipFile->close();
+
+ self::assertCorrectZipArchive($this->outputFilename);
+
+ FilesUtil::removeDir($this->outputDirname);
+ self::assertFalse(is_dir($this->outputDirname));
+ self::assertTrue(mkdir($this->outputDirname, 0755, true));
+
+ $zipFile->openFile($this->outputFilename);
+ $zipFile->extractTo($this->outputDirname, null, [
+ ZipOptions::EXTRACT_SYMLINKS => $allowSymlink,
+ ]);
+ $zipFile->close();
+
+ $splFileInfo = new \SplFileInfo($symlinkPath);
+
+ if ($allowSymlink) {
+ self::assertTrue($splFileInfo->isLink());
+ self::assertSame($splFileInfo->getLinkTarget(), $symlinkTarget);
+ } else {
+ self::assertFalse($splFileInfo->isLink());
+ self::assertStringEqualsFile($symlinkPath, $symlinkTarget);
+ }
+ }
+
+ /**
+ * @return \Generator
+ */
+ public function provideAllowSymlink()
+ {
+ yield 'allow' => [true];
+ yield 'deny' => [false];
+ }
+}
diff --git a/tests/Zip64Test.php b/tests/Zip64Test.php
index a87752f..d99e91f 100644
--- a/tests/Zip64Test.php
+++ b/tests/Zip64Test.php
@@ -2,6 +2,7 @@
namespace PhpZip\Tests;
+use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Exception\ZipException;
use PhpZip\ZipFile;
@@ -32,7 +33,7 @@ class Zip64Test extends ZipTestCase
$zipFile = new ZipFile();
for ($i = 0; $i < $countFiles; $i++) {
- $zipFile[$i . '.txt'] = (string) $i;
+ $zipFile->addFromString($i . '.txt', (string) $i, ZipCompressionMethod::STORED);
}
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
diff --git a/tests/ZipEntryTest.php b/tests/ZipEntryTest.php
index 4a32e60..7bb46b1 100644
--- a/tests/ZipEntryTest.php
+++ b/tests/ZipEntryTest.php
@@ -304,6 +304,55 @@ class ZipEntryTest extends TestCase
static::assertNull($zipEntry->getData());
}
+ /**
+ * @throws \Exception
+ */
+ public function testZipNewDataGuardClone()
+ {
+ $resource = fopen('php://temp', 'r+b');
+ static::assertNotFalse($resource);
+ fwrite($resource, random_bytes(1024));
+ rewind($resource);
+
+ $zipEntry = new ZipEntry('entry');
+ $zipEntry2 = new ZipEntry('entry2');
+
+ $zipData = new ZipNewData($zipEntry, $resource);
+ $zipData2 = new ZipNewData($zipEntry2, $resource);
+ $cloneData = clone $zipData;
+ $cloneData2 = clone $cloneData;
+
+ static::assertSame($zipData->getDataAsStream(), $resource);
+ static::assertSame($zipData2->getDataAsStream(), $resource);
+ static::assertSame($cloneData->getDataAsStream(), $resource);
+ static::assertSame($cloneData2->getDataAsStream(), $resource);
+
+ $validResource = \is_resource($resource);
+ static::assertTrue($validResource);
+
+ unset($cloneData);
+ $validResource = \is_resource($resource);
+ static::assertTrue($validResource);
+
+ unset($zipData);
+ $validResource = \is_resource($resource);
+ static::assertTrue($validResource);
+
+ unset($zipData2);
+ $validResource = \is_resource($resource);
+ static::assertTrue($validResource);
+
+ $reflectionClass = new \ReflectionClass($cloneData2);
+ static::assertSame(
+ $reflectionClass->getStaticProperties()['guardClonedStream'][(int) $resource],
+ 0
+ );
+
+ unset($cloneData2);
+ $validResource = \is_resource($resource);
+ static::assertFalse($validResource);
+ }
+
/**
* @dataProvider providePlatform
*
diff --git a/tests/ZipEventTest.php b/tests/ZipEventTest.php
deleted file mode 100644
index 63cd426..0000000
--- a/tests/ZipEventTest.php
+++ /dev/null
@@ -1,41 +0,0 @@
-openFile(__DIR__ . '/resources/apk.zip');
- static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
- static::assertTrue(isset($zipFile['META-INF/CERT.SF']));
- static::assertTrue(isset($zipFile['META-INF/CERT.RSA']));
- // the "META-INF/" folder will be deleted when saved
- // in the ZipFileExtended::onBeforeSave() method
- $zipFile->saveAsFile($this->outputFilename);
- static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
- static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
- static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
- $zipFile->close();
-
- static::assertCorrectZipArchive($this->outputFilename);
-
- $zipFile->openFile($this->outputFilename);
- static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
- static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
- static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
- $zipFile->close();
- }
-}
diff --git a/tests/ZipFileTest.php b/tests/ZipFileTest.php
index 7782916..4230f7b 100644
--- a/tests/ZipFileTest.php
+++ b/tests/ZipFileTest.php
@@ -1010,16 +1010,16 @@ class ZipFileTest extends ZipTestCase
'test1.txt' => random_bytes(255),
'test2.txt' => random_bytes(255),
'test/test 2/test3.txt' => random_bytes(255),
- 'test empty/dir' => null,
+ 'test empty/dir/' => null,
];
$zipFile = new ZipFile();
- foreach ($entries as $entryName => $value) {
- if ($value === null) {
+ foreach ($entries as $entryName => $contents) {
+ if ($contents === null) {
$zipFile->addEmptyDir($entryName);
} else {
- $zipFile->addFromString($entryName, $value);
+ $zipFile->addFromString($entryName, $contents);
}
}
$zipFile->saveAsFile($this->outputFilename);
@@ -1028,19 +1028,28 @@ class ZipFileTest extends ZipTestCase
static::assertTrue(mkdir($this->outputDirname, 0755, true));
$zipFile->openFile($this->outputFilename);
- $zipFile->extractTo($this->outputDirname);
+ $zipFile->extractTo($this->outputDirname, null, [], $extractedEntries);
- foreach ($entries as $entryName => $value) {
+ foreach ($entries as $entryName => $contents) {
$fullExtractedFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $entryName;
- if ($value === null) {
+ static::assertTrue(
+ isset($extractedEntries[$fullExtractedFilename]),
+ 'No extract info for ' . $fullExtractedFilename
+ );
+
+ if ($contents === null) {
static::assertTrue(is_dir($fullExtractedFilename));
static::assertTrue(FilesUtil::isEmptyDir($fullExtractedFilename));
} else {
static::assertTrue(is_file($fullExtractedFilename));
$contents = file_get_contents($fullExtractedFilename);
- static::assertSame($contents, $value);
+ static::assertSame($contents, $contents);
}
+
+ /** @var ZipEntry $entry */
+ $entry = $extractedEntries[$fullExtractedFilename];
+ static::assertSame($entry->getName(), $entryName);
}
$zipFile->close();
}
@@ -2420,6 +2429,59 @@ class ZipFileTest extends ZipTestCase
$zipFile->close();
}
+ /**
+ * @throws ZipEntryNotFoundException
+ * @throws ZipException
+ */
+ public function testCloneZipContainerInZipWriter()
+ {
+ $zipFile = new ZipFile();
+ $zipFile['file 1'] = 'contents';
+ $zipEntryBeforeWrite = $zipFile->getEntry('file 1');
+ $zipFile->saveAsFile($this->outputFilename);
+ $zipAfterBeforeWrite = $zipFile->getEntry('file 1');
+
+ static::assertSame($zipAfterBeforeWrite, $zipEntryBeforeWrite);
+
+ $zipFile->close();
+ }
+
+ /**
+ * @throws ZipException
+ */
+ public function testMultiSave()
+ {
+ $zipFile = new ZipFile();
+ $zipFile['file 1'] = 'contents';
+ for ($i = 0; $i < 10; $i++) {
+ $zipFile->saveAsFile($this->outputFilename);
+ self::assertCorrectZipArchive($this->outputFilename);
+ }
+ $zipFile->close();
+ }
+
+ /**
+ * @throws ZipEntryNotFoundException
+ * @throws ZipException
+ */
+ public function testNoData()
+ {
+ $this->setExpectedException(ZipException::class, 'No data for zip entry file');
+
+ $entryName = 'file';
+
+ $zipFile = new ZipFile();
+
+ try {
+ $zipFile[$entryName] = '';
+ $zipEntry = $zipFile->getEntry($entryName);
+ $zipEntry->setData(null);
+ $zipFile->getEntryContents($entryName);
+ } finally {
+ $zipFile->close();
+ }
+ }
+
/**
* @throws ZipEntryNotFoundException
* @throws ZipException
diff --git a/tests/ZipInfoTest.php b/tests/ZipInfoTest.php
new file mode 100644
index 0000000..67957d6
--- /dev/null
+++ b/tests/ZipInfoTest.php
@@ -0,0 +1,118 @@
+getAllInfo();
+ $zipFile->close();
+
+ self::assertCount(2, $zipAllInfo);
+ self::assertContainsOnlyInstancesOf(ZipInfo::class, $zipAllInfo);
+ }
+
+ /**
+ * @throws ZipEntryNotFoundException
+ * @throws ZipException
+ */
+ public function testZipEntryInfo()
+ {
+ $zipFile = new ZipFile();
+ $zipFile['entry'] = 'contents';
+ $zipFile['entry 2'] = 'contents';
+ $zipInfo = $zipFile->getEntryInfo('entry');
+ $zipFile->close();
+
+ self::assertInstanceOf(ZipInfo::class, $zipInfo);
+ }
+
+ /**
+ * @throws ZipEntryNotFoundException
+ * @throws ZipException
+ */
+ public function testZipInfoEntryNotFound()
+ {
+ $this->setExpectedException(
+ ZipEntryNotFoundException::class,
+ 'Zip Entry "unknown.name" was not found in the archive.'
+ );
+
+ $zipFile = new ZipFile();
+ $zipFile->getEntryInfo('unknown.name');
+ }
+
+ /**
+ * @throws ZipEntryNotFoundException
+ * @throws ZipException
+ */
+ public function testZipInfo()
+ {
+ $zipFile = new ZipFile();
+ $zipFile->openFile(__DIR__ . '/resources/Advanced-v1.0.0.epub');
+ $entryName = 'META-INF/container.xml';
+ $zipEntry = $zipFile->getEntry($entryName);
+ $zipInfo = $zipFile->getEntryInfo($entryName);
+ $zipFile->close();
+
+ self::assertSame($zipInfo->getName(), $zipEntry->getName());
+ self::assertSame($zipInfo->isFolder(), $zipEntry->isDirectory());
+ self::assertSame($zipInfo->getSize(), $zipEntry->getUncompressedSize());
+ self::assertSame($zipInfo->getCompressedSize(), $zipEntry->getCompressedSize());
+ self::assertSame($zipInfo->getMtime(), $zipEntry->getMTime()->getTimestamp());
+ self::assertSame(
+ $zipInfo->getCtime(),
+ $zipEntry->getCTime() !== null ? $zipEntry->getCTime()->getTimestamp() : null
+ );
+ self::assertSame(
+ $zipInfo->getAtime(),
+ $zipEntry->getATime() !== null ? $zipEntry->getATime()->getTimestamp() : null
+ );
+ self::assertNotEmpty($zipInfo->getAttributes());
+ self::assertSame($zipInfo->isEncrypted(), $zipEntry->isEncrypted());
+ self::assertSame($zipInfo->getComment(), $zipEntry->getComment());
+ self::assertSame($zipInfo->getCrc(), $zipEntry->getCrc());
+ self::assertSame(
+ $zipInfo->getMethod(),
+ ZipCompressionMethod::getCompressionMethodName($zipEntry->getCompressionMethod())
+ );
+ self::assertSame(
+ $zipInfo->getMethodName(),
+ ZipCompressionMethod::getCompressionMethodName($zipEntry->getCompressionMethod())
+ );
+ self::assertSame(
+ $zipInfo->getEncryptionMethodName(),
+ ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod())
+ );
+ self::assertSame($zipInfo->getPlatform(), ZipPlatform::getPlatformName($zipEntry->getExtractedOS()));
+ self::assertSame(ZipInfo::getPlatformName($zipEntry), ZipPlatform::getPlatformName($zipEntry->getExtractedOS()));
+ self::assertSame($zipInfo->getVersion(), $zipEntry->getExtractVersion());
+ self::assertNull($zipInfo->getEncryptionMethod());
+ self::assertSame($zipInfo->getCompressionLevel(), $zipEntry->getCompressionLevel());
+ self::assertSame($zipInfo->getCompressionMethod(), $zipEntry->getCompressionMethod());
+ self::assertNotEmpty($zipInfo->toArray());
+
+ self::assertSame((string) $zipInfo, 'PhpZip\Model\ZipInfo {Name="META-INF/container.xml", Size="249 bytes", Compressed size="169 bytes", Modified time="2019-04-08T14:59:08+00:00", Comment="", Method name="Deflated", Attributes="------", Platform="MS-DOS", Version=20}');
+ }
+}
diff --git a/tests/resources/Advanced-v1.0.0.epub b/tests/resources/Advanced-v1.0.0.epub
new file mode 100644
index 0000000..d12f996
Binary files /dev/null and b/tests/resources/Advanced-v1.0.0.epub differ