1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-08-18 05:01:20 +02:00

32 Commits

Author SHA1 Message Date
wapplay
501b52f6fc Merge branch 'release/3.3.3' 2020-07-12 00:01:42 +03:00
wapplay
d9022e80c5 LICENSE file added 2020-07-11 23:48:23 +03:00
wapplay
c10c425f7e Improved Windows compatibility, fix #54 2020-07-11 23:04:33 +03:00
wapplay
0655e282e9 Merge branch 'feature/overwrite' into develop 2020-07-11 22:21:37 +03:00
wapplay
391a55f378 Merge branch 'release/3.3.2' 2020-06-22 18:05:21 +03:00
wapplay
b64e9ac328 Merge tag '3.3.2' into develop
Tagging version 3.3.2 3.3.2
2020-06-22 18:05:21 +03:00
Ne-Lexa
f503fc164e Merge pull request #60 from Araqel/master
removed extra commas from .phpstorm.meta.php
2020-06-22 18:02:56 +03:00
Araqel Araqelyan
8ce666fb5e removed extra commas from .phpstorm.meta.php 2020-06-22 18:53:58 +04:00
wapplay
dbddcda001 #54 overwriting open zip archive fixed 2020-05-04 14:13:39 +03:00
wapplay
52ce79d4d2 Merge branch 'release/3.3.1' 2020-04-01 16:33:45 +03:00
wapplay
3942bf2005 Merge tag '3.3.1' into develop
Tagging version 3.3.1 3.3.1
2020-04-01 16:33:45 +03:00
Ne-Lexa
decf4f5095 Merge pull request #47 from chx/chx-patch-1
Do not pin random_compat version
2020-04-01 16:29:06 +03:00
chx
70d8b8200b Do not pin random_compat version 2020-03-23 09:49:26 -07:00
Ne-Lexa
8fdc21eece Merge branch 'release/3.3.0' 2020-02-04 12:01:18 +03:00
Ne-Lexa
74c0a49057 Merge tag '3.3.0' into develop
Tagging version 3.3.0 3.3.0
2020-02-04 12:01:18 +03:00
Ne-Lexa
ae6d47f47a removed test ZipInfo::__toString() (depends on time zone) 2020-02-04 11:58:47 +03:00
Ne-Lexa
b3b676e3af Merge branch 'release/3.3.0' 2020-02-04 11:47:43 +03:00
Ne-Lexa
f3d769739b Merge tag '3.2.2' into develop
Tagging hotfix 3.2.2 3.2.2

# Conflicts:
#	.travis.yml
#	tests/ZipFileTest.php
2020-02-04 11:36:29 +03:00
Ne-Lexa
074443dbc4 Merge branch 'hotfix/3.2.2' 2020-02-04 11:29:58 +03:00
Ne-Lexa
cbb693213e fix replace contents by file 2020-02-04 11:18:32 +03:00
Ne-Lexa
820c63c30f ZipInfo tests 2020-01-31 13:42:19 +03:00
Ne-Lexa
2235de6b35 Merge branch 'feature/zipcontainer-override-support' into develop 2020-01-31 12:13:16 +03:00
Ne-Lexa
6e33ed08ef run php 7.4 in xenial 2020-01-22 16:26:24 +03:00
Ne-Lexa
79e77a8c88 fix close container in ZipFile::close() 2020-01-22 16:07:22 +03:00
Ne-Lexa
8dcde47072 Added a new option for extracting unix symlinks.
Added new parameter to get the list of extracted files.
2020-01-22 12:53:16 +03:00
Ne-Lexa
47161bdb02 zip extra fields tests 2020-01-22 12:48:47 +03:00
Ne-Lexa
943cf3e777 Fixed problem with cloning a zip container. 2020-01-22 12:48:15 +03:00
Ne-Lexa
5ec656fde4 Implemented the ability to override the instance of ZipContainer.
ZipContainer will be cloned before writing the zip file.
Tested custom ZipWriter, ZipReader, ZipContainer and ZipFile.
2020-01-21 14:13:47 +03:00
Ne-Lexa
d305ab68bc Merge branch 'hotfix/3.2.1' 2020-01-14 16:44:56 +03:00
Ne-Lexa
e0da8c94be Merge tag '3.2.1' into develop
Tagging hotfix 3.2.1 3.2.1
2020-01-14 16:44:56 +03:00
Ne-Lexa
8487dac9df zip extra tests, php 32-bit compat 2020-01-14 16:31:22 +03:00
Ne-Lexa
d21fdb35bb fix#13 fix#16 fix#27 fix#31 fix#41
Tagging version 3.2.0 3.2.0
- Fix memory leak problem
- Add new methods: `getEntry()`, `getEntries()`, `addSplFile()`, `addFromFinder()`
- Fix large zip files problem
2020-01-09 17:41:47 +03:00
63 changed files with 3267 additions and 403 deletions

2
.gitignore vendored
View File

