1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-07-20 07:21:17 +02:00

Merge branch 'hotfix/3.1.13'

This commit is contained in:
Ne-Lexa
2019-12-09 11:20:56 +03:00
63 changed files with 6203 additions and 3150 deletions

1500
.php_cs Normal file

File diff suppressed because it is too large Load Diff

@@ -1,3 +1,5 @@
dist: trusty
language: php
php:
- '5.5'
@@ -6,12 +8,7 @@ php:
- '7.1'
- '7.2'
- '7.3'
# cache vendor dirs
cache:
directories:
- vendor
- $HOME/.composer/cache
- '7.4'
install:
- travis_retry composer self-update && composer --version

@@ -21,12 +21,13 @@
}
],
"require": {
"ext-zlib": "*",
"php": "^5.5 || ^7.0",
"psr/http-message": "^1.0"
"ext-zlib": "*",
"psr/http-message": "^1.0",
"paragonie/random_compat": ">=1 <9.99"
},
"require-dev": {
"phpunit/phpunit": "~4.8|~5.7",
"phpunit/phpunit": "^4.8|^5.7",
"zendframework/zend-diactoros": "^1.4"
},
"autoload": {
@@ -45,5 +46,15 @@
"ext-bz2": "Needed to support BZIP2 compression",
"ext-fileinfo": "Needed to get mime-type file"
},
"minimum-stability": "stable"
"minimum-stability": "stable",
"scripts": {
"php:fix": "php .php_cs --force",
"php:fix:debug": "php .php_cs"
},
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev",
"dev-develop": "3.2.x-dev"
}
}
}

@@ -2,75 +2,297 @@
namespace PhpZip\Crypto;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipCryptoException;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\CryptoUtil;
use PhpZip\Util\PackUtil;
/**
* Traditional PKWARE Encryption Engine.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
{
/**
* Encryption header size
*/
/** Encryption header size */
const STD_DEC_HDR_SIZE = 12;
/**
* Crc table
* Crc table.
*
* @var array
*/
private static $CRC_TABLE = [
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
0x00000000,
0x77073096,
0xee0e612c,
0x990951ba,
0x076dc419,
0x706af48f,
0xe963a535,
0x9e6495a3,
0x0edb8832,
0x79dcb8a4,
0xe0d5e91e,
0x97d2d988,
0x09b64c2b,
0x7eb17cbd,
0xe7b82d07,
0x90bf1d91,
0x1db71064,
0x6ab020f2,
0xf3b97148,
0x84be41de,
0x1adad47d,
0x6ddde4eb,
0xf4d4b551,
0x83d385c7,
0x136c9856,
0x646ba8c0,
0xfd62f97a,
0x8a65c9ec,
0x14015c4f,
0x63066cd9,
0xfa0f3d63,
0x8d080df5,
0x3b6e20c8,
0x4c69105e,
0xd56041e4,
0xa2677172,
0x3c03e4d1,
0x4b04d447,
0xd20d85fd,
0xa50ab56b,
0x35b5a8fa,
0x42b2986c,
0xdbbbc9d6,
0xacbcf940,
0x32d86ce3,
0x45df5c75,
0xdcd60dcf,
0xabd13d59,
0x26d930ac,
0x51de003a,
0xc8d75180,
0xbfd06116,
0x21b4f4b5,
0x56b3c423,
0xcfba9599,
0xb8bda50f,
0x2802b89e,
0x5f058808,
0xc60cd9b2,
0xb10be924,
0x2f6f7c87,
0x58684c11,
0xc1611dab,
0xb6662d3d,
0x76dc4190,
0x01db7106,
0x98d220bc,
0xefd5102a,
0x71b18589,
0x06b6b51f,
0x9fbfe4a5,
0xe8b8d433,
0x7807c9a2,
0x0f00f934,
0x9609a88e,
0xe10e9818,
0x7f6a0dbb,
0x086d3d2d,
0x91646c97,
0xe6635c01,
0x6b6b51f4,
0x1c6c6162,
0x856530d8,
0xf262004e,
0x6c0695ed,
0x1b01a57b,
0x8208f4c1,
0xf50fc457,
0x65b0d9c6,
0x12b7e950,
0x8bbeb8ea,
0xfcb9887c,
0x62dd1ddf,
0x15da2d49,
0x8cd37cf3,
0xfbd44c65,
0x4db26158,
0x3ab551ce,
0xa3bc0074,
0xd4bb30e2,
0x4adfa541,
0x3dd895d7,
0xa4d1c46d,
0xd3d6f4fb,
0x4369e96a,
0x346ed9fc,
0xad678846,
0xda60b8d0,
0x44042d73,
0x33031de5,
0xaa0a4c5f,
0xdd0d7cc9,
0x5005713c,
0x270241aa,
0xbe0b1010,
0xc90c2086,
0x5768b525,
0x206f85b3,
0xb966d409,
0xce61e49f,
0x5edef90e,
0x29d9c998,
0xb0d09822,
0xc7d7a8b4,
0x59b33d17,
0x2eb40d81,
0xb7bd5c3b,
0xc0ba6cad,
0xedb88320,
0x9abfb3b6,
0x03b6e20c,
0x74b1d29a,
0xead54739,
0x9dd277af,
0x04db2615,
0x73dc1683,
0xe3630b12,
0x94643b84,
0x0d6d6a3e,
0x7a6a5aa8,
0xe40ecf0b,
0x9309ff9d,
0x0a00ae27,
0x7d079eb1,
0xf00f9344,
0x8708a3d2,
0x1e01f268,
0x6906c2fe,
0xf762575d,
0x806567cb,
0x196c3671,
0x6e6b06e7,
0xfed41b76,
0x89d32be0,
0x10da7a5a,
0x67dd4acc,
0xf9b9df6f,
0x8ebeeff9,
0x17b7be43,
0x60b08ed5,
0xd6d6a3e8,
0xa1d1937e,
0x38d8c2c4,
0x4fdff252,
0xd1bb67f1,
0xa6bc5767,
0x3fb506dd,
0x48b2364b,
0xd80d2bda,
0xaf0a1b4c,
0x36034af6,
0x41047a60,
0xdf60efc3,
0xa867df55,
0x316e8eef,
0x4669be79,
0xcb61b38c,
0xbc66831a,
0x256fd2a0,
0x5268e236,
0xcc0c7795,
0xbb0b4703,
0x220216b9,
0x5505262f,
0xc5ba3bbe,
0xb2bd0b28,
0x2bb45a92,
0x5cb36a04,
0xc2d7ffa7,
0xb5d0cf31,
0x2cd99e8b,
0x5bdeae1d,
0x9b64c2b0,
0xec63f226,
0x756aa39c,
0x026d930a,
0x9c0906a9,
0xeb0e363f,
0x72076785,
0x05005713,
0x95bf4a82,
0xe2b87a14,
0x7bb12bae,
0x0cb61b38,
0x92d28e9b,
0xe5d5be0d,
0x7cdcefb7,
0x0bdbdf21,
0x86d3d2d4,
0xf1d4e242,
0x68ddb3f8,
0x1fda836e,
0x81be16cd,
0xf6b9265b,
0x6fb077e1,
0x18b74777,
0x88085ae6,
0xff0f6a70,
0x66063bca,
0x11010b5c,
0x8f659eff,
0xf862ae69,
0x616bffd3,
0x166ccf45,
0xa00ae278,
0xd70dd2ee,
0x4e048354,
0x3903b3c2,
0xa7672661,
0xd06016f7,
0x4969474d,
0x3e6e77db,
0xaed16a4a,
0xd9d65adc,
0x40df0b66,
0x37d83bf0,
0xa9bcae53,
0xdebb9ec5,
0x47b2cf7f,
0x30b5ffe9,
0xbdbdf21c,
0xcabac28a,
0x53b39330,
0x24b4a3a6,
0xbad03605,
0xcdd70693,
0x54de5729,
0x23d967bf,
0xb3667a2e,
0xc4614ab8,
0x5d681b02,
0x2a6f2b94,
0xb40bbe37,
0xc30c8ea1,
0x5a05df1b,
0x2d02ef8d,
];
/**
* Encryption keys
* Encryption keys.
*
* @var array
*/
private $keys = [];
/**
* @var ZipEntry
*/
/** @var ZipEntry */
private $entry;
/**
@@ -84,7 +306,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
}
/**
* Initial keys
* Initial keys.
*
* @param string $password
*/
@@ -93,6 +315,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
$this->keys[0] = 305419896;
$this->keys[1] = 591751049;
$this->keys[2] = 878082192;
foreach (unpack('C*', $password) as $b) {
$this->updateKeys($b);
}
@@ -101,21 +324,22 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
/**
* Update keys.
*
* @param string $charAt
* @param int $charAt
*/
private function updateKeys($charAt)
{
$this->keys[0] = self::crc32($this->keys[0], $charAt);
$this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff);
$this->keys[0] = $this->crc32($this->keys[0], $charAt);
$this->keys[1] += ($this->keys[0] & 0xff);
$this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1);
$this->keys[2] = PackUtil::toSignedInt32(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
$this->keys[2] = PackUtil::toSignedInt32($this->crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
}
/**
* Update crc.
*
* @param int $oldCrc
* @param string $charAt
* @param int $charAt
*
* @return int
*/
private function crc32($oldCrc, $charAt)
@@ -125,18 +349,25 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
/**
* @param string $content
* @return string
*
* @throws ZipAuthenticationException
*
* @return string
*/
public function decrypt($content)
{
if (\PHP_INT_SIZE === 4) {
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
}
$password = $this->entry->getPassword();
$this->initKeys($password);
$headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE)));
$byte = 0;
foreach ($headerBytes as &$byte) {
$byte = ($byte ^ $this->decryptByte()) & 0xff;
for ($i = 0; $i < self::STD_DEC_HDR_SIZE; $i++) {
$byte = ($headerBytes[$i] ^ $this->decryptByte()) & 0xff;
$this->updateKeys($byte);
}
@@ -147,19 +378,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
// compare against the CRC otherwise
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
}
if ($byte !== $checkByte) {
throw new ZipAuthenticationException(sprintf(
'Invalid password for zip entry "%s"',
$this->entry->getName()
));
throw new ZipAuthenticationException(
sprintf(
'Invalid password for zip entry "%s"',
$this->entry->getName()
)
);
}
$outputContent = "";
$outputContent = '';
foreach (unpack('C*', substr($content, self::STD_DEC_HDR_SIZE)) as $val) {
$val = ($val ^ $this->decryptByte()) & 0xff;
$this->updateKeys($val);
$outputContent .= pack('c', $val);
}
return $outputContent;
}
@@ -171,22 +407,34 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
private function decryptByte()
{
$temp = $this->keys[2] | 2;
return (($temp * ($temp ^ 1)) >> 8) & 0xffffff;
}
/**
* Encryption data
* Encryption data.
*
* @param string $data
* @return string
*
* @throws ZipCryptoException
*
* @return string
*/
public function encrypt($data)
{
if (\PHP_INT_SIZE === 4) {
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
}
$crc = $this->entry->isDataDescriptorRequired() ?
($this->entry->getDosTime() & 0x0000ffff) << 16 :
$this->entry->getCrc();
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
try {
$headerBytes = random_bytes(self::STD_DEC_HDR_SIZE);
} catch (\Exception $e) {
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
}
// Initialize again since the generated bytes were encrypted.
$password = $this->entry->getPassword();
@@ -196,13 +444,16 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
$headerBytes[self::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
$headerBytes = $this->encryptData($headerBytes);
return $headerBytes . $this->encryptData($data);
}
/**
* @param string $content
* @return string
*
* @throws ZipCryptoException
*
* @return string
*/
private function encryptData($content)
{
@@ -210,20 +461,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
throw new ZipCryptoException('content is null');
}
$buff = '';
foreach (unpack('C*', $content) as $val) {
$buff .= pack('c', $this->encryptByte($val));
}
return $buff;
}
/**
* @param int $byte
*
* @return int
*/
private function encryptByte($byte)
{
$tempVal = $byte ^ $this->decryptByte() & 0xff;
$this->updateKeys($byte);
return $tempVal;
}
}

@@ -8,12 +8,12 @@ use PhpZip\Exception\ZipCryptoException;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\CryptoUtil;
/**
* WinZip Aes Encryption Engine.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
@@ -24,18 +24,18 @@ class WinZipAesEngine implements ZipEncryptionEngine
* in bits (AES_BLOCK_SIZE_BITS).
*/
const AES_BLOCK_SIZE_BITS = 128;
const PWD_VERIFIER_BITS = 16;
/**
* The iteration count for the derived keys of the cipher, KLAC and MAC.
*/
/** The iteration count for the derived keys of the cipher, KLAC and MAC. */
const ITERATION_COUNT = 1000;
/**
* @var ZipEntry
*/
/** @var ZipEntry */
private $entry;
/**
* WinZipAesEngine constructor.
*
* @param ZipEntry $entry
*/
public function __construct(ZipEntry $entry)
@@ -47,17 +47,19 @@ class WinZipAesEngine implements ZipEncryptionEngine
* Decrypt from stream resource.
*
* @param string $content Input stream buffer
* @return string
*
* @throws ZipException
* @throws ZipAuthenticationException
* @throws ZipCryptoException
* @throws \PhpZip\Exception\ZipException
*
* @return string
*/
public function decrypt($content)
{
$extraFieldsCollection = $this->entry->getExtraFieldsCollection();
if (!isset($extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()])) {
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
throw new ZipCryptoException($this->entry->getName() . ' (missing extra field for WinZip AES entry)');
}
/**
@@ -78,32 +80,39 @@ class WinZipAesEngine implements ZipEncryptionEngine
// Init start, end and size of encrypted data.
$start = $pos;
$endPos = strlen($content);
$endPos = \strlen($content);
$footerSize = $sha1Size / 2;
$end = $endPos - $footerSize;
$size = $end - $start;
if (0 > $size) {
throw new ZipCryptoException($this->entry->getName() . " (false positive WinZip AES entry is too short)");
if ($size < 0) {
throw new ZipCryptoException($this->entry->getName() . ' (false positive WinZip AES entry is too short)');
}
// Load authentication code.
$authenticationCode = substr($content, $end, $footerSize);
if ($end + $footerSize !== $endPos) {
// This should never happen unless someone is writing to the
// end of the file concurrently!
throw new ZipCryptoException("Expected end of file after WinZip AES authentication code!");
throw new ZipCryptoException('Expected end of file after WinZip AES authentication code!');
}
$password = $this->entry->getPassword();
assert($password !== null);
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
// WinZip 99-character limit
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
if ($password === null) {
throw new ZipException(sprintf('Password not set for entry %s', $this->entry->getName()));
}
/**
* WinZip 99-character limit.
*
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
*/
$password = substr($password, 0, 99);
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(chr(0), $ctrIvSize);
$iv = str_repeat(\chr(0), $ctrIvSize);
do {
// Here comes the strange part about WinZip AES encryption:
// Its unorthodox use of the Password-Based Key Derivation
@@ -111,7 +120,7 @@ class WinZipAesEngine implements ZipEncryptionEngine
// Yes, the password verifier is only a 16 bit value.
// So we must use the MAC for password verification, too.
$keyParam = hash_pbkdf2(
"sha1",
'sha1',
$password,
$salt,
self::ITERATION_COUNT,
@@ -126,9 +135,11 @@ class WinZipAesEngine implements ZipEncryptionEngine
$content = substr($content, $start, $size);
$mac = hash_hmac('sha1', $content, $sha1MacParam, true);
if (substr($mac, 0, 10) !== $authenticationCode) {
throw new ZipAuthenticationException($this->entry->getName() .
" (authenticated WinZip AES entry content has been tampered with)");
if (strpos($mac, $authenticationCode) !== 0) {
throw new ZipAuthenticationException(
$this->entry->getName() .
' (authenticated WinZip AES entry content has been tampered with)'
);
}
return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false);
@@ -137,25 +148,27 @@ class WinZipAesEngine implements ZipEncryptionEngine
/**
* Decryption or encryption AES-CTR with Segment Integer Count (SIC).
*
* @param string $str Data
* @param string $key Key
* @param string $iv IV
* @param bool $encrypted If true encryption else decryption
* @param string $str Data
* @param string $key Key
* @param string $iv IV
* @param bool $encrypted If true encryption else decryption
*
* @return string
*/
private static function aesCtrSegmentIntegerCounter($str, $key, $iv, $encrypted = true)
{
$numOfBlocks = ceil(strlen($str) / 16);
$numOfBlocks = ceil(\strlen($str) / 16);
$ctrStr = '';
for ($i = 0; $i < $numOfBlocks; ++$i) {
for ($j = 0; $j < 16; ++$j) {
$n = ord($iv[$j]);
$n = \ord($iv[$j]);
if (++$n === 0x100) {
// overflow, set this one to 0, increment next
$iv[$j] = chr(0);
$iv[$j] = \chr(0);
} else {
// no overflow, just write incremented number back and abort
$iv[$j] = chr($n);
$iv[$j] = \chr($n);
break;
}
}
@@ -164,6 +177,7 @@ class WinZipAesEngine implements ZipEncryptionEngine
self::encryptCtr($data, $key, $iv) :
self::decryptCtr($data, $key, $iv);
}
return $ctrStr;
}
@@ -171,78 +185,101 @@ class WinZipAesEngine implements ZipEncryptionEngine
* Encrypt AES-CTR.
*
* @param string $data Raw data
* @param string $key Aes key
* @param string $iv Aes IV
* @param string $key Aes key
* @param string $iv Aes IV
*
* @return string Encrypted data
*/
private static function encryptCtr($data, $key, $iv)
{
if (extension_loaded("openssl")) {
$numBits = strlen($key) * 8;
if (\extension_loaded('openssl')) {
$numBits = \strlen($key) * 8;
/** @noinspection PhpComposerExtensionStubsInspection */
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
} elseif (extension_loaded("mcrypt")) {
/** @noinspection PhpDeprecationInspection */
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
} else {
throw new RuntimeException('Extension openssl or mcrypt not loaded');
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv);
}
if (\extension_loaded('mcrypt')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
}
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
/**
* Decrypt AES-CTR.
*
* @param string $data Encrypted data
* @param string $key Aes key
* @param string $iv Aes IV
* @param string $key Aes key
* @param string $iv Aes IV
*
* @return string Raw data
*/
private static function decryptCtr($data, $key, $iv)
{
if (extension_loaded("openssl")) {
$numBits = strlen($key) * 8;
if (\extension_loaded('openssl')) {
$numBits = \strlen($key) * 8;
/** @noinspection PhpComposerExtensionStubsInspection */
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
} elseif (extension_loaded("mcrypt")) {
/** @noinspection PhpDeprecationInspection */
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
} else {
throw new RuntimeException('Extension openssl or mcrypt not loaded');
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv);
}
if (\extension_loaded('mcrypt')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
}
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
/**
* Encryption string.
*
* @param string $content
*
* @throws ZipException
*
* @return string
* @throws \PhpZip\Exception\ZipException
*/
public function encrypt($content)
{
// Init key strength.
$password = $this->entry->getPassword();
if ($password === null) {
throw new ZipException('No password was set for the entry "'.$this->entry->getName().'"');
throw new ZipException('No password was set for the entry "' . $this->entry->getName() . '"');
}
// WinZip 99-character limit
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
/**
* WinZip 99-character limit.
*
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
*/
$password = substr($password, 0, 99);
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($this->entry->getEncryptionMethod());
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
$this->entry->getEncryptionMethod()
);
$keyStrengthBytes = $keyStrengthBits / 8;
$salt = CryptoUtil::randomBytes($keyStrengthBytes / 2);
try {
$salt = random_bytes($keyStrengthBytes / 2);
} catch (\Exception $e) {
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
}
$keyParam = hash_pbkdf2("sha1", $password, $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true);
$keyParam = hash_pbkdf2(
'sha1',
$password,
$salt,
self::ITERATION_COUNT,
(2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8,
true
);
$sha1HMacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
// Can you believe they "forgot" the nonce in the CTR mode IV?! :-(
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(chr(0), $ctrIvSize);
$iv = str_repeat(\chr(0), $ctrIvSize);
$key = substr($keyParam, 0, $keyStrengthBytes);
@@ -250,10 +287,9 @@ class WinZipAesEngine implements ZipEncryptionEngine
$mac = hash_hmac('sha1', $content, $sha1HMacParam, true);
return ($salt .
return $salt .
substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) .
$content .
substr($mac, 0, 10)
);
substr($mac, 0, 10);
}
}

@@ -5,9 +5,10 @@ namespace PhpZip\Crypto;
use PhpZip\Exception\ZipAuthenticationException;
/**
* Encryption Engine
* Encryption Engine.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
@@ -17,8 +18,10 @@ interface ZipEncryptionEngine
* Decryption string.
*
* @param string $encryptionContent
* @return string
*
* @throws ZipAuthenticationException
*
* @return string
*/
public function decrypt($encryptionContent);
@@ -26,6 +29,7 @@ interface ZipEncryptionEngine
* Encryption string.
*
* @param string $content
*
* @return string
*/
public function encrypt($content);

@@ -32,20 +32,19 @@ class Crc32Exception extends ZipException
* Crc32Exception constructor.
*
* @param string $name
* @param int $expected
* @param int $actual
* @param int $expected
* @param int $actual
*/
public function __construct($name, $expected, $actual)
{
parent::__construct(
sprintf(
"%s (expected CRC32 value 0x%x, but is actually 0x%x)",
'%s (expected CRC32 value 0x%x, but is actually 0x%x)',
$name,
$expected,
$actual
)
);
assert($expected != $actual);
$this->expectedCrc = $expected;
$this->actualCrc = $actual;
}

@@ -10,21 +10,22 @@ namespace PhpZip\Exception;
*/
class ZipEntryNotFoundException extends ZipException
{
/**
* @var string
*/
/** @var string */
private $entryName;
/**
* ZipEntryNotFoundException constructor.
*
* @param string $entryName
*/
public function __construct($entryName)
{
parent::__construct(sprintf(
"Zip Entry \"%s\" was not found in the archive.",
$entryName
));
parent::__construct(
sprintf(
'Zip Entry "%s" was not found in the archive.',
$entryName
)
);
$this->entryName = $entryName;
}

@@ -7,6 +7,7 @@ namespace PhpZip\Exception;
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @see \Exception
*/
class ZipException extends \Exception

@@ -1,14 +0,0 @@
<?php
namespace PhpZip\Exception;
/**
* Thrown if entry not found.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
* @deprecated Rename class exception, using ZipEntryNotFoundException
*/
class ZipNotFoundEntry extends ZipEntryNotFoundException
{
}

@@ -1,14 +0,0 @@
<?php
namespace PhpZip\Exception;
/**
* Thrown if entry unsupport compression method.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
* @deprecated Rename exception class, using ZipUnsupportMethodException
*/
class ZipUnsupportMethod extends ZipUnsupportMethodException
{
}

@@ -2,6 +2,9 @@
namespace PhpZip\Exception;
/**
* Class ZipUnsupportMethodException.
*/
class ZipUnsupportMethodException extends RuntimeException
{
}

@@ -23,12 +23,14 @@ interface ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize();
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*/
public function deserialize($data);

@@ -31,51 +31,60 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*/
public function count()
{
return sizeof($this->collection);
return \count($this->collection);
}
/**
* Returns the Extra Field with the given Header ID or null
* if no such Extra Field exists.
*
* @param int $headerId The requested Header ID.
* @return ExtraField The Extra Field with the given Header ID or
* if no such Extra Field exists.
* @throws ZipException If headerId is out of range.
* @param int $headerId the requested Header ID
*
* @throws ZipException if headerId is out of range
*
* @return ExtraField|null the Extra Field with the given Header ID or
* if no such Extra Field exists
*/
public function get($headerId)
{
if (0x0000 > $headerId || $headerId > 0xffff) {
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
if (isset($this->collection[$headerId])) {
return $this->collection[$headerId];
}
return null;
}
/**
* Stores the given Extra Field in this collection.
*
* @param ExtraField $extraField The Extra Field to store in this collection.
* @return ExtraField The Extra Field previously associated with the Header ID of
* of the given Extra Field or null if no such Extra Field existed.
* @throws ZipException If headerId is out of range.
* @param ExtraField $extraField the Extra Field to store in this collection
*
* @throws ZipException if headerId is out of range
*
* @return ExtraField the Extra Field previously associated with the Header ID of
* of the given Extra Field or null if no such Extra Field existed
*/
public function add(ExtraField $extraField)
{
$headerId = $extraField::getHeaderId();
if (0x0000 > $headerId || $headerId > 0xffff) {
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
$this->collection[$headerId] = $extraField;
return $extraField;
}
/**
* Returns Extra Field exists
* Returns Extra Field exists.
*
* @param int $headerId the requested Header ID
*
* @param int $headerId The requested Header ID.
* @return bool
*/
public function has($headerId)
@@ -86,34 +95,43 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
/**
* Removes the Extra Field with the given Header ID.
*
* @param int $headerId The requested Header ID.
* @return ExtraField The Extra Field with the given Header ID or null
* if no such Extra Field exists.
* @throws ZipException If headerId is out of range or extra field not found.
* @param int $headerId the requested Header ID
*
* @throws ZipException if headerId is out of range or extra field not found
*
* @return ExtraField the Extra Field with the given Header ID or null
* if no such Extra Field exists
*/
public function remove($headerId)
{
if (0x0000 > $headerId || $headerId > 0xffff) {
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
if (isset($this->collection[$headerId])) {
$ef = $this->collection[$headerId];
unset($this->collection[$headerId]);
return $ef;
}
throw new ZipException('ExtraField not found');
}
/**
* Whether a offset exists
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
* Whether a offset exists.
*
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
* @return boolean true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
* An offset to check for.
* </p>
*
* @return bool true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
*
* @since 5.0.0
*/
public function offsetExists($offset)
@@ -122,14 +140,19 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Offset to retrieve
* @link http://php.net/manual/en/arrayaccess.offsetget.php
* Offset to retrieve.
*
* @see http://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @return mixed Can return all value types.
* @since 5.0.0
* The offset to retrieve.
* </p>
*
* @throws ZipException
*
* @return mixed can return all value types
*
* @since 5.0.0
*/
public function offsetGet($offset)
{
@@ -137,23 +160,26 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Offset to set
* @link http://php.net/manual/en/arrayaccess.offsetset.php
* Offset to set.
*
* @see http://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
* @return void
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*
* @throws ZipException
*
* @since 5.0.0
*/
public function offsetSet($offset, $value)
{
if ($value instanceof ExtraField) {
if ($offset !== $value::getHeaderId()) {
throw new InvalidArgumentException("Value header id !== array access key");
throw new InvalidArgumentException('Value header id !== array access key');
}
$this->add($value);
} else {
@@ -162,13 +188,16 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Offset to unset
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
* Offset to unset.
*
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
* @return void
* The offset to unset.
* </p>
*
* @since 5.0.0
*
* @throws ZipException
*/
public function offsetUnset($offset)
@@ -177,9 +206,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
* Return the current element.
*
* @see http://php.net/manual/en/iterator.current.php
*
* @return mixed can return any type
*
* @since 5.0.0
*/
public function current()
@@ -188,9 +220,9 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
* Move forward to next element.
*
* @see http://php.net/manual/en/iterator.next.php
* @since 5.0.0
*/
public function next()
@@ -199,9 +231,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
* Return the key of the current element.
*
* @see http://php.net/manual/en/iterator.key.php
*
* @return mixed scalar on success, or null on failure
*
* @since 5.0.0
*/
public function key()
@@ -210,10 +245,13 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
* Checks if current position is valid.
*
* @see http://php.net/manual/en/iterator.valid.php
*
* @return bool The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*
* @since 5.0.0
*/
public function valid()
@@ -222,9 +260,9 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
* Rewind the Iterator to the first element.
*
* @see http://php.net/manual/en/iterator.rewind.php
* @since 5.0.0
*/
public function rewind()

@@ -13,16 +13,14 @@ use PhpZip\Model\ZipEntry;
use PhpZip\Util\StringUtil;
/**
* Extra Fields Factory
* Extra Fields Factory.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ExtraFieldsFactory
{
/**
* @var array|null
*/
/** @var array|null */
protected static $registry;
private function __construct()
@@ -30,18 +28,22 @@ class ExtraFieldsFactory
}
/**
* @param string $extra
* @param string $extra
* @param ZipEntry|null $entry
* @return ExtraFieldsCollection
*
* @throws ZipException
*
* @return ExtraFieldsCollection
*/
public static function createExtraFieldCollections($extra, ZipEntry $entry = null)
{
$extraFieldsCollection = new ExtraFieldsCollection();
if ($extra !== null) {
$extraLength = strlen($extra);
$extraLength = \strlen($extra);
if ($extraLength > 0xffff) {
throw new ZipException("Extra Fields too large: " . $extraLength);
throw new ZipException('Extra Fields too large: ' . $extraLength);
}
$pos = 0;
$endPos = $extraLength;
@@ -49,38 +51,53 @@ class ExtraFieldsFactory
while ($endPos - $pos >= 4) {
$unpack = unpack('vheaderId/vdataSize', substr($extra, $pos, 4));
$pos += 4;
$headerId = (int)$unpack['headerId'];
$dataSize = (int)$unpack['dataSize'];
$extraField = ExtraFieldsFactory::create($headerId);
$headerId = (int) $unpack['headerId'];
$dataSize = (int) $unpack['dataSize'];
$extraField = self::create($headerId);
if ($extraField instanceof Zip64ExtraField && $entry !== null) {
$extraField->setEntry($entry);
}
$extraField->deserialize(substr($extra, $pos, $dataSize));
if ($dataSize > 0) {
$content = substr($extra, $pos, $dataSize);
if ($content !== false) {
$extraField->deserialize($content);
$extraFieldsCollection[$headerId] = $extraField;
}
}
$pos += $dataSize;
$extraFieldsCollection[$headerId] = $extraField;
}
}
return $extraFieldsCollection;
}
/**
* @param ExtraFieldsCollection $extraFieldsCollection
* @return string
*
* @throws ZipException
*
* @return string
*/
public static function createSerializedData(ExtraFieldsCollection $extraFieldsCollection)
{
$extraData = '';
foreach ($extraFieldsCollection as $extraField) {
$data = $extraField->serialize();
$extraData .= pack('vv', $extraField::getHeaderId(), strlen($data));
$extraData .= pack('vv', $extraField::getHeaderId(), \strlen($data));
$extraData .= $data;
}
$size = strlen($extraData);
if (0x0000 > $size || $size > 0xffff) {
$size = \strlen($extraData);
if ($size < 0x0000 || $size > 0xffff) {
throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData);
}
return $extraData;
}
@@ -90,29 +107,31 @@ class ExtraFieldsFactory
* The returned Extra Field still requires proper initialization, for
* example by calling ExtraField::readFrom.
*
* @param int $headerId An unsigned short integer (two bytes) which indicates
* the type of the returned Extra Field.
* @return ExtraField A new Extra Field or null if not support header id.
* @throws ZipException If headerId is out of range.
* @param int $headerId an unsigned short integer (two bytes) which indicates
* the type of the returned Extra Field
*
* @throws ZipException if headerId is out of range
*
* @return ExtraField a new Extra Field or null if not support header id
*/
public static function create($headerId)
{
if (0x0000 > $headerId || $headerId > 0xffff) {
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
/**
* @var ExtraField $extraField
*/
if (isset(self::getRegistry()[$headerId])) {
$extraClassName = self::getRegistry()[$headerId];
$extraField = new $extraClassName;
/** @var ExtraField $extraField */
$extraField = new $extraClassName();
if ($headerId !== $extraField::getHeaderId()) {
throw new ZipException('Runtime error support headerId ' . $headerId);
}
} else {
$extraField = new DefaultExtraField($headerId);
}
return $extraField;
}
@@ -130,6 +149,7 @@ class ExtraFieldsFactory
self::$registry[ApkAlignmentExtraField::getHeaderId()] = ApkAlignmentExtraField::class;
self::$registry[JarMarkerExtraField::getHeaderId()] = JarMarkerExtraField::class;
}
return self::$registry;
}
@@ -151,6 +171,7 @@ class ExtraFieldsFactory
/**
* @param ZipEntry $entry
*
* @return Zip64ExtraField
*/
public static function createZip64Extra(ZipEntry $entry)
@@ -160,19 +181,22 @@ class ExtraFieldsFactory
/**
* @param ZipEntry $entry
* @param int $padding
* @param int $padding
*
* @return ApkAlignmentExtraField
*/
public static function createApkAlignExtra(ZipEntry $entry, $padding)
{
$padding = (int)$padding;
$padding = (int) $padding;
$multiple = 4;
if (StringUtil::endsWith($entry->getName(), '.so')) {
$multiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
}
$extraField = new ApkAlignmentExtraField();
$extraField->setMultiple($multiple);
$extraField->setPadding($padding);
return $extraField;
}
}

@@ -6,7 +6,7 @@ use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Extra\ExtraField;
/**
* Apk Alignment Extra Field
* Apk Alignment Extra Field.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
@@ -21,13 +21,10 @@ class ApkAlignmentExtraField implements ExtraField
const ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
/**
* @var int
*/
/** @var int */
private $multiple;
/**
* @var int
*/
/** @var int */
private $padding;
/**
@@ -44,6 +41,7 @@ class ApkAlignmentExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
@@ -53,25 +51,31 @@ class ApkAlignmentExtraField implements ExtraField
['vc*', $this->multiple],
array_fill(2, $this->padding, 0)
);
return call_user_func_array('pack', $args);
return \call_user_func_array('pack', $args);
}
return pack('v', $this->multiple);
}
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*/
public function deserialize($data)
{
$length = strlen($data);
$length = \strlen($data);
if ($length < 2) {
// This is APK alignment field.
// FORMAT:
// * uint16 alignment multiple (in bytes)
// * remaining bytes -- padding to achieve alignment of data which starts after
// the extra field
throw new InvalidArgumentException("Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.");
throw new InvalidArgumentException(
'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.'
);
}
$this->multiple = unpack('v', $data)[1];
$this->padding = $length - 2;

@@ -14,26 +14,23 @@ use PhpZip\Extra\ExtraField;
*/
class DefaultExtraField implements ExtraField
{
/**
* @var int
*/
/** @var int */
private static $headerId;
/**
* @var string
*/
/** @var string */
protected $data;
/**
* Constructs a new Extra Field.
*
* @param int $headerId an unsigned short integer (two bytes) indicating the
* type of the Extra Field.
* type of the Extra Field
*
* @throws ZipException
*/
public function __construct($headerId)
{
if (0x0000 > $headerId || $headerId > 0xffff) {
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
self::$headerId = $headerId;
@@ -53,6 +50,7 @@ class DefaultExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
@@ -62,6 +60,7 @@ class DefaultExtraField implements ExtraField
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*/
public function deserialize($data)

@@ -30,6 +30,7 @@ class JarMarkerExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
@@ -39,12 +40,14 @@ class JarMarkerExtraField implements ExtraField
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*
* @throws ZipException
*/
public function deserialize($data)
{
if (strlen($data) !== 0) {
if ($data !== '') {
throw new ZipException("JarMarker doesn't expect any data");
}
}

@@ -6,31 +6,31 @@ use PhpZip\Extra\ExtraField;
use PhpZip\Util\PackUtil;
/**
* NTFS Extra Field
* NTFS Extra Field.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class NtfsExtraField implements ExtraField
{
/**
* Modify time
* Modify time.
*
* @var int Unix Timestamp
*/
private $mtime;
/**
* Access Time
* Access Time.
*
* @var int Unix Timestamp
*/
private $atime;
/**
* Create Time
* Create Time.
*
* @var int Unix Time
*/
@@ -50,11 +50,13 @@ class NtfsExtraField implements ExtraField
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*/
public function deserialize($data)
{
$unpack = unpack('vtag/vsizeAttr', substr($data, 0, 4));
if ($unpack['sizeAttr'] === 24) {
$tagData = substr($data, 4, $unpack['sizeAttr']);
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
@@ -65,11 +67,13 @@ class NtfsExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
{
$serialize = '';
if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) {
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
$atimeLong = ($this->atime + 11644473600) * 10000000;
@@ -80,6 +84,7 @@ class NtfsExtraField implements ExtraField
. PackUtil::packLongLE($atimeLong)
. PackUtil::packLongLE($ctimeLong);
}
return $serialize;
}
@@ -96,7 +101,7 @@ class NtfsExtraField implements ExtraField
*/
public function setMtime($mtime)
{
$this->mtime = (int)$mtime;
$this->mtime = (int) $mtime;
}
/**
@@ -112,7 +117,7 @@ class NtfsExtraField implements ExtraField
*/
public function setAtime($atime)
{
$this->atime = (int)$atime;
$this->atime = (int) $atime;
}
/**
@@ -128,6 +133,6 @@ class NtfsExtraField implements ExtraField
*/
public function setCtime($ctime)
{
$this->ctime = (int)$ctime;
$this->ctime = (int) $ctime;
}
}

@@ -4,47 +4,54 @@ namespace PhpZip\Extra\Fields;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraField;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* WinZip AES Extra Field.
*
* @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2 (WinZip Computing, S.L.)
* @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2
* (WinZip Computing, S.L.)
* @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers (WinZip Computing, S.L.)
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class WinZipAesEntryExtraField implements ExtraField
{
const DATA_SIZE = 7;
const VENDOR_ID = 17729; // 'A' | ('E' << 8);
/**
* Entries of this type <em>do</em> include the standard ZIP CRC-32 value.
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion().
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see
* WinZipAesEntryExtraField::getVendorVersion().
*/
const VV_AE_1 = 1;
/**
* Entries of this type do <em>not</em> include the standard ZIP CRC-32 value.
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion().
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see
* WinZipAesEntryExtraField::getVendorVersion().
*/
const VV_AE_2 = 2;
const KEY_STRENGTH_128BIT = 128;
const KEY_STRENGTH_192BIT = 192;
const KEY_STRENGTH_256BIT = 256;
protected static $keyStrengths = [
self::KEY_STRENGTH_128BIT => 0x01,
self::KEY_STRENGTH_192BIT => 0x02,
self::KEY_STRENGTH_256BIT => 0x03
self::KEY_STRENGTH_256BIT => 0x03,
];
protected static $encryptionMethods = [
self::KEY_STRENGTH_128BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128,
self::KEY_STRENGTH_192BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192,
self::KEY_STRENGTH_256BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
self::KEY_STRENGTH_128BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128,
self::KEY_STRENGTH_192BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192,
self::KEY_STRENGTH_256BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256,
];
/**
@@ -94,14 +101,16 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Sets the vendor version.
*
* @param int $vendorVersion the vendor version
*
* @throws ZipException unsupport vendor version
*
* @see WinZipAesEntryExtraField::VV_AE_1
* @see WinZipAesEntryExtraField::VV_AE_2
* @param int $vendorVersion the vendor version.
* @throws ZipException Unsupport vendor version.
*/
public function setVendorVersion($vendorVersion)
{
if ($vendorVersion < self::VV_AE_1 || self::VV_AE_2 < $vendorVersion) {
if ($vendorVersion < self::VV_AE_1 || $vendorVersion > self::VV_AE_2) {
throw new ZipException($vendorVersion);
}
$this->vendorVersion = $vendorVersion;
@@ -118,8 +127,9 @@ class WinZipAesEntryExtraField implements ExtraField
}
/**
* @return bool|int
* @throws ZipException
*
* @return bool|int
*/
public function getKeyStrength()
{
@@ -127,16 +137,20 @@ class WinZipAesEntryExtraField implements ExtraField
}
/**
* @param int $encryptionStrength Encryption strength as bits.
* @param int $encryptionStrength encryption strength as bits
*
* @throws ZipException if unsupport encryption strength
*
* @return int
* @throws ZipException If unsupport encryption strength.
*/
public static function keyStrength($encryptionStrength)
{
$flipKeyStrength = array_flip(self::$keyStrengths);
if (!isset($flipKeyStrength[$encryptionStrength])) {
throw new ZipException("Unsupport encryption strength " . $encryptionStrength);
throw new ZipException('Unsupport encryption strength ' . $encryptionStrength);
}
return $flipKeyStrength[$encryptionStrength];
}
@@ -153,8 +167,9 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Internal encryption method.
*
* @return int
* @throws ZipException
*
* @return int
*/
public function getEncryptionMethod()
{
@@ -165,15 +180,19 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* @param int $encryptionMethod
* @return int
*
* @throws ZipException
*
* @return int
*/
public static function getKeyStrangeFromEncryptionMethod($encryptionMethod)
{
$flipKey = array_flip(self::$encryptionMethods);
if (!isset($flipKey[$encryptionMethod])) {
throw new ZipException("Unsupport encryption method " . $encryptionMethod);
throw new ZipException('Unsupport encryption method ' . $encryptionMethod);
}
return $flipKey[$encryptionMethod];
}
@@ -181,11 +200,12 @@ class WinZipAesEntryExtraField implements ExtraField
* Sets compression method.
*
* @param int $compressionMethod Compression method
* @throws ZipException Compression method out of range.
*
* @throws ZipException compression method out of range
*/
public function setMethod($compressionMethod)
{
if (0x0000 > $compressionMethod || $compressionMethod > 0xffff) {
if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
throw new ZipException('Compression method out of range');
}
$this->method = $compressionMethod;
@@ -204,7 +224,8 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Returns encryption strength.
*
* @param int $keyStrength Key strength in bits.
* @param int $keyStrength key strength in bits
*
* @return int
*/
public static function encryptionStrength($keyStrength)
@@ -216,6 +237,7 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
@@ -231,13 +253,16 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*
* @throws ZipException
*/
public function deserialize($data)
{
$size = strlen($data);
if (self::DATA_SIZE !== $size) {
$size = \strlen($data);
if ($size !== self::DATA_SIZE) {
throw new ZipException('WinZip AES Extra data invalid size: ' . $size . '. Must be ' . self::DATA_SIZE);
}
@@ -249,7 +274,8 @@ class WinZipAesEntryExtraField implements ExtraField
*/
$unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', $data);
$this->setVendorVersion($unpack['vendorVersion']);
if (self::VENDOR_ID !== $unpack['vendorId']) {
if ($unpack['vendorId'] !== self::VENDOR_ID) {
throw new ZipException('Vendor id invalid: ' . $unpack['vendorId'] . '. Must be ' . self::VENDOR_ID);
}
$this->setKeyStrength(self::keyStrength($unpack['keyStrength'])); // checked

@@ -3,14 +3,16 @@
namespace PhpZip\Extra\Fields;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
/**
* ZIP64 Extra Field
* ZIP64 Extra Field.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
@@ -18,13 +20,13 @@ class Zip64ExtraField implements ExtraField
{
/** The Header ID for a ZIP64 Extended Information Extra Field. */
const ZIP64_HEADER_ID = 0x0001;
/**
* @var ZipEntry
*/
/** @var ZipEntry */
protected $entry;
/**
* Zip64ExtraField constructor.
*
* @param ZipEntry $entry
*/
public function __construct(ZipEntry $entry = null)
@@ -56,61 +58,65 @@ class Zip64ExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
{
if ($this->entry === null) {
throw new RuntimeException("entry is null");
throw new RuntimeException('entry is null');
}
$data = '';
// Write out Uncompressed Size.
$size = $this->entry->getSize();
if (0xffffffff <= $size) {
if ($size >= 0xffffffff) {
$data .= PackUtil::packLongLE($size);
}
// Write out Compressed Size.
$compressedSize = $this->entry->getCompressedSize();
if (0xffffffff <= $compressedSize) {
if ($compressedSize >= 0xffffffff) {
$data .= PackUtil::packLongLE($compressedSize);
}
// Write out Relative Header Offset.
$offset = $this->entry->getOffset();
if (0xffffffff <= $offset) {
if ($offset >= 0xffffffff) {
$data .= PackUtil::packLongLE($offset);
}
return $data;
}
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
* @throws \PhpZip\Exception\ZipException
*
* @throws ZipException
*/
public function deserialize($data)
{
if ($this->entry === null) {
throw new RuntimeException("entry is null");
throw new RuntimeException('entry is null');
}
$off = 0;
// Read in Uncompressed Size.
$size = $this->entry->getSize();
if (0xffffffff <= $size) {
assert(0xffffffff === $size);
if ($this->entry->getSize() === 0xffffffff) {
$this->entry->setSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
$off += 8;
}
// Read in Compressed Size.
$compressedSize = $this->entry->getCompressedSize();
if (0xffffffff <= $compressedSize) {
assert(0xffffffff === $compressedSize);
if ($this->entry->getCompressedSize() === 0xffffffff) {
$this->entry->setCompressedSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
$off += 8;
}
// Read in Relative Header Offset.
$offset = $this->entry->getOffset();
if (0xffffffff <= $offset) {
assert(0xffffffff, $offset);
if ($this->entry->getOffset() === 0xffffffff) {
$this->entry->setOffset(PackUtil::unpackLongLE(substr($data, $off, 8)));
}
}

@@ -1,43 +0,0 @@
<?php
namespace PhpZip\Mapper;
/**
* Adds a offset value to the given position.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class OffsetPositionMapper extends PositionMapper
{
/**
* @var int
*/
private $offset;
/**
* @param int $offset
*/
public function __construct($offset)
{
$this->offset = (int)$offset;
}
/**
* @param int $position
* @return int
*/
public function map($position)
{
return parent::map($position) + $this->offset;
}
/**
* @param int $position
* @return int
*/
public function unmap($position)
{
return parent::unmap($position) - $this->offset;
}
}

@@ -1,30 +0,0 @@
<?php
namespace PhpZip\Mapper;
/**
* Maps a given position.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class PositionMapper
{
/**
* @param int $position
* @return int
*/
public function map($position)
{
return $position;
}
/**
* @param int $position
* @return int
*/
public function unmap($position)
{
return $position;
}
}

@@ -3,7 +3,7 @@
namespace PhpZip\Model;
/**
* Read End of Central Directory
* End of Central Directory.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
@@ -11,11 +11,14 @@ namespace PhpZip\Model;
class EndOfCentralDirectory
{
/** Zip64 End Of Central Directory Record. */
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
const ZIP64_END_OF_CD_RECORD_SIG = 0x06064B50;
/** Zip64 End Of Central Directory Locator. */
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
const ZIP64_END_OF_CD_LOCATOR_SIG = 0x07064B50;
/** End Of Central Directory Record signature. */
const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
const END_OF_CD_SIG = 0x06054B50;
/**
* The minimum length of the End Of Central Directory Record.
*
@@ -34,6 +37,7 @@ class EndOfCentralDirectory
* zipfile comment length 2
*/
const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
/**
* The length of the Zip64 End Of Central Directory Locator.
* zip64 end of central dir locator
@@ -43,9 +47,10 @@ class EndOfCentralDirectory
* central directory 4
* relative offset of the zip64
* end of central directory record 8
* total number of disks 4
* total number of disks 4.
*/
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
const ZIP64_END_OF_CD_LOCATOR_LEN = 20;
/**
* The minimum length of the Zip64 End Of Central Directory Record.
*
@@ -68,38 +73,46 @@ class EndOfCentralDirectory
* the starting disk number 8
*/
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
/**
* @var string|null The archive comment.
*/
private $comment;
/**
* @var int
*/
/** @var int Count files. */
private $entryCount;
/**
* @var bool
*/
private $zip64 = false;
/** @var int Central Directory Offset. */
private $cdOffset;
/** @var int */
private $cdSize;
/** @var string|null The archive comment. */
private $comment;
/** @var bool Zip64 extension */
private $zip64;
/**
* EndOfCentralDirectory constructor.
* @param int $entryCount
* @param null|string $comment
* @param bool $zip64
*
* @param int $entryCount
* @param int $cdOffset
* @param int $cdSize
* @param bool $zip64
* @param mixed|null $comment
*/
public function __construct($entryCount, $comment, $zip64 = false)
public function __construct($entryCount, $cdOffset, $cdSize, $zip64, $comment = null)
{
$this->entryCount = $entryCount;
$this->comment = $comment;
$this->cdOffset = $cdOffset;
$this->cdSize = $cdSize;
$this->zip64 = $zip64;
$this->comment = $comment;
}
/**
* @return null|string
* @param string|null $comment
*/
public function getComment()
public function setComment($comment)
{
return $this->comment;
$this->comment = $comment;
}
/**
@@ -110,6 +123,30 @@ class EndOfCentralDirectory
return $this->entryCount;
}
/**
* @return int
*/
public function getCdOffset()
{
return $this->cdOffset;
}
/**
* @return int
*/
public function getCdSize()
{
return $this->cdSize;
}
/**
* @return string|null
*/
public function getComment()
{
return $this->comment;
}
/**
* @return bool
*/

@@ -9,20 +9,19 @@ use PhpZip\Model\ZipEntry;
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
class OutputOffsetEntry
{
/**
* @var int
*/
/** @var int */
private $offset;
/**
* @var ZipEntry
*/
/** @var ZipEntry */
private $entry;
/**
* @param int $pos
* @param int $pos
* @param ZipEntry $entry
*/
public function __construct($pos, ZipEntry $entry)

@@ -10,65 +10,60 @@ use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\DateTimeConverter;
use PhpZip\Util\StringUtil;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* Abstract ZIP entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
abstract class ZipAbstractEntry implements ZipEntry
{
/**
* @var int Bit flags for init state.
*/
private $init;
/**
* @var string Entry name (filename in archive)
*/
/** @var string Entry name (filename in archive) */
private $name;
/**
* @var int Made by platform
*/
private $platform;
/**
* @var int
*/
private $versionNeededToExtract = 20;
/**
* @var int Compression method
*/
private $method;
/**
* @var int
*/
private $general;
/**
* @var int Dos time
*/
private $dosTime;
/**
* @var int Crc32
*/
private $crc;
/**
* @var int Compressed size
*/
/** @var int Made by platform */
private $createdOS = self::UNKNOWN;
/** @var int Extracted by platform */
private $extractedOS = self::UNKNOWN;
/** @var int */
private $softwareVersion = self::UNKNOWN;
/** @var int */
private $versionNeededToExtract = self::UNKNOWN;
/** @var int Compression method */
private $method = self::UNKNOWN;
/** @var int */
private $generalPurposeBitFlags = 0;
/** @var int Dos time */
private $dosTime = self::UNKNOWN;
/** @var int Crc32 */
private $crc = self::UNKNOWN;
/** @var int Compressed size */
private $compressedSize = self::UNKNOWN;
/**
* @var int Uncompressed size
*/
/** @var int Uncompressed size */
private $size = self::UNKNOWN;
/**
* @var int External attributes
*/
private $externalAttributes;
/**
* @var int Relative Offset Of Local File Header.
*/
private $offset = self::UNKNOWN;
/** @var int Internal attributes */
private $internalAttributes = 0;
/** @var int External attributes */
private $externalAttributes = 0;
/** @var int relative Offset Of Local File Header */
private $offset = 0;
/**
* Collections of Extra Fields.
* Keys from Header ID [int] and value Extra Field [ExtraField].
@@ -77,27 +72,27 @@ abstract class ZipAbstractEntry implements ZipEntry
* @var ExtraFieldsCollection
*/
private $extraFieldsCollection;
/**
* @var string Comment field.
*/
/** @var string|null comment field */
private $comment;
/**
* @var string Entry password for read or write encryption data.
*/
/** @var string entry password for read or write encryption data */
private $password;
/**
* Encryption method.
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
*
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
*
* @var int
*/
private $encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL;
/**
* @var int
*/
private $compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;
/** @var int */
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
/**
* ZipAbstractEntry constructor.
@@ -109,12 +104,15 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* @param ZipEntry $entry
*
* @throws ZipException
*/
public function setEntry(ZipEntry $entry)
{
$this->setName($entry->getName());
$this->setPlatform($entry->getPlatform());
$this->setSoftwareVersion($entry->getSoftwareVersion());
$this->setCreatedOS($entry->getCreatedOS());
$this->setExtractedOS($entry->getExtractedOS());
$this->setVersionNeededToExtract($entry->getVersionNeededToExtract());
$this->setMethod($entry->getMethod());
$this->setGeneralPurposeBitFlags($entry->getGeneralPurposeBitFlags());
@@ -122,6 +120,7 @@ abstract class ZipAbstractEntry implements ZipEntry
$this->setCrc($entry->getCrc());
$this->setCompressedSize($entry->getCompressedSize());
$this->setSize($entry->getSize());
$this->setInternalAttributes($entry->getInternalAttributes());
$this->setExternalAttributes($entry->getExternalAttributes());
$this->setOffset($entry->getOffset());
$this->setExtra($entry->getExtra());
@@ -146,87 +145,150 @@ abstract class ZipAbstractEntry implements ZipEntry
* Set entry name.
*
* @param string $name New entry name
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setName($name)
{
$length = strlen($name);
if (0x0000 > $length || $length > 0xffff) {
$length = \strlen($name);
if ($length < 0x0000 || $length > 0xffff) {
throw new ZipException('Illegal zip entry name parameter');
}
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
$this->name = $name;
$this->externalAttributes = $this->isDirectory() ? 0x10 : 0;
return $this;
}
/**
* Sets the indexed General Purpose Bit Flag.
*
* @param int $mask
* @param int $mask
* @param bool $bit
*
* @return ZipEntry
*/
public function setGeneralPurposeBitFlag($mask, $bit)
{
if ($bit) {
$this->general |= $mask;
$this->generalPurposeBitFlags |= $mask;
} else {
$this->general &= ~$mask;
$this->generalPurposeBitFlags &= ~$mask;
}
return $this;
}
/**
* @return int Get platform
*
* @deprecated Use {@see ZipEntry::getCreatedOS()}
* @noinspection PhpUsageOfSilenceOperatorInspection
*/
public function getPlatform()
{
return $this->isInit(self::BIT_PLATFORM) ? $this->platform & 0xffff : self::UNKNOWN;
@trigger_error('ZipEntry::getPlatform() is deprecated. Use ZipEntry::getCreatedOS()', \E_USER_DEPRECATED);
return $this->getCreatedOS();
}
/**
* Set platform
*
* @param int $platform
*
* @return ZipEntry
*
* @throws ZipException
*
* @deprecated Use {@see ZipEntry::setCreatedOS()}
* @noinspection PhpUsageOfSilenceOperatorInspection
*/
public function setPlatform($platform)
{
$known = self::UNKNOWN !== $platform;
if ($known) {
if (0x00 > $platform || $platform > 0xff) {
throw new ZipException("Platform out of range");
}
$this->platform = $platform;
} else {
$this->platform = 0;
@trigger_error('ZipEntry::setPlatform() is deprecated. Use ZipEntry::setCreatedOS()', \E_USER_DEPRECATED);
return $this->setCreatedOS($platform);
}
/**
* @return int platform
*/
public function getCreatedOS()
{
return $this->createdOS;
}
/**
* Set platform.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCreatedOS($platform)
{
$platform = (int) $platform;
if ($platform < 0x00 || $platform > 0xff) {
throw new ZipException('Platform out of range');
}
$this->setInit(self::BIT_PLATFORM, $known);
$this->createdOS = $platform;
return $this;
}
/**
* @param int $mask
* @return bool
* @return int
*/
protected function isInit($mask)
public function getExtractedOS()
{
return ($this->init & $mask) !== 0;
return $this->extractedOS;
}
/**
* @param int $mask
* @param bool $init
* Set extracted OS.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
protected function setInit($mask, $init)
public function setExtractedOS($platform)
{
if ($init) {
$this->init |= $mask;
} else {
$this->init &= ~$mask;
$platform = (int) $platform;
if ($platform < 0x00 || $platform > 0xff) {
throw new ZipException('Platform out of range');
}
$this->extractedOS = $platform;
return $this;
}
/**
* @return int
*/
public function getSoftwareVersion()
{
return $this->softwareVersion;
}
/**
* @param int $softwareVersion
*
* @return ZipEntry
*/
public function setSoftwareVersion($softwareVersion)
{
$this->softwareVersion = (int) $softwareVersion;
return $this;
}
/**
@@ -236,6 +298,24 @@ abstract class ZipAbstractEntry implements ZipEntry
*/
public function getVersionNeededToExtract()
{
if ($this->versionNeededToExtract === self::UNKNOWN) {
$method = $this->getMethod();
if ($method === self::METHOD_WINZIP_AES) {
return 51;
}
if ($method === ZipFile::METHOD_BZIP2) {
return 46;
}
if ($this->isZip64ExtensionsRequired()) {
return 45;
}
return $method === ZipFile::METHOD_DEFLATED || $this->isDirectory() ? 20 : 10;
}
return $this->versionNeededToExtract;
}
@@ -243,11 +323,13 @@ abstract class ZipAbstractEntry implements ZipEntry
* Set version needed to extract.
*
* @param int $version
*
* @return ZipEntry
*/
public function setVersionNeededToExtract($version)
{
$this->versionNeededToExtract = $version;
return $this;
}
@@ -256,8 +338,8 @@ abstract class ZipAbstractEntry implements ZipEntry
*/
public function isZip64ExtensionsRequired()
{
return 0xffffffff <= $this->getCompressedSize()
|| 0xffffffff <= $this->getSize();
return $this->getCompressedSize() >= 0xffffffff
|| $this->getSize() >= 0xffffffff;
}
/**
@@ -273,12 +355,14 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* Sets the compressed size of this entry.
*
* @param int $compressedSize The Compressed Size.
* @param int $compressedSize the Compressed Size
*
* @return ZipEntry
*/
public function setCompressedSize($compressedSize)
{
$this->compressedSize = $compressedSize;
return $this;
}
@@ -295,12 +379,14 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* Sets the uncompressed size of this entry.
*
* @param int $size The (Uncompressed) Size.
* @param int $size the (Uncompressed) Size
*
* @return ZipEntry
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
@@ -316,49 +402,59 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* @param int $offset
*
* @return ZipEntry
*/
public function setOffset($offset)
{
$this->offset = $offset;
$this->offset = (int) $offset;
return $this;
}
/**
* Returns the General Purpose Bit Flags.
*
* @return int
*/
public function getGeneralPurposeBitFlags()
{
return $this->general & 0xffff;
return $this->generalPurposeBitFlags & 0xffff;
}
/**
* Sets the General Purpose Bit Flags.
*
* @var int general
* @return ZipEntry
* @param mixed $general
*
* @throws ZipException
*
* @return ZipEntry
*
* @var int general
*/
public function setGeneralPurposeBitFlags($general)
{
if (0x0000 > $general || $general > 0xffff) {
if ($general < 0x0000 || $general > 0xffff) {
throw new ZipException('general out of range');
}
$this->general = $general;
if ($this->method === ZipFileInterface::METHOD_DEFLATED) {
$this->generalPurposeBitFlags = $general;
if ($this->method === ZipFile::METHOD_DEFLATED) {
$bit1 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG1);
$bit2 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG2);
if ($bit1 && !$bit2) {
$this->compressionLevel = ZipFileInterface::LEVEL_BEST_COMPRESSION;
$this->compressionLevel = ZipFile::LEVEL_BEST_COMPRESSION;
} elseif (!$bit1 && $bit2) {
$this->compressionLevel = ZipFileInterface::LEVEL_FAST;
$this->compressionLevel = ZipFile::LEVEL_FAST;
} elseif ($bit1 && $bit2) {
$this->compressionLevel = ZipFileInterface::LEVEL_SUPER_FAST;
$this->compressionLevel = ZipFile::LEVEL_SUPER_FAST;
} else {
$this->compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
$this->compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
}
}
return $this;
}
@@ -376,35 +472,38 @@ abstract class ZipAbstractEntry implements ZipEntry
* Returns the indexed General Purpose Bit Flag.
*
* @param int $mask
*
* @return bool
*/
public function getGeneralPurposeBitFlag($mask)
{
return ($this->general & $mask) !== 0;
return ($this->generalPurposeBitFlags & $mask) !== 0;
}
/**
* Sets the encryption property to false and removes any other
* encryption artifacts.
*
* @return ZipEntry
* @throws ZipException
*
* @return ZipEntry
*/
public function disableEncryption()
{
$this->setEncrypted(false);
$headerId = WinZipAesEntryExtraField::getHeaderId();
if (isset($this->extraFieldsCollection[$headerId])) {
/**
* @var WinZipAesEntryExtraField $field
*/
/** @var WinZipAesEntryExtraField $field */
$field = $this->extraFieldsCollection[$headerId];
if (self::METHOD_WINZIP_AES === $this->getMethod()) {
if ($this->getMethod() === self::METHOD_WINZIP_AES) {
$this->setMethod($field === null ? self::UNKNOWN : $field->getMethod());
}
unset($this->extraFieldsCollection[$headerId]);
}
$this->password = null;
return $this;
}
@@ -412,12 +511,14 @@ abstract class ZipAbstractEntry implements ZipEntry
* Sets the encryption flag for this ZIP entry.
*
* @param bool $encrypted
*
* @return ZipEntry
*/
public function setEncrypted($encrypted)
{
$encrypted = (bool)$encrypted;
$encrypted = (bool) $encrypted;
$this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);
return $this;
}
@@ -428,59 +529,60 @@ abstract class ZipAbstractEntry implements ZipEntry
*/
public function getMethod()
{
$isInit = $this->isInit(self::BIT_METHOD);
return $isInit ?
$this->method & 0xffff :
self::UNKNOWN;
return $this->method;
}
/**
* Sets the compression method for this entry.
*
* @param int $method
*
* @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
*
* @return ZipEntry
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
*/
public function setMethod($method)
{
if ($method === self::UNKNOWN) {
$this->method = $method;
$this->setInit(self::BIT_METHOD, false);
return $this;
}
if (0x0000 > $method || $method > 0xffff) {
if ($method < 0x0000 || $method > 0xffff) {
throw new ZipException('method out of range: ' . $method);
}
switch ($method) {
case self::METHOD_WINZIP_AES:
case ZipFileInterface::METHOD_STORED:
case ZipFileInterface::METHOD_DEFLATED:
case ZipFileInterface::METHOD_BZIP2:
case ZipFile::METHOD_STORED:
case ZipFile::METHOD_DEFLATED:
case ZipFile::METHOD_BZIP2:
$this->method = $method;
$this->setInit(self::BIT_METHOD, true);
break;
default:
throw new ZipException($this->name . " (unsupported compression method $method)");
throw new ZipException($this->name . " (unsupported compression method {$method})");
}
return $this;
}
/**
* Get Unix Timestamp
* Get Unix Timestamp.
*
* @return int
*/
public function getTime()
{
if (!$this->isInit(self::BIT_DATE_TIME)) {
if ($this->getDosTime() === self::UNKNOWN) {
return self::UNKNOWN;
}
return DateTimeConverter::toUnixTimestamp($this->getDosTime());
}
/**
* Get Dos Time
* Get Dos Time.
*
* @return int
*/
@@ -490,70 +592,96 @@ abstract class ZipAbstractEntry implements ZipEntry
}
/**
* Set Dos Time
* Set Dos Time.
*
* @param int $dosTime
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setDosTime($dosTime)
{
$dosTime = sprintf('%u', $dosTime);
if (0x00000000 > $dosTime || $dosTime > 0xffffffff) {
$dosTime = (int) $dosTime;
if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
throw new ZipException('DosTime out of range');
}
$this->dosTime = $dosTime;
$this->setInit(self::BIT_DATE_TIME, true);
return $this;
}
/**
* Set time from unix timestamp.
*
* @param int $unixTimestamp
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setTime($unixTimestamp)
{
$known = self::UNKNOWN != $unixTimestamp;
$known = $unixTimestamp !== self::UNKNOWN;
if ($known) {
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
} else {
$this->dosTime = 0;
}
$this->setInit(self::BIT_DATE_TIME, $known);
return $this;
}
/**
* Returns the external file attributes.
*
* @return int The external file attributes.
* @return int the external file attributes
*/
public function getExternalAttributes()
{
if (!$this->isInit(self::BIT_EXTERNAL_ATTR)) {
return $this->isDirectory() ? 0x10 : 0;
}
return $this->externalAttributes;
}
/**
* Sets the external file attributes.
*
* @param int $externalAttributes the external file attributes.
* @param int $externalAttributes the external file attributes
*
* @return ZipEntry
*/
public function setExternalAttributes($externalAttributes)
{
$known = self::UNKNOWN != $externalAttributes;
if ($known) {
$this->externalAttributes = $externalAttributes;
} else {
$this->externalAttributes = 0;
}
$this->setInit(self::BIT_EXTERNAL_ATTR, $known);
$this->externalAttributes = $externalAttributes;
return $this;
}
/**
* Sets the internal file attributes.
*
* @param int $attributes the internal file attributes
*
* @return ZipEntry
*/
public function setInternalAttributes($attributes)
{
$this->internalAttributes = (int) $attributes;
return $this;
}
/**
* Returns the internal file attributes.
*
* @return int the internal file attributes
*/
public function getInternalAttributes()
{
return $this->internalAttributes;
}
/**
* Returns true if and only if this ZIP entry represents a directory entry
* (i.e. end with '/').
@@ -575,8 +703,10 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* Returns a protective copy of the serialized Extra Fields.
* @return string
*
* @throws ZipException
*
* @return string
*/
public function getExtra()
{
@@ -591,41 +721,50 @@ abstract class ZipAbstractEntry implements ZipEntry
* (application) data.
* Consider storing such data in a separate entry instead.
*
* @param string $data The byte array holding the serialized Extra Fields.
* @param string $data the byte array holding the serialized Extra Fields
*
* @throws ZipException if the serialized Extra Fields exceed 64 KB
*
* @return ZipEntry
*/
public function setExtra($data)
{
$this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);
return $this;
}
/**
* Returns comment entry
* Returns comment entry.
*
* @return string
*/
public function getComment()
{
return null !== $this->comment ? $this->comment : "";
return $this->comment !== null ? $this->comment : '';
}
/**
* Set entry comment.
*
* @param $comment
* @return ZipEntry
* @param string|null $comment
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setComment($comment)
{
if ($comment !== null) {
$commentLength = strlen($comment);
if (0x0000 > $commentLength || $commentLength > 0xffff) {
throw new ZipException("Comment too long");
$commentLength = \strlen($comment);
if ($commentLength < 0x0000 || $commentLength > 0xffff) {
throw new ZipException('Comment too long');
}
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
}
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
$this->comment = $comment;
return $this;
}
@@ -634,11 +773,11 @@ abstract class ZipAbstractEntry implements ZipEntry
*/
public function isDataDescriptorRequired()
{
return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) == self::UNKNOWN;
return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) === self::UNKNOWN;
}
/**
* Return crc32 content or 0 for WinZip AES v2
* Return crc32 content or 0 for WinZip AES v2.
*
* @return int
*/
@@ -651,12 +790,13 @@ abstract class ZipAbstractEntry implements ZipEntry
* Set crc32 content.
*
* @param int $crc
*
* @return ZipEntry
*/
public function setCrc($crc)
{
$this->crc = $crc;
$this->setInit(self::BIT_CRC, true);
$this->crc = (int) $crc;
return $this;
}
@@ -669,24 +809,29 @@ abstract class ZipAbstractEntry implements ZipEntry
}
/**
* Set password and encryption method from entry
* Set password and encryption method from entry.
*
* @param string $password
* @param int|null $encryptionMethod
*
* @param string $password
* @param null|int $encryptionMethod
* @return ZipEntry
* @throws ZipException
*
* @return ZipEntry
*/
public function setPassword($password, $encryptionMethod = null)
{
$this->password = $password;
if ($encryptionMethod !== null) {
$this->setEncryptionMethod($encryptionMethod);
}
if (!empty($this->password)) {
$this->setEncrypted(true);
} else {
$this->disableEncryption();
}
return $this;
}
@@ -699,30 +844,33 @@ abstract class ZipAbstractEntry implements ZipEntry
}
/**
* Set encryption method
*
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
* Set encryption method.
*
* @param int $encryptionMethod
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
*/
public function setEncryptionMethod($encryptionMethod)
{
if (null !== $encryptionMethod) {
if ($encryptionMethod !== null) {
if (
$encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
$encryptionMethod !== ZipFile::ENCRYPTION_METHOD_TRADITIONAL
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
) {
throw new ZipException('Invalid encryption method');
}
$this->encryptionMethod = $encryptionMethod;
}
return $this;
}
@@ -736,22 +884,26 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* @param int $compressionLevel
*
* @return ZipEntry
*/
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION)
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
{
if ($compressionLevel < ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ||
$compressionLevel > ZipFileInterface::LEVEL_BEST_COMPRESSION
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
) {
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
ZipFileInterface::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFileInterface::LEVEL_BEST_COMPRESSION);
throw new InvalidArgumentException(
'Invalid compression level. Minimum level ' .
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION
);
}
$this->compressionLevel = $compressionLevel;
return $this;
}
/**
* Clone extra fields
* Clone extra fields.
*/
public function __clone()
{

@@ -2,26 +2,29 @@
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
/**
* Source Entry Changes
* Source Entry Changes.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
class ZipChangesEntry extends ZipAbstractEntry
{
/**
* @var ZipSourceEntry
*/
/** @var ZipSourceEntry */
protected $entry;
/**
* ZipChangesEntry constructor.
*
* @param ZipSourceEntry $entry
*
* @throws ZipException
* @throws \PhpZip\Exception\InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __construct(ZipSourceEntry $entry)
{
@@ -47,8 +50,9 @@ class ZipChangesEntry extends ZipAbstractEntry
/**
* Returns an string content of the given entry.
*
* @return null|string
* @throws ZipException
*
* @return string|null
*/
public function getEntryContent()
{

@@ -3,7 +3,6 @@
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\ZipFileInterface;
/**
* @author Ne-Lexa alexey@nelexa.ru
@@ -11,23 +10,22 @@ use PhpZip\ZipFileInterface;
*/
class ZipNewEntry extends ZipAbstractEntry
{
/**
* @var resource|string|null
*/
/** @var resource|string|null */
protected $content;
/**
* @var bool
*/
/** @var bool */
private $clone = false;
/**
* ZipNewEntry constructor.
*
* @param string|resource|null $content
*/
public function __construct($content = null)
{
parent::__construct();
if ($content !== null && !is_string($content) && !is_resource($content)) {
if ($content !== null && !\is_string($content) && !\is_resource($content)) {
throw new InvalidArgumentException('invalid content');
}
$this->content = $content;
@@ -36,39 +34,23 @@ class ZipNewEntry extends ZipAbstractEntry
/**
* Returns an string content of the given entry.
*
* @return null|string
* @return string|null
*/
public function getEntryContent()
{
if (is_resource($this->content)) {
if (\is_resource($this->content)) {
if (stream_get_meta_data($this->content)['seekable']) {
rewind($this->content);
}
return stream_get_contents($this->content);
}
return $this->content;
}
/**
* Version needed to extract.
*
* @return int
*/
public function getVersionNeededToExtract()
{
$method = $this->getMethod();
return self::METHOD_WINZIP_AES === $method ? 51 :
(
ZipFileInterface::METHOD_BZIP2 === $method ? 46 :
(
$this->isZip64ExtensionsRequired() ? 45 :
(ZipFileInterface::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
)
);
}
/**
* Clone extra fields
* Clone extra fields.
*/
public function __clone()
{
@@ -78,7 +60,7 @@ class ZipNewEntry extends ZipAbstractEntry
public function __destruct()
{
if (!$this->clone && $this->content !== null && is_resource($this->content)) {
if (!$this->clone && $this->content !== null && \is_resource($this->content)) {
fclose($this->content);
$this->content = null;
}

@@ -12,28 +12,31 @@ use PhpZip\Exception\ZipException;
*/
class ZipNewFileEntry extends ZipAbstractEntry
{
/**
* @var string Filename
*/
/** @var string Filename */
protected $file;
/**
* ZipNewEntry constructor.
*
* @param string $file
*
* @throws ZipException
*/
public function __construct($file)
{
parent::__construct();
if ($file === null) {
throw new InvalidArgumentException("file is null");
throw new InvalidArgumentException('file is null');
}
$file = (string)$file;
$file = (string) $file;
if (!is_file($file)) {
throw new ZipException("File $file does not exist.");
throw new ZipException("File {$file} does not exist.");
}
if (!is_readable($file)) {
throw new ZipException("The '$file' file could not be read. Check permissions.");
throw new ZipException("The '{$file}' file could not be read. Check permissions.");
}
$this->file = $file;
}
@@ -41,13 +44,14 @@ class ZipNewFileEntry extends ZipAbstractEntry
/**
* Returns an string content of the given entry.
*
* @return null|string
* @return string|null
*/
public function getEntryContent()
{
if (!is_file($this->file)) {
throw new RuntimeException("File {$this->file} does not exist.");
}
return file_get_contents($this->file);
}
}

@@ -9,34 +9,27 @@ use PhpZip\Stream\ZipInputStreamInterface;
* This class is used to represent a ZIP file entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipSourceEntry extends ZipAbstractEntry
{
/**
* Max size cached content in memory.
*/
/** Max size cached content in memory. */
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 524288; // 512 kb
/**
* @var ZipInputStreamInterface
*/
/** @var ZipInputStreamInterface */
protected $inputStream;
/**
* @var string|resource Cached entry content.
*/
/** @var string|resource cached entry content */
protected $entryContent;
/**
* @var string
*/
protected $readPassword;
/**
* @var bool
*/
/** @var bool */
private $clone = false;
/**
* ZipSourceEntry constructor.
*
* @param ZipInputStreamInterface $inputStream
*/
public function __construct(ZipInputStreamInterface $inputStream)
@@ -56,6 +49,8 @@ class ZipSourceEntry extends ZipAbstractEntry
/**
* Returns an string content of the given entry.
*
* @throws ZipException
*
* @return string
*/
public function getEntryContent()
@@ -63,23 +58,28 @@ class ZipSourceEntry extends ZipAbstractEntry
if ($this->entryContent === null) {
// In order not to unpack again, we cache the content in memory or on disk
$content = $this->inputStream->readEntryContent($this);
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
$this->entryContent = $content;
} else {
$this->entryContent = fopen('php://temp', 'r+b');
fwrite($this->entryContent, $content);
}
return $content;
}
if (is_resource($this->entryContent)) {
if (\is_resource($this->entryContent)) {
rewind($this->entryContent);
return stream_get_contents($this->entryContent);
}
return $this->entryContent;
}
/**
* Clone extra fields
* Clone extra fields.
*/
public function __clone()
{
@@ -89,7 +89,7 @@ class ZipSourceEntry extends ZipAbstractEntry
public function __destruct()
{
if (!$this->clone && $this->entryContent !== null && is_resource($this->entryContent)) {
if (!$this->clone && $this->entryContent !== null && \is_resource($this->entryContent)) {
fclose($this->entryContent);
}
}

@@ -4,32 +4,27 @@ namespace PhpZip\Model;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraFieldsCollection;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* ZIP file entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
interface ZipEntry
{
// Bit masks for initialized fields.
const BIT_PLATFORM = 1,
BIT_METHOD = 2 /* 1 << 1 */,
BIT_CRC = 4 /* 1 << 2 */,
BIT_DATE_TIME = 64 /* 1 << 6 */,
BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/
;
/** The unknown value for numeric properties. */
const UNKNOWN = -1;
/** Windows platform. */
const PLATFORM_FAT = 0;
/** Unix platform. */
const PLATFORM_UNIX = 3;
/** MacOS platform */
const PLATFORM_OS_X = 19;
@@ -40,26 +35,33 @@ interface ZipEntry
const METHOD_WINZIP_AES = 99;
/** General Purpose Bit Flag mask for encrypted data. */
const GPBF_ENCRYPTED = 1; // 1 << 0
// (For Methods 8 and 9 - Deflating)
// Bit 2 Bit 1
// 0 0 Normal compression
// 0 1 Maximum compression
// 1 0 Fast compression
// 1 1 Super Fast compression
const GPBF_ENCRYPTED = 1;
// (For Methods 8 and 9 - Deflating)
// Bit 2 Bit 1
// 0 0 Normal compression
// 0 1 Maximum compression
// 1 0 Fast compression
// 1 1 Super Fast compression
const GPBF_COMPRESSION_FLAG1 = 2; // 1 << 1
const GPBF_COMPRESSION_FLAG2 = 4; // 1 << 2
/** General Purpose Bit Flag mask for data descriptor. */
const GPBF_DATA_DESCRIPTOR = 8; // 1 << 3
/** General Purpose Bit Flag mask for strong encryption. */
const GPBF_STRONG_ENCRYPTION = 64; // 1 << 6
/** General Purpose Bit Flag mask for UTF-8. */
const GPBF_UTF8 = 2048; // 1 << 11
/** Local File Header signature. */
const LOCAL_FILE_HEADER_SIG = 0x04034B50;
/** Data Descriptor signature. */
const DATA_DESCRIPTOR_SIG = 0x08074B50;
/**
* The minimum length of the Local File Header record.
*
@@ -76,6 +78,7 @@ interface ZipEntry
* extra field length 2
*/
const LOCAL_FILE_HEADER_MIN_LEN = 30;
/**
* Local File Header signature 4
* Version Needed To Extract 2
@@ -85,12 +88,11 @@ interface ZipEntry
* Last Mod File Date 2
* CRC-32 4
* Compressed Size 4
* Uncompressed Size 4
* Uncompressed Size 4.
*/
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26;
/**
* Default compression level for bzip2
*/
/** Default compression level for bzip2 */
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
/**
@@ -104,25 +106,77 @@ interface ZipEntry
* Set entry name.
*
* @param string $name New entry name
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setName($name);
/**
* @return int Get platform
*
* @deprecated Use {@see ZipEntry::getCreatedOS()}
*/
public function getPlatform();
/**
* Set platform
*
* @param int $platform
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*
* @deprecated Use {@see ZipEntry::setCreatedOS()}
*/
public function setPlatform($platform);
/**
* Returns created OS.
*
* @return int Get platform
*/
public function getCreatedOS();
/**
* Set created OS.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCreatedOS($platform);
/**
* @return int
*/
public function getExtractedOS();
/**
* Set extracted OS.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setExtractedOS($platform);
/**
* @return int
*/
public function getSoftwareVersion();
/**
* @param int $softwareVersion
*
* @return ZipEntry
*/
public function setSoftwareVersion($softwareVersion);
/**
* Version needed to extract.
*
@@ -134,6 +188,7 @@ interface ZipEntry
* Set version needed to extract.
*
* @param int $version
*
* @return ZipEntry
*/
public function setVersionNeededToExtract($version);
@@ -153,9 +208,11 @@ interface ZipEntry
/**
* Sets the compressed size of this entry.
*
* @param int $compressedSize The Compressed Size.
* @return ZipEntry
* @param int $compressedSize the Compressed Size
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCompressedSize($compressedSize);
@@ -169,9 +226,11 @@ interface ZipEntry
/**
* Sets the uncompressed size of this entry.
*
* @param int $size The (Uncompressed) Size.
* @return ZipEntry
* @param int $size the (Uncompressed) Size
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setSize($size);
@@ -184,8 +243,10 @@ interface ZipEntry
/**
* @param int $offset
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setOffset($offset);
@@ -207,9 +268,13 @@ interface ZipEntry
/**
* Sets the General Purpose Bit Flags.
*
* @var int general
* @return ZipEntry
* @param mixed $general
*
* @throws ZipException
*
* @return ZipEntry
*
* @var int general
*/
public function setGeneralPurposeBitFlags($general);
@@ -217,6 +282,7 @@ interface ZipEntry
* Returns the indexed General Purpose Bit Flag.
*
* @param int $mask
*
* @return bool
*/
public function getGeneralPurposeBitFlag($mask);
@@ -224,8 +290,9 @@ interface ZipEntry
/**
* Sets the indexed General Purpose Bit Flag.
*
* @param int $mask
* @param int $mask
* @param bool $bit
*
* @return ZipEntry
*/
public function setGeneralPurposeBitFlag($mask, $bit);
@@ -241,6 +308,7 @@ interface ZipEntry
* Sets the encryption flag for this ZIP entry.
*
* @param bool $encrypted
*
* @return ZipEntry
*/
public function setEncrypted($encrypted);
@@ -264,13 +332,15 @@ interface ZipEntry
* Sets the compression method for this entry.
*
* @param int $method
*
* @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
*
* @return ZipEntry
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
*/
public function setMethod($method);
/**
* Get Unix Timestamp
* Get Unix Timestamp.
*
* @return int
*/
@@ -280,37 +350,62 @@ interface ZipEntry
* Set time from unix timestamp.
*
* @param int $unixTimestamp
*
* @return ZipEntry
*/
public function setTime($unixTimestamp);
/**
* Get Dos Time
* Get Dos Time.
*
* @return int
*/
public function getDosTime();
/**
* Set Dos Time
* Set Dos Time.
*
* @param int $dosTime
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setDosTime($dosTime);
/**
* Returns the external file attributes.
*
* @return int The external file attributes.
* @return int the external file attributes
*/
public function getExternalAttributes();
/**
* Sets the internal file attributes.
*
* @param int $attributes the internal file attributes
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setInternalAttributes($attributes);
/**
* Returns the internal file attributes.
*
* @return int the internal file attributes
*/
public function getInternalAttributes();
/**
* Sets the external file attributes.
*
* @param int $externalAttributes the external file attributes.
* @return ZipEntry
* @param int $externalAttributes the external file attributes
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setExternalAttributes($externalAttributes);
@@ -335,13 +430,16 @@ interface ZipEntry
* (application) data.
* Consider storing such data in a separate entry instead.
*
* @param string $data The byte array holding the serialized Extra Fields.
* @param string $data the byte array holding the serialized Extra Fields
*
* @throws ZipException if the serialized Extra Fields exceed 64 KB
*
* @return ZipEntry
*/
public function setExtra($data);
/**
* Returns comment entry
* Returns comment entry.
*
* @return string
*/
@@ -351,6 +449,7 @@ interface ZipEntry
* Set entry comment.
*
* @param $comment
*
* @return ZipEntry
*/
public function setComment($comment);
@@ -361,7 +460,7 @@ interface ZipEntry
public function isDataDescriptorRequired();
/**
* Return crc32 content or 0 for WinZip AES v2
* Return crc32 content or 0 for WinZip AES v2.
*
* @return int
*/
@@ -371,8 +470,10 @@ interface ZipEntry
* Set crc32 content.
*
* @param int $crc
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCrc($crc);
@@ -382,10 +483,11 @@ interface ZipEntry
public function getPassword();
/**
* Set password and encryption method from entry
* Set password and encryption method from entry.
*
* @param string $password
* @param int|null $encryptionMethod
*
* @param string $password
* @param null|int $encryptionMethod
* @return ZipEntry
*/
public function setPassword($password, $encryptionMethod = null);
@@ -396,32 +498,36 @@ interface ZipEntry
public function getEncryptionMethod();
/**
* Set encryption method
*
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
* Set encryption method.
*
* @param int $encryptionMethod
* @return ZipEntry
*
* @throws ZipException
*
* @return ZipEntry
*
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
*/
public function setEncryptionMethod($encryptionMethod);
/**
* Returns an string content of the given entry.
*
* @return null|string
* @throws ZipException
*
* @return string|null
*/
public function getEntryContent();
/**
* @param int $compressionLevel
*
* @return ZipEntry
*/
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION);
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION);
/**
* @return int

@@ -8,18 +8,15 @@ namespace PhpZip\Model;
*/
class ZipEntryMatcher implements \Countable
{
/**
* @var ZipModel
*/
/** @var ZipModel */
protected $zipModel;
/**
* @var array
*/
/** @var array */
protected $matches = [];
/**
* ZipEntryMatcher constructor.
*
* @param ZipModel $zipModel
*/
public function __construct(ZipModel $zipModel)
@@ -29,44 +26,59 @@ class ZipEntryMatcher implements \Countable
/**
* @param string|array $entries
*
* @return ZipEntryMatcher
*/
public function add($entries)
{
$entries = (array)$entries;
$entries = array_map(function ($entry) {
return $entry instanceof ZipEntry ? $entry->getName() : $entry;
}, $entries);
$this->matches = array_unique(
array_merge(
$this->matches,
array_keys(
array_intersect_key(
$this->zipModel->getEntries(),
array_flip($entries)
$entries = (array) $entries;
$entries = array_map(
static function ($entry) {
return $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
},
$entries
);
$this->matches = array_values(
array_map(
'strval',
array_unique(
array_merge(
$this->matches,
array_keys(
array_intersect_key(
$this->zipModel->getEntries(),
array_flip($entries)
)
)
)
)
)
);
return $this;
}
/**
* @param string $regexp
*
* @return ZipEntryMatcher
*/
public function match($regexp)
{
array_walk($this->zipModel->getEntries(), function (
/** @noinspection PhpUnusedParameterInspection */
$entry,
$entryName
) use ($regexp) {
if (preg_match($regexp, $entryName)) {
$this->matches[] = $entryName;
array_walk(
$this->zipModel->getEntries(),
function (
/** @noinspection PhpUnusedParameterInspection */
$entry,
$entryName
) use ($regexp) {
if (preg_match($regexp, $entryName)) {
$this->matches[] = (string) $entryName;
}
}
});
);
$this->matches = array_unique($this->matches);
return $this;
}
@@ -76,6 +88,7 @@ class ZipEntryMatcher implements \Countable
public function all()
{
$this->matches = array_keys($this->zipModel->getEntries());
return $this;
}
@@ -90,9 +103,12 @@ class ZipEntryMatcher implements \Countable
public function invoke(callable $callable)
{
if (!empty($this->matches)) {
array_walk($this->matches, function ($entryName) use ($callable) {
call_user_func($callable, $entryName);
});
array_walk(
$this->matches,
static function ($entryName) use ($callable) {
$callable($entryName);
}
);
}
}
@@ -106,24 +122,31 @@ class ZipEntryMatcher implements \Countable
public function delete()
{
array_walk($this->matches, function ($entry) {
$this->zipModel->deleteEntry($entry);
});
array_walk(
$this->matches,
function ($entry) {
$this->zipModel->deleteEntry($entry);
}
);
$this->matches = [];
}
/**
* @param string|null $password
* @param int|null $encryptionMethod
* @param int|null $encryptionMethod
*/
public function setPassword($password, $encryptionMethod = null)
{
array_walk($this->matches, function ($entry) use ($password, $encryptionMethod) {
$entry = $this->zipModel->getEntry($entry);
if (!$entry->isDirectory()) {
$this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod);
array_walk(
$this->matches,
function ($entry) use ($password, $encryptionMethod) {
$entry = $this->zipModel->getEntry($entry);
if (!$entry->isDirectory()) {
$this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod);
}
}
});
);
}
/**
@@ -131,36 +154,47 @@ class ZipEntryMatcher implements \Countable
*/
public function setEncryptionMethod($encryptionMethod)
{
array_walk($this->matches, function ($entry) use ($encryptionMethod) {
$entry = $this->zipModel->getEntry($entry);
if (!$entry->isDirectory()) {
$this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod);
array_walk(
$this->matches,
function ($entry) use ($encryptionMethod) {
$entry = $this->zipModel->getEntry($entry);
if (!$entry->isDirectory()) {
$this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod);
}
}
});
);
}
public function disableEncryption()
{
array_walk($this->matches, function ($entry) {
$entry = $this->zipModel->getEntry($entry);
if (!$entry->isDirectory()) {
$entry = $this->zipModel->getEntryForChanges($entry);
$entry->disableEncryption();
array_walk(
$this->matches,
function ($entry) {
$entry = $this->zipModel->getEntry($entry);
if (!$entry->isDirectory()) {
$entry = $this->zipModel->getEntryForChanges($entry);
$entry->disableEncryption();
}
}
});
);
}
/**
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* Count elements of an object.
*
* @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.
* </p>
* <p>
* The return value is cast to an integer.
*
* @since 5.1.0
*/
public function count()
{
return count($this->matches);
return \count($this->matches);
}
}

@@ -1,14 +1,15 @@
<?php /** @noinspection PhpMissingBreakStatementInspection */
<?php
namespace PhpZip\Model;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\Fields\NtfsExtraField;
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Util\FilesUtil;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* Zip info
* Zip info.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
@@ -17,53 +18,96 @@ class ZipInfo
{
// made by constants
const MADE_BY_MS_DOS = 0;
const MADE_BY_AMIGA = 1;
const MADE_BY_OPEN_VMS = 2;
const MADE_BY_UNIX = 3;
const MADE_BY_VM_CMS = 4;
const MADE_BY_ATARI = 5;
const MADE_BY_OS_2 = 6;
const MADE_BY_MACINTOSH = 7;
const MADE_BY_Z_SYSTEM = 8;
const MADE_BY_CP_M = 9;
const MADE_BY_WINDOWS_NTFS = 10;
const MADE_BY_MVS = 11;
const MADE_BY_VSE = 12;
const MADE_BY_ACORN_RISC = 13;
const MADE_BY_VFAT = 14;
const MADE_BY_ALTERNATE_MVS = 15;
const MADE_BY_BEOS = 16;
const MADE_BY_TANDEM = 17;
const MADE_BY_OS_400 = 18;
const MADE_BY_OS_X = 19;
const MADE_BY_UNKNOWN = 20;
const UNX_IFMT = 0170000; /* Unix file type mask */
const UNX_IFREG = 0100000; /* Unix regular file */
const UNX_IFSOCK = 0140000; /* Unix socket (BSD, not SysV or Amiga) */
const UNX_IFLNK = 0120000; /* Unix symbolic link (not SysV, Amiga) */
const UNX_IFBLK = 0060000; /* Unix block special (not Amiga) */
const UNX_IFDIR = 0040000; /* Unix directory */
const UNX_IFCHR = 0020000; /* Unix character special (not Amiga) */
const UNX_IFIFO = 0010000; /* Unix fifo (BCC, not MSC or Amiga) */
const UNX_ISUID = 04000; /* Unix set user id on execution */
const UNX_ISGID = 02000; /* Unix set group id on execution */
const UNX_ISVTX = 01000; /* Unix directory permissions control */
const UNX_ENFMT = self::UNX_ISGID; /* Unix record locking enforcement flag */
const UNX_IRWXU = 00700; /* Unix read, write, execute: owner */
const UNX_IRUSR = 00400; /* Unix read permission: owner */
const UNX_IWUSR = 00200; /* Unix write permission: owner */
const UNX_IXUSR = 00100; /* Unix execute permission: owner */
const UNX_IRWXG = 00070; /* Unix read, write, execute: group */
const UNX_IRGRP = 00040; /* Unix read permission: group */
const UNX_IWGRP = 00020; /* Unix write permission: group */
const UNX_IXGRP = 00010; /* Unix execute permission: group */
const UNX_IRWXO = 00007; /* Unix read, write, execute: other */
const UNX_IROTH = 00004; /* Unix read permission: other */
const UNX_IWOTH = 00002; /* Unix write permission: other */
const UNX_IXOTH = 00001; /* Unix execute permission: other */
const UNX_IFMT = 0170000; // Unix file type mask
private static $valuesMadeBy = [
const UNX_IFREG = 0100000; // Unix regular file
const UNX_IFSOCK = 0140000; // Unix socket (BSD, not SysV or Amiga)
const UNX_IFLNK = 0120000; // Unix symbolic link (not SysV, Amiga)
const UNX_IFBLK = 0060000; // Unix block special (not Amiga)
const UNX_IFDIR = 0040000; // Unix directory
const UNX_IFCHR = 0020000; // Unix character special (not Amiga)
const UNX_IFIFO = 0010000; // Unix fifo (BCC, not MSC or Amiga)
const UNX_ISUID = 04000; // Unix set user id on execution
const UNX_ISGID = 02000; // Unix set group id on execution
const UNX_ISVTX = 01000; // Unix directory permissions control
const UNX_ENFMT = self::UNX_ISGID; // Unix record locking enforcement flag
const UNX_IRWXU = 00700; // Unix read, write, execute: owner
const UNX_IRUSR = 00400; // Unix read permission: owner
const UNX_IWUSR = 00200; // Unix write permission: owner
const UNX_IXUSR = 00100; // Unix execute permission: owner
const UNX_IRWXG = 00070; // Unix read, write, execute: group
const UNX_IRGRP = 00040; // Unix read permission: group
const UNX_IWGRP = 00020; // Unix write permission: group
const UNX_IXGRP = 00010; // Unix execute permission: group
const UNX_IRWXO = 00007; // Unix read, write, execute: other
const UNX_IROTH = 00004; // Unix read permission: other
const UNX_IWOTH = 00002; // Unix write permission: other
const UNX_IXOTH = 00001; // Unix execute permission: other
private static $platformNames = [
self::MADE_BY_MS_DOS => 'FAT',
self::MADE_BY_AMIGA => 'Amiga',
self::MADE_BY_OPEN_VMS => 'OpenVMS',
@@ -86,9 +130,9 @@ class ZipInfo
self::MADE_BY_OS_X => 'Mac OS X',
];
private static $valuesCompressionMethod = [
private static $compressionMethodNames = [
ZipEntry::UNKNOWN => 'unknown',
ZipFileInterface::METHOD_STORED => 'no compression',
ZipFile::METHOD_STORED => 'no compression',
1 => 'shrink',
2 => 'reduce level 1',
3 => 'reduce level 2',
@@ -96,7 +140,7 @@ class ZipInfo
5 => 'reduce level 4',
6 => 'implode',
7 => 'reserved for Tokenizing compression algorithm',
ZipFileInterface::METHOD_DEFLATED => 'deflate',
ZipFile::METHOD_DEFLATED => 'deflate',
9 => 'deflate64',
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
11 => 'reserved by PKWARE',
@@ -113,80 +157,64 @@ class ZipInfo
ZipEntry::METHOD_WINZIP_AES => 'WinZip AES',
];
/**
* @var string
*/
/** @var string */
private $name;
/**
* @var bool
*/
/** @var bool */
private $folder;
/**
* @var int
*/
/** @var int */
private $size;
/**
* @var int
*/
/** @var int */
private $compressedSize;
/**
* @var int
*/
/** @var int */
private $mtime;
/**
* @var int|null
*/
/** @var int|null */
private $ctime;
/**
* @var int|null
*/
/** @var int|null */
private $atime;
/**
* @var bool
*/
/** @var bool */
private $encrypted;
/**
* @var string|null
*/
/** @var string|null */
private $comment;
/**
* @var int
*/
/** @var int */
private $crc;
/**
* @var string
*/
/** @var string */
private $methodName;
/**
* @var int
*/
/** @var int */
private $compressionMethod;
/**
* @var string
*/
/** @var string */
private $platform;
/**
* @var int
*/
/** @var int */
private $version;
/**
* @var string
*/
/** @var string */
private $attributes;
/**
* @var int|null
*/
/** @var int|null */
private $encryptionMethod;
/**
* @var int|null
*/
/** @var int|null */
private $compressionLevel;
/**
* ZipInfo constructor.
*
* @param ZipEntry $entry
* @throws \PhpZip\Exception\ZipException
*
* @throws ZipException
* @noinspection PhpMissingBreakStatementInspection
*/
public function __construct(ZipEntry $entry)
{
@@ -195,7 +223,8 @@ class ZipInfo
$ctime = null;
$field = $entry->getExtraFieldsCollection()->get(NtfsExtraField::getHeaderId());
if (null !== $field && $field instanceof NtfsExtraField) {
if ($field instanceof NtfsExtraField) {
/**
* @var NtfsExtraField $field
*/
@@ -206,12 +235,8 @@ class ZipInfo
$this->name = $entry->getName();
$this->folder = $entry->isDirectory();
$this->size = PHP_INT_SIZE === 4 ?
sprintf('%u', $entry->getSize()) :
$entry->getSize();
$this->compressedSize = PHP_INT_SIZE === 4 ?
sprintf('%u', $entry->getCompressedSize()) :
$entry->getCompressedSize();
$this->size = $entry->getSize();
$this->compressedSize = $entry->getCompressedSize();
$this->mtime = $mtime;
$this->ctime = $ctime;
$this->atime = $atime;
@@ -225,65 +250,71 @@ class ZipInfo
$this->version = $entry->getVersionNeededToExtract();
$this->compressionLevel = $entry->getCompressionLevel();
$attributes = str_repeat(" ", 12);
$attributes = str_repeat(' ', 12);
$externalAttributes = $entry->getExternalAttributes();
$externalAttributes = PHP_INT_SIZE === 4 ?
sprintf('%u', $externalAttributes) :
$externalAttributes;
$xattr = (($externalAttributes >> 16) & 0xFFFF);
switch ($entry->getPlatform()) {
switch ($entry->getCreatedOS()) {
case self::MADE_BY_MS_DOS:
case self::MADE_BY_WINDOWS_NTFS:
if ($entry->getPlatform() != self::MADE_BY_MS_DOS ||
($xattr & 0700) !=
(0400 |
if ($entry->getCreatedOS() !== self::MADE_BY_MS_DOS ||
($xattr & self::UNX_IRWXU) !==
(self::UNX_IRUSR |
(!($externalAttributes & 1) << 7) |
(($externalAttributes & 0x10) << 2))
) {
$xattr = $externalAttributes & 0xFF;
$attributes = ".r.-... ";
$attributes = '.r.-... ';
$attributes[2] = ($xattr & 0x01) ? '-' : 'w';
$attributes[5] = ($xattr & 0x02) ? 'h' : '-';
$attributes[6] = ($xattr & 0x04) ? 's' : '-';
$attributes[4] = ($xattr & 0x20) ? 'a' : '-';
if ($xattr & 0x10) {
$attributes[0] = 'd';
$attributes[3] = 'x';
} else {
$attributes[0] = '-';
}
if ($xattr & 0x08) {
$attributes[0] = 'V';
} else {
$ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION));
if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) {
$ext = strtolower(pathinfo($entry->getName(), \PATHINFO_EXTENSION));
if (\in_array($ext, ['com', 'exe', 'btm', 'cmd', 'bat'])) {
$attributes[3] = 'x';
}
}
break;
} /* else: fall through! */
} // else: fall through!
// no break
default: /* assume Unix-like */
default: // assume Unix-like
switch ($xattr & self::UNX_IFMT) {
case self::UNX_IFDIR:
$attributes[0] = 'd';
break;
case self::UNX_IFREG:
$attributes[0] = '-';
break;
case self::UNX_IFLNK:
$attributes[0] = 'l';
break;
case self::UNX_IFBLK:
$attributes[0] = 'b';
break;
case self::UNX_IFCHR:
$attributes[0] = 'c';
break;
case self::UNX_IFIFO:
$attributes[0] = 'p';
break;
case self::UNX_IFSOCK:
$attributes[0] = 's';
break;
@@ -302,91 +333,97 @@ class ZipInfo
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
} else {
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-';
} /* S==undefined */
} // S==undefined
if ($xattr & self::UNX_IXGRP) {
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x';
} /* == UNX_ENFMT */
} // == UNX_ENFMT
else {
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-';
} /* SunOS 4.1.x */
} // SunOS 4.1.x
if ($xattr & self::UNX_IXOTH) {
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x';
} /* "sticky bit" */
} // "sticky bit"
else {
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-';
} /* T==undefined */
} // T==undefined
}
$this->attributes = trim($attributes);
}
/**
* @param ZipEntry $entry
*
* @throws ZipException
*
* @return int
* @throws \PhpZip\Exception\ZipException
*/
private static function getMethodId(ZipEntry $entry)
{
$method = $entry->getMethod();
if ($entry->isEncrypted()) {
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
if (null !== $field) {
/**
* @var WinZipAesEntryExtraField $field
*/
$method = $field->getMethod();
}
if ($entry->isEncrypted() && $entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
if ($field !== null) {
/** @var WinZipAesEntryExtraField $field */
$method = $field->getMethod();
}
}
return $method;
}
/**
* @param ZipEntry $entry
*
* @throws ZipException
*
* @return string
* @throws \PhpZip\Exception\ZipException
*/
private static function getEntryMethodName(ZipEntry $entry)
{
$return = '';
$compressionMethod = $entry->getMethod();
if ($entry->isEncrypted()) {
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
$return .= ucfirst(self::$compressionMethodNames[$entry->getMethod()]);
/** @var WinZipAesEntryExtraField|null $field */
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
if (null !== $field) {
/**
* @var WinZipAesEntryExtraField $field
*/
if ($field !== null) {
$return .= '-' . $field->getKeyStrength();
if (isset(self::$valuesCompressionMethod[$field->getMethod()])) {
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$field->getMethod()]);
}
$compressionMethod = $field->getMethod();
}
} else {
$return .= 'ZipCrypto';
if (isset(self::$valuesCompressionMethod[$entry->getMethod()])) {
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
}
}
} elseif (isset(self::$valuesCompressionMethod[$entry->getMethod()])) {
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
} else {
$return = 'unknown';
$return .= ' ';
}
if (isset(self::$compressionMethodNames[$compressionMethod])) {
$return .= ucfirst(self::$compressionMethodNames[$compressionMethod]);
} else {
$return .= 'unknown';
}
return $return;
}
/**
* @param ZipEntry $entry
*
* @return string
*/
public static function getPlatformName(ZipEntry $entry)
{
if (isset(self::$valuesMadeBy[$entry->getPlatform()])) {
return self::$valuesMadeBy[$entry->getPlatform()];
} else {
return 'unknown';
if (isset(self::$platformNames[$entry->getCreatedOS()])) {
return self::$platformNames[$entry->getCreatedOS()];
}
return 'unknown';
}
/**
@@ -399,6 +436,7 @@ class ZipInfo
/**
* @return string
*
* @deprecated use \PhpZip\Model\ZipInfo::getName()
*/
public function getPath()
@@ -407,7 +445,7 @@ class ZipInfo
}
/**
* @return boolean
* @return bool
*/
public function isFolder()
{
@@ -463,7 +501,7 @@ class ZipInfo
}
/**
* @return boolean
* @return bool
*/
public function isEncrypted()
{
@@ -471,7 +509,7 @@ class ZipInfo
}
/**
* @return null|string
* @return string|null
*/
public function getComment()
{
@@ -488,6 +526,7 @@ class ZipInfo
/**
* @return string
*
* @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
*/
public function getMethod()
@@ -566,7 +605,7 @@ class ZipInfo
'method_name' => $this->getMethodName(),
'compression_method' => $this->getCompressionMethod(),
'platform' => $this->getPlatform(),
'version' => $this->getVersion()
'version' => $this->getVersion(),
];
}
@@ -580,9 +619,9 @@ class ZipInfo
. ($this->isFolder() ? 'Folder, ' : '')
. 'Size="' . FilesUtil::humanSize($this->getSize()) . '"'
. ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()) . '"'
. ', Modified time="' . date(DATE_W3C, $this->getMtime()) . '", '
. ($this->getCtime() !== null ? 'Created time="' . date(DATE_W3C, $this->getCtime()) . '", ' : '')
. ($this->getAtime() !== null ? 'Accessed time="' . date(DATE_W3C, $this->getAtime()) . '", ' : '')
. ', Modified time="' . date(\DATE_W3C, $this->getMtime()) . '", '
. ($this->getCtime() !== null ? 'Created time="' . date(\DATE_W3C, $this->getCtime()) . '", ' : '')
. ($this->getAtime() !== null ? 'Accessed time="' . date(\DATE_W3C, $this->getAtime()) . '", ' : '')
. ($this->isEncrypted() ? 'Encrypted, ' : '')
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')

@@ -7,68 +7,63 @@ use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Entry\ZipChangesEntry;
use PhpZip\Model\Entry\ZipSourceEntry;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* Zip Model
* Zip Model.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipModel implements \Countable
{
/**
* @var ZipSourceEntry[]
*/
/** @var ZipSourceEntry[] */
protected $inputEntries = [];
/**
* @var ZipEntry[]
*/
/** @var ZipEntry[] */
protected $outEntries = [];
/**
* @var string|null
*/
/** @var string|null */
protected $archiveComment;
/**
* @var string|null
*/
/** @var string|null */
protected $archiveCommentChanges;
/**
* @var bool
*/
/** @var bool */
protected $archiveCommentChanged = false;
/**
* @var int|null
*/
/** @var int|null */
protected $zipAlign;
/**
* @var bool
*/
/** @var bool */
private $zip64;
/**
* @param ZipSourceEntry[] $entries
* @param ZipSourceEntry[] $entries
* @param EndOfCentralDirectory $endOfCentralDirectory
*
* @return ZipModel
*/
public static function newSourceModel(array $entries, EndOfCentralDirectory $endOfCentralDirectory)
{
$model = new self;
$model = new self();
$model->inputEntries = $entries;
$model->outEntries = $entries;
$model->archiveComment = $endOfCentralDirectory->getComment();
$model->zip64 = $endOfCentralDirectory->isZip64();
return $model;
}
/**
* @return null|string
* @return string|null
*/
public function getArchiveComment()
{
if ($this->archiveCommentChanged) {
return $this->archiveCommentChanges;
}
return $this->archiveComment;
}
@@ -77,13 +72,15 @@ class ZipModel implements \Countable
*/
public function setArchiveComment($comment)
{
if ($comment !== null && strlen($comment) !== 0) {
$comment = (string)$comment;
$length = strlen($comment);
if (0x0000 > $length || $length > 0xffff) {
if ($comment !== null && $comment !== '') {
$comment = (string) $comment;
$length = \strlen($comment);
if ($length > 0xffff) {
throw new InvalidArgumentException('Length comment out of range');
}
}
if ($comment !== $this->archiveComment) {
$this->archiveCommentChanges = $comment;
$this->archiveCommentChanged = true;
@@ -95,7 +92,8 @@ class ZipModel implements \Countable
/**
* Specify a password for extracting files.
*
* @param null|string $password
* @param string|null $password
*
* @throws ZipException
*/
public function setReadPassword($password)
@@ -110,6 +108,7 @@ class ZipModel implements \Countable
/**
* @param string $entryName
* @param string $password
*
* @throws ZipEntryNotFoundException
* @throws ZipException
*/
@@ -118,6 +117,7 @@ class ZipModel implements \Countable
if (!isset($this->inputEntries[$entryName])) {
throw new ZipEntryNotFoundException($entryName);
}
if ($this->inputEntries[$entryName]->isEncrypted()) {
$this->inputEntries[$entryName]->setPassword($password);
}
@@ -136,7 +136,7 @@ class ZipModel implements \Countable
*/
public function setZipAlign($zipAlign)
{
$this->zipAlign = $zipAlign === null ? null : (int)$zipAlign;
$this->zipAlign = $zipAlign === null ? null : (int) $zipAlign;
}
/**
@@ -144,11 +144,11 @@ class ZipModel implements \Countable
*/
public function isZipAlign()
{
return $this->zipAlign != null;
return $this->zipAlign !== null;
}
/**
* @param null|string $writePassword
* @param string|null $writePassword
*/
public function setWritePassword($writePassword)
{
@@ -156,7 +156,7 @@ class ZipModel implements \Countable
}
/**
* Remove password
* Remove password.
*/
public function removePassword()
{
@@ -182,15 +182,16 @@ class ZipModel implements \Countable
/**
* @param string|ZipEntry $old
* @param string|ZipEntry $new
*
* @throws ZipException
*/
public function renameEntry($old, $new)
{
$old = $old instanceof ZipEntry ? $old->getName() : (string)$old;
$new = $new instanceof ZipEntry ? $new->getName() : (string)$new;
$old = $old instanceof ZipEntry ? $old->getName() : (string) $old;
$new = $new instanceof ZipEntry ? $new->getName() : (string) $new;
if (isset($this->outEntries[$new])) {
throw new InvalidArgumentException("New entry name " . $new . ' is exists.');
throw new InvalidArgumentException('New entry name ' . $new . ' is exists.');
}
$entry = $this->getEntryForChanges($old);
@@ -201,45 +202,57 @@ class ZipModel implements \Countable
/**
* @param string|ZipEntry $entry
* @return ZipChangesEntry|ZipEntry
* @throws ZipException
*
* @throws ZipEntryNotFoundException
* @throws ZipException
*
* @return ZipChangesEntry|ZipEntry
*/
public function getEntryForChanges($entry)
{
$entry = $this->getEntry($entry);
if ($entry instanceof ZipSourceEntry) {
$entry = new ZipChangesEntry($entry);
$this->addEntry($entry);
}
return $entry;
}
/**
* @param string|ZipEntry $entryName
* @return ZipEntry
*
* @throws ZipEntryNotFoundException
*
* @return ZipEntry
*/
public function getEntry($entryName)
{
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
if (isset($this->outEntries[$entryName])) {
return $this->outEntries[$entryName];
}
throw new ZipEntryNotFoundException($entryName);
}
/**
* @param string|ZipEntry $entry
*
* @return bool
*/
public function deleteEntry($entry)
{
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
if (isset($this->outEntries[$entry])) {
unset($this->outEntries[$entry]);
return true;
}
return false;
}
@@ -263,11 +276,13 @@ class ZipModel implements \Countable
/**
* @param string|ZipEntry $entryName
*
* @return bool
*/
public function hasEntry($entryName)
{
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
return isset($this->outEntries[$entryName]);
}
@@ -280,21 +295,24 @@ class ZipModel implements \Countable
}
/**
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* Count elements of an object.
*
* @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.
* </p>
* <p>
* The return value is cast to an integer.
*
* @since 5.1.0
*/
public function count()
{
return sizeof($this->outEntries);
return \count($this->outEntries);
}
/**
* Undo all changes done in the archive
* Undo all changes done in the archive.
*/
public function unchangeAll()
{
@@ -303,7 +321,7 @@ class ZipModel implements \Countable
}
/**
* Undo change archive comment
* Undo change archive comment.
*/
public function unchangeArchiveComment()
{
@@ -315,22 +333,26 @@ class ZipModel implements \Countable
* Revert all changes done to an entry with the given name.
*
* @param string|ZipEntry $entry Entry name or ZipEntry
*
* @return bool
*/
public function unchangeEntry($entry)
{
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
if (isset($this->outEntries[$entry]) && isset($this->inputEntries[$entry])) {
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
if (isset($this->outEntries[$entry], $this->inputEntries[$entry])) {
$this->outEntries[$entry] = $this->inputEntries[$entry];
return true;
}
return false;
}
/**
* @param int $encryptionMethod
*/
public function setEncryptionMethod($encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256)
public function setEncryptionMethod($encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256)
{
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
}

@@ -5,59 +5,77 @@ namespace PhpZip\Stream;
use Psr\Http\Message\StreamInterface;
/**
* Implement PSR Message Stream
* Implement PSR Message Stream.
*/
class ResponseStream implements StreamInterface
{
/**
* @var array
*/
/** @var array */
private static $readWriteHash = [
'read' => [
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a+' => true,
'r' => true,
'w+' => true,
'r+' => true,
'x+' => true,
'c+' => true,
'rb' => true,
'w+b' => true,
'r+b' => true,
'x+b' => true,
'c+b' => true,
'rt' => true,
'w+t' => true,
'r+t' => true,
'x+t' => true,
'c+t' => true,
'a+' => true,
],
'write' => [
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
'w' => true,
'w+' => true,
'rw' => true,
'r+' => true,
'x+' => true,
'c+' => true,
'wb' => true,
'w+b' => true,
'r+b' => true,
'x+b' => true,
'c+b' => true,
'w+t' => true,
'r+t' => true,
'x+t' => true,
'c+t' => true,
'a' => true,
'a+' => true,
],
];
/**
* @var resource
*/
/** @var resource */
private $stream;
/**
* @var int
*/
/** @var int */
private $size;
/**
* @var bool
*/
/** @var bool */
private $seekable;
/**
* @var bool
*/
/** @var bool */
private $readable;
/**
* @var bool
*/
/** @var bool */
private $writable;
/**
* @var array|mixed|null
*/
/** @var array|mixed|null */
private $uri;
/**
* @param resource $stream Stream resource to wrap.
* @param resource $stream stream resource to wrap
*
* @throws \InvalidArgumentException if the stream is not a stream resource
*/
public function __construct($stream)
{
if (!is_resource($stream)) {
if (!\is_resource($stream)) {
throw new \InvalidArgumentException('Stream must be a resource');
}
$this->stream = $stream;
@@ -74,11 +92,13 @@ class ResponseStream implements StreamInterface
* The keys returned are identical to the keys returned from PHP's
* stream_get_meta_data() function.
*
* @link http://php.net/manual/en/function.stream-get-meta-data.php
* @param string $key Specific metadata to retrieve.
* @see http://php.net/manual/en/function.stream-get-meta-data.php
*
* @param string $key specific metadata to retrieve
*
* @return array|mixed|null Returns an associative array if no key is
* provided. Returns a specific key value if a key is provided and the
* value is found, or null if the key is not found.
* provided. Returns a specific key value if a key is provided and the
* value is found, or null if the key is not found.
*/
public function getMetadata($key = null)
{
@@ -86,6 +106,7 @@ class ResponseStream implements StreamInterface
return $key ? null : [];
}
$meta = stream_get_meta_data($this->stream);
return isset($meta[$key]) ? $meta[$key] : null;
}
@@ -101,6 +122,7 @@ class ResponseStream implements StreamInterface
* string casting operations.
*
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
*
* @return string
*/
public function __toString()
@@ -109,7 +131,8 @@ class ResponseStream implements StreamInterface
return '';
}
$this->rewind();
return (string)stream_get_contents($this->stream);
return (string) stream_get_contents($this->stream);
}
/**
@@ -118,9 +141,10 @@ class ResponseStream implements StreamInterface
* If the stream is not seekable, this method will raise an exception;
* otherwise, it will perform a seek(0).
*
* @throws \RuntimeException on failure
*
* @see http://www.php.net/manual/en/function.fseek.php
* @see seek()
* @link http://www.php.net/manual/en/function.fseek.php
* @throws \RuntimeException on failure.
*/
public function rewind()
{
@@ -130,13 +154,14 @@ class ResponseStream implements StreamInterface
/**
* Get the size of the stream if known.
*
* @return int|null Returns the size in bytes if known, or null if unknown.
* @return int|null returns the size in bytes if known, or null if unknown
*/
public function getSize()
{
if ($this->size !== null) {
return $this->size;
}
if (!$this->stream) {
return null;
}
@@ -145,18 +170,22 @@ class ResponseStream implements StreamInterface
clearstatcache(true, $this->uri);
}
$stats = fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}
/**
* Returns the current position of the file read/write pointer
* Returns the current position of the file read/write pointer.
*
* @throws \RuntimeException on error
*
* @return int Position of the file pointer
* @throws \RuntimeException on error.
*/
public function tell()
{
@@ -186,16 +215,18 @@ class ResponseStream implements StreamInterface
/**
* Seek to a position in the stream.
*
* @link http://www.php.net/manual/en/function.fseek.php
* @see http://www.php.net/manual/en/function.fseek.php
*
* @param int $offset Stream offset
* @param int $whence Specifies how the cursor position will be calculated
* based on the seek offset. Valid values are identical to the built-in
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
* offset bytes SEEK_CUR: Set position to current location plus offset
* SEEK_END: Set position to end-of-stream plus offset.
* @throws \RuntimeException on failure.
* based on the seek offset. Valid values are identical to the built-in
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
* offset bytes SEEK_CUR: Set position to current location plus offset
* SEEK_END: Set position to end-of-stream plus offset.
*
* @throws \RuntimeException on failure
*/
public function seek($offset, $whence = SEEK_SET)
public function seek($offset, $whence = \SEEK_SET)
{
$this->seekable && fseek($this->stream, $offset, $whence);
}
@@ -213,13 +244,16 @@ class ResponseStream implements StreamInterface
/**
* Write data to the stream.
*
* @param string $string The string that is to be written.
* @return int Returns the number of bytes written to the stream.
* @throws \RuntimeException on failure.
* @param string $string the string that is to be written
*
* @throws \RuntimeException on failure
*
* @return int returns the number of bytes written to the stream
*/
public function write($string)
{
$this->size = null;
return $this->writable ? fwrite($this->stream, $string) : false;
}
@@ -237,23 +271,26 @@ class ResponseStream implements StreamInterface
* Read data from the stream.
*
* @param int $length Read up to $length bytes from the object and return
* them. Fewer than $length bytes may be returned if underlying stream
* call returns fewer bytes.
* @return string Returns the data read from the stream, or an empty string
* if no bytes are available.
* @throws \RuntimeException if an error occurs.
* them. Fewer than $length bytes may be returned if underlying stream
* call returns fewer bytes.
*
* @throws \RuntimeException if an error occurs
*
* @return string returns the data read from the stream, or an empty string
* if no bytes are available
*/
public function read($length)
{
return $this->readable ? fread($this->stream, $length) : "";
return $this->readable ? fread($this->stream, $length) : '';
}
/**
* Returns the remaining contents in a string
* Returns the remaining contents in a string.
*
* @throws \RuntimeException if unable to read or an error occurs while
* reading
*
* @return string
* @throws \RuntimeException if unable to read or an error occurs while
* reading.
*/
public function getContents()
{
@@ -261,7 +298,7 @@ class ResponseStream implements StreamInterface
}
/**
* Closes the stream when the destructed
* Closes the stream when the destructed.
*/
public function __destruct()
{
@@ -270,12 +307,10 @@ class ResponseStream implements StreamInterface
/**
* Closes the stream and any underlying resources.
*
* @return void
*/
public function close()
{
if (is_resource($this->stream)) {
if (\is_resource($this->stream)) {
fclose($this->stream);
}
$this->detach();
@@ -293,6 +328,7 @@ class ResponseStream implements StreamInterface
$result = $this->stream;
$this->stream = $this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = false;
return $result;
}
}

@@ -14,61 +14,45 @@ use PhpZip\Extra\ExtraFieldsCollection;
use PhpZip\Extra\ExtraFieldsFactory;
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Mapper\OffsetPositionMapper;
use PhpZip\Mapper\PositionMapper;
use PhpZip\Model\EndOfCentralDirectory;
use PhpZip\Model\Entry\ZipSourceEntry;
use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipModel;
use PhpZip\Util\PackUtil;
use PhpZip\Util\StringUtil;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* Read zip file
* Read zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipInputStream implements ZipInputStreamInterface
{
/**
* @var resource
*/
/** @var resource */
protected $in;
/**
* @var PositionMapper
*/
protected $mapper;
/**
* @var int The number of bytes in the preamble of this ZIP file.
*/
protected $preamble = 0;
/**
* @var int The number of bytes in the postamble of this ZIP file.
*/
protected $postamble = 0;
/**
* @var ZipModel
*/
/** @var ZipModel */
protected $zipModel;
/**
* ZipInputStream constructor.
*
* @param resource $in
*/
public function __construct($in)
{
if (!is_resource($in)) {
if (!\is_resource($in)) {
throw new RuntimeException('$in must be resource');
}
$this->in = $in;
$this->mapper = new PositionMapper();
}
/**
* @return ZipModel
* @throws ZipException
*
* @return ZipModel
*/
public function readZip()
{
@@ -76,11 +60,12 @@ class ZipInputStream implements ZipInputStreamInterface
$endOfCentralDirectory = $this->readEndOfCentralDirectory();
$entries = $this->mountCentralDirectory($endOfCentralDirectory);
$this->zipModel = ZipModel::newSourceModel($entries, $endOfCentralDirectory);
return $this->zipModel;
}
/**
* Check zip file signature
* Check zip file signature.
*
* @throws ZipException if this not .ZIP file.
*/
@@ -90,152 +75,203 @@ class ZipInputStream implements ZipInputStreamInterface
// Constraint: A ZIP file must start with a Local File Header
// or a (ZIP64) End Of Central Directory Record if it's empty.
$signatureBytes = fread($this->in, 4);
if (strlen($signatureBytes) < 4) {
throw new ZipException("Invalid zip file.");
if (\strlen($signatureBytes) < 4) {
throw new ZipException('Invalid zip file.');
}
$signature = unpack('V', $signatureBytes)[1];
if (
ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature
&& EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
&& EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
$signature !== ZipEntry::LOCAL_FILE_HEADER_SIG
&& $signature !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG
&& $signature !== EndOfCentralDirectory::END_OF_CD_SIG
) {
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
throw new ZipException(
'Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: ' . $signature
);
}
}
/**
* @return EndOfCentralDirectory
* @throws ZipException
*
* @return EndOfCentralDirectory
*/
protected function readEndOfCentralDirectory()
{
if (!$this->findEndOfCentralDirectory()) {
throw new ZipException('Invalid zip file. The end of the central directory could not be found.');
}
$positionECD = ftell($this->in) - 4;
$buffer = fread($this->in, fstat($this->in)['size'] - $positionECD);
$unpack = unpack(
'vdiskNo/vcdDiskNo/vcdEntriesDisk/' .
'vcdEntries/VcdSize/VcdPos/vcommentLength',
substr($buffer, 0, 18)
);
if (
$unpack['diskNo'] !== 0 ||
$unpack['cdDiskNo'] !== 0 ||
$unpack['cdEntriesDisk'] !== $unpack['cdEntries']
) {
throw new ZipException(
'ZIP file spanning/splitting is not supported!'
);
}
// .ZIP file comment (variable sizeECD)
$comment = null;
// Search for End of central directory record.
$stats = fstat($this->in);
$size = $stats['size'];
$max = $size - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
if ($unpack['commentLength'] > 0) {
$comment = substr($buffer, 18, $unpack['commentLength']);
}
// Check for ZIP64 End Of Central Directory Locator exists.
$zip64ECDLocatorPosition = $positionECD - EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_LEN;
fseek($this->in, $zip64ECDLocatorPosition);
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
if ($zip64ECDLocatorPosition > 0 && unpack(
'V',
fread($this->in, 4)
)[1] === EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG) {
$positionECD = $this->findZip64ECDPosition();
$endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD);
$endCentralDirectory->setComment($comment);
} else {
$endCentralDirectory = new EndOfCentralDirectory(
$unpack['cdEntries'],
$unpack['cdPos'],
$unpack['cdSize'],
false,
$comment
);
}
return $endCentralDirectory;
}
/**
* @throws ZipException
*
* @return bool
*/
protected function findEndOfCentralDirectory()
{
$max = fstat($this->in)['size'] - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
if ($max < 0) {
throw new ZipException('Too short to be a zip file');
}
$min = $max >= 0xffff ? $max - 0xffff : 0;
for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) {
fseek($this->in, $endOfCentralDirRecordPos, SEEK_SET);
// Search for End of central directory record.
for ($position = $max; $position >= $min; $position--) {
fseek($this->in, $position);
// end of central dir signature 4 bytes (0x06054b50)
if (EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($this->in, 4))[1]) {
if (unpack('V', fread($this->in, 4))[1] !== EndOfCentralDirectory::END_OF_CD_SIG) {
continue;
}
// number of this disk - 2 bytes
// number of the disk with the start of the
// central directory - 2 bytes
// total number of entries in the central
// directory on this disk - 2 bytes
// total number of entries in the central
// directory - 2 bytes
// size of the central directory - 4 bytes
// offset of start of central directory with
// respect to the starting disk number - 4 bytes
// ZIP file comment length - 2 bytes
$data = unpack(
'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength',
fread($this->in, 18)
);
if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) {
throw new ZipException(
"ZIP file spanning/splitting is not supported!"
);
}
// .ZIP file comment (variable size)
if ($data['commentLength'] > 0) {
$comment = '';
$offset = 0;
while ($offset < $data['commentLength']) {
$read = min(8192 /* chunk size */, $data['commentLength'] - $offset);
$comment .= fread($this->in, $read);
$offset += $read;
}
}
$this->preamble = $endOfCentralDirRecordPos;
$this->postamble = $size - ftell($this->in);
// Check for ZIP64 End Of Central Directory Locator.
$endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN;
fseek($this->in, $endOfCentralDirLocatorPos, SEEK_SET);
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
if (
0 > $endOfCentralDirLocatorPos ||
ftell($this->in) === $size ||
EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($this->in, 4))[1]
) {
// Seek and check first CFH, probably requiring an offset mapper.
$offset = $endOfCentralDirRecordPos - $data['cdSize'];
fseek($this->in, $offset, SEEK_SET);
$offset -= $data['cdPos'];
if ($offset !== 0) {
$this->mapper = new OffsetPositionMapper($offset);
}
$entryCount = $data['cdEntries'];
return new EndOfCentralDirectory($entryCount, $comment);
}
// number of the disk with the
// start of the zip64 end of
// central directory 4 bytes
$zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($this->in, 4))[1];
// relative offset of the zip64
// end of central directory record 8 bytes
$zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($this->in, 8));
// total number of disks 4 bytes
$totalDisks = unpack('V', fread($this->in, 4))[1];
if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) {
throw new ZipException("ZIP file spanning/splitting is not supported!");
}
fseek($this->in, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET);
// zip64 end of central dir
// signature 4 bytes (0x06064b50)
$zip64EndOfCentralDirSig = unpack('V', fread($this->in, 4))[1];
if (EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) {
throw new ZipException("Expected ZIP64 End Of Central Directory Record!");
}
// size of zip64 end of central
// directory record 8 bytes
// version made by 2 bytes
// version needed to extract 2 bytes
fseek($this->in, 12, SEEK_CUR);
// number of this disk 4 bytes
$diskNo = unpack('V', fread($this->in, 4))[1];
// number of the disk with the
// start of the central directory 4 bytes
$cdDiskNo = unpack('V', fread($this->in, 4))[1];
// total number of entries in the
// central directory on this disk 8 bytes
$cdEntriesDisk = PackUtil::unpackLongLE(fread($this->in, 8));
// total number of entries in the
// central directory 8 bytes
$cdEntries = PackUtil::unpackLongLE(fread($this->in, 8));
if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) {
throw new ZipException("ZIP file spanning/splitting is not supported!");
}
if ($cdEntries < 0 || 0x7fffffff < $cdEntries) {
throw new ZipException("Total Number Of Entries In The Central Directory out of range!");
}
// size of the central directory 8 bytes
fseek($this->in, 8, SEEK_CUR);
// offset of start of central
// directory with respect to
// the starting disk number 8 bytes
$cdPos = PackUtil::unpackLongLE(fread($this->in, 8));
// zip64 extensible data sector (variable size)
fseek($this->in, $cdPos, SEEK_SET);
$this->preamble = $zip64EndOfCentralDirectoryRecordPos;
$entryCount = $cdEntries;
$zip64 = true;
return new EndOfCentralDirectory($entryCount, $comment, $zip64);
return true;
}
// Start recovering file entries from min.
$this->preamble = $min;
$this->postamble = $size - $min;
return new EndOfCentralDirectory(0, $comment);
return false;
}
/**
* Read Zip64 end of central directory locator and returns
* Zip64 end of central directory position.
*
* number of the disk with the
* start of the zip64 end of
* central directory 4 bytes
* relative offset of the zip64
* end of central directory record 8 bytes
* total number of disks 4 bytes
*
* @throws ZipException
*
* @return int Zip64 End Of Central Directory position
*/
protected function findZip64ECDPosition()
{
$diskNo = unpack('V', fread($this->in, 4))[1];
$zip64ECDPos = PackUtil::unpackLongLE(fread($this->in, 8));
$totalDisks = unpack('V', fread($this->in, 4))[1];
if ($diskNo !== 0 || $totalDisks > 1) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
return $zip64ECDPos;
}
/**
* Read zip64 end of central directory locator and zip64 end
* of central directory record.
*
* zip64 end of central dir
* signature 4 bytes (0x06064b50)
* size of zip64 end of central
* directory record 8 bytes
* version made by 2 bytes
* version needed to extract 2 bytes
* number of this disk 4 bytes
* number of the disk with the
* start of the central directory 4 bytes
* total number of entries in the
* central directory on this disk 8 bytes
* total number of entries in the
* central directory 8 bytes
* size of the central directory 8 bytes
* offset of start of central
* directory with respect to
* the starting disk number 8 bytes
* zip64 extensible data sector (variable size)
*
* @param int $zip64ECDPosition
*
* @throws ZipException
*
* @return EndOfCentralDirectory
*/
protected function readZip64EndOfCentralDirectory($zip64ECDPosition)
{
fseek($this->in, $zip64ECDPosition);
$buffer = fread($this->in, 56 /* zip64 end of cd rec length */);
if (unpack('V', $buffer)[1] !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG) {
throw new ZipException('Expected ZIP64 End Of Central Directory Record!');
}
$data = unpack(
'VdiskNo/VcdDiskNo',
substr($buffer, 16)
);
$cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8));
$entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8));
$cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8));
$cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8));
if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
if ($entryCount < 0 || $entryCount > 0x7fffffff) {
throw new ZipException('Total Number Of Entries In The Central Directory out of range!');
}
// skip zip64 extensible data sector (variable sizeEndCD)
return new EndOfCentralDirectory(
$entryCount,
$cdPos,
$cdSize,
true
);
}
/**
@@ -247,151 +283,148 @@ class ZipInputStream implements ZipInputStreamInterface
* file header or additional data to be read.
*
* @param EndOfCentralDirectory $endOfCentralDirectory
* @return ZipEntry[]
*
* @throws ZipException
*
* @return ZipEntry[]
*/
protected function mountCentralDirectory(EndOfCentralDirectory $endOfCentralDirectory)
{
$numEntries = $endOfCentralDirectory->getEntryCount();
$entries = [];
for (; $numEntries > 0; $numEntries--) {
$entry = $this->readEntry();
// Re-load virtual offset after ZIP64 Extended Information
// Extra Field may have been parsed, map it to the real
// offset and conditionally update the preamble size from it.
$lfhOff = $this->mapper->map($entry->getOffset());
$lfhOff = PHP_INT_SIZE === 4 ? sprintf('%u', $lfhOff) : $lfhOff;
if ($lfhOff < $this->preamble) {
$this->preamble = $lfhOff;
}
fseek($this->in, $endOfCentralDirectory->getCdOffset());
if (!($cdStream = fopen('php://temp', 'w+b'))) {
throw new ZipException('Temp resource can not open from write');
}
stream_copy_to_stream($this->in, $cdStream, $endOfCentralDirectory->getCdSize());
rewind($cdStream);
for ($numEntries = $endOfCentralDirectory->getEntryCount(); $numEntries > 0; $numEntries--) {
$entry = $this->readCentralDirectoryEntry($cdStream);
$entries[$entry->getName()] = $entry;
}
if (($numEntries % 0x10000) !== 0) {
throw new ZipException("Expected " . abs($numEntries) .
($numEntries > 0 ? " more" : " less") .
" entries in the Central Directory!");
}
if ($this->preamble + $this->postamble >= fstat($this->in)['size']) {
$this->checkZipFileSignature();
}
fclose($cdStream);
return $entries;
}
/**
* @return ZipEntry
* Read central directory entry.
*
* central file header signature 4 bytes (0x02014b50)
* version made by 2 bytes
* version needed to extract 2 bytes
* general purpose bit flag 2 bytes
* compression method 2 bytes
* last mod file time 2 bytes
* last mod file date 2 bytes
* crc-32 4 bytes
* compressed size 4 bytes
* uncompressed size 4 bytes
* file name length 2 bytes
* extra field length 2 bytes
* file comment length 2 bytes
* disk number start 2 bytes
* internal file attributes 2 bytes
* external file attributes 4 bytes
* relative offset of local header 4 bytes
*
* file name (variable size)
* extra field (variable size)
* file comment (variable size)
*
* @param resource $stream
*
* @throws ZipException
*
* @return ZipEntry
*/
public function readEntry()
public function readCentralDirectoryEntry($stream)
{
// central file header signature 4 bytes (0x02014b50)
$fileHeaderSig = unpack('V', fread($this->in, 4))[1];
if ($fileHeaderSig !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
if (unpack('V', fread($stream, 4))[1] !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
throw new ZipException('Corrupt zip file. Cannot read central dir entry.');
}
// version made by 2 bytes
// version needed to extract 2 bytes
// general purpose bit flag 2 bytes
// compression method 2 bytes
// last mod file time 2 bytes
// last mod file date 2 bytes
// crc-32 4 bytes
// compressed size 4 bytes
// uncompressed size 4 bytes
// file name length 2 bytes
// extra field length 2 bytes
// file comment length 2 bytes
// disk number start 2 bytes
// internal file attributes 2 bytes
// external file attributes 4 bytes
// relative offset of local header 4 bytes
$data = unpack(
'vversionMadeBy/vversionNeededToExtract/vgpbf/' .
'vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' .
'VrawSize/vfileLength/vextraLength/vcommentLength/' .
'VrawInternalAttributes/VrawExternalAttributes/VlfhOff',
fread($this->in, 42)
'vversionMadeBy/vversionNeededToExtract/' .
'vgeneralPurposeBitFlag/vcompressionMethod/' .
'VlastModFile/Vcrc/VcompressedSize/' .
'VuncompressedSize/vfileNameLength/vextraFieldLength/' .
'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' .
'VexternalFileAttributes/VoffsetLocalHeader',
fread($stream, 42)
);
// $utf8 = ($data['gpbf'] & ZipEntry::GPBF_UTF8) !== 0;
$createdOS = ($data['versionMadeBy'] & 0xFF00) >> 8;
$softwareVersion = $data['versionMadeBy'] & 0x00FF;
// See appendix D of PKWARE's ZIP File Format Specification.
$name = '';
$offset = 0;
while ($offset < $data['fileLength']) {
$read = min(8192 /* chunk size */, $data['fileLength'] - $offset);
$name .= fread($this->in, $read);
$offset += $read;
$extractOS = ($data['versionNeededToExtract'] & 0xFF00) >> 8;
$extractVersion = $data['versionNeededToExtract'] & 0x00FF;
$name = fread($stream, $data['fileNameLength']);
$extra = '';
if ($data['extraFieldLength'] > 0) {
$extra = fread($stream, $data['extraFieldLength']);
}
$comment = null;
if ($data['fileCommentLength'] > 0) {
$comment = fread($stream, $data['fileCommentLength']);
}
$entry = new ZipSourceEntry($this);
$entry->setName($name);
$entry->setVersionNeededToExtract($data['versionNeededToExtract']);
$entry->setPlatform($data['versionMadeBy'] >> 8);
$entry->setMethod($data['rawMethod']);
$entry->setGeneralPurposeBitFlags($data['gpbf']);
$entry->setDosTime($data['rawTime']);
$entry->setCrc($data['rawCrc']);
$entry->setCompressedSize($data['rawCompressedSize']);
$entry->setSize($data['rawSize']);
$entry->setExternalAttributes($data['rawExternalAttributes']);
$entry->setOffset($data['lfhOff']); // must be unmapped!
if ($data['extraLength'] > 0) {
$extra = '';
$offset = 0;
while ($offset < $data['extraLength']) {
$read = min(8192 /* chunk size */, $data['extraLength'] - $offset);
$extra .= fread($this->in, $read);
$offset += $read;
}
$entry->setExtra($extra);
}
if ($data['commentLength'] > 0) {
$comment = '';
$offset = 0;
while ($offset < $data['commentLength']) {
$read = min(8192 /* chunk size */, $data['commentLength'] - $offset);
$comment .= fread($this->in, $read);
$offset += $read;
}
$entry->setComment($comment);
}
$entry->setCreatedOS($createdOS);
$entry->setSoftwareVersion($softwareVersion);
$entry->setVersionNeededToExtract($extractVersion);
$entry->setExtractedOS($extractOS);
$entry->setMethod($data['compressionMethod']);
$entry->setGeneralPurposeBitFlags($data['generalPurposeBitFlag']);
$entry->setDosTime($data['lastModFile']);
$entry->setCrc($data['crc']);
$entry->setCompressedSize($data['compressedSize']);
$entry->setSize($data['uncompressedSize']);
$entry->setInternalAttributes($data['internalFileAttributes']);
$entry->setExternalAttributes($data['externalFileAttributes']);
$entry->setOffset($data['offsetLocalHeader']);
$entry->setComment($comment);
$entry->setExtra($extra);
return $entry;
}
/**
* @param ZipEntry $entry
* @return string
*
* @throws ZipException
*
* @return string
*/
public function readEntryContent(ZipEntry $entry)
{
if ($entry->isDirectory()) {
return null;
}
if (!($entry instanceof ZipSourceEntry)) {
throw new InvalidArgumentException('entry must be ' . ZipSourceEntry::class);
}
$isEncrypted = $entry->isEncrypted();
if ($isEncrypted && $entry->getPassword() === null) {
throw new ZipException("Can not password from entry " . $entry->getName());
throw new ZipException('Can not password from entry ' . $entry->getName());
}
$pos = $entry->getOffset();
$pos = PHP_INT_SIZE === 4
? sprintf('%u', $pos) // PHP 32-Bit
: $pos; // PHP 64-Bit
$startPos = $pos = $entry->getOffset();
$startPos = $pos = $this->mapper->map($pos);
fseek($this->in, $startPos);
// local file header signature 4 bytes (0x04034b50)
if (unpack('V', fread($this->in, 4))[1] !== ZipEntry::LOCAL_FILE_HEADER_SIG) {
throw new ZipException($entry->getName() . " (expected Local File Header)");
throw new ZipException($entry->getName() . ' (expected Local File Header)');
}
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS);
// file name length 2 bytes
@@ -399,7 +432,9 @@ class ZipInputStream implements ZipInputStreamInterface
$data = unpack('vfileLength/vextraLength', fread($this->in, 4));
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
if ($entry->getCrc() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('Missing crc for entry %s', $entry->getName()));
}
$method = $entry->getMethod();
@@ -407,10 +442,11 @@ class ZipInputStream implements ZipInputStreamInterface
// Get raw entry content
$compressedSize = $entry->getCompressedSize();
$compressedSize = PHP_INT_SIZE === 4 ? sprintf('%u', $compressedSize) : $compressedSize;
$content = '';
if ($compressedSize > 0) {
$offset = 0;
while ($offset < $compressedSize) {
$read = min(8192 /* chunk size */, $compressedSize - $offset);
$content .= fread($this->in, $read);
@@ -419,6 +455,7 @@ class ZipInputStream implements ZipInputStreamInterface
}
$skipCheckCrc = false;
if ($isEncrypted) {
if ($method === ZipEntry::METHOD_WINZIP_AES) {
// Strong Encryption Specification - WinZip AES
@@ -435,12 +472,13 @@ class ZipInputStream implements ZipInputStreamInterface
// Traditional PKWARE Decryption
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
$content = $zipCryptoEngine->decrypt($content);
$entry->setEncryptionMethod(ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
$entry->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
}
if (!$skipCheckCrc) {
// Check CRC32 in the Local File Header or Data Descriptor.
$localCrc = null;
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
// The CRC32 is in the Data Descriptor after the compressed size.
// Note the Data Descriptor's Signature is optional:
@@ -448,72 +486,88 @@ class ZipInputStream implements ZipInputStreamInterface
// but older apps might not.
fseek($this->in, $pos + $compressedSize);
$localCrc = unpack('V', fread($this->in, 4))[1];
if ($localCrc === ZipEntry::DATA_DESCRIPTOR_SIG) {
$localCrc = unpack('V', fread($this->in, 4))[1];
}
} else {
fseek($this->in, $startPos + 14);
// The CRC32 in the Local File Header.
$localCrc = sprintf('%u', fread($this->in, 4)[1]);
$localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc;
$localCrc = fread($this->in, 4)[1];
}
$crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc();
if ($crc != $localCrc) {
throw new Crc32Exception($entry->getName(), $crc, $localCrc);
if (\PHP_INT_SIZE === 4) {
if (sprintf('%u', $entry->getCrc()) === sprintf('%u', $localCrc)) {
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
}
} elseif ($localCrc !== $entry->getCrc()) {
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
}
}
}
switch ($method) {
case ZipFileInterface::METHOD_STORED:
case ZipFile::METHOD_STORED:
break;
case ZipFileInterface::METHOD_DEFLATED:
case ZipFile::METHOD_DEFLATED:
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$content = @gzinflate($content);
break;
case ZipFileInterface::METHOD_BZIP2:
if (!extension_loaded('bz2')) {
case ZipFile::METHOD_BZIP2:
if (!\extension_loaded('bz2')) {
throw new ZipException('Extension bzip2 not install');
}
/** @noinspection PhpComposerExtensionStubsInspection */
$content = bzdecompress($content);
if (is_int($content)) { // decompress error
if (\is_int($content)) { // decompress error
$content = false;
}
break;
default:
throw new ZipUnsupportMethodException($entry->getName() .
" (compression method " . $method . " is not supported)");
throw new ZipUnsupportMethodException(
$entry->getName() .
' (compression method ' . $method . ' is not supported)'
);
}
if ($content === false) {
if ($isEncrypted) {
throw new ZipAuthenticationException(sprintf(
'Invalid password for zip entry "%s"',
$entry->getName()
));
throw new ZipAuthenticationException(
sprintf(
'Invalid password for zip entry "%s"',
$entry->getName()
)
);
}
throw new ZipException(sprintf(
'Failed to get the contents of the zip entry "%s"',
$entry->getName()
));
throw new ZipException(
sprintf(
'Failed to get the contents of the zip entry "%s"',
$entry->getName()
)
);
}
if (!$skipCheckCrc) {
$localCrc = crc32($content);
$localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc;
$crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc();
if ($crc != $localCrc) {
if (sprintf('%u', $entry->getCrc()) !== sprintf('%u', $localCrc)) {
if ($isEncrypted) {
throw new ZipAuthenticationException(sprintf(
'Invalid password for zip entry "%s"',
$entry->getName()
));
throw new ZipAuthenticationException(
sprintf(
'Invalid password for zip entry "%s"',
$entry->getName()
)
);
}
throw new Crc32Exception($entry->getName(), $crc, $localCrc);
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
}
}
return $content;
}
@@ -529,36 +583,40 @@ class ZipInputStream implements ZipInputStreamInterface
* Copy the input stream of the LOC entry zip and the data into
* the output stream and zip the alignment if necessary.
*
* @param ZipEntry $entry
* @param ZipEntry $entry
* @param ZipOutputStreamInterface $out
*
* @throws ZipException
*/
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out)
{
$pos = $entry->getOffset();
assert(ZipEntry::UNKNOWN !== $pos);
$pos = PHP_INT_SIZE === 4 ? sprintf('%u', $pos) : $pos;
$pos = $this->mapper->map($pos);
$nameLength = strlen($entry->getName());
if ($pos === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('Missing local header offset for entry %s', $entry->getName()));
}
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
$nameLength = \strlen($entry->getName());
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, \SEEK_SET);
$sourceExtraLength = $destExtraLength = unpack('v', fread($this->in, 2))[1];
if ($sourceExtraLength > 0) {
// read Local File Header extra fields
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, SEEK_SET);
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, \SEEK_SET);
$extra = '';
$offset = 0;
while ($offset < $sourceExtraLength) {
$read = min(8192 /* chunk size */, $sourceExtraLength - $offset);
$extra .= fread($this->in, $read);
$offset += $read;
}
$extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($extra, $entry);
if (isset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]) && $this->zipModel->isZipAlign()) {
unset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]);
$destExtraLength = strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection));
$destExtraLength = \strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection));
}
} else {
$extraFieldsCollection = new ExtraFieldsCollection();
@@ -567,12 +625,12 @@ class ZipInputStream implements ZipInputStreamInterface
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
$copyInToOutLength = $entry->getCompressedSize();
fseek($this->in, $pos, SEEK_SET);
fseek($this->in, $pos, \SEEK_SET);
if (
$this->zipModel->isZipAlign() &&
!$entry->isEncrypted() &&
$entry->getMethod() === ZipFileInterface::METHOD_STORED
$entry->getMethod() === ZipFile::METHOD_STORED
) {
if (StringUtil::endsWith($entry->getName(), '.so')) {
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
@@ -599,23 +657,25 @@ class ZipInputStream implements ZipInputStreamInterface
// from input stream to output stream
stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2);
// write new extra field length (2 bytes) to output stream
fwrite($out->getStream(), pack('v', strlen($extra)));
fwrite($out->getStream(), pack('v', \strlen($extra)));
// skip 2 bytes to input stream
fseek($this->in, 2, SEEK_CUR);
fseek($this->in, 2, \SEEK_CUR);
// copy name from input stream to output stream
stream_copy_to_stream($this->in, $out->getStream(), $nameLength);
// write extra field to output stream
fwrite($out->getStream(), $extra);
// skip source extraLength from input stream
fseek($this->in, $sourceExtraLength, SEEK_CUR);
fseek($this->in, $sourceExtraLength, \SEEK_CUR);
} else {
$copyInToOutLength += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $sourceExtraLength + $nameLength;
}
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
// crc-32 4 bytes
// compressed size 4 bytes
// uncompressed size 4 bytes
$copyInToOutLength += 12;
if ($entry->isZip64ExtensionsRequired()) {
// compressed size +4 bytes
// uncompressed size +4 bytes
@@ -627,20 +687,18 @@ class ZipInputStream implements ZipInputStreamInterface
}
/**
* @param ZipEntry $entry
* @param ZipEntry $entry
* @param ZipOutputStreamInterface $out
*/
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out)
{
$offset = $entry->getOffset();
$offset = PHP_INT_SIZE === 4 ? sprintf('%u', $offset) : $offset;
$offset = $this->mapper->map($offset);
$nameLength = strlen($entry->getName());
$nameLength = \strlen($entry->getName());
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, \SEEK_SET);
$extraLength = unpack('v', fread($this->in, 2))[1];
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, SEEK_SET);
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, \SEEK_SET);
// copy raw data from input stream to output stream
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
}

@@ -7,7 +7,7 @@ use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipModel;
/**
* Read zip file
* Read zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
@@ -20,14 +20,22 @@ interface ZipInputStreamInterface
public function readZip();
/**
* Read central directory entry.
*
* @param resource $stream
*
* @throws ZipException
*
* @return ZipEntry
*/
public function readEntry();
public function readCentralDirectoryEntry($stream);
/**
* @param ZipEntry $entry
* @return string
*
* @throws ZipException
*
* @return string
*/
public function readEntryContent(ZipEntry $entry);
@@ -40,13 +48,13 @@ interface ZipInputStreamInterface
* Copy the input stream of the LOC entry zip and the data into
* the output stream and zip the alignment if necessary.
*
* @param ZipEntry $entry
* @param ZipEntry $entry
* @param ZipOutputStreamInterface $out
*/
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out);
/**
* @param ZipEntry $entry
* @param ZipEntry $entry
* @param ZipOutputStreamInterface $out
*/
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out);

@@ -19,33 +19,31 @@ use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipModel;
use PhpZip\Util\PackUtil;
use PhpZip\Util\StringUtil;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* Write zip file
* Write zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipOutputStream implements ZipOutputStreamInterface
{
/**
* @var resource
*/
/** @var resource */
protected $out;
/**
* @var ZipModel
*/
/** @var ZipModel */
protected $zipModel;
/**
* ZipOutputStream constructor.
*
* @param resource $out
* @param ZipModel $zipModel
*/
public function __construct($out, ZipModel $zipModel)
{
if (!is_resource($out)) {
if (!\is_resource($out)) {
throw new InvalidArgumentException('$out must be resource');
}
$this->out = $out;
@@ -59,11 +57,13 @@ class ZipOutputStream implements ZipOutputStreamInterface
{
$entries = $this->zipModel->getEntries();
$outPosEntries = [];
foreach ($entries as $entry) {
$outPosEntries[] = new OutputOffsetEntry(ftell($this->out), $entry);
$this->writeEntry($entry);
}
$centralDirectoryOffset = ftell($this->out);
foreach ($outPosEntries as $outputEntry) {
$this->writeCentralDirectoryHeader($outputEntry);
}
@@ -72,12 +72,14 @@ class ZipOutputStream implements ZipOutputStreamInterface
/**
* @param ZipEntry $entry
*
* @throws ZipException
*/
public function writeEntry(ZipEntry $entry)
{
if ($entry instanceof ZipSourceEntry) {
$entry->getInputStream()->copyEntry($entry, $this);
return;
}
@@ -88,16 +90,17 @@ class ZipOutputStream implements ZipOutputStreamInterface
$extra = $entry->getExtra();
$nameLength = strlen($entry->getName());
$extraLength = strlen($extra);
$nameLength = \strlen($entry->getName());
$extraLength = \strlen($extra);
// zip align
if (
$this->zipModel->isZipAlign() &&
!$entry->isEncrypted() &&
$entry->getMethod() === ZipFileInterface::METHOD_STORED
$entry->getMethod() === ZipFile::METHOD_STORED
) {
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
if (StringUtil::endsWith($entry->getName(), '.so')) {
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
}
@@ -120,15 +123,16 @@ class ZipOutputStream implements ZipOutputStreamInterface
$extraFieldsCollection->add($alignExtra);
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
$extraLength = strlen($extra);
$extraLength = \strlen($extra);
}
$size = $nameLength + $extraLength;
if ($size > 0xffff) {
throw new ZipException(
$entry->getName() . " (the total size of " . $size .
" bytes for the name, extra fields and comment " .
"exceeds the maximum size of " . 0xffff . " bytes)"
$entry->getName() . ' (the total size of ' . $size .
' bytes for the name, extra fields and comment ' .
'exceeds the maximum size of ' . 0xffff . ' bytes)'
);
}
@@ -140,7 +144,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
// local file header signature 4 bytes (0x04034b50)
ZipEntry::LOCAL_FILE_HEADER_SIG,
// version needed to extract 2 bytes
$entry->getVersionNeededToExtract(),
($entry->getExtractedOS() << 8) | $entry->getVersionNeededToExtract(),
// general purpose bit flag 2 bytes
$entry->getGeneralPurposeBitFlags(),
// compression method 2 bytes
@@ -160,9 +164,11 @@ class ZipOutputStream implements ZipOutputStreamInterface
$extraLength
)
);
if ($nameLength > 0) {
fwrite($this->out, $entry->getName());
}
if ($extraLength > 0) {
fwrite($this->out, $extra);
}
@@ -173,8 +179,18 @@ class ZipOutputStream implements ZipOutputStreamInterface
fwrite($this->out, $entryContent);
}
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
assert(ZipEntry::UNKNOWN !== $entry->getSize());
if ($entry->getCrc() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('No crc for entry %s', $entry->getName()));
}
if ($entry->getSize() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('No uncompressed size for entry %s', $entry->getName()));
}
if ($entry->getCompressedSize() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('No compressed size for entry %s', $entry->getName()));
}
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
// data descriptor signature 4 bytes (0x08074b50)
// crc-32 4 bytes
@@ -187,25 +203,32 @@ class ZipOutputStream implements ZipOutputStreamInterface
} else {
fwrite($this->out, pack('VV', $entry->getCompressedSize(), $entry->getSize()));
}
} elseif ($compressedSize != $entry->getCompressedSize()) {
} elseif ($compressedSize !== $entry->getCompressedSize()) {
throw new ZipException(
$entry->getName() . " (expected compressed entry size of "
. $entry->getCompressedSize() . " bytes, " .
"but is actually " . $compressedSize . " bytes)"
$entry->getName() . ' (expected compressed entry size of '
. $entry->getCompressedSize() . ' bytes, ' .
'but is actually ' . $compressedSize . ' bytes)'
);
}
}
/**
* @param ZipEntry $entry
* @return null|string
*
* @throws ZipException
*
* @return string|null
*/
protected function entryCommitChangesAndReturnContent(ZipEntry $entry)
{
if ($entry->getPlatform() === ZipEntry::UNKNOWN) {
$entry->setPlatform(ZipEntry::PLATFORM_UNIX);
if ($entry->getCreatedOS() === ZipEntry::UNKNOWN) {
$entry->setCreatedOS(ZipEntry::PLATFORM_UNIX);
}
if ($entry->getExtractedOS() === ZipEntry::UNKNOWN) {
$entry->setExtractedOS(ZipEntry::PLATFORM_UNIX);
}
if ($entry->getTime() === ZipEntry::UNKNOWN) {
$entry->setTime(time());
}
@@ -216,7 +239,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
$utf8 = true;
if ($encrypted && $entry->getPassword() === null) {
throw new ZipException("Can not password from entry " . $entry->getName());
throw new ZipException(sprintf('Password not set for entry %s', $entry->getName()));
}
// Compose General Purpose Bit Flag.
@@ -226,11 +249,12 @@ class ZipOutputStream implements ZipOutputStreamInterface
$entryContent = null;
$extraFieldsCollection = $entry->getExtraFieldsCollection();
if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) {
$entryContent = $entry->getEntryContent();
if ($entryContent !== null) {
$entry->setSize(strlen($entryContent));
$entry->setSize(\strlen($entryContent));
$entry->setCrc(crc32($entryContent));
if ($encrypted && $method === ZipEntry::METHOD_WINZIP_AES) {
@@ -238,26 +262,28 @@ class ZipOutputStream implements ZipOutputStreamInterface
* @var WinZipAesEntryExtraField $field
*/
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
if ($field !== null) {
$method = $field->getMethod();
}
}
switch ($method) {
case ZipFileInterface::METHOD_STORED:
case ZipFile::METHOD_STORED:
break;
case ZipFileInterface::METHOD_DEFLATED:
case ZipFile::METHOD_DEFLATED:
$entryContent = gzdeflate($entryContent, $entry->getCompressionLevel());
break;
case ZipFileInterface::METHOD_BZIP2:
$compressionLevel = $entry->getCompressionLevel() === ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ?
case ZipFile::METHOD_BZIP2:
$compressionLevel = $entry->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ?
ZipEntry::LEVEL_DEFAULT_BZIP2_COMPRESSION :
$entry->getCompressionLevel();
/** @noinspection PhpComposerExtensionStubsInspection */
$entryContent = bzcompress($entryContent, $compressionLevel);
if (is_int($entryContent)) {
if (\is_int($entryContent)) {
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
}
break;
@@ -268,22 +294,22 @@ class ZipOutputStream implements ZipOutputStreamInterface
break;
default:
throw new ZipException($entry->getName() . " (unsupported compression method " . $method . ")");
throw new ZipException($entry->getName() . ' (unsupported compression method ' . $method . ')');
}
if ($method === ZipFileInterface::METHOD_DEFLATED) {
if ($method === ZipFile::METHOD_DEFLATED) {
$bit1 = false;
$bit2 = false;
switch ($entry->getCompressionLevel()) {
case ZipFileInterface::LEVEL_BEST_COMPRESSION:
case ZipFile::LEVEL_BEST_COMPRESSION:
$bit1 = true;
break;
case ZipFileInterface::LEVEL_FAST:
case ZipFile::LEVEL_FAST:
$bit2 = true;
break;
case ZipFileInterface::LEVEL_SUPER_FAST:
case ZipFile::LEVEL_SUPER_FAST:
$bit1 = true;
$bit2 = true;
break;
@@ -294,17 +320,24 @@ class ZipOutputStream implements ZipOutputStreamInterface
}
if ($encrypted) {
if (in_array($entry->getEncryptionMethod(), [
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128,
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192,
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256,
], true)) {
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits
if (\in_array(
$entry->getEncryptionMethod(),
[
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128,
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192,
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256,
],
true
)) {
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
$entry->getEncryptionMethod()
); // size bits
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
$field->setKeyStrength($keyStrength);
$field->setMethod($method);
$size = $entry->getSize();
if ($size >= 20 && $method !== ZipFileInterface::METHOD_BZIP2) {
if ($size >= 20 && $method !== ZipFile::METHOD_BZIP2) {
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
} else {
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
@@ -315,13 +348,13 @@ class ZipOutputStream implements ZipOutputStreamInterface
$winZipAesEngine = new WinZipAesEngine($entry);
$entryContent = $winZipAesEngine->encrypt($entryContent);
} elseif ($entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL) {
} elseif ($entry->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_TRADITIONAL) {
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
$entryContent = $zipCryptoEngine->encrypt($entryContent);
}
}
$compressedSize = strlen($entryContent);
$compressedSize = \strlen($entryContent);
$entry->setCompressedSize($compressedSize);
}
}
@@ -334,25 +367,31 @@ class ZipOutputStream implements ZipOutputStreamInterface
} elseif ($extraFieldsCollection->has(Zip64ExtraField::getHeaderId())) {
$extraFieldsCollection->remove(Zip64ExtraField::getHeaderId());
}
return $entryContent;
}
/**
* @param ZipEntry $entry
* @param string $content
* @return string
* @param string $content
*
* @throws ZipException
*
* @return string
*/
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
{
if ($content !== null) {
$entryContent = gzdeflate($content, $entry->getCompressionLevel());
if (strlen($entryContent) < strlen($content)) {
$entry->setMethod(ZipFileInterface::METHOD_DEFLATED);
if (\strlen($entryContent) < \strlen($content)) {
$entry->setMethod(ZipFile::METHOD_DEFLATED);
return $entryContent;
}
$entry->setMethod(ZipFileInterface::METHOD_STORED);
$entry->setMethod(ZipFile::METHOD_STORED);
}
return $content;
}
@@ -369,12 +408,12 @@ class ZipOutputStream implements ZipOutputStreamInterface
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
// UNKNOWN!
if (($compressedSize | $size) === ZipEntry::UNKNOWN) {
throw new RuntimeException("invalid entry");
throw new RuntimeException('invalid entry');
}
$extra = $entry->getExtra();
$extraSize = strlen($extra);
$extraSize = \strlen($extra);
$commentLength = strlen($entry->getComment());
$commentLength = \strlen($entry->getComment());
fwrite(
$this->out,
pack(
@@ -382,9 +421,9 @@ class ZipOutputStream implements ZipOutputStreamInterface
// central file header signature 4 bytes (0x02014b50)
self::CENTRAL_FILE_HEADER_SIG,
// version made by 2 bytes
($entry->getPlatform() << 8) | 63,
($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
// version needed to extract 2 bytes
$entry->getVersionNeededToExtract(),
($entry->getExtractedOS() << 8) | $entry->getVersionNeededToExtract(),
// general purpose bit flag 2 bytes
$entry->getGeneralPurposeBitFlags(),
// compression method 2 bytes
@@ -398,7 +437,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
// uncompressed size 4 bytes
$entry->getSize(),
// file name length 2 bytes
strlen($entry->getName()),
\strlen($entry->getName()),
// extra field length 2 bytes
$extraSize,
// file comment length 2 bytes
@@ -406,7 +445,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
// disk number start 2 bytes
0,
// internal file attributes 2 bytes
0,
$entry->getInternalAttributes(),
// external file attributes 4 bytes
$entry->getExternalAttributes(),
// relative offset of local header 4 bytes
@@ -415,82 +454,116 @@ class ZipOutputStream implements ZipOutputStreamInterface
);
// file name (variable size)
fwrite($this->out, $entry->getName());
if ($extraSize > 0) {
// extra field (variable size)
fwrite($this->out, $extra);
}
if ($commentLength > 0) {
// file comment (variable size)
fwrite($this->out, $entry->getComment());
}
}
/**
* @param int $centralDirectoryOffset
*/
protected function writeEndOfCentralDirectoryRecord($centralDirectoryOffset)
{
$centralDirectoryEntriesCount = count($this->zipModel);
$cdEntriesCount = \count($this->zipModel);
$position = ftell($this->out);
$centralDirectorySize = $position - $centralDirectoryOffset;
$centralDirectoryEntriesZip64 = $centralDirectoryEntriesCount > 0xffff;
$centralDirectorySizeZip64 = $centralDirectorySize > 0xffffffff;
$centralDirectoryOffsetZip64 = $centralDirectoryOffset > 0xffffffff;
$centralDirectoryEntries16 = $centralDirectoryEntriesZip64 ? 0xffff : (int)$centralDirectoryEntriesCount;
$centralDirectorySize32 = $centralDirectorySizeZip64 ? 0xffffffff : $centralDirectorySize;
$centralDirectoryOffset32 = $centralDirectoryOffsetZip64 ? 0xffffffff : $centralDirectoryOffset;
$zip64 // ZIP64 extensions?
= $centralDirectoryEntriesZip64
|| $centralDirectorySizeZip64
|| $centralDirectoryOffsetZip64;
if ($zip64) {
// [zip64 end of central directory record]
// relative offset of the zip64 end of central directory record
$zip64EndOfCentralDirectoryOffset = $position;
// zip64 end of central dir
$cdEntriesZip64 = $cdEntriesCount > 0xFFFF;
$cdSizeZip64 = $centralDirectorySize > 0xFFFFFFFF;
$cdOffsetZip64 = $centralDirectoryOffset > 0xFFFFFFFF;
$zip64Required = $cdEntriesZip64 || $cdSizeZip64 || $cdOffsetZip64;
if ($zip64Required) {
$zip64EndOfCentralDirectoryOffset = ftell($this->out);
// find max software version, version needed to extract and most common platform
list($softwareVersion, $versionNeededToExtract) = array_reduce(
$this->zipModel->getEntries(),
static function (array $carry, ZipEntry $entry) {
$carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
$carry[1] = max($carry[1], $entry->getVersionNeededToExtract() & 0xFF);
return $carry;
},
[10 /* simple file min ver */, 45 /* zip64 ext min ver */]
);
$createdOS = $extractedOS = ZipEntry::PLATFORM_FAT;
$versionMadeBy = ($createdOS << 8) | max($softwareVersion, 45 /* zip64 ext min ver */);
$versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, 45 /* zip64 ext min ver */);
// signature 4 bytes (0x06064b50)
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG));
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG));
// size of zip64 end of central
// directory record 8 bytes
fwrite($this->out, PackUtil::packLongLE(EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN - 12));
// version made by 2 bytes
// version needed to extract 2 bytes
// due to potential use of BZIP2 compression
// number of this disk 4 bytes
// number of the disk with the
// start of the central directory 4 bytes
fwrite($this->out, pack('vvVV', 63, 46, 0, 0));
fwrite($this->out, PackUtil::packLongLE(44));
fwrite(
$this->out,
pack(
'vvVV',
// version made by 2 bytes
$versionMadeBy & 0xFFFF,
// version needed to extract 2 bytes
$versionExtractedBy & 0xFFFF,
// number of this disk 4 bytes
0,
// number of the disk with the
// start of the central directory 4 bytes
0
)
);
// total number of entries in the
// central directory on this disk 8 bytes
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
fwrite($this->out, PackUtil::packLongLE($cdEntriesCount));
// total number of entries in the
// central directory 8 bytes
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
fwrite($this->out, PackUtil::packLongLE($cdEntriesCount));
// size of the central directory 8 bytes
fwrite($this->out, PackUtil::packLongLE($centralDirectorySize));
// offset of start of central
// directory with respect to
// the starting disk number 8 bytes
fwrite($this->out, PackUtil::packLongLE($centralDirectoryOffset));
// zip64 extensible data sector (variable size)
// [zip64 end of central directory locator]
// signature 4 bytes (0x07064b50)
// number of the disk with the
// start of the zip64 end of
// central directory 4 bytes
fwrite($this->out, pack('VV', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG, 0));
// write zip64 end of central directory locator
fwrite(
$this->out,
pack(
'VV',
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG,
// number of the disk with the
// start of the zip64 end of
// central directory 4 bytes
0
)
);
// relative offset of the zip64
// end of central directory record 8 bytes
fwrite($this->out, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset));
// total number of disks 4 bytes
fwrite($this->out, pack('V', 1));
}
$comment = $this->zipModel->getArchiveComment();
$commentLength = strlen($comment);
$commentLength = $comment !== null ? \strlen($comment) : 0;
fwrite(
$this->out,
pack(
'VvvvvVVv',
// end of central dir signature 4 bytes (0x06054b50)
EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG,
EndOfCentralDirectory::END_OF_CD_SIG,
// number of this disk 2 bytes
0,
// number of the disk with the
@@ -498,20 +571,21 @@ class ZipOutputStream implements ZipOutputStreamInterface
0,
// total number of entries in the
// central directory on this disk 2 bytes
$centralDirectoryEntries16,
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
// total number of entries in
// the central directory 2 bytes
$centralDirectoryEntries16,
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
// size of the central directory 4 bytes
$centralDirectorySize32,
$cdSizeZip64 ? 0xFFFFFFFF : $centralDirectorySize,
// offset of start of central
// directory with respect to
// the starting disk number 4 bytes
$centralDirectoryOffset32,
$cdOffsetZip64 ? 0xFFFFFFFF : $centralDirectoryOffset,
// .ZIP file comment length 2 bytes
$commentLength
)
);
if ($commentLength > 0) {
// .ZIP file comment (variable size)
fwrite($this->out, $comment);

@@ -5,7 +5,7 @@ namespace PhpZip\Stream;
use PhpZip\Model\ZipEntry;
/**
* Write zip file
* Write zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT

@@ -2,38 +2,26 @@
namespace PhpZip\Util;
use PhpZip\Exception\RuntimeException;
/**
* Crypto Utils
* Crypto Utils.
*
* @deprecated
*/
class CryptoUtil
{
/**
* Returns random bytes.
*
* @param int $length
*
* @throws \Exception
*
* @return string
*
* @deprecated Use random_bytes()
*/
final public static function randomBytes($length)
{
$length = (int)$length;
if (function_exists('random_bytes')) {
try {
return random_bytes($length);
} catch (\Exception $e) {
throw new \RuntimeException("Could not generate a random string.");
}
} elseif (function_exists('openssl_random_pseudo_bytes')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return openssl_random_pseudo_bytes($length);
} elseif (function_exists('mcrypt_create_iv')) {
/** @noinspection PhpDeprecationInspection */
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_create_iv($length);
} else {
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
return random_bytes($length);
}
}

@@ -28,13 +28,14 @@ class DateTimeConverter
* Convert a 32 bit integer DOS date/time value to a UNIX timestamp value.
*
* @param int $dosTime Dos date/time
*
* @return int Unix timestamp
*/
public static function toUnixTimestamp($dosTime)
{
if (self::MIN_DOS_TIME > $dosTime) {
if ($dosTime < self::MIN_DOS_TIME) {
$dosTime = self::MIN_DOS_TIME;
} elseif (self::MAX_DOS_TIME < $dosTime) {
} elseif ($dosTime > self::MAX_DOS_TIME) {
$dosTime = self::MAX_DOS_TIME;
}
@@ -51,17 +52,19 @@ class DateTimeConverter
/**
* Converts a UNIX timestamp value to a DOS date/time value.
*
* @param int $unixTimestamp The number of seconds since midnight, January 1st,
* 1970 AD UTC.
* @return int A DOS date/time value reflecting the local time zone and
* rounded down to even seconds
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME.
* @throws ZipException If unix timestamp is negative.
* @param int $unixTimestamp the number of seconds since midnight, January 1st,
* 1970 AD UTC
*
* @throws ZipException if unix timestamp is negative
*
* @return int a DOS date/time value reflecting the local time zone and
* rounded down to even seconds
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME
*/
public static function toDosTime($unixTimestamp)
{
if (0 > $unixTimestamp) {
throw new ZipException("Negative unix timestamp: " . $unixTimestamp);
if ($unixTimestamp < 0) {
throw new ZipException('Negative unix timestamp: ' . $unixTimestamp);
}
$date = getdate($unixTimestamp);
@@ -71,8 +74,9 @@ class DateTimeConverter
}
$date['year'] -= 1980;
return ($date['year'] << 25 | $date['mon'] << 21 |
return $date['year'] << 25 | $date['mon'] << 21 |
$date['mday'] << 16 | $date['hours'] << 11 |
$date['minutes'] << 5 | $date['seconds'] >> 1);
$date['minutes'] << 5 | $date['seconds'] >> 1;
}
}

@@ -10,14 +10,16 @@ use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
class FilesUtil
final class FilesUtil
{
/**
* Is empty directory
* Is empty directory.
*
* @param string $dir Directory
*
* @return bool
*/
public static function isEmptyDir($dir)
@@ -25,13 +27,14 @@ class FilesUtil
if (!is_readable($dir)) {
return false;
}
return count(scandir($dir)) === 2;
return \count(scandir($dir)) === 2;
}
/**
* Remove recursive directory.
*
* @param string $dir Directory path.
* @param string $dir directory path
*/
public static function removeDir($dir)
{
@@ -39,6 +42,7 @@ class FilesUtil
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileInfo) {
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
$function($fileInfo->getRealPath());
@@ -46,11 +50,11 @@ class FilesUtil
rmdir($dir);
}
/**
* Convert glob pattern to regex pattern.
*
* @param string $globPattern
*
* @return string
*/
public static function convertGlobToRegEx($globPattern)
@@ -61,16 +65,19 @@ class FilesUtil
$inCurrent = 0;
$chars = str_split($globPattern);
$regexPattern = '';
foreach ($chars as $currentChar) {
switch ($currentChar) {
case '*':
$regexPattern .= ($escaping ? "\\*" : '.*');
$regexPattern .= ($escaping ? '\\*' : '.*');
$escaping = false;
break;
case '?':
$regexPattern .= ($escaping ? "\\?" : '.');
$regexPattern .= ($escaping ? '\\?' : '.');
$escaping = false;
break;
case '.':
case '(':
case ')':
@@ -83,41 +90,45 @@ class FilesUtil
$regexPattern .= '\\' . $currentChar;
$escaping = false;
break;
case '\\':
if ($escaping) {
$regexPattern .= "\\\\";
$regexPattern .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
break;
case '{':
if ($escaping) {
$regexPattern .= "\\{";
$regexPattern .= '\\{';
} else {
$regexPattern = '(';
$inCurrent++;
}
$escaping = false;
break;
case '}':
if ($inCurrent > 0 && !$escaping) {
$regexPattern .= ')';
$inCurrent--;
} elseif ($escaping) {
$regexPattern = "\\}";
$regexPattern = '\\}';
} else {
$regexPattern = "}";
$regexPattern = '}';
}
$escaping = false;
break;
case ',':
if ($inCurrent > 0 && !$escaping) {
$regexPattern .= '|';
} elseif ($escaping) {
$regexPattern .= "\\,";
$regexPattern .= '\\,';
} else {
$regexPattern = ",";
$regexPattern = ',';
}
break;
default:
@@ -125,6 +136,7 @@ class FilesUtil
$regexPattern .= $currentChar;
}
}
return $regexPattern;
}
@@ -132,8 +144,9 @@ class FilesUtil
* Search files.
*
* @param string $inputDir
* @param bool $recursive
* @param array $ignoreFiles
* @param bool $recursive
* @param array $ignoreFiles
*
* @return array Searched file list
*/
public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
@@ -153,11 +166,13 @@ class FilesUtil
new \IteratorIterator($directoryIterator);
$fileList = [];
foreach ($iterator as $file) {
if ($file instanceof \SplFileInfo) {
$fileList[] = $file->getPathname();
}
}
return $fileList;
}
@@ -165,21 +180,27 @@ class FilesUtil
* Search files from glob pattern.
*
* @param string $globPattern
* @param int $flags
* @param bool $recursive
* @param int $flags
* @param bool $recursive
*
* @return array Searched file list
*/
public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
{
$flags = (int)$flags;
$recursive = (bool)$recursive;
$flags = (int) $flags;
$recursive = (bool) $recursive;
$files = glob($globPattern, $flags);
if (!$recursive) {
return $files;
}
foreach (glob(dirname($globPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
foreach (glob(\dirname($globPattern) . '/*', \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));
}
return $files;
}
@@ -188,48 +209,58 @@ class FilesUtil
*
* @param string $folder
* @param string $pattern
* @param bool $recursive
* @param bool $recursive
*
* @return array Searched file list
*/
public static function regexFileSearch($folder, $pattern, $recursive = true)
{
$directoryIterator = $recursive ? new \RecursiveDirectoryIterator($folder) : new \DirectoryIterator($folder);
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator($directoryIterator);
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator(
$directoryIterator
);
$regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
$fileList = [];
foreach ($regexIterator as $file) {
if ($file instanceof \SplFileInfo) {
$fileList[] = $file->getPathname();
}
}
return $fileList;
}
/**
* Convert bytes to human size.
*
* @param int $size Size bytes
* @param int $size Size bytes
* @param string|null $unit Unit support 'GB', 'MB', 'KB'
*
* @return string
*/
public static function humanSize($size, $unit = null)
{
if (($unit === null && $size >= 1 << 30) || $unit === "GB") {
return number_format($size / (1 << 30), 2) . "GB";
if (($unit === null && $size >= 1 << 30) || $unit === 'GB') {
return number_format($size / (1 << 30), 2) . 'GB';
}
if (($unit === null && $size >= 1 << 20) || $unit === "MB") {
return number_format($size / (1 << 20), 2) . "MB";
if (($unit === null && $size >= 1 << 20) || $unit === 'MB') {
return number_format($size / (1 << 20), 2) . 'MB';
}
if (($unit === null && $size >= 1 << 10) || $unit === "KB") {
return number_format($size / (1 << 10), 2) . "KB";
if (($unit === null && $size >= 1 << 10) || $unit === 'KB') {
return number_format($size / (1 << 10), 2) . 'KB';
}
return number_format($size) . " bytes";
return number_format($size) . ' bytes';
}
/**
* Normalizes zip path.
*
* @param string $path Zip path
*
* @return string
*/
public static function normalizeZipPath($path)
@@ -237,7 +268,7 @@ class FilesUtil
return implode(
'/',
array_filter(
explode('/', (string)$path),
explode('/', (string) $path),
static function ($part) {
return $part !== '.' && $part !== '..';
}

@@ -13,7 +13,7 @@ use PhpZip\Util\StringUtil;
class IgnoreFilesFilterIterator extends \FilterIterator
{
/**
* Ignore list files
* Ignore list files.
*
* @var array
*/
@@ -21,7 +21,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
/**
* @param \Iterator $iterator
* @param array $ignoreFiles
* @param array $ignoreFiles
*/
public function __construct(\Iterator $iterator, array $ignoreFiles)
{
@@ -30,9 +30,12 @@ class IgnoreFilesFilterIterator extends \FilterIterator
}
/**
* Check whether the current element of the iterator is acceptable
* @link http://php.net/manual/en/filteriterator.accept.php
* @return bool true if the current element is acceptable, otherwise false.
* Check whether the current element of the iterator is acceptable.
*
* @see http://php.net/manual/en/filteriterator.accept.php
*
* @return bool true if the current element is acceptable, otherwise false
*
* @since 5.1.0
*/
public function accept()
@@ -42,6 +45,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
*/
$fileInfo = $this->current();
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
foreach ($this->ignoreFiles as $ignoreFile) {
// handler dir and sub dir
if ($fileInfo->isDir()
@@ -56,6 +60,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
return false;
}
}
return true;
}
}

@@ -13,7 +13,7 @@ use PhpZip\Util\StringUtil;
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
{
/**
* Ignore list files
* Ignore list files.
*
* @var array
*/
@@ -21,7 +21,7 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
/**
* @param \RecursiveIterator $iterator
* @param array $ignoreFiles
* @param array $ignoreFiles
*/
public function __construct(\RecursiveIterator $iterator, array $ignoreFiles)
{
@@ -30,9 +30,12 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
}
/**
* Check whether the current element of the iterator is acceptable
* @link http://php.net/manual/en/filteriterator.accept.php
* @return bool true if the current element is acceptable, otherwise false.
* Check whether the current element of the iterator is acceptable.
*
* @see http://php.net/manual/en/filteriterator.accept.php
*
* @return bool true if the current element is acceptable, otherwise false
*
* @since 5.1.0
*/
public function accept()
@@ -42,10 +45,11 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
*/
$fileInfo = $this->current();
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
foreach ($this->ignoreFiles as $ignoreFile) {
// handler dir and sub dir
if ($fileInfo->isDir()
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
&& $ignoreFile[\strlen($ignoreFile) - 1] === '/'
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
) {
return false;
@@ -56,15 +60,16 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
return false;
}
}
return true;
}
/**
* @return IgnoreFilesRecursiveFilterIterator
* @noinspection PhpMissingParentCallCommonInspection
*/
public function getChildren()
{
/** @noinspection PhpUndefinedMethodInspection */
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
}
}

@@ -3,22 +3,24 @@
namespace PhpZip\Util;
/**
* Pack util
* Pack util.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
class PackUtil
final class PackUtil
{
/**
* @param int|string $longValue
*
* @return string
*/
public static function packLongLE($longValue)
{
if (PHP_INT_SIZE === 8 && PHP_VERSION_ID >= 506030) {
return pack("P", $longValue);
if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) {
return pack('P', $longValue);
}
$left = 0xffffffff00000000;
@@ -32,31 +34,36 @@ class PackUtil
/**
* @param string|int $value
*
* @return int
*/
public static function unpackLongLE($value)
{
if (PHP_INT_SIZE === 8 && PHP_VERSION_ID >= 506030) {
if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) {
return unpack('P', $value)[1];
}
$unpack = unpack('Va/Vb', $value);
return $unpack['a'] + ($unpack['b'] << 32);
}
/**
* Cast to signed int 32-bit
* Cast to signed int 32-bit.
*
* @param int $int
*
* @return int
*/
public static function toSignedInt32($int)
{
if (PHP_INT_SIZE === 8) {
$int = $int & 0xffffffff;
if (\PHP_INT_SIZE === 8) {
$int &= 0xffffffff;
if ($int & 0x80000000) {
return $int - 0x100000000;
}
}
return $int;
}
}

@@ -3,54 +3,32 @@
namespace PhpZip\Util;
/**
* String Util
* String Util.
*
* @internal
*/
class StringUtil
final class StringUtil
{
/**
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function startsWith($haystack, $needle)
{
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
return $needle === '' || strrpos($haystack, $needle, -\strlen($haystack)) !== false;
}
/**
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function endsWith($haystack, $needle)
{
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0
return $needle === '' || (($temp = \strlen($haystack) - \strlen($needle)) >= 0
&& strpos($haystack, $needle, $temp) !== false);
}
/**
* @param string $str
* @return string
*/
public static function cp866toUtf8($str)
{
if (function_exists('iconv')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return iconv('CP866', 'UTF-8//IGNORE', $str);
} elseif (function_exists('mb_convert_encoding')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return mb_convert_encoding($str, 'UTF-8', 'CP866');
} elseif (class_exists('UConverter')) {
/** @noinspection PhpComposerExtensionStubsInspection */
$converter = new \UConverter('UTF-8', 'CP866');
return $converter->convert($str, false);
} else {
static $cp866Utf8Pairs;
if (empty($cp866Utf8Pairs)) {
$cp866Utf8Pairs = require __DIR__ . '/encodings/cp866-utf8.php';
}
return strtr($str, $cp866Utf8Pairs);
}
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

@@ -18,6 +18,7 @@ use Psr\Http\Message\ResponseInterface;
* Support ZipAlign functional.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
@@ -25,67 +26,66 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
{
/**
* Method for Stored (uncompressed) entries.
*
* @see ZipEntry::setMethod()
*/
const METHOD_STORED = 0;
/**
* Method for Deflated compressed entries.
*
* @see ZipEntry::setMethod()
*/
const METHOD_DEFLATED = 8;
/**
* Method for BZIP2 compressed entries.
* Require php extension bz2.
*
* @see ZipEntry::setMethod()
*/
const METHOD_BZIP2 = 12;
/**
* Default compression level.
*/
/** Default compression level. */
const LEVEL_DEFAULT_COMPRESSION = -1;
/**
* Compression level for fastest compression.
*/
/** Compression level for fastest compression. */
const LEVEL_FAST = 2;
/**
* Compression level for fastest compression.
*/
/** Compression level for fastest compression. */
const LEVEL_BEST_SPEED = 1;
const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED;
/**
* Compression level for best compression.
*/
/** Compression level for best compression. */
const LEVEL_BEST_COMPRESSION = 9;
/**
* No specified method for set encryption method to Traditional PKWARE encryption.
*/
/** No specified method for set encryption method to Traditional PKWARE encryption. */
const ENCRYPTION_METHOD_TRADITIONAL = 0;
/**
* No specified method for set encryption method to WinZip AES encryption.
* Default value 256 bit
* Default value 256 bit.
*/
const ENCRYPTION_METHOD_WINZIP_AES = self::ENCRYPTION_METHOD_WINZIP_AES_256;
/**
* No specified method for set encryption method to WinZip AES encryption 128 bit.
*/
/** No specified method for set encryption method to WinZip AES encryption 128 bit. */
const ENCRYPTION_METHOD_WINZIP_AES_128 = 2;
/**
* No specified method for set encryption method to WinZip AES encryption 194 bit.
*/
/** No specified method for set encryption method to WinZip AES encryption 194 bit. */
const ENCRYPTION_METHOD_WINZIP_AES_192 = 3;
/**
* No specified method for set encryption method to WinZip AES encryption 256 bit.
*/
/** No specified method for set encryption method to WinZip AES encryption 256 bit. */
const ENCRYPTION_METHOD_WINZIP_AES_256 = 1;
/**
* Open zip archive from file
* Open zip archive from file.
*
* @param string $filename
*
* @throws ZipException if can't open file
*
* @return ZipFileInterface
* @throws ZipException if can't open file.
*/
public function openFile($filename);
@@ -93,35 +93,39 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Open zip archive from raw string data.
*
* @param string $data
*
* @throws ZipException if can't open temp stream
*
* @return ZipFileInterface
* @throws ZipException if can't open temp stream.
*/
public function openFromString($data);
/**
* Open zip archive from stream resource
* Open zip archive from stream resource.
*
* @param resource $handle
*
* @return ZipFileInterface
*/
public function openFromStream($handle);
/**
* @return string[] Returns the list files.
* @return string[] returns the list files
*/
public function getListFiles();
/**
* Returns the file comment.
*
* @return string The file comment.
* @return string the file comment
*/
public function getArchiveComment();
/**
* Set archive comment.
*
* @param null|string $comment
* @param string|null $comment
*
* @return ZipFileInterface
*/
public function setArchiveComment($comment = null);
@@ -132,8 +136,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* (i.e. end with '/').
*
* @param string $entryName
* @return bool
*
* @throws ZipEntryNotFoundException
*
* @return bool
*/
public function isDirectory($entryName);
@@ -141,18 +147,22 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Returns entry comment.
*
* @param string $entryName
* @return string
*
* @throws ZipEntryNotFoundException
*
* @return string
*/
public function getEntryComment($entryName);
/**
* Set entry comment.
*
* @param string $entryName
* @param string $entryName
* @param string|null $comment
* @return ZipFileInterface
*
* @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
*/
public function setEntryComment($entryName, $comment = null);
@@ -160,6 +170,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Returns the entry contents.
*
* @param string $entryName
*
* @return string
*/
public function getEntryContents($entryName);
@@ -168,6 +179,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Checks if there is an entry in the archive.
*
* @param string $entryName
*
* @return bool
*/
public function hasEntry($entryName);
@@ -176,8 +188,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Get info by entry.
*
* @param string|ZipEntry $entryName
* @return ZipInfo
*
* @throws ZipEntryNotFoundException
*
* @return ZipInfo
*/
public function getEntryInfo($entryName);
@@ -194,60 +208,68 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
public function matcher();
/**
* Extract the archive contents
* Extract the archive contents.
*
* Extract the complete archive or the given files to the specified destination.
*
* @param string $destination 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.
* @return ZipFileInterface
* @param string $destination 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.
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function extractTo($destination, $entries = null);
/**
* Add entry from the string.
*
* @param string $localName Zip entry name.
* @param string $contents String contents.
* @param string $localName zip entry name
* @param string $contents string contents
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
*
* @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED
* @see ZipFileInterface::METHOD_DEFLATED
* @see ZipFileInterface::METHOD_BZIP2
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFromString($localName, $contents, $compressionMethod = null);
/**
* Add entry from the file.
*
* @param string $filename Destination file.
* @param string|null $localName Zip Entry name.
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* @param string $filename destination file
* @param string|null $localName zip Entry name
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED
* @see ZipFileInterface::METHOD_DEFLATED
* @see ZipFileInterface::METHOD_BZIP2
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFile($filename, $localName = null, $compressionMethod = null);
/**
* Add entry from the stream.
*
* @param resource $stream Stream resource.
* @param string $localName Zip Entry name.
* @param resource $stream stream resource
* @param string $localName zip Entry name
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
*
* @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED
* @see ZipFileInterface::METHOD_DEFLATED
* @see ZipFileInterface::METHOD_BZIP2
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFromStream($stream, $localName, $compressionMethod = null);
@@ -255,6 +277,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Add an empty directory in the zip archive.
*
* @param string $dirName
*
* @return ZipFileInterface
*/
public function addEmptyDir($dirName);
@@ -262,54 +285,60 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
/**
* Add directory not recursively to the zip archive.
*
* @param string $inputDir Input directory
* @param string $localPath Add files to this directory, or the root.
* @param string $inputDir Input directory
* @param string $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
*
* @return ZipFileInterface
*/
public function addDir($inputDir, $localPath = "/", $compressionMethod = null);
public function addDir($inputDir, $localPath = '/', $compressionMethod = null);
/**
* Add recursive directory to the zip archive.
*
* @param string $inputDir Input directory
* @param string $localPath Add files to this directory, or the root.
* @param string $inputDir Input directory
* @param string $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
*
* @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED
* @see ZipFileInterface::METHOD_DEFLATED
* @see ZipFileInterface::METHOD_BZIP2
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addDirRecursive($inputDir, $localPath = "/", $compressionMethod = null);
public function addDirRecursive($inputDir, $localPath = '/', $compressionMethod = null);
/**
* Add directories from directory iterator.
*
* @param \Iterator $iterator Directory iterator.
* @param string $localPath Add files to this directory, or the root.
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* @param \Iterator $iterator directory iterator
* @param string $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED
* @see ZipFileInterface::METHOD_DEFLATED
* @see ZipFileInterface::METHOD_BZIP2
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null);
/**
* Add files from glob pattern.
*
* @param string $inputDir Input directory
* @param string $globPattern Glob pattern.
* @param string|null $localPath Add files to this directory, or the root.
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* @param string $inputDir Input directory
* @param string $globPattern glob pattern
* @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
@@ -318,12 +347,13 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
/**
* Add files recursively from glob pattern.
*
* @param string $inputDir Input directory
* @param string $globPattern Glob pattern.
* @param string|null $localPath Add files to this directory, or the root.
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* @param string $inputDir Input directory
* @param string $globPattern glob pattern
* @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
@@ -332,56 +362,64 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
/**
* Add files from regex pattern.
*
* @param string $inputDir Search files in this directory.
* @param string $regexPattern Regex pattern.
* @param string|null $localPath Add files to this directory, or the root.
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* @param string $inputDir search files in this directory
* @param string $regexPattern regex pattern
* @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
* @internal param bool $recursive Recursive search.
*
* @internal param bool $recursive Recursive search
*/
public function addFilesFromRegex($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null);
public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null);
/**
* Add files recursively from regex pattern.
*
* @param string $inputDir Search files in this directory.
* @param string $regexPattern Regex pattern.
* @param string|null $localPath Add files to this directory, or the root.
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method.
* @param string $inputDir search files in this directory
* @param string $regexPattern regex pattern
* @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
* @internal param bool $recursive Recursive search.
*
* @internal param bool $recursive Recursive search
*/
public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null);
public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null);
/**
* Add array data to archive.
* Keys is local names.
* Values is contents.
*
* @param array $mapData Associative array for added to zip.
* @param array $mapData associative array for added to zip
*/
public function addAll(array $mapData);
/**
* Rename the entry.
*
* @param string $oldName Old entry name.
* @param string $newName New entry name.
* @return ZipFileInterface
* @param string $oldName old entry name
* @param string $newName new entry name
*
* @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
*/
public function rename($oldName, $newName);
/**
* Delete entry by name.
*
* @param string $entryName Zip Entry name.
* @param string $entryName zip Entry name
*
* @throws ZipEntryNotFoundException if entry not found
*
* @return ZipFileInterface
* @throws ZipEntryNotFoundException If entry not found.
*/
public function deleteFromName($entryName);
@@ -389,6 +427,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Delete entries by glob pattern.
*
* @param string $globPattern Glob pattern
*
* @return ZipFileInterface
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
@@ -398,12 +437,14 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Delete entries by regex pattern.
*
* @param string $regexPattern Regex pattern
*
* @return ZipFileInterface
*/
public function deleteFromRegex($regexPattern);
/**
* Delete all entries
* Delete all entries.
*
* @return ZipFileInterface
*/
public function deleteAll();
@@ -412,34 +453,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set compression level for new entries.
*
* @param int $compressionLevel
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
* @see ZipFileInterface::LEVEL_SUPER_FAST
* @see ZipFileInterface::LEVEL_FAST
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
*
* @return ZipFileInterface
*
* @see ZipFile::LEVEL_SUPER_FAST
* @see ZipFile::LEVEL_FAST
* @see ZipFile::LEVEL_BEST_COMPRESSION
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
*/
public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
/**
* @param string $entryName
* @param int $compressionLevel
* @return ZipFileInterface
* @param int $compressionLevel
*
* @throws ZipException
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
* @see ZipFileInterface::LEVEL_SUPER_FAST
* @see ZipFileInterface::LEVEL_FAST
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
*
* @return ZipFileInterface
*
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
* @see ZipFile::LEVEL_SUPER_FAST
* @see ZipFile::LEVEL_FAST
* @see ZipFile::LEVEL_BEST_COMPRESSION
*/
public function setCompressionLevelEntry($entryName, $compressionLevel);
/**
* @param string $entryName
* @param int $compressionMethod
* @return ZipFileInterface
* @param int $compressionMethod
*
* @throws ZipException
* @see ZipFileInterface::METHOD_STORED
* @see ZipFileInterface::METHOD_DEFLATED
* @see ZipFileInterface::METHOD_BZIP2
*
* @return ZipFileInterface
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function setCompressionMethodEntry($entryName, $compressionMethod);
@@ -447,8 +496,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* zipalign is optimization to Android application (APK) files.
*
* @param int|null $align
*
* @return ZipFileInterface
* @link https://developer.android.com/studio/command-line/zipalign.html
*
* @see https://developer.android.com/studio/command-line/zipalign.html
*/
public function setZipAlign($align = null);
@@ -456,8 +507,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set password to all input encrypted entries.
*
* @param string $password Password
*
* @return ZipFileInterface
* @deprecated using ZipFileInterface::setReadPassword()
*
* @deprecated using ZipFile::setReadPassword()
*/
public function withReadPassword($password);
@@ -465,6 +518,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set password to all input encrypted entries.
*
* @param string $password Password
*
* @return ZipFileInterface
*/
public function setReadPassword($password);
@@ -473,7 +527,8 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set password to concrete input entry.
*
* @param string $entryName
* @param string $password Password
* @param string $password Password
*
* @return ZipFileInterface
*/
public function setReadPasswordEntry($entryName, $password);
@@ -481,18 +536,21 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
/**
* Set password for all entries for update.
*
* @param string $password If password null then encryption clear
* @param string $password If password null then encryption clear
* @param int|null $encryptionMethod Encryption method
*
* @return ZipFileInterface
* @deprecated using ZipFileInterface::setPassword()
*
* @deprecated using ZipFile::setPassword()
*/
public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
/**
* Sets a new password for all files in the archive.
*
* @param string $password
* @param string $password
* @param int|null $encryptionMethod Encryption method
*
* @return ZipFileInterface
*/
public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
@@ -500,41 +558,49 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
/**
* Sets a new password of an entry defined by its name.
*
* @param string $entryName
* @param string $password
* @param string $entryName
* @param string $password
* @param int|null $encryptionMethod
*
* @return ZipFileInterface
*/
public function setPasswordEntry($entryName, $password, $encryptionMethod = null);
/**
* Remove password for all entries for update.
*
* @return ZipFileInterface
* @deprecated using ZipFileInterface::disableEncryption()
*
* @deprecated using ZipFile::disableEncryption()
*/
public function withoutPassword();
/**
* Disable encryption for all entries that are already in the archive.
*
* @return ZipFileInterface
*/
public function disableEncryption();
/**
* Disable encryption of an entry defined by its name.
*
* @param string $entryName
*
* @return ZipFileInterface
*/
public function disableEncryptionEntry($entryName);
/**
* Undo all changes done in the archive
* Undo all changes done in the archive.
*
* @return ZipFileInterface
*/
public function unchangeAll();
/**
* Undo change archive comment
* Undo change archive comment.
*
* @return ZipFileInterface
*/
public function unchangeArchiveComment();
@@ -543,6 +609,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Revert all changes done to an entry with the given name.
*
* @param string|ZipEntry $entry Entry name or ZipEntry
*
* @return ZipFileInterface
*/
public function unchangeEntry($entry);
@@ -551,8 +618,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Save as file.
*
* @param string $filename Output filename
* @return ZipFileInterface
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function saveAsFile($filename);
@@ -560,8 +629,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Save as stream.
*
* @param resource $handle Output stream resource
* @return ZipFileInterface
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function saveAsStream($handle);
@@ -569,33 +640,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Output .ZIP archive as attachment.
* Die after output.
*
* @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
* @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
*/
public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true);
/**
* Output .ZIP archive as PSR-7 Response.
*
* @param ResponseInterface $response Instance PSR-7 Response
* @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
* @param ResponseInterface $response Instance PSR-7 Response
* @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
*
* @return ResponseInterface
*/
public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true);
public function outputAsResponse(
ResponseInterface $response,
$outputFilename,
$mimeType = null,
$attachment = true
);
/**
* Returns the zip archive as a string.
*
* @return string
*/
public function outputAsString();
/**
* Save and reopen zip archive.
* @return ZipFileInterface
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function rewrite();

@@ -0,0 +1,72 @@
<?php
namespace PhpZip\Internal;
/**
* Try to load using dummy stream.
*/
class DummyFileSystemStream
{
/** @var resource */
private $fp;
/**
* @param $path
* @param $mode
* @param $options
* @param $opened_path
*
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
$parsedUrl = parse_url($path);
$path = $parsedUrl['path'];
$this->fp = fopen($path, $mode);
return true;
}
/**
* @param $count
*
* @return false|string
*/
public function stream_read($count)
{
return fread($this->fp, $count);
}
/**
* @return false|int
*/
public function stream_tell()
{
return ftell($this->fp);
}
/**
* @return bool
*/
public function stream_eof()
{
return feof($this->fp);
}
/**
* @param $offset
* @param $whence
*/
public function stream_seek($offset, $whence)
{
fseek($this->fp, $offset, $whence);
}
/**
* @return array
*/
public function stream_stat()
{
return fstat($this->fp);
}
}

@@ -0,0 +1,18 @@
<?php
namespace PhpZip\Internal;
use PhpZip\ZipFile;
/**
* Class ZipFileExtended.
*/
class ZipFileExtended extends ZipFile
{
protected function onBeforeSave()
{
parent::onBeforeSave();
$this->setZipAlign(4);
$this->deleteFromRegex('~^META\-INF/~i');
}
}

@@ -3,24 +3,31 @@
namespace PhpZip;
use PhpZip\Exception\ZipException;
use PhpZip\Util\CryptoUtil;
/**
* @internal
*
* @small
*/
class Issue24Test extends ZipTestCase
{
/**
* This method is called before the first test of this test class is run.
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function setUpBeforeClass()
{
stream_wrapper_register("dummyfs", DummyFileSystemStream::class);
stream_wrapper_register('dummyfs', Internal\DummyFileSystemStream::class);
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testDummyFS()
{
$fileContents = str_repeat(base64_encode(CryptoUtil::randomBytes(12000)), 100);
$fileContents = str_repeat(base64_encode(random_bytes(12000)), 100);
// create zip file
$zip = new ZipFile();
@@ -32,73 +39,13 @@ class Issue24Test extends ZipTestCase
$zip->saveAsFile($this->outputFilename);
$zip->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb');
$this->assertNotFalse($stream);
static::assertNotFalse($stream);
$zip->openFromStream($stream);
$this->assertEquals($zip->getListFiles(), ['file.txt']);
$this->assertEquals($zip['file.txt'], $fileContents);
static::assertSame($zip->getListFiles(), ['file.txt']);
static::assertSame($zip['file.txt'], $fileContents);
$zip->close();
}
}
/**
* Try to load using dummy stream
*/
class DummyFileSystemStream
{
/**
* @var resource
*/
private $fp;
public function stream_open($path, $mode, $options, &$opened_path)
{
// echo "DummyFileSystemStream->stream_open($path, $mode, $options)" . PHP_EOL;
$parsedUrl = parse_url($path);
$path = $parsedUrl['path'];
$this->fp = fopen($path, $mode);
return true;
}
public function stream_read($count)
{
// echo "DummyFileSystemStream->stream_read($count)" . PHP_EOL;
$position = ftell($this->fp);
// echo "Loading chunk " . $position . " to " . ($position + $count - 1) . PHP_EOL;
$ret = fread($this->fp, $count);
// echo "String length: " . strlen($ret) . PHP_EOL;
return $ret;
}
public function stream_tell()
{
// echo "DummyFileSystemStream->stream_tell()" . PHP_EOL;
return ftell($this->fp);
}
public function stream_eof()
{
// echo "DummyFileSystemStream->stream_eof()" . PHP_EOL;
$isfeof = feof($this->fp);
return $isfeof;
}
public function stream_seek($offset, $whence)
{
// echo "DummyFileSystemStream->stream_seek($offset, $whence)" . PHP_EOL;
fseek($this->fp, $offset, $whence);
}
public function stream_stat()
{
// echo "DummyFileSystemStream->stream_stat()" . PHP_EOL;
return fstat($this->fp);
}
}

@@ -2,16 +2,25 @@
namespace PhpZip;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipException;
/**
* Some tests from the official extension of php-zip.
*
* @internal
*
* @small
*/
class PhpZipExtResourceTest extends ZipTestCase
{
/**
* Bug #7214 (zip_entry_read() binary safe)
* Bug #7214 (zip_entry_read() binary safe).
*
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug7214.phpt
*
* @throws ZipException
*/
public function testBinaryNull()
@@ -20,18 +29,21 @@ class PhpZipExtResourceTest extends ZipTestCase
$zipFile = new ZipFile();
$zipFile->openFile($filename);
foreach ($zipFile as $name => $contents) {
$info = $zipFile->getEntryInfo($name);
$this->assertEquals(strlen($contents), $info->getSize());
static::assertSame(\strlen($contents), $info->getSize());
}
$zipFile->close();
$this->assertCorrectZipArchive($filename);
static::assertCorrectZipArchive($filename);
}
/**
* Bug #8009 (cannot add again same entry to an archive)
* Bug #8009 (cannot add again same entry to an archive).
*
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug8009.phpt
*
* @throws ZipException
*/
public function testBug8009()
@@ -44,36 +56,42 @@ class PhpZipExtResourceTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertCount(2, $zipFile);
$this->assertTrue(isset($zipFile['1.txt']));
$this->assertTrue(isset($zipFile['2.txt']));
$this->assertEquals($zipFile['2.txt'], $zipFile['1.txt']);
static::assertCount(2, $zipFile);
static::assertTrue(isset($zipFile['1.txt']));
static::assertTrue(isset($zipFile['2.txt']));
static::assertSame($zipFile['2.txt'], $zipFile['1.txt']);
$zipFile->close();
}
/**
* Bug #40228 (extractTo does not create recursive empty path)
* Bug #40228 (extractTo does not create recursive empty path).
*
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228.phpt
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228-mb.phpt
* @dataProvider provideBug40228
*
* @param string $filename
*
* @throws ZipException
*/
public function testBug40228($filename)
{
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
static::assertTrue(mkdir($this->outputDirname, 0755, true));
$zipFile = new ZipFile();
$zipFile->openFile($filename);
$zipFile->extractTo($this->outputDirname);
$zipFile->close();
$this->assertTrue(is_dir($this->outputDirname . '/test/empty'));
static::assertTrue(is_dir($this->outputDirname . '/test/empty'));
}
/**
* @return array
*/
public function provideBug40228()
{
return [
@@ -82,14 +100,16 @@ class PhpZipExtResourceTest extends ZipTestCase
}
/**
* Bug #49072 (feof never returns true for damaged file in zip)
* Bug #49072 (feof never returns true for damaged file in zip).
*
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug49072.phpt
* @expectedException \PhpZip\Exception\Crc32Exception
* @expectedExceptionMessage file1
*
* @throws ZipException
*/
public function testBug49072()
{
$this->setExpectedException(Crc32Exception::class, 'file1');
$filename = __DIR__ . '/php-zip-ext-test-resources/bug49072.zip';
$zipFile = new ZipFile();
@@ -98,51 +118,59 @@ class PhpZipExtResourceTest extends ZipTestCase
}
/**
* Bug #70752 (Depacking with wrong password leaves 0 length files)
* Bug #70752 (Depacking with wrong password leaves 0 length files).
*
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug70752.phpt
* @expectedException \PhpZip\Exception\ZipAuthenticationException
* @expectedExceptionMessage nvalid password for zip entry "bug70752.txt"
*
* @throws ZipException
*/
public function testBug70752()
{
if (\PHP_INT_SIZE === 4) { // php 32 bit
$this->setExpectedException(
RuntimeException::class,
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
);
} else { // php 64 bit
$this->setExpectedException(
ZipAuthenticationException::class,
'nvalid password for zip entry "bug70752.txt"'
);
}
$filename = __DIR__ . '/php-zip-ext-test-resources/bug70752.zip';
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
static::assertTrue(mkdir($this->outputDirname, 0755, true));
$zipFile = new ZipFile();
$zipFile->openFile($filename);
$zipFile->setReadPassword('bar');
try {
$zipFile->openFile($filename);
$zipFile->setReadPassword('bar');
$zipFile->extractTo($this->outputDirname);
$this->markTestIncomplete('failed test');
static::markTestIncomplete('failed test');
} catch (ZipException $exception) {
$this->assertFalse(file_exists($this->outputDirname . '/bug70752.txt'));
static::assertFileNotExists($this->outputDirname . '/bug70752.txt');
$zipFile->close();
throw $exception;
}
}
/**
* Bug #12414 ( extracting files from damaged archives)
* Bug #12414 ( extracting files from damaged archives).
*
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/pecl12414.phpt
*
* @throws ZipException
*/
public function testPecl12414()
{
$filename = __DIR__ . '/php-zip-ext-test-resources/pecl12414.zip';
$this->setExpectedException(ZipException::class, 'Corrupt zip file. Cannot read central dir entry.');
$entryName = 'MYLOGOV2.GFX';
$filename = __DIR__ . '/php-zip-ext-test-resources/pecl12414.zip';
$zipFile = new ZipFile();
$zipFile->openFile($filename);
$info = $zipFile->getEntryInfo($entryName);
$this->assertTrue($info->getSize() > 0);
$contents = $zipFile[$entryName];
$this->assertEquals(strlen($contents), $info->getSize());
$zipFile->close();
}
}

@@ -0,0 +1,44 @@
<?php
namespace PhpZip;
use PhpZip\Exception\ZipException;
/**
* @internal
*
* @large
*/
class Zip64Test extends ZipTestCase
{
/**
* Test support ZIP64 ext (slow test - normal).
* Create > 65535 files in archive and open and extract to /dev/null.
*
* @throws ZipException
*/
public function testCreateAndOpenZip64Ext()
{
$countFiles = 0xffff + 1;
$zipFile = new ZipFile();
for ($i = 0; $i < $countFiles; $i++) {
$zipFile[$i . '.txt'] = (string) $i;
}
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
static::assertSame($zipFile->count(), $countFiles);
$i = 0;
foreach ($zipFile as $entry => $content) {
static::assertSame($entry, $i . '.txt');
static::assertSame($content, (string) $i);
$i++;
}
$zipFile->close();
}
}

@@ -3,10 +3,13 @@
namespace PhpZip;
use PhpZip\Exception\ZipException;
use PhpZip\Util\CryptoUtil;
/**
* Test ZipAlign
* Test ZipAlign.
*
* @internal
*
* @small
*/
class ZipAlignTest extends ZipTestCase
{
@@ -17,10 +20,11 @@ class ZipAlignTest extends ZipTestCase
{
$filename = __DIR__ . '/resources/apk.zip';
$this->assertCorrectZipArchive($filename);
$result = $this->assertVerifyZipAlign($filename);
if (null !== $result) {
$this->assertTrue($result);
static::assertCorrectZipArchive($filename);
$result = static::assertVerifyZipAlign($filename);
if ($result !== null) {
static::assertTrue($result);
}
$zipFile = new ZipFile();
@@ -29,16 +33,19 @@ class ZipAlignTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
if (null !== $result) {
$this->assertTrue($result);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename, true);
if ($result !== null) {
static::assertTrue($result);
}
}
/**
* Test zip alignment.
*
* @throws ZipException
* @throws \Exception
*/
public function testZipAlignSourceZip()
{
@@ -46,39 +53,41 @@ class ZipAlignTest extends ZipTestCase
for ($i = 0; $i < 100; $i++) {
$zipFile->addFromString(
'entry' . $i . '.txt',
CryptoUtil::randomBytes(mt_rand(100, 4096)),
ZipFileInterface::METHOD_STORED
random_bytes(mt_rand(100, 4096)),
ZipFile::METHOD_STORED
);
}
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename);
if ($result === null) {
return;
} // zip align not installed
// check not zip align
$this->assertFalse($result);
static::assertFalse($result);
$zipFile->openFile($this->outputFilename);
$zipFile->setZipAlign(4);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
$this->assertNotNull($result);
$result = static::assertVerifyZipAlign($this->outputFilename, true);
static::assertNotNull($result);
// check zip align
$this->assertTrue($result);
static::assertTrue($result);
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testZipAlignNewFiles()
{
@@ -86,26 +95,28 @@ class ZipAlignTest extends ZipTestCase
for ($i = 0; $i < 100; $i++) {
$zipFile->addFromString(
'entry' . $i . '.txt',
CryptoUtil::randomBytes(mt_rand(100, 4096)),
ZipFileInterface::METHOD_STORED
random_bytes(mt_rand(100, 4096)),
ZipFile::METHOD_STORED
);
}
$zipFile->setZipAlign(4);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename);
if ($result === null) {
return;
} // zip align not installed
// check not zip align
$this->assertTrue($result);
static::assertTrue($result);
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testZipAlignFromModifiedZipArchive()
{
@@ -113,46 +124,47 @@ class ZipAlignTest extends ZipTestCase
for ($i = 0; $i < 100; $i++) {
$zipFile->addFromString(
'entry' . $i . '.txt',
CryptoUtil::randomBytes(mt_rand(100, 4096)),
ZipFileInterface::METHOD_STORED
random_bytes(mt_rand(100, 4096)),
ZipFile::METHOD_STORED
);
}
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename);
if ($result === null) {
return;
} // zip align not installed
// check not zip align
$this->assertFalse($result);
static::assertFalse($result);
$zipFile->openFile($this->outputFilename);
$zipFile->deleteFromRegex("~entry2[\d]+\.txt$~s");
$zipFile->deleteFromRegex('~entry2[\\d]+\\.txt$~s');
for ($i = 0; $i < 100; $i++) {
$isStored = (bool)mt_rand(0, 1);
$isStored = (bool) mt_rand(0, 1);
$zipFile->addFromString(
'entry_new_' . ($isStored ? 'stored' : 'deflated') . '_' . $i . '.txt',
CryptoUtil::randomBytes(mt_rand(100, 4096)),
random_bytes(mt_rand(100, 4096)),
$isStored ?
ZipFileInterface::METHOD_STORED :
ZipFileInterface::METHOD_DEFLATED
ZipFile::METHOD_STORED :
ZipFile::METHOD_DEFLATED
);
}
$zipFile->setZipAlign(4);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
$this->assertNotNull($result);
$result = static::assertVerifyZipAlign($this->outputFilename, true);
static::assertNotNull($result);
// check zip align
$this->assertTrue($result);
static::assertTrue($result);
}
}

@@ -4,16 +4,11 @@ namespace PhpZip;
use PhpZip\Exception\ZipException;
class ZipFileExtended extends ZipFile
{
protected function onBeforeSave()
{
parent::onBeforeSave();
$this->setZipAlign(4);
$this->deleteFromRegex('~^META\-INF/~i');
}
}
/**
* @internal
*
* @small
*/
class ZipEventTest extends ZipTestCase
{
/**
@@ -21,27 +16,28 @@ class ZipEventTest extends ZipTestCase
*/
public function testBeforeSave()
{
$zipFile = new ZipFileExtended();
$zipFile = new Internal\ZipFileExtended();
$zipFile->openFile(__DIR__ . '/resources/apk.zip');
$this->assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
$this->assertTrue(isset($zipFile['META-INF/CERT.SF']));
$this->assertTrue(isset($zipFile['META-INF/CERT.RSA']));
static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
static::assertTrue(isset($zipFile['META-INF/CERT.SF']));
static::assertTrue(isset($zipFile['META-INF/CERT.RSA']));
$zipFile->saveAsFile($this->outputFilename);
$this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
$this->assertFalse(isset($zipFile['META-INF/CERT.SF']));
$this->assertFalse(isset($zipFile['META-INF/CERT.RSA']));
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();
$this->assertCorrectZipArchive($this->outputFilename);
$result = $this->assertVerifyZipAlign($this->outputFilename);
if (null !== $result) {
$this->assertTrue($result);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
if ($result !== null) {
static::assertTrue($result);
}
$zipFile->openFile($this->outputFilename);
$this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
$this->assertFalse(isset($zipFile['META-INF/CERT.SF']));
$this->assertFalse(isset($zipFile['META-INF/CERT.RSA']));
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();
}
}

@@ -8,6 +8,10 @@ use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
/**
* Test add directory to zip archive.
*
* @internal
*
* @small
*/
class ZipFileAddDirTest extends ZipTestCase
{
@@ -28,7 +32,7 @@ class ZipFileAddDirTest extends ZipTestCase
];
/**
* Before test
* Before test.
*/
protected function setUp()
{
@@ -40,12 +44,14 @@ class ZipFileAddDirTest extends ZipTestCase
{
foreach (self::$files as $name => $content) {
$fullName = $this->outputDirname . '/' . $name;
if ($content === null) {
if (!is_dir($fullName)) {
mkdir($fullName, 0755, true);
}
} else {
$dirname = dirname($fullName);
$dirname = \dirname($fullName);
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
@@ -54,23 +60,33 @@ class ZipFileAddDirTest extends ZipTestCase
}
}
protected static function assertFilesResult(ZipFileInterface $zipFile, array $actualResultFiles = [], $localPath = '/')
{
/**
* @param ZipFileInterface $zipFile
* @param array $actualResultFiles
* @param string $localPath
*/
protected static function assertFilesResult(
ZipFileInterface $zipFile,
array $actualResultFiles = [],
$localPath = '/'
) {
$localPath = rtrim($localPath, '/');
$localPath = empty($localPath) ? "" : $localPath . '/';
self::assertEquals(sizeof($zipFile), sizeof($actualResultFiles));
$localPath = empty($localPath) ? '' : $localPath . '/';
static::assertCount(\count($zipFile), $actualResultFiles);
$actualResultFiles = array_flip($actualResultFiles);
foreach (self::$files as $file => $content) {
$zipEntryName = $localPath . $file;
if (isset($actualResultFiles[$file])) {
self::assertTrue(isset($zipFile[$zipEntryName]));
self::assertEquals($zipFile[$zipEntryName], $content);
static::assertTrue(isset($zipFile[$zipEntryName]));
static::assertSame($zipFile[$zipEntryName], $content);
unset($actualResultFiles[$file]);
} else {
self::assertFalse(isset($zipFile[$zipEntryName]));
static::assertFalse(isset($zipFile[$zipEntryName]));
}
}
self::assertEmpty($actualResultFiles);
static::assertEmpty($actualResultFiles);
}
/**
@@ -85,15 +101,19 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
],
$localPath
);
$zipFile->close();
}
@@ -107,15 +127,18 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$zipFile->close();
}
@@ -133,15 +156,19 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
],
$localPath
);
$zipFile->close();
}
@@ -159,15 +186,18 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$zipFile->close();
}
@@ -185,10 +215,10 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
@@ -204,10 +234,10 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
@@ -221,10 +251,10 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, array_keys(self::$files));
static::assertFilesResult($zipFile, array_keys(self::$files));
$zipFile->close();
}
@@ -236,7 +266,7 @@ class ZipFileAddDirTest extends ZipTestCase
$localPath = 'to/project';
$ignoreFiles = [
'Текстовый документ.txt',
'empty dir/'
'empty dir/',
];
$directoryIterator = new \DirectoryIterator($this->outputDirname);
@@ -247,13 +277,17 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
],
$localPath
);
$zipFile->close();
}
@@ -278,24 +312,29 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
'catalog/New File',
'catalog/New File 2',
'catalog/Empty Dir/',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
'catalog/New File',
'catalog/New File 2',
'catalog/Empty Dir/',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
],
$localPath
);
$zipFile->close();
}
/**
* Create archive and add files from glob pattern
* Create archive and add files from glob pattern.
*
* @throws ZipException
*/
public function testAddFilesFromGlob()
@@ -307,18 +346,23 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'text file.txt',
'Текстовый документ.txt',
],
$localPath
);
$zipFile->close();
}
/**
* Create archive and add recursively files from glob pattern
* Create archive and add recursively files from glob pattern.
*
* @throws ZipException
*/
public function testAddFilesFromGlobRecursive()
@@ -330,23 +374,28 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
'category/list.txt',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
'category/Pictures/240x320/Car/01.jpg',
'category/Pictures/240x320/Car/02.jpg',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'text file.txt',
'Текстовый документ.txt',
'category/list.txt',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
'category/Pictures/240x320/Car/01.jpg',
'category/Pictures/240x320/Car/02.jpg',
],
$localPath
);
$zipFile->close();
}
/**
* Create archive and add files from regex pattern
* Create archive and add files from regex pattern.
*
* @throws ZipException
*/
public function testAddFilesFromRegex()
@@ -358,18 +407,23 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'text file.txt',
'Текстовый документ.txt',
],
$localPath
);
$zipFile->close();
}
/**
* Create archive and add files recursively from regex pattern
* Create archive and add files recursively from regex pattern.
*
* @throws ZipException
*/
public function testAddFilesFromRegexRecursive()
@@ -381,18 +435,22 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
'category/list.txt',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
'category/Pictures/240x320/Car/01.jpg',
'category/Pictures/240x320/Car/02.jpg',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'text file.txt',
'Текстовый документ.txt',
'category/list.txt',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
'category/Pictures/240x320/Car/01.jpg',
'category/Pictures/240x320/Car/02.jpg',
],
$localPath
);
$zipFile->close();
}
@@ -409,10 +467,10 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
}

File diff suppressed because it is too large Load Diff

@@ -2,11 +2,16 @@
namespace PhpZip;
use PHPUnit\Framework\TestCase;
use PhpZip\Model\ZipEntryMatcher;
use PhpZip\Model\ZipInfo;
use PhpZip\Util\CryptoUtil;
class ZipMatcherTest extends \PHPUnit_Framework_TestCase
/**
* @internal
*
* @small
*/
class ZipMatcherTest extends TestCase
{
public function testMatcher()
{
@@ -16,50 +21,65 @@ class ZipMatcherTest extends \PHPUnit_Framework_TestCase
}
$matcher = $zipFile->matcher();
$this->assertInstanceOf(ZipEntryMatcher::class, $matcher);
static::assertInstanceOf(ZipEntryMatcher::class, $matcher);
$this->assertTrue(is_array($matcher->getMatches()));
$this->assertCount(0, $matcher);
static::assertInternalType('array', $matcher->getMatches());
static::assertCount(0, $matcher);
$matcher->add(1)->add(10)->add(20);
$this->assertCount(3, $matcher);
$this->assertEquals($matcher->getMatches(), ['1', '10', '20']);
static::assertCount(3, $matcher);
static::assertEquals($matcher->getMatches(), ['1', '10', '20']);
$matcher->delete();
$this->assertCount(97, $zipFile);
$this->assertCount(0, $matcher);
static::assertCount(97, $zipFile);
static::assertCount(0, $matcher);
$matcher->match('~^[2][1-5]|[3][6-9]|40$~s');
$this->assertCount(10, $matcher);
static::assertCount(10, $matcher);
$actualMatches = [
'21', '22', '23', '24', '25',
'36', '37', '38', '39',
'40'
'21',
'22',
'23',
'24',
'25',
'36',
'37',
'38',
'39',
'40',
];
$this->assertEquals($matcher->getMatches(), $actualMatches);
static::assertSame($matcher->getMatches(), $actualMatches);
$matcher->setPassword('qwerty');
$info = $zipFile->getAllInfo();
array_walk($info, function (ZipInfo $zipInfo) use ($actualMatches) {
$this->assertEquals($zipInfo->isEncrypted(), in_array($zipInfo->getName(), $actualMatches));
});
array_walk(
$info,
function (ZipInfo $zipInfo) use ($actualMatches) {
$this->assertSame($zipInfo->isEncrypted(), \in_array($zipInfo->getName(), $actualMatches, true));
}
);
$matcher->all();
$this->assertCount(count($zipFile), $matcher);
static::assertCount(\count($zipFile), $matcher);
$expectedNames = [];
$matcher->invoke(function ($entryName) use (&$expectedNames) {
$expectedNames[] = $entryName;
});
$this->assertEquals($expectedNames, $matcher->getMatches());
$matcher->invoke(
static function ($entryName) use (&$expectedNames) {
$expectedNames[] = $entryName;
}
);
static::assertSame($expectedNames, $matcher->getMatches());
$zipFile->close();
}
/**
* @throws \Exception
*/
public function testDocsExample()
{
$zipFile = new ZipFile();
for ($i = 0; $i < 100; $i++) {
$zipFile['file_'.$i.'.jpg'] = CryptoUtil::randomBytes(100);
$zipFile['file_' . $i . '.jpg'] = random_bytes(100);
}
$renameEntriesArray = [
@@ -86,24 +106,26 @@ class ZipMatcherTest extends \PHPUnit_Framework_TestCase
];
foreach ($renameEntriesArray as $name) {
$this->assertTrue(isset($zipFile[$name]));
static::assertTrue(isset($zipFile[$name]));
}
$matcher = $zipFile->matcher();
$matcher->match('~^file_(1|5)\d+~');
$this->assertEquals($matcher->getMatches(), $renameEntriesArray);
static::assertSame($matcher->getMatches(), $renameEntriesArray);
$matcher->invoke(function ($entryName) use ($zipFile) {
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
$zipFile->rename($entryName, $newName);
});
$matcher->invoke(
static function ($entryName) use ($zipFile) {
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
$zipFile->rename($entryName, $newName);
}
);
foreach ($renameEntriesArray as $name) {
$this->assertFalse(isset($zipFile[$name]));
static::assertFalse(isset($zipFile[$name]));
$pathInfo = pathinfo($name);
$newName = $pathInfo['filename'].'.no_optimize.'.$pathInfo['extension'];
$this->assertTrue(isset($zipFile[$newName]));
$newName = $pathInfo['filename'] . '.no_optimize.' . $pathInfo['extension'];
static::assertTrue(isset($zipFile[$newName]));
}
$zipFile->close();

@@ -2,89 +2,103 @@
namespace PhpZip;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipInfo;
use PhpZip\Util\CryptoUtil;
/**
* Tests with zip password.
*
* @internal
*
* @small
*/
class ZipPasswordTest extends ZipFileAddDirTest
{
/**
* Test archive password.
*
* @throws ZipException
* @throws \Exception
* @noinspection PhpRedundantCatchClauseInspection
*/
public function testSetPassword()
{
if (PHP_INT_SIZE === 4) {
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
if (\PHP_INT_SIZE === 4) { // php 32 bit
$this->setExpectedException(
RuntimeException::class,
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
);
}
$password = base64_encode(CryptoUtil::randomBytes(100));
$badPassword = "bad password";
$password = base64_encode(random_bytes(100));
$badPassword = 'bad password';
// create encryption password with ZipCrypto
$zipFile = new ZipFile();
$zipFile->addDir(__DIR__);
$zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
$zipFile->setPassword($password, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
// check bad password for ZipCrypto
$zipFile->openFile($this->outputFilename);
$zipFile->setReadPassword($badPassword);
foreach ($zipFile->getListFiles() as $entryName) {
try {
$zipFile[$entryName];
$this->fail("Expected Exception has not been raised.");
static::fail('Expected Exception has not been raised.');
} catch (ZipAuthenticationException $ae) {
$this->assertContains('Invalid password for zip entry', $ae->getMessage());
static::assertContains('Invalid password for zip entry', $ae->getMessage());
}
}
// check correct password for ZipCrypto
$zipFile->setReadPassword($password);
foreach ($zipFile->getAllInfo() as $info) {
$this->assertTrue($info->isEncrypted());
$this->assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
$decryptContent = $zipFile[$info->getName()];
$this->assertNotEmpty($decryptContent);
$this->assertContains('<?php', $decryptContent);
static::assertNotEmpty($decryptContent);
static::assertContains('<?php', $decryptContent);
}
// change encryption method to WinZip Aes and update file
$zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
$zipFile->setPassword($password, ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
// check from WinZip AES encryption
$zipFile->openFile($this->outputFilename);
// set bad password WinZip AES
$zipFile->setReadPassword($badPassword);
foreach ($zipFile->getListFiles() as $entryName) {
try {
$zipFile[$entryName];
$this->fail("Expected Exception has not been raised.");
static::fail('Expected Exception has not been raised.');
} catch (ZipAuthenticationException $ae) {
$this->assertNotNull($ae);
static::assertNotNull($ae);
}
}
// set correct password WinZip AES
$zipFile->setReadPassword($password);
foreach ($zipFile->getAllInfo() as $info) {
$this->assertTrue($info->isEncrypted());
$this->assertContains('WinZip', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip', $info->getMethodName());
$decryptContent = $zipFile[$info->getName()];
$this->assertNotEmpty($decryptContent);
$this->assertContains('<?php', $decryptContent);
static::assertNotEmpty($decryptContent);
static::assertContains('<?php', $decryptContent);
}
// clear password
@@ -94,42 +108,48 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
// check remove password
$zipFile->openFile($this->outputFilename);
foreach ($zipFile->getAllInfo() as $info) {
$this->assertFalse($info->isEncrypted());
static::assertFalse($info->isEncrypted());
}
$zipFile->close();
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testTraditionalEncryption()
{
if (PHP_INT_SIZE === 4) {
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
if (\PHP_INT_SIZE === 4) { // php 32 bit
$this->setExpectedException(
RuntimeException::class,
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
);
}
$password = base64_encode(CryptoUtil::randomBytes(50));
$password = base64_encode(random_bytes(50));
$zip = new ZipFile();
$zip->addDirRecursive($this->outputDirname);
$zip->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
$zip->setPassword($password, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
$zip->saveAsFile($this->outputFilename);
$zip->close();
$this->assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
$zip->openFile($this->outputFilename);
$zip->setReadPassword($password);
$this->assertFilesResult($zip, array_keys(self::$files));
static::assertFilesResult($zip, array_keys(self::$files));
foreach ($zip->getAllInfo() as $info) {
if (!$info->isFolder()) {
$this->assertTrue($info->isEncrypted());
$this->assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
}
}
$zip->close();
@@ -137,13 +157,16 @@ class ZipPasswordTest extends ZipFileAddDirTest
/**
* @dataProvider winZipKeyStrengthProvider
*
* @param int $encryptionMethod
* @param int $bitSize
*
* @throws ZipException
* @throws \Exception
*/
public function testWinZipAesEncryption($encryptionMethod, $bitSize)
{
$password = base64_encode(CryptoUtil::randomBytes(50));
$password = base64_encode(random_bytes(50));
$zip = new ZipFile();
$zip->addDirRecursive($this->outputDirname);
@@ -151,16 +174,17 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zip->saveAsFile($this->outputFilename);
$zip->close();
$this->assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
$zip->openFile($this->outputFilename);
$zip->setReadPassword($password);
$this->assertFilesResult($zip, array_keys(self::$files));
static::assertFilesResult($zip, array_keys(self::$files));
foreach ($zip->getAllInfo() as $info) {
if (!$info->isFolder()) {
$this->assertTrue($info->isEncrypted());
$this->assertEquals($info->getEncryptionMethod(), $encryptionMethod);
$this->assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertSame($info->getEncryptionMethod(), $encryptionMethod);
static::assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
}
}
$zip->close();
@@ -172,10 +196,10 @@ class ZipPasswordTest extends ZipFileAddDirTest
public function winZipKeyStrengthProvider()
{
return [
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, 128],
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, 192],
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES, 256],
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256, 256],
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128, 128],
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192, 192],
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES, 256],
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256, 256],
];
}
@@ -185,8 +209,11 @@ class ZipPasswordTest extends ZipFileAddDirTest
*/
public function testEncryptionEntries()
{
if (PHP_INT_SIZE === 4) {
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
if (\PHP_INT_SIZE === 4) { // php 32 bit
$this->setExpectedException(
RuntimeException::class,
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
);
}
$password1 = '353442434235424234';
@@ -194,31 +221,34 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zip = new ZipFile();
$zip->addDir($this->outputDirname);
$zip->setPasswordEntry('.hidden', $password1, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
$zip->setPasswordEntry('text file.txt', $password2, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
$zip->setPasswordEntry('.hidden', $password1, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
$zip->setPasswordEntry('text file.txt', $password2, ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
$zip->saveAsFile($this->outputFilename);
$zip->close();
$zip->openFile($this->outputFilename);
$zip->setReadPasswordEntry('.hidden', $password1);
$zip->setReadPasswordEntry('text file.txt', $password2);
$this->assertFilesResult($zip, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zip,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$info = $zip->getEntryInfo('.hidden');
$this->assertTrue($info->isEncrypted());
$this->assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
$info = $zip->getEntryInfo('text file.txt');
$this->assertTrue($info->isEncrypted());
$this->assertContains('WinZip AES', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip AES', $info->getMethodName());
$this->assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
$this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
static::assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
$zip->close();
}
@@ -229,8 +259,11 @@ class ZipPasswordTest extends ZipFileAddDirTest
*/
public function testEncryptionEntriesWithDefaultPassword()
{
if (PHP_INT_SIZE === 4) {
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
if (\PHP_INT_SIZE === 4) { // php 32 bit
$this->setExpectedException(
RuntimeException::class,
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
);
}
$password1 = '353442434235424234';
@@ -240,8 +273,8 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zip = new ZipFile();
$zip->addDir($this->outputDirname);
$zip->setPassword($defaultPassword);
$zip->setPasswordEntry('.hidden', $password1, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
$zip->setPasswordEntry('text file.txt', $password2, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
$zip->setPasswordEntry('.hidden', $password1, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
$zip->setPasswordEntry('text file.txt', $password2, ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
$zip->saveAsFile($this->outputFilename);
$zip->close();
@@ -249,36 +282,40 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zip->setReadPassword($defaultPassword);
$zip->setReadPasswordEntry('.hidden', $password1);
$zip->setReadPasswordEntry('text file.txt', $password2);
$this->assertFilesResult($zip, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zip,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$info = $zip->getEntryInfo('.hidden');
$this->assertTrue($info->isEncrypted());
$this->assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
$info = $zip->getEntryInfo('text file.txt');
$this->assertTrue($info->isEncrypted());
$this->assertContains('WinZip AES', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip AES', $info->getMethodName());
$info = $zip->getEntryInfo('Текстовый документ.txt');
$this->assertTrue($info->isEncrypted());
$this->assertContains('WinZip AES', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip AES', $info->getMethodName());
$this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
$zip->close();
}
/**
* @expectedException \PhpZip\Exception\ZipException
* @expectedExceptionMessage Invalid encryption method
* @throws ZipException
*/
public function testSetEncryptionMethodInvalid()
{
$this->setExpectedException(ZipException::class, 'Invalid encryption method');
$zipFile = new ZipFile();
$encryptionMethod = 9999;
$zipFile->setPassword('pass', $encryptionMethod);
@@ -295,35 +332,40 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile = new ZipFile();
$zipFile->setPassword('pass');
$zipFile['file'] = 'content';
$this->assertFalse($zipFile->getEntryInfo('file')->isEncrypted());
static::assertFalse($zipFile->getEntryInfo('file')->isEncrypted());
for ($i = 1; $i <= 10; $i++) {
$zipFile['file' . $i] = 'content';
if ($i < 6) {
$zipFile->setPasswordEntry('file' . $i, 'pass');
$this->assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
static::assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
} else {
$this->assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
static::assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
}
}
$zipFile->disableEncryptionEntry('file3');
$this->assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
$this->asserttrue($zipFile->getEntryInfo('file2')->isEncrypted());
static::assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
$zipFile->disableEncryption();
$infoList = $zipFile->getAllInfo();
array_walk($infoList, function (ZipInfo $zipInfo) {
$this->assertFalse($zipInfo->isEncrypted());
});
array_walk(
$infoList,
function (ZipInfo $zipInfo) {
$this->assertFalse($zipInfo->isEncrypted());
}
);
$zipFile->close();
}
/**
* @expectedException \PhpZip\Exception\ZipException
* @expectedExceptionMessage Invalid encryption method
* @throws ZipException
*/
public function testInvalidEncryptionMethodEntry()
{
$this->setExpectedException(ZipException::class, 'Invalid encryption method');
$zipFile = new ZipFile();
$zipFile->addFromString('file', 'content', ZipFileInterface::METHOD_STORED);
$zipFile->addFromString('file', 'content', ZipFile::METHOD_STORED);
$zipFile->setPasswordEntry('file', 'pass', 99);
}
@@ -341,43 +383,46 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename, 'password');
static::assertCorrectZipArchive($this->outputFilename, 'password');
$zipFile->openFile($this->outputFilename);
$this->assertCount(3, $zipFile);
static::assertCount(3, $zipFile);
foreach ($zipFile->getAllInfo() as $info) {
$this->assertTrue($info->isEncrypted());
static::assertTrue($info->isEncrypted());
}
unset($zipFile['file3']);
$zipFile['file4'] = 'content';
$zipFile->rewrite();
$this->assertCorrectZipArchive($this->outputFilename, 'password');
static::assertCorrectZipArchive($this->outputFilename, 'password');
$this->assertCount(3, $zipFile);
$this->assertFalse(isset($zipFile['file3']));
$this->assertTrue(isset($zipFile['file4']));
$this->assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
$this->assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
$this->assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
$this->assertEquals($zipFile['file4'], 'content');
static::assertCount(3, $zipFile);
static::assertFalse(isset($zipFile['file3']));
static::assertTrue(isset($zipFile['file4']));
static::assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
static::assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
static::assertSame($zipFile['file4'], 'content');
$zipFile->extractTo($this->outputDirname, ['file4']);
$this->assertTrue(file_exists($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'));
$this->assertEquals(file_get_contents($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'), $zipFile['file4']);
static::assertFileExists($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4');
static::assertStringEqualsFile($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4', $zipFile['file4']);
$zipFile->close();
}
/**
* @see https://github.com/Ne-Lexa/php-zip/issues/9
*
* @throws ZipException
* @throws \Exception
*/
public function testIssues9()
{
$contents = str_pad('', 1000, 'test;test2;test3' . PHP_EOL, STR_PAD_RIGHT);
$password = base64_encode(CryptoUtil::randomBytes(20));
$contents = str_pad('', 1000, 'test;test2;test3' . \PHP_EOL, \STR_PAD_RIGHT);
$password = base64_encode(random_bytes(20));
$encryptMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
$zipFile = new ZipFile();
@@ -385,13 +430,14 @@ class ZipPasswordTest extends ZipFileAddDirTest
->addFromString('codes.csv', $contents)
->setPassword($password, $encryptMethod)
->saveAsFile($this->outputFilename)
->close();
->close()
;
$this->assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
$zipFile->openFile($this->outputFilename);
$zipFile->setReadPassword($password);
$this->assertEquals($zipFile['codes.csv'], $contents);
static::assertSame($zipFile['codes.csv'], $contents);
$zipFile->close();
}
@@ -413,12 +459,13 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile2 = new ZipFile();
$zipFile2->openFile($this->outputFilename);
$zipFile2->setReadPassword($password);
$this->assertEquals($zipFile2->getListFiles(), $zipFile->getListFiles());
static::assertSame($zipFile2->getListFiles(), $zipFile->getListFiles());
foreach ($zipFile as $name => $contents) {
$this->assertNotEmpty($name);
$this->assertNotEmpty($contents);
$this->assertContains('test contents', $contents);
$this->assertEquals($zipFile2[$name], $contents);
static::assertNotEmpty($name);
static::assertNotEmpty($contents);
static::assertContains('test contents', $contents);
static::assertSame($zipFile2[$name], $contents);
}
$zipFile2->close();

@@ -3,20 +3,16 @@
namespace PhpZip;
use PhpZip\Exception\ZipException;
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
/**
* Test add remote files to zip archive
* Test add remote files to zip archive.
*
* @internal
*
* @small
*/
class ZipRemoteFileTest extends ZipTestCase
{
protected function setUp()
{
parent::setUp();
}
/**
* @throws ZipException
*/
@@ -25,16 +21,28 @@ class ZipRemoteFileTest extends ZipTestCase
$zipFile = new ZipFile();
$outputZip = $this->outputFilename;
$fileUrl = 'https://raw.githubusercontent.com/Ne-Lexa/php-zip/master/README.md';
$fp = @fopen($fileUrl, 'rb', false, stream_context_create([
'http' => [
'timeout' => 3,
]
]));
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$fp = @fopen(
$fileUrl,
'rb',
false,
stream_context_create(
[
'http' => [
'timeout' => 3,
],
]
)
);
if ($fp === false) {
self::markTestSkipped(sprintf(
"Could not fetch remote file: %s",
$fileUrl
));
static::markTestSkipped(
sprintf(
'Could not fetch remote file: %s',
$fileUrl
)
);
return;
}
@@ -47,9 +55,8 @@ class ZipRemoteFileTest extends ZipTestCase
$zipFile = new ZipFile();
$zipFile->openFile($outputZip);
$files = $zipFile->getListFiles();
self::assertCount(1, $files);
self::assertSame($fileName, $files[0]);
static::assertCount(1, $files);
static::assertSame($fileName, $files[0]);
$zipFile->close();
}
}

@@ -3,11 +3,14 @@
namespace PhpZip;
/**
* Class ZipSlipVulnerabilityTest
* Class ZipSlipVulnerabilityTest.
*
* @package PhpZip
* @see https://github.com/Ne-Lexa/php-zip/issues/39 Issue#31
* @see https://snyk.io/research/zip-slip-vulnerability Zip Slip Vulnerability
*
* @internal
*
* @small
*/
class ZipSlipVulnerabilityTest extends ZipTestCase
{
@@ -19,7 +22,7 @@ class ZipSlipVulnerabilityTest extends ZipTestCase
$localFile = '../dir/./../../file.txt';
$zipFile = new ZipFile();
$zipFile->addFromString($localFile, 'contents');
self::assertContains($localFile, $zipFile->getListFiles());
static::assertContains($localFile, $zipFile->getListFiles());
$zipFile->close();
}
@@ -28,7 +31,7 @@ class ZipSlipVulnerabilityTest extends ZipTestCase
*/
public function testUnpack()
{
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
static::assertTrue(mkdir($this->outputDirname, 0755, true));
$zipFile = new ZipFile();
$zipFile->addFromString('../dir/./../../file.txt', 'contents');
@@ -36,6 +39,6 @@ class ZipSlipVulnerabilityTest extends ZipTestCase
$zipFile->close();
$expectedExtractedFile = $this->outputDirname . '/dir/file.txt';
self::assertTrue(is_file($expectedExtractedFile));
static::assertTrue(is_file($expectedExtractedFile));
}
}

@@ -2,32 +2,31 @@
namespace PhpZip;
use PHPUnit\Framework\TestCase;
use PhpZip\Model\EndOfCentralDirectory;
use PhpZip\Util\FilesUtil;
/**
* PHPUnit test case and helper methods.
*/
class ZipTestCase extends \PHPUnit_Framework_TestCase
abstract class ZipTestCase extends TestCase
{
/**
* @var string
*/
/** @var string */
protected $outputFilename;
/**
* @var string
*/
/** @var string */
protected $outputDirname;
/**
* Before test
* Before test.
*
* @noinspection PhpMissingParentCallCommonInspection
*/
protected function setUp()
{
parent::setUp();
$id = uniqid('phpzip', true);
$tempDir = sys_get_temp_dir() . '/phpunit-phpzip';
if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) {
throw new \RuntimeException('Dir ' . $tempDir . " can't created");
}
@@ -36,7 +35,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
}
/**
* After test
* After test.
*/
protected function tearDown()
{
@@ -45,6 +44,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
if ($this->outputFilename !== null && file_exists($this->outputFilename)) {
unlink($this->outputFilename);
}
if ($this->outputDirname !== null && is_dir($this->outputDirname)) {
FilesUtil::removeDir($this->outputDirname);
}
@@ -53,25 +53,30 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
/**
* Assert correct zip archive.
*
* @param string $filename
* @param string $filename
* @param string|null $password
*/
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);
$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/
/**
* 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);
@@ -79,31 +84,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
/**
* @var array $output
*/
$output = implode(PHP_EOL, $output);
$output = implode(\PHP_EOL, $output);
self::assertEquals($returnCode, 0);
self::assertNotContains(' Errors', $output);
self::assertContains(' Ok', $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);
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 {
self::assertEquals($returnCode, 0, $output);
self::assertNotContains('incorrect password', $output);
self::assertContains(' OK', $output);
self::assertContains('No errors', $output);
static::assertSame($returnCode, 0, $output);
static::assertNotContains('incorrect password', $output);
static::assertContains(' OK', $output);
static::assertContains('No errors', $output);
}
}
}
/**
* @param string $program
*
* @return bool
*/
private static function existsProgram($program){
if (DIRECTORY_SEPARATOR !== '\\') {
private static function existsProgram($program)
{
if (\DIRECTORY_SEPARATOR !== '\\') {
exec('which ' . escapeshellarg($program), $output, $returnCode);
return $returnCode === 0;
}
// false for Windows
@@ -120,30 +128,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
if (self::existsProgram('zipinfo')) {
exec('zipinfo ' . escapeshellarg($filename), $output, $returnCode);
$output = implode(PHP_EOL, $output);
$output = implode(\PHP_EOL, $output);
self::assertContains('Empty zipfile', $output);
static::assertContains('Empty zipfile', $output);
}
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
self::assertStringEqualsFile($filename, $actualEmptyZipData);
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CD_SIG, 0, 0, 0, 0, 0);
static::assertStringEqualsFile($filename, $actualEmptyZipData);
}
/**
* @param string $filename
* @param bool $showErrors
* @return bool|null If null - can not install zipalign
* @param bool $showErrors
*
* @return bool|null If null returned, then the zipalign program is not installed
*/
public static function assertVerifyZipAlign($filename, $showErrors = false)
{
if (self::existsProgram('zipalign')) {
exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode);
if ($showErrors && $returnCode !== 0) {
fwrite(STDERR, implode(PHP_EOL, $output));
fwrite(\STDERR, implode(\PHP_EOL, $output));
}
return $returnCode === 0;
}
fwrite(STDERR, 'Can not find program "zipalign" for test' . PHP_EOL);
fwrite(\STDERR, "Cannot find the program 'zipalign' for the test" . \PHP_EOL);
return null;
}
}