@@ -2,4 +2,4 @@
*.iml
/.idea
/composer.lock
/.php_cs.cache
/*.cache

37
.php_cs
View File

@@ -1,7 +1,7 @@
<?php
/**
* PHP Code Style Fixer (config created for version 2.16.1 (Yellow Bird)).
* PHP Code Style Fixer (config created for version 2.16.4 (Yellow Bird)).
*
* Use one of the following console commands to just see the
* changes that will be made.
@@ -119,7 +119,7 @@ $rules = [
*
* Risky!
* Risky as new docblocks might mean more, e.g. a Doctrine entity
* might have a new column in database
* might have a new column in database.
*/
'comment_to_phpdoc' => [
'ignored_tags' => [
@@ -238,6 +238,7 @@ $rules = [
'iconv',
'mime_content_type',
'rename',
'rmdir',
'unlink',
],
],
@@ -283,7 +284,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 +328,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 +726,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 +752,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,
/*
@@ -1140,7 +1147,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] `@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
@@ -1153,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
@@ -1190,8 +1197,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 +1373,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 +1471,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';
}

View File

@@ -82,7 +82,7 @@ namespace PHPSTORM_META {
\PhpZip\Constants\DosCodePage::CP_NORDIC,
\PhpZip\Constants\DosCodePage::CP_CYRILLIC_RUSSIAN,
\PhpZip\Constants\DosCodePage::CP_GREEK2,
\PhpZip\Constants\DosCodePage::CP_THAI,
\PhpZip\Constants\DosCodePage::CP_THAI
);
expectedArguments(\PhpZip\Model\ZipEntry::setCharset(), 0, argumentsSet('dos_charset'));
expectedArguments(\PhpZip\Constants\DosCodePage::toUTF8(), 1, argumentsSet('dos_charset'));
@@ -92,7 +92,7 @@ namespace PHPSTORM_META {
"zip_os",
\PhpZip\Constants\ZipPlatform::OS_UNIX,
\PhpZip\Constants\ZipPlatform::OS_DOS,
\PhpZip\Constants\ZipPlatform::OS_MAC_OSX,
\PhpZip\Constants\ZipPlatform::OS_MAC_OSX
);
expectedArguments(\PhpZip\Model\ZipEntry::setCreatedOS(), 0, argumentsSet('zip_os'));
expectedArguments(\PhpZip\Model\ZipEntry::setExtractedOS(), 0, argumentsSet('zip_os'));
@@ -107,4 +107,22 @@ namespace PHPSTORM_META {
\PhpZip\Constants\GeneralPurposeBitFlag::UTF8
);
expectedArguments(\PhpZip\Model\ZipEntry::setGeneralPurposeBitFlags(), 0, argumentsSet('zip_gpbf'));
registerArgumentsSet(
"winzip_aes_vendor_version",
\PhpZip\Model\Extra\Fields\WinZipAesExtraField::VERSION_AE1,
\PhpZip\Model\Extra\Fields\WinZipAesExtraField::VERSION_AE2
);
registerArgumentsSet(
"winzip_aes_key_strength",
\PhpZip\Model\Extra\Fields\WinZipAesExtraField::KEY_STRENGTH_256BIT,
\PhpZip\Model\Extra\Fields\WinZipAesExtraField::KEY_STRENGTH_128BIT,
\PhpZip\Model\Extra\Fields\WinZipAesExtraField::KEY_STRENGTH_192BIT
);
expectedArguments(\PhpZip\Model\Extra\Fields\WinZipAesExtraField::__construct(), 0, argumentsSet('winzip_aes_vendor_version'));
expectedArguments(\PhpZip\Model\Extra\Fields\WinZipAesExtraField::__construct(), 1, argumentsSet('winzip_aes_key_strength'));
expectedArguments(\PhpZip\Model\Extra\Fields\WinZipAesExtraField::__construct(), 2, argumentsSet('compression_methods'));
expectedArguments(\PhpZip\Model\Extra\Fields\WinZipAesExtraField::setVendorVersion(), 0, argumentsSet('winzip_aes_vendor_version'));
expectedArguments(\PhpZip\Model\Extra\Fields\WinZipAesExtraField::setKeyStrength(), 0, argumentsSet('winzip_aes_key_strength'));
expectedArguments(\PhpZip\Model\Extra\Fields\WinZipAesExtraField::setCompressionMethod(), 0, argumentsSet('compression_methods'));
}

View File

@@ -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
@@ -39,8 +39,8 @@ matrix:
- php: 7.4
os: linux
dist: bionic
env: COVERAGE=true ZIPALIGN_INSTALL=true PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests --coverage-clover=coverage.clover"
dist: xenial
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

21
LICENSE Normal file
View File

@@ -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.

View File

@@ -3,6 +3,7 @@
`PhpZip` - php библиотека для продвинутой работы с ZIP-архивами.
[![Build Status](https://travis-ci.org/Ne-Lexa/php-zip.svg?branch=master)](https://travis-ci.org/Ne-Lexa/php-zip)
[![Code Coverage](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
[![Total Downloads](https://poser.pugx.org/nelexa/zip/downloads)](https://packagist.org/packages/nelexa/zip)
[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/)

View File

@@ -3,6 +3,7 @@
`PhpZip` is a php-library for extended work with ZIP-archives.
[![Build Status](https://travis-ci.org/Ne-Lexa/php-zip.svg?branch=master)](https://travis-ci.org/Ne-Lexa/php-zip)
[![Code Coverage](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
[![Total Downloads](https://poser.pugx.org/nelexa/zip/downloads)](https://packagist.org/packages/nelexa/zip)
[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/)

View File

@@ -24,13 +24,14 @@
"php": "^5.5.9 || ^7.0",
"ext-zlib": "*",
"psr/http-message": "^1.0",
"paragonie/random_compat": ">=1 <9.99",
"paragonie/random_compat": "*",
"symfony/finder": "^3.0|^4.0|^5.0"
},
"require-dev": {
"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"

View File

@@ -22,6 +22,12 @@
</testsuite>
</testsuites>
<groups>
<exclude>
<group>large</group>
</exclude>
</groups>
<filter>
<whitelist>
<directory>src</directory>

View File

@@ -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';
}

View File

@@ -354,7 +354,9 @@ class ZipReader
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);
@@ -366,7 +368,7 @@ class ZipReader
/** @var UnicodePathExtraField|null $unicodePathExtraField */
$unicodePathExtraField = $zipEntry->getExtraField(UnicodePathExtraField::HEADER_ID);
if ($unicodePathExtraField !== null) {
if ($unicodePathExtraField !== null && $unicodePathExtraField->getCrc32() === crc32($entryName)) {
$unicodePath = $unicodePathExtraField->getUnicodeValue();
if ($unicodePath !== null) {
@@ -543,25 +545,23 @@ class ZipReader
/** @var string|ZipExtraField|null $className */
$className = ZipExtraDriver::getClassNameOrNull($headerId);
if ($className !== null) {
try {
$extraField = $local ?
\call_user_func([$className, 'unpackLocalFileData'], $bufferData, $zipEntry) :
\call_user_func([$className, 'unpackCentralDirData'], $bufferData, $zipEntry);
} catch (\Throwable $e) {
throw new \RuntimeException(
sprintf(
'Error parse %s extra field 0x%04X',
$local ? 'local' : 'central directory',
$headerId
)
);
try {
if ($className !== null) {
try {
$extraField = $local ?
\call_user_func([$className, 'unpackLocalFileData'], $bufferData, $zipEntry) :
\call_user_func([$className, 'unpackCentralDirData'], $bufferData, $zipEntry);
} catch (\Throwable $e) {
// skip errors while parsing invalid data
continue;
}
} else {
$extraField = new UnrecognizedExtraField($headerId, $bufferData);
}
} else {
$extraField = new UnrecognizedExtraField($headerId, $bufferData);
$collection->add($extraField);
} finally {
$pos += $data['dataSize'];
}
$collection->add($extraField);
$pos += $data['dataSize'];
}
}

View File

@@ -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;
}
/**

View File

@@ -4,6 +4,7 @@ namespace PhpZip\Model\Data;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipData;
use PhpZip\Model\ZipEntry;
/**
* Class ZipFileData.
@@ -16,11 +17,12 @@ class ZipFileData implements ZipData
/**
* ZipStringData constructor.
*
* @param ZipEntry $zipEntry
* @param \SplFileInfo $fileInfo
*
* @throws ZipException
*/
public function __construct(\SplFileInfo $fileInfo)
public function __construct(ZipEntry $zipEntry, \SplFileInfo $fileInfo)
{
if (!$fileInfo->isFile()) {
throw new ZipException('$fileInfo is not a file.');
@@ -31,6 +33,7 @@ class ZipFileData implements ZipData
}
$this->file = $fileInfo;
$zipEntry->setUncompressedSize($fileInfo->getSize());
}
/**

View File

@@ -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<int, int> array of resource ids and the number of class clones
*/
private static $guardClonedStream = [];
/** @var ZipEntry */
private $zipEntry;
/** @var resource */
private $stream;
/**
* ZipStringData constructor.
*
@@ -30,7 +39,9 @@ class ZipNewData implements ZipData
$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);
@@ -38,6 +49,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;
}
/**
@@ -46,7 +63,7 @@ class ZipNewData implements ZipData
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;
@@ -79,8 +96,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);
}

View File

@@ -29,16 +29,6 @@ abstract class AbstractUnicodeExtraField implements ZipExtraField
$this->unicodeValue = (string) $unicodeValue;
}
/**
* @param string $unicodeValue
*
* @return static
*/
public static function create($unicodeValue)
{
return new static(crc32($unicodeValue), $unicodeValue);
}
/**
* @return int the CRC32 checksum of the filename or comment as
* encoded in the central directory of the zip file
@@ -48,6 +38,14 @@ abstract class AbstractUnicodeExtraField implements ZipExtraField
return $this->crc32;
}
/**
* @param int $crc32
*/
public function setCrc32($crc32)
{
$this->crc32 = (int) $crc32;
}
/**
* @return string
*/
@@ -62,7 +60,6 @@ abstract class AbstractUnicodeExtraField implements ZipExtraField
public function setUnicodeValue($unicodeValue)
{
$this->unicodeValue = $unicodeValue;
$this->crc32 = crc32($unicodeValue);
}
/**
@@ -78,19 +75,18 @@ abstract class AbstractUnicodeExtraField implements ZipExtraField
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
if (\strlen($buffer) < 5) {
throw new ZipException('UniCode path extra data must have at least 5 bytes.');
throw new ZipException('Unicode path extra data must have at least 5 bytes.');
}
$version = unpack('C', $buffer)[1];
$data = unpack('Cversion/Vcrc32', $buffer);
if ($version !== self::DEFAULT_VERSION) {
throw new ZipException(sprintf('Unsupported version [%d] for UniCode path extra data.', $version));
if ($data['version'] !== self::DEFAULT_VERSION) {
throw new ZipException(sprintf('Unsupported version [%d] for Unicode path extra data.', $data['version']));
}
$crc32 = unpack('V', substr($buffer, 1))[1];
$unicodeValue = substr($buffer, 5);
return new static($crc32, $unicodeValue);
return new static($data['crc32'], $unicodeValue);
}
/**

View File

@@ -79,10 +79,10 @@ class AsiExtraField implements ZipExtraField
*/
public function __construct($mode, $uid = self::USER_GID_PID, $gid = self::USER_GID_PID, $link = '')
{
$this->mode = (int) $mode;
$this->uid = (int) $uid;
$this->gid = (int) $gid;
$this->link = (string) $link;
$this->mode = $mode;
$this->uid = $uid;
$this->gid = $gid;
$this->link = $link;
}
/**
@@ -121,7 +121,7 @@ class AsiExtraField implements ZipExtraField
$link = '';
if ($data['linkSize'] > 0) {
$link = substr($buffer, 8);
$link = substr($buffer, 10);
}
return new self($data['mode'], $data['uid'], $data['gid'], $link);
@@ -191,7 +191,7 @@ class AsiExtraField implements ZipExtraField
*/
public function setLink($link)
{
$this->link = $link;
$this->link = (string) $link;
$this->mode = $this->getPermissionsMode($this->mode);
}
@@ -214,11 +214,13 @@ class AsiExtraField implements ZipExtraField
*/
protected function getPermissionsMode($mode)
{
$type = UnixStat::UNX_IFMT;
$type = 0;
if ($this->isLink()) {
$type = UnixStat::UNX_IFLNK;
} elseif ($this->isDirectory()) {
} elseif (($mode & UnixStat::UNX_IFREG) !== 0) {
$type = UnixStat::UNX_IFREG;
} elseif (($mode & UnixStat::UNX_IFDIR) !== 0) {
$type = UnixStat::UNX_IFDIR;
}
@@ -264,7 +266,7 @@ class AsiExtraField implements ZipExtraField
*/
public function setUserId($uid)
{
$this->uid = $uid;
$this->uid = (int) $uid;
}
/**
@@ -280,7 +282,7 @@ class AsiExtraField implements ZipExtraField
*/
public function setGroupId($gid)
{
$this->gid = $gid;
$this->gid = (int) $gid;
}
/**

View File

@@ -359,6 +359,25 @@ class ExtendedTimestampExtraField implements ZipExtraField
public function setModifyTime($unixTime)
{
$this->modifyTime = $unixTime;
$this->updateFlags();
}
private function updateFlags()
{
$flags = 0;
if ($this->modifyTime !== null) {
$flags |= self::MODIFY_TIME_BIT;
}
if ($this->accessTime !== null) {
$flags |= self::ACCESS_TIME_BIT;
}
if ($this->createTime !== null) {
$flags |= self::CREATE_TIME_BIT;
}
$this->flags = $flags;
}
/**
@@ -370,6 +389,7 @@ class ExtendedTimestampExtraField implements ZipExtraField
public function setAccessTime($unixTime)
{
$this->accessTime = $unixTime;
$this->updateFlags();
}
/**
@@ -381,6 +401,7 @@ class ExtendedTimestampExtraField implements ZipExtraField
public function setCreateTime($unixTime)
{
$this->createTime = $unixTime;
$this->updateFlags();
}
/**

View File

@@ -115,25 +115,6 @@ class NewUnixExtraField implements ZipExtraField
return new self($data['version'], $gid, $uid);
}
/**
* Converts a signed byte into an unsigned integer representation
* (e.g., -1 becomes 255).
*
* @param int $b byte to convert to int
*
* @return int representation of the provided byte
*
* @since 1.5
*/
public static function signedByteToUnsignedInt($b)
{
if ($b >= 0) {
return $b;
}
return 256 + $b;
}
/**
* Populate data from this array as if it was in central directory data.
*
@@ -160,10 +141,10 @@ class NewUnixExtraField implements ZipExtraField
return pack(
'CCVCV',
$this->version,
4, // GIDSize
$this->gid,
4, // UIDSize
$this->uid
$this->uid,
4, // GIDSize
$this->gid
);
}
@@ -233,6 +214,14 @@ class NewUnixExtraField implements ZipExtraField
$this->gid = $gid & 0xffffffff;
}
/**
* @return int
*/
public function getVersion()
{
return $this->version;
}
/**
* @return string
*/

View File

@@ -3,6 +3,7 @@
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
@@ -10,7 +11,7 @@ use PhpZip\Util\PackUtil;
/**
* NTFS Extra Field.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @license MIT
*/
@@ -31,42 +32,45 @@ class NtfsExtraField implements ZipExtraField
* A.M. January 1, 1601 Coordinated Universal Time (UTC).
* this is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
*/
const EPOCH_OFFSET = -11644473600;
const EPOCH_OFFSET = -116444736000000000;
/** @var int Modify ntfs time */
private $modifyTime;
private $modifyNtfsTime;
/** @var int Access ntfs time */
private $accessTime;
private $accessNtfsTime;
/** @var int Create ntfs time */
private $createTime;
private $createNtfsTime;
/**
* @param int $modifyTime
* @param int $accessTime
* @param int $createTime
* @param int $modifyNtfsTime
* @param int $accessNtfsTime
* @param int $createNtfsTime
*/
public function __construct($modifyTime, $accessTime, $createTime)
public function __construct($modifyNtfsTime, $accessNtfsTime, $createNtfsTime)
{
$this->modifyTime = (int) $modifyTime;
$this->accessTime = (int) $accessTime;
$this->createTime = (int) $createTime;
$this->modifyNtfsTime = (int) $modifyNtfsTime;
$this->accessNtfsTime = (int) $accessNtfsTime;
$this->createNtfsTime = (int) $createNtfsTime;
}
/**
* @param \DateTimeInterface $mtime
* @param \DateTimeInterface $atime
* @param \DateTimeInterface $ctime
* @param \DateTimeInterface $modifyDateTime
* @param \DateTimeInterface $accessDateTime
* @param \DateTimeInterface $createNtfsTime
*
* @return NtfsExtraField
*/
public static function create(\DateTimeInterface $mtime, \DateTimeInterface $atime, \DateTimeInterface $ctime)
{
public static function create(
\DateTimeInterface $modifyDateTime,
\DateTimeInterface $accessDateTime,
\DateTimeInterface $createNtfsTime
) {
return new self(
self::dateTimeToNtfsTime($mtime),
self::dateTimeToNtfsTime($atime),
self::dateTimeToNtfsTime($ctime)
self::dateTimeToNtfsTime($modifyDateTime),
self::dateTimeToNtfsTime($accessDateTime),
self::dateTimeToNtfsTime($createNtfsTime)
);
}
@@ -88,10 +92,16 @@ class NtfsExtraField implements ZipExtraField
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return NtfsExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
if (\PHP_INT_SIZE === 4) {
throw new ZipException('not supported for php-32bit');
}
$buffer = substr($buffer, 4);
$modifyTime = 0;
@@ -121,6 +131,8 @@ class NtfsExtraField implements ZipExtraField
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return NtfsExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
@@ -138,13 +150,61 @@ class NtfsExtraField implements ZipExtraField
{
$data = pack('Vvv', 0, self::TIME_ATTR_TAG, self::TIME_ATTR_SIZE);
// refactoring will be needed when php 5.5 support ends
$data .= PackUtil::packLongLE($this->modifyTime);
$data .= PackUtil::packLongLE($this->accessTime);
$data .= PackUtil::packLongLE($this->createTime);
$data .= PackUtil::packLongLE($this->modifyNtfsTime);
$data .= PackUtil::packLongLE($this->accessNtfsTime);
$data .= PackUtil::packLongLE($this->createNtfsTime);
return $data;
}
/**
* @return int
*/
public function getModifyNtfsTime()
{
return $this->modifyNtfsTime;
}
/**
* @param int $modifyNtfsTime
*/
public function setModifyNtfsTime($modifyNtfsTime)
{
$this->modifyNtfsTime = (int) $modifyNtfsTime;
}
/**
* @return int
*/
public function getAccessNtfsTime()
{
return $this->accessNtfsTime;
}
/**
* @param int $accessNtfsTime
*/
public function setAccessNtfsTime($accessNtfsTime)
{
$this->accessNtfsTime = (int) $accessNtfsTime;
}
/**
* @return int
*/
public function getCreateNtfsTime()
{
return $this->createNtfsTime;
}
/**
* @param int $createNtfsTime
*/
public function setCreateNtfsTime($createNtfsTime)
{
$this->createNtfsTime = (int) $createNtfsTime;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
@@ -161,7 +221,7 @@ class NtfsExtraField implements ZipExtraField
*/
public function getModifyDateTime()
{
return self::ntfsTimeToDateTime($this->modifyTime);
return self::ntfsTimeToDateTime($this->modifyNtfsTime);
}
/**
@@ -169,7 +229,7 @@ class NtfsExtraField implements ZipExtraField
*/
public function setModifyDateTime(\DateTimeInterface $modifyTime)
{
$this->modifyTime = self::dateTimeToNtfsTime($modifyTime);
$this->modifyNtfsTime = self::dateTimeToNtfsTime($modifyTime);
}
/**
@@ -177,7 +237,7 @@ class NtfsExtraField implements ZipExtraField
*/
public function getAccessDateTime()
{
return self::ntfsTimeToDateTime($this->accessTime);
return self::ntfsTimeToDateTime($this->accessNtfsTime);
}
/**
@@ -185,7 +245,7 @@ class NtfsExtraField implements ZipExtraField
*/
public function setAccessDateTime(\DateTimeInterface $accessTime)
{
$this->accessTime = self::dateTimeToNtfsTime($accessTime);
$this->accessNtfsTime = self::dateTimeToNtfsTime($accessTime);
}
/**
@@ -193,7 +253,7 @@ class NtfsExtraField implements ZipExtraField
*/
public function getCreateDateTime()
{
return self::ntfsTimeToDateTime($this->createTime);
return self::ntfsTimeToDateTime($this->createNtfsTime);
}
/**
@@ -201,7 +261,17 @@ class NtfsExtraField implements ZipExtraField
*/
public function setCreateDateTime(\DateTimeInterface $createTime)
{
$this->createTime = self::dateTimeToNtfsTime($createTime);
$this->createNtfsTime = self::dateTimeToNtfsTime($createTime);
}
/**
* @param float $timestamp Float timestamp
*
* @return int
*/
public static function timestampToNtfsTime($timestamp)
{
return (int) (((float) $timestamp * 10000000) - self::EPOCH_OFFSET);
}
/**
@@ -211,23 +281,34 @@ class NtfsExtraField implements ZipExtraField
*/
public static function dateTimeToNtfsTime(\DateTimeInterface $dateTime)
{
return $dateTime->getTimestamp() * 10000000 + self::EPOCH_OFFSET;
return self::timestampToNtfsTime((float) $dateTime->format('U.u'));
}
/**
* @param int $time
* @param int $ntfsTime
*
* @return float Float unix timestamp
*/
public static function ntfsTimeToTimestamp($ntfsTime)
{
return (float) (($ntfsTime + self::EPOCH_OFFSET) / 10000000);
}
/**
* @param int $ntfsTime
*
* @return \DateTimeInterface
*/
public static function ntfsTimeToDateTime($time)
public static function ntfsTimeToDateTime($ntfsTime)
{
$timestamp = (int) ($time / 10000000 + self::EPOCH_OFFSET);
$timestamp = self::ntfsTimeToTimestamp($ntfsTime);
$dateTime = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6f', $timestamp));
try {
return new \DateTimeImmutable('@' . $timestamp);
} catch (\Exception $e) {
throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp, 1, $e);
if ($dateTime === false) {
throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp);
}
return $dateTime;
}
/**
@@ -238,17 +319,17 @@ class NtfsExtraField implements ZipExtraField
$args = [self::HEADER_ID];
$format = '0x%04x NtfsExtra:';
if ($this->modifyTime !== 0) {
if ($this->modifyNtfsTime !== 0) {
$format .= ' Modify:[%s]';
$args[] = $this->getModifyDateTime()->format(\DATE_ATOM);
}
if ($this->accessTime !== 0) {
if ($this->accessNtfsTime !== 0) {
$format .= ' Access:[%s]';
$args[] = $this->getAccessDateTime()->format(\DATE_ATOM);
}
if ($this->createTime !== 0) {
if ($this->createNtfsTime !== 0) {
$format .= ' Create:[%s]';
$args[] = $this->getCreateDateTime()->format(\DATE_ATOM);
}

View File

@@ -2,6 +2,7 @@
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\RuntimeException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
@@ -56,7 +57,7 @@ class UnrecognizedExtraField implements ZipExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
throw new \RuntimeException('Unsupport parse');
throw new RuntimeException('Unsupport parse');
}
/**
@@ -67,7 +68,7 @@ class UnrecognizedExtraField implements ZipExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
throw new \RuntimeException('Unsupport parse');
throw new RuntimeException('Unsupport parse');
}
/**

View File

@@ -256,7 +256,7 @@ class WinZipAesExtraField implements ZipExtraField
$vendorVersion = (int) $vendorVersion;
if (!\in_array($vendorVersion, self::$allowVendorVersions, true)) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf(
'Unsupport WinZip AES vendor version: %d',
$vendorVersion
@@ -294,7 +294,7 @@ class WinZipAesExtraField implements ZipExtraField
$keyStrength = (int) $keyStrength;
if (!isset(self::$encryptionStrengths[$keyStrength])) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf(
'Key strength %d not support value. Allow values: %s',
$keyStrength,

View File

@@ -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;
}
}
}

View File

@@ -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;
/**

View File

@@ -963,9 +963,12 @@ class ZipEntry
{
$dosTime = (int) $dosTime;
if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
throw new InvalidArgumentException('DosTime out of range');
if (\PHP_INT_SIZE === 8) {
if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
throw new InvalidArgumentException('DosTime out of range');
}
}
$this->dosTime = $dosTime;
return $this;
@@ -1010,9 +1013,12 @@ class ZipEntry
{
$this->externalAttributes = (int) $externalAttributes;
if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
if (\PHP_INT_SIZE === 8) {
if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
}
}
$this->externalAttributes = $externalAttributes;
return $this;

View File

@@ -89,7 +89,10 @@ class ZipEntryMatcher implements \Countable
*/
public function all()
{
$this->matches = array_keys($this->zipContainer->getEntries());
$this->matches = array_map(
'strval',
array_keys($this->zipContainer->getEntries())
);
return $this;
}
@@ -192,10 +195,7 @@ class ZipEntryMatcher implements \Countable
*
* @see http://php.net/manual/en/countable.count.php
*
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* @return int the custom count as an integer
*
* @since 5.1.0
*/

View File

@@ -43,11 +43,12 @@ 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);
@rmdir($dir);
}
/**
@@ -197,10 +198,10 @@ final class FilesUtil
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;
@@ -272,7 +273,7 @@ final class FilesUtil
public static function normalizeZipPath($path)
{
return implode(
'/',
\DIRECTORY_SEPARATOR,
array_filter(
explode('/', (string) $path),
static function ($part) {
@@ -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);
}
/**

View File

@@ -1,11 +1,8 @@
<?php
/** @noinspection AdditionOperationOnArraysInspection */
/** @noinspection PhpUsageOfSilenceOperatorInspection */
namespace PhpZip;
use PhpZip\Constants\UnixStat;
use PhpZip\Constants\ZipCompressionLevel;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipEncryptionMethod;
@@ -20,6 +17,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 +66,7 @@ class ZipFile implements ZipFileInterface
*/
public function __construct()
{
$this->zipContainer = new ZipContainer();
$this->zipContainer = $this->createZipContainer(null);
}
/**
@@ -90,6 +88,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 +138,9 @@ class ZipFile implements ZipFileInterface
}
if (!($handle = fopen('php://temp', 'r+b'))) {
throw new ZipException("Can't open temp stream.");
// @codeCoverageIgnoreStart
throw new ZipException('A temporary resource cannot be opened for writing.');
// @codeCoverageIgnoreEnd
}
fwrite($handle, $data);
rewind($handle);
@@ -151,7 +161,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;
}
@@ -244,8 +254,8 @@ class ZipFile implements ZipFileInterface
*
* @param string $entryName
*
* @throws ZipException
* @throws ZipEntryNotFoundException
* @throws ZipException
*
* @return string
*/
@@ -260,8 +270,8 @@ class ZipFile implements ZipFileInterface
* @param string $entryName
* @param string|null $comment
*
* @throws ZipEntryNotFoundException
* @throws ZipException
* @throws ZipEntryNotFoundException
*
* @return ZipFile
*/
@@ -277,8 +287,8 @@ class ZipFile implements ZipFileInterface
*
* @param string $entryName
*
* @throws ZipEntryNotFoundException
* @throws ZipException
* @throws ZipEntryNotFoundException
*
* @return string
*/
@@ -296,8 +306,8 @@ class ZipFile implements ZipFileInterface
/**
* @param string $entryName
*
* @throws ZipEntryNotFoundException
* @throws ZipException
* @throws ZipEntryNotFoundException
*
* @return resource
*/
@@ -314,8 +324,8 @@ class ZipFile implements ZipFileInterface
*
* @param string|ZipEntry $entryName
*
* @throws ZipException
* @throws ZipEntryNotFoundException
* @throws ZipException
*
* @return ZipInfo
*/
@@ -363,15 +373,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 +400,15 @@ class ZipFile implements ZipFileInterface
throw new ZipException('Destination is not writable directory');
}
$extractedEntries = [];
if ($extractedEntries === null) {
$extractedEntries = [];
}
$defaultOptions = [
ZipOptions::EXTRACT_SYMLINKS => false,
];
/** @noinspection AdditionOperationOnArraysInspection */
$options += $defaultOptions;
$zipEntries = $this->zipContainer->getEntries();
@@ -417,9 +440,6 @@ class ZipFile implements ZipFileInterface
$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();
@@ -435,7 +455,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 +493,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 +501,7 @@ class ZipFile implements ZipFileInterface
$file
)
);
// @codeCoverageIgnoreEnd
}
try {
@@ -486,9 +510,8 @@ class ZipFile implements ZipFileInterface
unlink($file);
throw $e;
} finally {
fclose($handle);
}
fclose($handle);
if ($unixMode === 0) {
$unixMode = 0644;
@@ -503,8 +526,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 +540,7 @@ class ZipFile implements ZipFileInterface
touch($dir, $lastMod);
}
// ksort($extractedEntries);
ksort($extractedEntries);
return $this;
}
@@ -537,19 +562,12 @@ class ZipFile implements ZipFileInterface
*/
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);
@@ -578,6 +596,30 @@ class ZipFile implements ZipFileInterface
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
@@ -593,6 +635,7 @@ class ZipFile implements ZipFileInterface
ZipOptions::COMPRESSION_METHOD => null,
ZipOptions::MODIFIED_TIME => null,
];
/** @noinspection AdditionOperationOnArraysInspection */
$options += $defaultOptions;
if ($options[ZipOptions::STORE_ONLY_FILES]) {
@@ -629,6 +672,7 @@ class ZipFile implements ZipFileInterface
ZipOptions::COMPRESSION_METHOD => null,
ZipOptions::MODIFIED_TIME => null,
];
/** @noinspection AdditionOperationOnArraysInspection */
$options += $defaultOptions;
if (!$file->isReadable()) {
@@ -643,18 +687,28 @@ class ZipFile implements ZipFileInterface
}
}
$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);
$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) {
@@ -665,30 +719,17 @@ class ZipFile implements ZipFileInterface
ZipCompressionMethod::DEFLATED;
}
$zipEntry->setUncompressedSize($file->getSize());
$zipEntry->setCompressionMethod($compressionMethod);
$zipData = new ZipFileData($file);
$zipData = new ZipFileData($zipEntry, $file);
} elseif ($file->isDir()) {
$zipEntry->setCompressionMethod(ZipCompressionMethod::STORED);
$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;
@@ -781,17 +822,9 @@ class ZipFile implements ZipFileInterface
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'];
@@ -842,14 +875,7 @@ class ZipFile implements ZipFileInterface
*/
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);
@@ -1044,6 +1070,7 @@ class ZipFile implements ZipFileInterface
* @throws ZipException
*
* @return ZipFile
*
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
private function addGlob(
@@ -1571,19 +1598,39 @@ class ZipFile implements ZipFileInterface
{
$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);
$reopen = false;
if ($this->reader !== null) {
$meta = $this->reader->getStreamMetaData();
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;
}
}
}
if (!@rename($tempFilename, $filename)) {
if (is_file($tempFilename)) {
unlink($tempFilename);
}
throw new ZipException('Can not move ' . $tempFilename . ' to ' . $filename);
throw new ZipException(sprintf('Cannot move %s to %s', $tempFilename, $filename));
}
if ($reopen) {
return $this->openFile($filename);
}
return $this;
@@ -1769,8 +1816,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();
}
/**
@@ -1788,24 +1836,11 @@ class ZipFile implements ZipFileInterface
$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']);
}
/**

View File

@@ -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.

View File

@@ -0,0 +1,126 @@
<?php
namespace PhpZip\Tests;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
use PhpZip\Model\Extra\Fields\NtfsExtraField;
use PhpZip\Tests\Internal\CustomZip\ZipFileCustomWriter;
use PhpZip\Tests\Internal\CustomZip\ZipFileWithBeforeSave;
use PhpZip\Tests\Internal\Epub\EpubFile;
use PhpZip\ZipFile;
/**
* Checks the ability to create own file-type class, reader, writer and container.
**.
*
* @internal
*
* @small
*/
final class CustomZipFormatTest extends ZipTestCase
{
/**
* @throws ZipException
*
* @see http://www.epubtest.org/test-books source epub files
*/
public function testEpub()
{
$epubFile = new EpubFile();
$epubFile->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);
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
/** @noinspection PhpUndefinedMethodInspection */
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\Fields\AbstractUnicodeExtraField;
/**
* Class AbstractUnicodeExtraFieldTest.
*/
abstract class AbstractUnicodeExtraFieldTest extends TestCase
{
/**
* @return string|AbstractUnicodeExtraField
*
* @psalm-var class-string<\PhpZip\Model\Extra\Fields\AbstractUnicodeExtraField>
*/
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");
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,101 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\Fields\AsiExtraField;
/**
* @internal
*
* @small
*/
final class AsiExtraFieldTest extends TestCase
{
/**
* @dataProvider provideExtraField
*
* @param int $mode
* @param int $uid
* @param int $gid
* @param string $link
* @param string $binaryData
*
* @throws ZipException
*/
public function testExtraField($mode, $uid, $gid, $link, $binaryData)
{
$asiExtraField = new AsiExtraField($mode, $uid, $gid, $link);
self::assertSame($asiExtraField->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");
}
}

View File

@@ -0,0 +1,158 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Model\Extra\Fields\ExtendedTimestampExtraField;
/**
* Class ExtendedTimestampExtraFieldTest.
*
* @internal
*
* @small
*/
final class ExtendedTimestampExtraFieldTest extends TestCase
{
/**
* @dataProvider provideExtraField
*
* @param int $flags
* @param int|null $modifyTime
* @param int|null $accessTime
* @param int|null $createTime
* @param string $localData
* @param string $cdData
*
* @noinspection PhpTooManyParametersInspection
*/
public function testExtraField(
$flags,
$modifyTime,
$accessTime,
$createTime,
$localData,
$cdData
) {
$localExtraField = new ExtendedTimestampExtraField($flags, $modifyTime, $accessTime, $createTime);
self::assertSame($localExtraField->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);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\Fields\JarMarkerExtraField;
/**
* Class JarMarkerExtraFieldTest.
*
* @internal
*
* @small
*/
final class JarMarkerExtraFieldTest extends TestCase
{
/**
* @throws ZipException
*/
public function testExtraField()
{
$jarField = new JarMarkerExtraField();
self::assertSame('', $jarField->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");
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
/**
* Class NewUnixExtraFieldTest.
*
* @internal
*
* @small
*/
final class NewUnixExtraFieldTest extends TestCase
{
/**
* @dataProvider provideExtraField
*
* @param int $version
* @param int $uid
* @param int $gid
* @param string $binaryData
*
* @throws ZipException
*/
public function testExtraField($version, $uid, $gid, $binaryData)
{
$extraField = new NewUnixExtraField($version, $uid, $gid);
self::assertSame($extraField->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());
}
}

View File

@@ -0,0 +1,245 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Model\Extra\Fields\NtfsExtraField;
/**
* Class NtfsExtraFieldTest.
*
* @internal
*
* @small
*/
final class NtfsExtraFieldTest extends TestCase
{
protected function setUp()
{
if (\PHP_INT_SIZE === 4) {
self::markTestSkipped('only 64 bit test');
}
}
/**
* @dataProvider provideExtraField
*
* @param int $modifyNtfsTime
* @param int $accessNtfsTime
* @param int $createNtfsTime
* @param float $modifyTimestamp
* @param float $accessTimestamp
* @param float $createTimestamp
* @param string $binaryData
*
* @throws \Exception
*
* @noinspection PhpTooManyParametersInspection
*/
public function testExtraField(
$modifyNtfsTime,
$accessNtfsTime,
$createNtfsTime,
$modifyTimestamp,
$accessTimestamp,
$createTimestamp,
$binaryData
) {
$extraField = new NtfsExtraField($modifyNtfsTime, $accessNtfsTime, $createNtfsTime);
self::assertSame($extraField->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);
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Model\Extra\Fields\OldUnixExtraField;
/**
* Class OldUnixExtraFieldTest.
*
* @internal
*
* @small
*/
final class OldUnixExtraFieldTest extends TestCase
{
/**
* @dataProvider provideExtraField
*
* @param int|null $accessTime
* @param int|null $modifyTime
* @param int|null $uid
* @param int|null $gid
* @param string $localBinaryData
* @param string $cdBinaryData
*
* @noinspection PhpTooManyParametersInspection
*
* @throws \Exception
*/
public function testExtraField(
$accessTime,
$modifyTime,
$uid,
$gid,
$localBinaryData,
$cdBinaryData
) {
$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::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);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PhpZip\Model\Extra\Fields\UnicodeCommentExtraField;
/**
* @internal
*
* @small
*/
final class UnicodeCommentExtraFieldTest extends AbstractUnicodeExtraFieldTest
{
/**
* {@inheritdoc}
*/
protected function getUnicodeExtraFieldClassName()
{
return UnicodeCommentExtraField::class;
}
/**
* @return array
*/
public function provideExtraField()
{
return [
[
4293813303,
'комментарий',
"\xAA\xAE\xAC\xAC\xA5\xAD\xE2\xA0\xE0\xA8\xA9",
"\x017d\xEE\xFF\xD0\xBA\xD0\xBE\xD0\xBC\xD0\xBC\xD0\xB5\xD0\xBD\xD1\x82\xD0\xB0\xD1\x80\xD0\xB8\xD0\xB9",
],
[
897024324,
'תגובה',
"\x9A\x82\x85\x81\x84",
"\x01D\x81w5\xD7\xAA\xD7\x92\xD7\x95\xD7\x91\xD7\x94",
],
];
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
/**
* Class UnicodePathExtraFieldTest.
*
* @internal
*
* @small
*/
final class UnicodePathExtraFieldTest extends AbstractUnicodeExtraFieldTest
{
/**
* {@inheritdoc}
*/
protected function getUnicodeExtraFieldClassName()
{
return UnicodePathExtraField::class;
}
/**
* @return array
*/
public function provideExtraField()
{
return [
[
2728523760,
'txt\מבחן עברי.txt',
"txt/\x8E\x81\x87\x8F \x92\x81\x98\x89.txt",
"\x01\xF0\xF7\xA1\xA2txt\\\xD7\x9E\xD7\x91\xD7\x97\xD7\x9F \xD7\xA2\xD7\x91\xD7\xA8\xD7\x99.txt",
],
[
953311492,
'ä\ü.txt',
"\x84/\x81.txt",
"\x01\x04a\xD28\xC3\xA4\\\xC3\xBC.txt",
],
[
2965532848,
'Ölfässer.txt',
"\x99lf\x84sser.txt",
"\x01\xB0p\xC2\xB0\xC3\x96lf\xC3\xA4sser.txt",
],
[
3434671236,
'Как заработать в интернете.mp4',
"\x8A\xA0\xAA \xA7\xA0\xE0\xA0\xA1\xAE\xE2\xA0\xE2\xEC \xA2 \xA8\xAD\xE2\xA5\xE0\xAD\xA5\xE2\xA5.mp4",
"\x01\x84\xEC\xB8\xCC\xD0\x9A\xD0\xB0\xD0\xBA \xD0\xB7\xD0\xB0\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xB0\xD1\x82\xD1\x8C \xD0\xB2 \xD0\xB8\xD0\xBD\xD1\x82\xD0\xB5\xD1\x80\xD0\xBD\xD0\xB5\xD1\x82\xD0\xB5.mp4",
],
];
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Exception\RuntimeException;
use PhpZip\Model\Extra\Fields\UnrecognizedExtraField;
/**
* Class UnrecognizedExtraFieldTest.
*
* @internal
*
* @small
*/
final class UnrecognizedExtraFieldTest extends TestCase
{
public function testExtraField()
{
$headerId = 0xF00D;
$binaryData = "\x01\x02\x03\x04\x05";
$unrecognizedExtraField = new UnrecognizedExtraField($headerId, $binaryData);
self::assertSame($unrecognizedExtraField->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");
}
}

View File

@@ -0,0 +1,240 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipUnsupportMethodException;
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
/**
* @internal
*
* @small
*/
final class WinZipAesExtraFieldTest extends TestCase
{
/**
* @dataProvider provideExtraField
*
* @param int $vendorVersion
* @param int $keyStrength
* @param int $compressionMethod
* @param int $saltSize
* @param string $binaryData
*
* @throws ZipException
* @throws ZipUnsupportMethodException
*/
public function testExtraField(
$vendorVersion,
$keyStrength,
$compressionMethod,
$saltSize,
$binaryData
) {
$extraField = new WinZipAesExtraField($vendorVersion, $keyStrength, $compressionMethod);
self::assertSame($extraField->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
);
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace PhpZip\Tests\Extra\Fields;
use PHPUnit\Framework\TestCase;
use PhpZip\Constants\ZipConstants;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
use PhpZip\Model\ZipEntry;
/**
* @internal
*
* @small
*/
final class Zip64ExtraFieldTest extends TestCase
{
protected function setUp()
{
if (\PHP_INT_SIZE === 4) {
self::markTestSkipped('only 64 bit test');
}
}
/**
* @dataProvider provideExtraField
*
* @param int|null $uncompressedSize
* @param int|null $compressedSize
* @param int|null $localHeaderOffset
* @param int|null $diskStart
* @param string|null $localBinaryData
* @param string|null $cdBinaryData
*
* @throws ZipException
*
* @noinspection PhpTooManyParametersInspection
*/
public function testExtraField(
$uncompressedSize,
$compressedSize,
$localHeaderOffset,
$diskStart,
$localBinaryData,
$cdBinaryData
) {
$extraField = new Zip64ExtraField(
$uncompressedSize,
$compressedSize,
$localHeaderOffset,
$diskStart
);
self::assertSame($extraField->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);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace PhpZip\Tests\Internal\CustomZip;
use PhpZip\IO\ZipWriter;
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
use PhpZip\Model\Extra\Fields\NtfsExtraField;
use PhpZip\Model\ZipContainer;
/**
* Class CustomZipWriter.
*/
class CustomZipWriter extends ZipWriter
{
/**
* ZipWriter constructor.
*
* @param ZipContainer $container
*/
public function __construct(ZipContainer $container)
{
// dump($container);
parent::__construct($container);
// dd($this->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);
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace PhpZip\Tests\Internal\CustomZip;
use PhpZip\IO\ZipWriter;
use PhpZip\ZipFile;
/**
* Class ZipFileCustomWriter.
*/
class ZipFileCustomWriter extends ZipFile
{
/**
* @return ZipWriter
*/
protected function createZipWriter()
{
return new CustomZipWriter($this->zipContainer);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace PhpZip\Tests\Internal\CustomZip;
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
use PhpZip\Model\Extra\Fields\NtfsExtraField;
use PhpZip\ZipFile;
/**
* Class ZipFileWithBeforeSave.
*/
class ZipFileWithBeforeSave extends ZipFile
{
/**
* Event before save or output.
*/
protected function onBeforeSave()
{
$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);
}
}
}

View File

@@ -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;

View File

@@ -0,0 +1,98 @@
<?php
/** @noinspection PhpComposerExtensionStubsInspection */
namespace PhpZip\Tests\Internal\Epub;
use PhpZip\Constants\ZipPlatform;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\IO\ZipReader;
use PhpZip\IO\ZipWriter;
use PhpZip\Model\ImmutableZipContainer;
use PhpZip\Model\ZipContainer;
use PhpZip\Model\ZipEntry;
use PhpZip\ZipFile;
/**
* Class EpubFile.
*
* @property EpubZipContainer $zipContainer
*/
class EpubFile extends ZipFile
{
/**
* @return ZipWriter
*/
protected function createZipWriter()
{
return new EpubWriter($this->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;
}
}

View File

@@ -0,0 +1,159 @@
<?php
/** @noinspection PhpComposerExtensionStubsInspection */
namespace PhpZip\Tests\Internal\Epub;
use PhpZip\Exception\ZipException;
/**
* Class EpubInfo.
*
* @see http://idpf.org/epub/30/spec/epub30-publications.html
*/
class EpubInfo
{
/** @var string|null */
private $title;
/** @var string|null */
private $creator;
/** @var string|null */
private $language;
/** @var string|null */
private $publisher;
/** @var string|null */
private $description;
/** @var string|null */
private $rights;
/** @var string|null */
private $date;
/** @var string|null */
private $subject;
/**
* EpubInfo constructor.
*
* @param $xmlContents
*
* @throws ZipException
*/
public function __construct($xmlContents)
{
$doc = new \DOMDocument();
$doc->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,
];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace PhpZip\Tests\Internal\Epub;
use PhpZip\IO\ZipReader;
/**
* Class EpubReader.
*/
class EpubReader extends ZipReader
{
/**
* @return bool
*
* @see https://github.com/w3c/epubcheck/issues/334
*/
protected function isZip64Support()
{
return false;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace PhpZip\Tests\Internal\Epub;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipPlatform;
use PhpZip\Exception\ZipUnsupportMethodException;
use PhpZip\IO\ZipWriter;
use PhpZip\Model\Data\ZipNewData;
use PhpZip\Model\ZipEntry;
/**
* Class EpubWriter.
*
* @property EpubZipContainer $zipContainer
*/
class EpubWriter extends ZipWriter
{
/**
* @throws ZipUnsupportMethodException
*/
protected function beforeWrite()
{
parent::beforeWrite();
if (!$this->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());
}
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace PhpZip\Tests\Internal\Epub;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Model\ZipContainer;
/**
* Class EpubZipContainer.
*/
class EpubZipContainer extends ZipContainer
{
/**
* @throws ZipEntryNotFoundException
*
* @return string
*/
public function getMimeType()
{
return $this->getEntry('mimetype')->getData()->getDataAsString();
}
}

View File

@@ -76,7 +76,7 @@ class Zip64Test extends ZipTestCase
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);

78
tests/SymlinkTest.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
namespace PhpZip\Tests;
use PhpZip\Constants\ZipOptions;
use PhpZip\Util\FilesUtil;
use PhpZip\ZipFile;
use Symfony\Component\Finder\Finder;
/**
* @internal
*
* @small
*/
final class SymlinkTest extends ZipTestCase
{
/**
* @dataProvider provideAllowSymlink
*
* @param bool $allowSymlink
*
* @throws \Exception
*/
public function testSymlink($allowSymlink)
{
if (self::skipTestForWindows()) {
return;
}
if (!is_dir($this->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];
}
}

View File

@@ -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();

View File

@@ -266,7 +266,7 @@ class ZipEntryTest extends TestCase
$dosEntryName = DosCodePage::fromUTF8($entryName, $charset);
static::assertSame(DosCodePage::toUTF8($dosEntryName, $charset), $entryName);
$unicodePathExtraField = UnicodePathExtraField::create($entryName);
$unicodePathExtraField = new UnicodePathExtraField(crc32($dosEntryName), $entryName);
$zipEntry = new ZipEntry($dosEntryName, $charset);
static::assertSame($zipEntry->getName(), $dosEntryName);
@@ -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
*
@@ -800,6 +849,12 @@ class ZipEntryTest extends TestCase
*/
public function testInvalidDosTime($dosTime)
{
if (\PHP_INT_SIZE === 4) {
static::markTestSkipped('only 64 bit test');
return;
}
$this->setExpectedException(InvalidArgumentException::class, 'DosTime out of range');
$zipEntry = new ZipEntry('entry');
@@ -951,6 +1006,12 @@ class ZipEntryTest extends TestCase
*/
public function testInvalidExternalAttributes($externalAttributes)
{
if (\PHP_INT_SIZE === 4) {
static::markTestSkipped('only 64 bit test');
return;
}
$this->setExpectedException(InvalidArgumentException::class, 'external attributes out of range');
$zipEntry = new ZipEntry('entry');
@@ -1510,9 +1571,10 @@ class ZipEntryTest extends TestCase
public function testClone()
{
$newUnixExtra = new NewUnixExtraField();
$zipData = new ZipFileData(new \SplFileInfo(__FILE__));
$zipEntry = new ZipEntry('entry');
$zipData = new ZipFileData($zipEntry, new \SplFileInfo(__FILE__));
$zipEntry->addExtraField($newUnixExtra);
$zipEntry->setData($zipData);

View File

@@ -1,41 +0,0 @@
<?php
namespace PhpZip\Tests;
use PhpZip\Exception\ZipException;
use PhpZip\Tests\Internal\ZipFileExtended;
/**
* @internal
*
* @small
*/
class ZipEventTest extends ZipTestCase
{
/**
* @throws ZipException
*/
public function testBeforeSave()
{
$zipFile = new ZipFileExtended();
$zipFile->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();
}
}

View File

@@ -75,7 +75,7 @@ abstract class ZipFileSetTestCase extends ZipTestCase
$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,

View File

@@ -10,6 +10,7 @@ use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipUnsupportMethodException;
use PhpZip\Model\Data\ZipFileData;
use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipInfo;
use PhpZip\Util\FilesUtil;
@@ -33,7 +34,7 @@ class ZipFileTest extends ZipTestCase
$this->setExpectedException(ZipException::class, 'does not exist');
$zipFile = new ZipFile();
$zipFile->openFile(uniqid('', true));
$zipFile->openFile(uniqid('', false));
}
/**
@@ -41,12 +42,16 @@ class ZipFileTest extends ZipTestCase
*/
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));
@@ -1009,16 +1014,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);
@@ -1027,19 +1032,33 @@ 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) {
$fullExtractedFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $entryName;
foreach ($entries as $entryName => $contents) {
$name = $entryName;
if ($value === null) {
if (\DIRECTORY_SEPARATOR === '\\') {
$name = str_replace('/', '\\', $name);
}
$fullExtractedFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $name;
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();
}
@@ -1383,14 +1402,18 @@ class ZipFileTest extends ZipTestCase
/**
* @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));
@@ -1428,7 +1451,7 @@ class ZipFileTest extends ZipTestCase
$this->setExpectedException(InvalidArgumentException::class, 'does not exist');
$zipFile = new ZipFile();
$zipFile->addDir(uniqid('', true));
$zipFile->addDir(uniqid('', false));
}
/**
@@ -1461,7 +1484,7 @@ class ZipFileTest extends ZipTestCase
$this->setExpectedException(InvalidArgumentException::class, 'does not exist');
$zipFile = new ZipFile();
$zipFile->addDirRecursive(uniqid('', true));
$zipFile->addDirRecursive(uniqid('', false));
}
/**
@@ -1701,7 +1724,9 @@ class ZipFileTest extends ZipTestCase
*/
public function testSaveAsFileNotWritable()
{
$this->setExpectedException(InvalidArgumentException::class, 'can not open from write');
if (static::skipTestForWindows()) {
return;
}
if (static::skipTestForRootUser()) {
return;
@@ -1712,6 +1737,8 @@ class ZipFileTest extends ZipTestCase
$this->outputFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . basename($this->outputFilename);
$this->setExpectedExceptionRegExp(InvalidArgumentException::class, '~Cannot open ".*?" for writing.~');
$zipFile = new ZipFile();
$zipFile->saveAsFile($this->outputFilename);
}
@@ -1867,7 +1894,7 @@ class ZipFileTest extends ZipTestCase
*/
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);
@@ -1878,7 +1905,7 @@ class ZipFileTest extends ZipTestCase
*/
public function testAddEmptyDirEmptyName()
{
$this->setExpectedException(InvalidArgumentException::class, 'Empty dir name');
$this->setExpectedException(InvalidArgumentException::class, 'Empty entry name');
$zipFile = new ZipFile();
$zipFile->addEmptyDir('');
@@ -1933,6 +1960,8 @@ class ZipFileTest extends ZipTestCase
*/
public function testRewriteString()
{
$this->setExpectedException(ZipException::class, 'Overwrite is only supported for open local files');
$zipFile = new ZipFile();
$zipFile['file'] = 'content';
$zipFile['file2'] = 'content2';
@@ -1963,6 +1992,87 @@ class ZipFileTest extends ZipTestCase
$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
*/
@@ -2418,4 +2528,87 @@ class ZipFileTest extends ZipTestCase
static::assertSame($zipFile->getEntry($newEntryName)->getCompressionMethod(), ZipCompressionMethod::STORED);
$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
*/
public function testReplaceEntryContentsByFile()
{
$entryName = basename(__FILE__);
$zipFile = new ZipFile();
$zipFile[$entryName] = 'contents';
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$zipFile->openFile($this->outputFilename);
$entry = $zipFile->getEntry($entryName);
$data = new ZipFileData($entry, new \SplFileInfo(__FILE__));
$entry->setData($data);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
static::assertSame(
$zipFile->getEntryContents($entryName),
file_get_contents(__FILE__)
);
$zipFile->close();
}
}

116
tests/ZipInfoTest.php Normal file
View File

@@ -0,0 +1,116 @@
<?php
namespace PhpZip\Tests;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Constants\ZipPlatform;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipInfo;
use PhpZip\ZipFile;
/**
* Testing the {@see ZipInfo} class.
*
* {@see ZipInfo} is {@deprecated}. Use the {@see ZipEntry} class.
*
* @internal
*
* @small
*/
final class ZipInfoTest extends ZipTestCase
{
public function testZipAllInfo()
{
$zipFile = new ZipFile();
$zipFile['entry'] = 'contents';
$zipFile['entry 2'] = 'contents';
$zipAllInfo = $zipFile->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());
}
}

View File

@@ -77,7 +77,8 @@ class ZipPasswordTest extends ZipFileSetTestCase
$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 @@ class ZipPasswordTest extends ZipFileSetTestCase
);
}
$password = base64_encode(random_bytes(50));
$password = md5(random_bytes(50));
$zip = new ZipFile();
$zip->addDirRecursive($this->outputDirname);

View File

@@ -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;
}

View File

@@ -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 @@ abstract class ZipTestCase extends TestCase
*/
public static function assertCorrectZipArchive($filename, $password = null)
{
if (self::existsProgram('unzip')) {
$command = 'unzip';
if ($password !== null) {
$command .= ' -P ' . escapeshellarg($password);
}
$command .= ' -t ' . escapeshellarg($filename);
$command .= ' 2>&1';
exec($command, $output, $returnCode);
$output = implode(\PHP_EOL, $output);
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);
}
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);
}
}
/**
* @param string $filename
* @param string|null $password
*/
private static function assertCorrectZipArchiveFrom7z($filename, $password = null)
{
$command = '7z t';
if ($password !== null) {
$command .= ' -p' . escapeshellarg($password);
}
$command .= ' ' . escapeshellarg($filename) . ' 2>&1';
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 @@ abstract class ZipTestCase extends TestCase
*/
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 @@ abstract class ZipTestCase extends TestCase
}
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 @@ abstract class ZipTestCase extends TestCase
return false;
}
/**
* @return bool
*/
public static function skipTestForWindows()
{
if (\DIRECTORY_SEPARATOR === '\\') {
static::markTestSkipped('Skip on Windows');
return true;
}
return false;
}
}

Binary file not shown.