1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-07-20 15:31:15 +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

View File

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

View File

@@ -21,12 +21,13 @@
} }
], ],
"require": { "require": {
"ext-zlib": "*",
"php": "^5.5 || ^7.0", "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": { "require-dev": {
"phpunit/phpunit": "~4.8|~5.7", "phpunit/phpunit": "^4.8|^5.7",
"zendframework/zend-diactoros": "^1.4" "zendframework/zend-diactoros": "^1.4"
}, },
"autoload": { "autoload": {
@@ -45,5 +46,15 @@
"ext-bz2": "Needed to support BZIP2 compression", "ext-bz2": "Needed to support BZIP2 compression",
"ext-fileinfo": "Needed to get mime-type file" "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"
}
}
} }

View File

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

View File

@@ -8,12 +8,12 @@ use PhpZip\Exception\ZipCryptoException;
use PhpZip\Exception\ZipException; use PhpZip\Exception\ZipException;
use PhpZip\Extra\Fields\WinZipAesEntryExtraField; use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Model\ZipEntry; use PhpZip\Model\ZipEntry;
use PhpZip\Util\CryptoUtil;
/** /**
* WinZip Aes Encryption Engine. * WinZip Aes Encryption Engine.
* *
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru * @author Ne-Lexa alexey@nelexa.ru
* @license MIT * @license MIT
*/ */
@@ -24,18 +24,18 @@ class WinZipAesEngine implements ZipEncryptionEngine
* in bits (AES_BLOCK_SIZE_BITS). * in bits (AES_BLOCK_SIZE_BITS).
*/ */
const AES_BLOCK_SIZE_BITS = 128; const AES_BLOCK_SIZE_BITS = 128;
const PWD_VERIFIER_BITS = 16; 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; const ITERATION_COUNT = 1000;
/**
* @var ZipEntry /** @var ZipEntry */
*/
private $entry; private $entry;
/** /**
* WinZipAesEngine constructor. * WinZipAesEngine constructor.
*
* @param ZipEntry $entry * @param ZipEntry $entry
*/ */
public function __construct(ZipEntry $entry) public function __construct(ZipEntry $entry)
@@ -47,17 +47,19 @@ class WinZipAesEngine implements ZipEncryptionEngine
* Decrypt from stream resource. * Decrypt from stream resource.
* *
* @param string $content Input stream buffer * @param string $content Input stream buffer
* @return string *
* @throws ZipException
* @throws ZipAuthenticationException * @throws ZipAuthenticationException
* @throws ZipCryptoException * @throws ZipCryptoException
* @throws \PhpZip\Exception\ZipException *
* @return string
*/ */
public function decrypt($content) public function decrypt($content)
{ {
$extraFieldsCollection = $this->entry->getExtraFieldsCollection(); $extraFieldsCollection = $this->entry->getExtraFieldsCollection();
if (!isset($extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()])) { 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. // Init start, end and size of encrypted data.
$start = $pos; $start = $pos;
$endPos = strlen($content); $endPos = \strlen($content);
$footerSize = $sha1Size / 2; $footerSize = $sha1Size / 2;
$end = $endPos - $footerSize; $end = $endPos - $footerSize;
$size = $end - $start; $size = $end - $start;
if (0 > $size) { if ($size < 0) {
throw new ZipCryptoException($this->entry->getName() . " (false positive WinZip AES entry is too short)"); throw new ZipCryptoException($this->entry->getName() . ' (false positive WinZip AES entry is too short)');
} }
// Load authentication code. // Load authentication code.
$authenticationCode = substr($content, $end, $footerSize); $authenticationCode = substr($content, $end, $footerSize);
if ($end + $footerSize !== $endPos) { if ($end + $footerSize !== $endPos) {
// This should never happen unless someone is writing to the // This should never happen unless someone is writing to the
// end of the file concurrently! // 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(); $password = $this->entry->getPassword();
assert($password !== null);
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
// WinZip 99-character limit if ($password === null) {
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ 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); $password = substr($password, 0, 99);
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8; $ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(chr(0), $ctrIvSize); $iv = str_repeat(\chr(0), $ctrIvSize);
do { do {
// Here comes the strange part about WinZip AES encryption: // Here comes the strange part about WinZip AES encryption:
// Its unorthodox use of the Password-Based Key Derivation // 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. // Yes, the password verifier is only a 16 bit value.
// So we must use the MAC for password verification, too. // So we must use the MAC for password verification, too.
$keyParam = hash_pbkdf2( $keyParam = hash_pbkdf2(
"sha1", 'sha1',
$password, $password,
$salt, $salt,
self::ITERATION_COUNT, self::ITERATION_COUNT,
@@ -126,9 +135,11 @@ class WinZipAesEngine implements ZipEncryptionEngine
$content = substr($content, $start, $size); $content = substr($content, $start, $size);
$mac = hash_hmac('sha1', $content, $sha1MacParam, true); $mac = hash_hmac('sha1', $content, $sha1MacParam, true);
if (substr($mac, 0, 10) !== $authenticationCode) { if (strpos($mac, $authenticationCode) !== 0) {
throw new ZipAuthenticationException($this->entry->getName() . throw new ZipAuthenticationException(
" (authenticated WinZip AES entry content has been tampered with)"); $this->entry->getName() .
' (authenticated WinZip AES entry content has been tampered with)'
);
} }
return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false); return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false);
@@ -141,21 +152,23 @@ class WinZipAesEngine implements ZipEncryptionEngine
* @param string $key Key * @param string $key Key
* @param string $iv IV * @param string $iv IV
* @param bool $encrypted If true encryption else decryption * @param bool $encrypted If true encryption else decryption
*
* @return string * @return string
*/ */
private static function aesCtrSegmentIntegerCounter($str, $key, $iv, $encrypted = true) private static function aesCtrSegmentIntegerCounter($str, $key, $iv, $encrypted = true)
{ {
$numOfBlocks = ceil(strlen($str) / 16); $numOfBlocks = ceil(\strlen($str) / 16);
$ctrStr = ''; $ctrStr = '';
for ($i = 0; $i < $numOfBlocks; ++$i) { for ($i = 0; $i < $numOfBlocks; ++$i) {
for ($j = 0; $j < 16; ++$j) { for ($j = 0; $j < 16; ++$j) {
$n = ord($iv[$j]); $n = \ord($iv[$j]);
if (++$n === 0x100) { if (++$n === 0x100) {
// overflow, set this one to 0, increment next // overflow, set this one to 0, increment next
$iv[$j] = chr(0); $iv[$j] = \chr(0);
} else { } else {
// no overflow, just write incremented number back and abort // no overflow, just write incremented number back and abort
$iv[$j] = chr($n); $iv[$j] = \chr($n);
break; break;
} }
} }
@@ -164,6 +177,7 @@ class WinZipAesEngine implements ZipEncryptionEngine
self::encryptCtr($data, $key, $iv) : self::encryptCtr($data, $key, $iv) :
self::decryptCtr($data, $key, $iv); self::decryptCtr($data, $key, $iv);
} }
return $ctrStr; return $ctrStr;
} }
@@ -173,21 +187,23 @@ class WinZipAesEngine implements ZipEncryptionEngine
* @param string $data Raw data * @param string $data Raw data
* @param string $key Aes key * @param string $key Aes key
* @param string $iv Aes IV * @param string $iv Aes IV
*
* @return string Encrypted data * @return string Encrypted data
*/ */
private static function encryptCtr($data, $key, $iv) private static function encryptCtr($data, $key, $iv)
{ {
if (extension_loaded("openssl")) { if (\extension_loaded('openssl')) {
$numBits = strlen($key) * 8; $numBits = \strlen($key) * 8;
/** @noinspection PhpComposerExtensionStubsInspection */ /** @noinspection PhpComposerExtensionStubsInspection */
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv); 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');
} }
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');
} }
/** /**
@@ -196,53 +212,74 @@ class WinZipAesEngine implements ZipEncryptionEngine
* @param string $data Encrypted data * @param string $data Encrypted data
* @param string $key Aes key * @param string $key Aes key
* @param string $iv Aes IV * @param string $iv Aes IV
*
* @return string Raw data * @return string Raw data
*/ */
private static function decryptCtr($data, $key, $iv) private static function decryptCtr($data, $key, $iv)
{ {
if (extension_loaded("openssl")) { if (\extension_loaded('openssl')) {
$numBits = strlen($key) * 8; $numBits = \strlen($key) * 8;
/** @noinspection PhpComposerExtensionStubsInspection */ /** @noinspection PhpComposerExtensionStubsInspection */
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv); 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');
} }
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. * Encryption string.
* *
* @param string $content * @param string $content
*
* @throws ZipException
*
* @return string * @return string
* @throws \PhpZip\Exception\ZipException
*/ */
public function encrypt($content) public function encrypt($content)
{ {
// Init key strength. // Init key strength.
$password = $this->entry->getPassword(); $password = $this->entry->getPassword();
if ($password === null) { 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); $password = substr($password, 0, 99);
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($this->entry->getEncryptionMethod()); $keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
$this->entry->getEncryptionMethod()
);
$keyStrengthBytes = $keyStrengthBits / 8; $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); $sha1HMacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
// Can you believe they "forgot" the nonce in the CTR mode IV?! :-( // Can you believe they "forgot" the nonce in the CTR mode IV?! :-(
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8; $ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(chr(0), $ctrIvSize); $iv = str_repeat(\chr(0), $ctrIvSize);
$key = substr($keyParam, 0, $keyStrengthBytes); $key = substr($keyParam, 0, $keyStrengthBytes);
@@ -250,10 +287,9 @@ class WinZipAesEngine implements ZipEncryptionEngine
$mac = hash_hmac('sha1', $content, $sha1HMacParam, true); $mac = hash_hmac('sha1', $content, $sha1HMacParam, true);
return ($salt . return $salt .
substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) . substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) .
$content . $content .
substr($mac, 0, 10) substr($mac, 0, 10);
);
} }
} }

View File

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

View File

@@ -39,13 +39,12 @@ class Crc32Exception extends ZipException
{ {
parent::__construct( parent::__construct(
sprintf( sprintf(
"%s (expected CRC32 value 0x%x, but is actually 0x%x)", '%s (expected CRC32 value 0x%x, but is actually 0x%x)',
$name, $name,
$expected, $expected,
$actual $actual
) )
); );
assert($expected != $actual);
$this->expectedCrc = $expected; $this->expectedCrc = $expected;
$this->actualCrc = $actual; $this->actualCrc = $actual;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
namespace PhpZip\Model; namespace PhpZip\Model;
/** /**
* Read End of Central Directory * End of Central Directory.
* *
* @author Ne-Lexa alexey@nelexa.ru * @author Ne-Lexa alexey@nelexa.ru
* @license MIT * @license MIT
@@ -11,11 +11,14 @@ namespace PhpZip\Model;
class EndOfCentralDirectory class EndOfCentralDirectory
{ {
/** Zip64 End Of Central Directory Record. */ /** 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. */ /** 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. */ /** 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. * The minimum length of the End Of Central Directory Record.
* *
@@ -34,6 +37,7 @@ class EndOfCentralDirectory
* zipfile comment length 2 * zipfile comment length 2
*/ */
const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22; const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
/** /**
* The length of the Zip64 End Of Central Directory Locator. * The length of the Zip64 End Of Central Directory Locator.
* zip64 end of central dir locator * zip64 end of central dir locator
@@ -43,9 +47,10 @@ class EndOfCentralDirectory
* central directory 4 * central directory 4
* relative offset of the zip64 * relative offset of the zip64
* end of central directory record 8 * 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. * The minimum length of the Zip64 End Of Central Directory Record.
* *
@@ -68,38 +73,46 @@ class EndOfCentralDirectory
* the starting disk number 8 * the starting disk number 8
*/ */
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56; const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
/**
* @var string|null The archive comment. /** @var int Count files. */
*/
private $comment;
/**
* @var int
*/
private $entryCount; private $entryCount;
/**
* @var bool /** @var int Central Directory Offset. */
*/ private $cdOffset;
private $zip64 = false;
/** @var int */
private $cdSize;
/** @var string|null The archive comment. */
private $comment;
/** @var bool Zip64 extension */
private $zip64;
/** /**
* EndOfCentralDirectory constructor. * EndOfCentralDirectory constructor.
*
* @param int $entryCount * @param int $entryCount
* @param null|string $comment * @param int $cdOffset
* @param int $cdSize
* @param bool $zip64 * @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->entryCount = $entryCount;
$this->comment = $comment; $this->cdOffset = $cdOffset;
$this->cdSize = $cdSize;
$this->zip64 = $zip64; $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 $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 * @return bool
*/ */

View File

@@ -9,16 +9,15 @@ use PhpZip\Model\ZipEntry;
* *
* @author Ne-Lexa alexey@nelexa.ru * @author Ne-Lexa alexey@nelexa.ru
* @license MIT * @license MIT
*
* @internal
*/ */
class OutputOffsetEntry class OutputOffsetEntry
{ {
/** /** @var int */
* @var int
*/
private $offset; private $offset;
/**
* @var ZipEntry /** @var ZipEntry */
*/
private $entry; private $entry;
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipModel; use PhpZip\Model\ZipModel;
/** /**
* Read zip file * Read zip file.
* *
* @author Ne-Lexa alexey@nelexa.ru * @author Ne-Lexa alexey@nelexa.ru
* @license MIT * @license MIT
@@ -20,14 +20,22 @@ interface ZipInputStreamInterface
public function readZip(); public function readZip();
/** /**
* Read central directory entry.
*
* @param resource $stream
*
* @throws ZipException
*
* @return ZipEntry * @return ZipEntry
*/ */
public function readEntry(); public function readCentralDirectoryEntry($stream);
/** /**
* @param ZipEntry $entry * @param ZipEntry $entry
* @return string *
* @throws ZipException * @throws ZipException
*
* @return string
*/ */
public function readEntryContent(ZipEntry $entry); public function readEntryContent(ZipEntry $entry);

View File

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

View File

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

View File

@@ -2,38 +2,26 @@
namespace PhpZip\Util; namespace PhpZip\Util;
use PhpZip\Exception\RuntimeException;
/** /**
* Crypto Utils * Crypto Utils.
*
* @deprecated
*/ */
class CryptoUtil class CryptoUtil
{ {
/** /**
* Returns random bytes. * Returns random bytes.
* *
* @param int $length * @param int $length
*
* @throws \Exception
*
* @return string * @return string
*
* @deprecated Use random_bytes()
*/ */
final public static function randomBytes($length) final public static function randomBytes($length)
{ {
$length = (int)$length;
if (function_exists('random_bytes')) {
try {
return random_bytes($length); 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');
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,54 +3,32 @@
namespace PhpZip\Util; namespace PhpZip\Util;
/** /**
* String Util * String Util.
*
* @internal
*/ */
class StringUtil final class StringUtil
{ {
/** /**
* @param string $haystack * @param string $haystack
* @param string $needle * @param string $needle
*
* @return bool * @return bool
*/ */
public static function startsWith($haystack, $needle) 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 $haystack
* @param string $needle * @param string $needle
*
* @return bool * @return bool
*/ */
public static function endsWith($haystack, $needle) 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); && 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);
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ use Psr\Http\Message\ResponseInterface;
* Support ZipAlign functional. * Support ZipAlign functional.
* *
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru * @author Ne-Lexa alexey@nelexa.ru
* @license MIT * @license MIT
*/ */
@@ -25,67 +26,66 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
{ {
/** /**
* Method for Stored (uncompressed) entries. * Method for Stored (uncompressed) entries.
*
* @see ZipEntry::setMethod() * @see ZipEntry::setMethod()
*/ */
const METHOD_STORED = 0; const METHOD_STORED = 0;
/** /**
* Method for Deflated compressed entries. * Method for Deflated compressed entries.
*
* @see ZipEntry::setMethod() * @see ZipEntry::setMethod()
*/ */
const METHOD_DEFLATED = 8; const METHOD_DEFLATED = 8;
/** /**
* Method for BZIP2 compressed entries. * Method for BZIP2 compressed entries.
* Require php extension bz2. * Require php extension bz2.
*
* @see ZipEntry::setMethod() * @see ZipEntry::setMethod()
*/ */
const METHOD_BZIP2 = 12; const METHOD_BZIP2 = 12;
/** /** Default compression level. */
* Default compression level.
*/
const LEVEL_DEFAULT_COMPRESSION = -1; const LEVEL_DEFAULT_COMPRESSION = -1;
/**
* Compression level for fastest compression. /** Compression level for fastest compression. */
*/
const LEVEL_FAST = 2; const LEVEL_FAST = 2;
/**
* Compression level for fastest compression. /** Compression level for fastest compression. */
*/
const LEVEL_BEST_SPEED = 1; const LEVEL_BEST_SPEED = 1;
const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED; const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED;
/**
* Compression level for best compression. /** Compression level for best compression. */
*/
const LEVEL_BEST_COMPRESSION = 9; 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; const ENCRYPTION_METHOD_TRADITIONAL = 0;
/** /**
* No specified method for set encryption method to WinZip AES encryption. * 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; 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; 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; 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; const ENCRYPTION_METHOD_WINZIP_AES_256 = 1;
/** /**
* Open zip archive from file * Open zip archive from file.
* *
* @param string $filename * @param string $filename
*
* @throws ZipException if can't open file
*
* @return ZipFileInterface * @return ZipFileInterface
* @throws ZipException if can't open file.
*/ */
public function openFile($filename); public function openFile($filename);
@@ -93,35 +93,39 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Open zip archive from raw string data. * Open zip archive from raw string data.
* *
* @param string $data * @param string $data
*
* @throws ZipException if can't open temp stream
*
* @return ZipFileInterface * @return ZipFileInterface
* @throws ZipException if can't open temp stream.
*/ */
public function openFromString($data); public function openFromString($data);
/** /**
* Open zip archive from stream resource * Open zip archive from stream resource.
* *
* @param resource $handle * @param resource $handle
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function openFromStream($handle); public function openFromStream($handle);
/** /**
* @return string[] Returns the list files. * @return string[] returns the list files
*/ */
public function getListFiles(); public function getListFiles();
/** /**
* Returns the file comment. * Returns the file comment.
* *
* @return string The file comment. * @return string the file comment
*/ */
public function getArchiveComment(); public function getArchiveComment();
/** /**
* Set archive comment. * Set archive comment.
* *
* @param null|string $comment * @param string|null $comment
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function setArchiveComment($comment = null); public function setArchiveComment($comment = null);
@@ -132,8 +136,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* (i.e. end with '/'). * (i.e. end with '/').
* *
* @param string $entryName * @param string $entryName
* @return bool *
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
*
* @return bool
*/ */
public function isDirectory($entryName); public function isDirectory($entryName);
@@ -141,8 +147,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Returns entry comment. * Returns entry comment.
* *
* @param string $entryName * @param string $entryName
* @return string *
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
*
* @return string
*/ */
public function getEntryComment($entryName); public function getEntryComment($entryName);
@@ -151,8 +159,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* *
* @param string $entryName * @param string $entryName
* @param string|null $comment * @param string|null $comment
* @return ZipFileInterface *
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
*/ */
public function setEntryComment($entryName, $comment = null); public function setEntryComment($entryName, $comment = null);
@@ -160,6 +170,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Returns the entry contents. * Returns the entry contents.
* *
* @param string $entryName * @param string $entryName
*
* @return string * @return string
*/ */
public function getEntryContents($entryName); public function getEntryContents($entryName);
@@ -168,6 +179,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Checks if there is an entry in the archive. * Checks if there is an entry in the archive.
* *
* @param string $entryName * @param string $entryName
*
* @return bool * @return bool
*/ */
public function hasEntry($entryName); public function hasEntry($entryName);
@@ -176,8 +188,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Get info by entry. * Get info by entry.
* *
* @param string|ZipEntry $entryName * @param string|ZipEntry $entryName
* @return ZipInfo *
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
*
* @return ZipInfo
*/ */
public function getEntryInfo($entryName); public function getEntryInfo($entryName);
@@ -194,60 +208,68 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
public function matcher(); public function matcher();
/** /**
* Extract the archive contents * Extract the archive contents.
* *
* Extract the complete archive or the given files to the specified destination. * Extract the complete archive or the given files to the specified destination.
* *
* @param string $destination Location where to extract the files. * @param string $destination location where to extract the files
* @param array|string|null $entries The entries to extract. It accepts either * @param array|string|null $entries The entries to extract. It accepts either
* a single entry name or an array of names. * a single entry name or an array of names.
* @return ZipFileInterface *
* @throws ZipException * @throws ZipException
*
* @return ZipFileInterface
*/ */
public function extractTo($destination, $entries = null); public function extractTo($destination, $entries = null);
/** /**
* Add entry from the string. * Add entry from the string.
* *
* @param string $localName Zip entry name. * @param string $localName zip entry name
* @param string $contents String contents. * @param string $contents string contents
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method. * If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED *
* @see ZipFileInterface::METHOD_DEFLATED * @see ZipFile::METHOD_STORED
* @see ZipFileInterface::METHOD_BZIP2 * @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/ */
public function addFromString($localName, $contents, $compressionMethod = null); public function addFromString($localName, $contents, $compressionMethod = null);
/** /**
* Add entry from the file. * Add entry from the file.
* *
* @param string $filename Destination file. * @param string $filename destination file
* @param string|null $localName Zip Entry name. * @param string|null $localName zip Entry name
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* If null, then auto choosing method. * ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED *
* @see ZipFileInterface::METHOD_DEFLATED * @see ZipFile::METHOD_STORED
* @see ZipFileInterface::METHOD_BZIP2 * @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/ */
public function addFile($filename, $localName = null, $compressionMethod = null); public function addFile($filename, $localName = null, $compressionMethod = null);
/** /**
* Add entry from the stream. * Add entry from the stream.
* *
* @param resource $stream Stream resource. * @param resource $stream stream resource
* @param string $localName Zip Entry name. * @param string $localName zip Entry name
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method. * If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED *
* @see ZipFileInterface::METHOD_DEFLATED * @see ZipFile::METHOD_STORED
* @see ZipFileInterface::METHOD_BZIP2 * @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/ */
public function addFromStream($stream, $localName, $compressionMethod = null); 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. * Add an empty directory in the zip archive.
* *
* @param string $dirName * @param string $dirName
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function addEmptyDir($dirName); public function addEmptyDir($dirName);
@@ -263,41 +286,46 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Add directory not recursively to the zip archive. * Add directory not recursively to the zip archive.
* *
* @param string $inputDir Input directory * @param string $inputDir Input directory
* @param string $localPath Add files to this directory, or the root. * @param string $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method. * If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function addDir($inputDir, $localPath = "/", $compressionMethod = null); public function addDir($inputDir, $localPath = '/', $compressionMethod = null);
/** /**
* Add recursive directory to the zip archive. * Add recursive directory to the zip archive.
* *
* @param string $inputDir Input directory * @param string $inputDir Input directory
* @param string $localPath Add files to this directory, or the root. * @param string $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
* If null, then auto choosing method. * If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED *
* @see ZipFileInterface::METHOD_DEFLATED * @see ZipFile::METHOD_STORED
* @see ZipFileInterface::METHOD_BZIP2 * @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. * Add directories from directory iterator.
* *
* @param \Iterator $iterator Directory iterator. * @param \Iterator $iterator directory iterator
* @param string $localPath Add files to this directory, or the root. * @param string $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* If null, then auto choosing method. * ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @see ZipFileInterface::METHOD_STORED *
* @see ZipFileInterface::METHOD_DEFLATED * @see ZipFile::METHOD_STORED
* @see ZipFileInterface::METHOD_BZIP2 * @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/ */
public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null); public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null);
@@ -305,11 +333,12 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Add files from glob pattern. * Add files from glob pattern.
* *
* @param string $inputDir Input directory * @param string $inputDir Input directory
* @param string $globPattern Glob pattern. * @param string $globPattern glob pattern
* @param string|null $localPath Add files to this directory, or the root. * @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* If null, then auto choosing method. * ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/ */
@@ -319,11 +348,12 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Add files recursively from glob pattern. * Add files recursively from glob pattern.
* *
* @param string $inputDir Input directory * @param string $inputDir Input directory
* @param string $globPattern Glob pattern. * @param string $globPattern glob pattern
* @param string|null $localPath Add files to this directory, or the root. * @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* If null, then auto choosing method. * ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface * @return ZipFileInterface
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax * @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. * Add files from regex pattern.
* *
* @param string $inputDir Search files in this directory. * @param string $inputDir search files in this directory
* @param string $regexPattern Regex pattern. * @param string $regexPattern regex pattern
* @param string|null $localPath Add files to this directory, or the root. * @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* If null, then auto choosing method. * ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface * @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. * Add files recursively from regex pattern.
* *
* @param string $inputDir Search files in this directory. * @param string $inputDir search files in this directory
* @param string $regexPattern Regex pattern. * @param string $regexPattern regex pattern
* @param string|null $localPath Add files to this directory, or the root. * @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method. * @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* If null, then auto choosing method. * ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface * @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. * Add array data to archive.
* Keys is local names. * Keys is local names.
* Values is contents. * 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); public function addAll(array $mapData);
/** /**
* Rename the entry. * Rename the entry.
* *
* @param string $oldName Old entry name. * @param string $oldName old entry name
* @param string $newName New entry name. * @param string $newName new entry name
* @return ZipFileInterface *
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
*/ */
public function rename($oldName, $newName); public function rename($oldName, $newName);
/** /**
* Delete entry by name. * Delete entry by name.
* *
* @param string $entryName Zip Entry name. * @param string $entryName zip Entry name
*
* @throws ZipEntryNotFoundException if entry not found
*
* @return ZipFileInterface * @return ZipFileInterface
* @throws ZipEntryNotFoundException If entry not found.
*/ */
public function deleteFromName($entryName); public function deleteFromName($entryName);
@@ -389,6 +427,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Delete entries by glob pattern. * Delete entries by glob pattern.
* *
* @param string $globPattern Glob pattern * @param string $globPattern Glob pattern
*
* @return ZipFileInterface * @return ZipFileInterface
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax * @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. * Delete entries by regex pattern.
* *
* @param string $regexPattern Regex pattern * @param string $regexPattern Regex pattern
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function deleteFromRegex($regexPattern); public function deleteFromRegex($regexPattern);
/** /**
* Delete all entries * Delete all entries.
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function deleteAll(); public function deleteAll();
@@ -412,34 +453,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set compression level for new entries. * Set compression level for new entries.
* *
* @param int $compressionLevel * @param int $compressionLevel
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION *
* @see ZipFileInterface::LEVEL_SUPER_FAST
* @see ZipFileInterface::LEVEL_FAST
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
* @return ZipFileInterface * @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); public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
/** /**
* @param string $entryName * @param string $entryName
* @param int $compressionLevel * @param int $compressionLevel
* @return ZipFileInterface *
* @throws ZipException * @throws ZipException
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION *
* @see ZipFileInterface::LEVEL_SUPER_FAST * @return ZipFileInterface
* @see ZipFileInterface::LEVEL_FAST *
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION * @see ZipFile::LEVEL_DEFAULT_COMPRESSION
* @see ZipFile::LEVEL_SUPER_FAST
* @see ZipFile::LEVEL_FAST
* @see ZipFile::LEVEL_BEST_COMPRESSION
*/ */
public function setCompressionLevelEntry($entryName, $compressionLevel); public function setCompressionLevelEntry($entryName, $compressionLevel);
/** /**
* @param string $entryName * @param string $entryName
* @param int $compressionMethod * @param int $compressionMethod
* @return ZipFileInterface *
* @throws ZipException * @throws ZipException
* @see ZipFileInterface::METHOD_STORED *
* @see ZipFileInterface::METHOD_DEFLATED * @return ZipFileInterface
* @see ZipFileInterface::METHOD_BZIP2 *
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/ */
public function setCompressionMethodEntry($entryName, $compressionMethod); public function setCompressionMethodEntry($entryName, $compressionMethod);
@@ -447,8 +496,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* zipalign is optimization to Android application (APK) files. * zipalign is optimization to Android application (APK) files.
* *
* @param int|null $align * @param int|null $align
*
* @return ZipFileInterface * @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); public function setZipAlign($align = null);
@@ -456,8 +507,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set password to all input encrypted entries. * Set password to all input encrypted entries.
* *
* @param string $password Password * @param string $password Password
*
* @return ZipFileInterface * @return ZipFileInterface
* @deprecated using ZipFileInterface::setReadPassword() *
* @deprecated using ZipFile::setReadPassword()
*/ */
public function withReadPassword($password); public function withReadPassword($password);
@@ -465,6 +518,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Set password to all input encrypted entries. * Set password to all input encrypted entries.
* *
* @param string $password Password * @param string $password Password
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function setReadPassword($password); public function setReadPassword($password);
@@ -474,6 +528,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* *
* @param string $entryName * @param string $entryName
* @param string $password Password * @param string $password Password
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function setReadPasswordEntry($entryName, $password); public function setReadPasswordEntry($entryName, $password);
@@ -483,8 +538,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* *
* @param string $password If password null then encryption clear * @param string $password If password null then encryption clear
* @param int|null $encryptionMethod Encryption method * @param int|null $encryptionMethod Encryption method
*
* @return ZipFileInterface * @return ZipFileInterface
* @deprecated using ZipFileInterface::setPassword() *
* @deprecated using ZipFile::setPassword()
*/ */
public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256); public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
@@ -493,6 +550,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* *
* @param string $password * @param string $password
* @param int|null $encryptionMethod Encryption method * @param int|null $encryptionMethod Encryption method
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256); public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
@@ -503,38 +561,46 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* @param string $entryName * @param string $entryName
* @param string $password * @param string $password
* @param int|null $encryptionMethod * @param int|null $encryptionMethod
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function setPasswordEntry($entryName, $password, $encryptionMethod = null); public function setPasswordEntry($entryName, $password, $encryptionMethod = null);
/** /**
* Remove password for all entries for update. * Remove password for all entries for update.
*
* @return ZipFileInterface * @return ZipFileInterface
* @deprecated using ZipFileInterface::disableEncryption() *
* @deprecated using ZipFile::disableEncryption()
*/ */
public function withoutPassword(); public function withoutPassword();
/** /**
* Disable encryption for all entries that are already in the archive. * Disable encryption for all entries that are already in the archive.
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function disableEncryption(); public function disableEncryption();
/** /**
* Disable encryption of an entry defined by its name. * Disable encryption of an entry defined by its name.
*
* @param string $entryName * @param string $entryName
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function disableEncryptionEntry($entryName); public function disableEncryptionEntry($entryName);
/** /**
* Undo all changes done in the archive * Undo all changes done in the archive.
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function unchangeAll(); public function unchangeAll();
/** /**
* Undo change archive comment * Undo change archive comment.
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function unchangeArchiveComment(); public function unchangeArchiveComment();
@@ -543,6 +609,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Revert all changes done to an entry with the given name. * Revert all changes done to an entry with the given name.
* *
* @param string|ZipEntry $entry Entry name or ZipEntry * @param string|ZipEntry $entry Entry name or ZipEntry
*
* @return ZipFileInterface * @return ZipFileInterface
*/ */
public function unchangeEntry($entry); public function unchangeEntry($entry);
@@ -551,8 +618,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Save as file. * Save as file.
* *
* @param string $filename Output filename * @param string $filename Output filename
* @return ZipFileInterface *
* @throws ZipException * @throws ZipException
*
* @return ZipFileInterface
*/ */
public function saveAsFile($filename); public function saveAsFile($filename);
@@ -560,8 +629,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Save as stream. * Save as stream.
* *
* @param resource $handle Output stream resource * @param resource $handle Output stream resource
* @return ZipFileInterface *
* @throws ZipException * @throws ZipException
*
* @return ZipFileInterface
*/ */
public function saveAsStream($handle); public function saveAsStream($handle);
@@ -582,20 +653,29 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* @param string $outputFilename Output filename * @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type * @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
*
* @return ResponseInterface * @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. * Returns the zip archive as a string.
*
* @return string * @return string
*/ */
public function outputAsString(); public function outputAsString();
/** /**
* Save and reopen zip archive. * Save and reopen zip archive.
* @return ZipFileInterface *
* @throws ZipException * @throws ZipException
*
* @return ZipFileInterface
*/ */
public function rewrite(); public function rewrite();

View File

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

View File

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

View File

@@ -3,24 +3,31 @@
namespace PhpZip; namespace PhpZip;
use PhpZip\Exception\ZipException; use PhpZip\Exception\ZipException;
use PhpZip\Util\CryptoUtil;
/**
* @internal
*
* @small
*/
class Issue24Test extends ZipTestCase class Issue24Test extends ZipTestCase
{ {
/** /**
* This method is called before the first test of this test class is run. * This method is called before the first test of this test class is run.
*
* @noinspection PhpMissingParentCallCommonInspection
*/ */
public static function setUpBeforeClass() public static function setUpBeforeClass()
{ {
stream_wrapper_register("dummyfs", DummyFileSystemStream::class); stream_wrapper_register('dummyfs', Internal\DummyFileSystemStream::class);
} }
/** /**
* @throws ZipException * @throws ZipException
* @throws \Exception
*/ */
public function testDummyFS() public function testDummyFS()
{ {
$fileContents = str_repeat(base64_encode(CryptoUtil::randomBytes(12000)), 100); $fileContents = str_repeat(base64_encode(random_bytes(12000)), 100);
// create zip file // create zip file
$zip = new ZipFile(); $zip = new ZipFile();
@@ -32,73 +39,13 @@ class Issue24Test extends ZipTestCase
$zip->saveAsFile($this->outputFilename); $zip->saveAsFile($this->outputFilename);
$zip->close(); $zip->close();
$this->assertCorrectZipArchive($this->outputFilename); static::assertCorrectZipArchive($this->outputFilename);
$stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb'); $stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb');
$this->assertNotFalse($stream); static::assertNotFalse($stream);
$zip->openFromStream($stream); $zip->openFromStream($stream);
$this->assertEquals($zip->getListFiles(), ['file.txt']); static::assertSame($zip->getListFiles(), ['file.txt']);
$this->assertEquals($zip['file.txt'], $fileContents); static::assertSame($zip['file.txt'], $fileContents);
$zip->close(); $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);
}
}

View File

@@ -2,16 +2,25 @@
namespace PhpZip; namespace PhpZip;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipException; use PhpZip\Exception\ZipException;
/** /**
* Some tests from the official extension of php-zip. * Some tests from the official extension of php-zip.
*
* @internal
*
* @small
*/ */
class PhpZipExtResourceTest extends ZipTestCase 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 * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug7214.phpt
*
* @throws ZipException * @throws ZipException
*/ */
public function testBinaryNull() public function testBinaryNull()
@@ -20,18 +29,21 @@ class PhpZipExtResourceTest extends ZipTestCase
$zipFile = new ZipFile(); $zipFile = new ZipFile();
$zipFile->openFile($filename); $zipFile->openFile($filename);
foreach ($zipFile as $name => $contents) { foreach ($zipFile as $name => $contents) {
$info = $zipFile->getEntryInfo($name); $info = $zipFile->getEntryInfo($name);
$this->assertEquals(strlen($contents), $info->getSize()); static::assertSame(\strlen($contents), $info->getSize());
} }
$zipFile->close(); $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 * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug8009.phpt
*
* @throws ZipException * @throws ZipException
*/ */
public function testBug8009() public function testBug8009()
@@ -44,36 +56,42 @@ class PhpZipExtResourceTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename); $zipFile->saveAsFile($this->outputFilename);
$zipFile->close(); $zipFile->close();
$this->assertCorrectZipArchive($this->outputFilename); static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename); $zipFile->openFile($this->outputFilename);
$this->assertCount(2, $zipFile); static::assertCount(2, $zipFile);
$this->assertTrue(isset($zipFile['1.txt'])); static::assertTrue(isset($zipFile['1.txt']));
$this->assertTrue(isset($zipFile['2.txt'])); static::assertTrue(isset($zipFile['2.txt']));
$this->assertEquals($zipFile['2.txt'], $zipFile['1.txt']); static::assertSame($zipFile['2.txt'], $zipFile['1.txt']);
$zipFile->close(); $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.phpt
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228-mb.phpt * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228-mb.phpt
* @dataProvider provideBug40228 * @dataProvider provideBug40228
*
* @param string $filename * @param string $filename
*
* @throws ZipException * @throws ZipException
*/ */
public function testBug40228($filename) public function testBug40228($filename)
{ {
$this->assertTrue(mkdir($this->outputDirname, 0755, true)); static::assertTrue(mkdir($this->outputDirname, 0755, true));
$zipFile = new ZipFile(); $zipFile = new ZipFile();
$zipFile->openFile($filename); $zipFile->openFile($filename);
$zipFile->extractTo($this->outputDirname); $zipFile->extractTo($this->outputDirname);
$zipFile->close(); $zipFile->close();
$this->assertTrue(is_dir($this->outputDirname . '/test/empty')); static::assertTrue(is_dir($this->outputDirname . '/test/empty'));
} }
/**
* @return array
*/
public function provideBug40228() public function provideBug40228()
{ {
return [ 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 * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug49072.phpt
* @expectedException \PhpZip\Exception\Crc32Exception *
* @expectedExceptionMessage file1
* @throws ZipException * @throws ZipException
*/ */
public function testBug49072() public function testBug49072()
{ {
$this->setExpectedException(Crc32Exception::class, 'file1');
$filename = __DIR__ . '/php-zip-ext-test-resources/bug49072.zip'; $filename = __DIR__ . '/php-zip-ext-test-resources/bug49072.zip';
$zipFile = new ZipFile(); $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 * @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 * @throws ZipException
*/ */
public function testBug70752() 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'; $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 = new ZipFile();
try {
$zipFile->openFile($filename); $zipFile->openFile($filename);
$zipFile->setReadPassword('bar'); $zipFile->setReadPassword('bar');
try {
$zipFile->extractTo($this->outputDirname); $zipFile->extractTo($this->outputDirname);
$this->markTestIncomplete('failed test'); static::markTestIncomplete('failed test');
} catch (ZipException $exception) { } catch (ZipException $exception) {
$this->assertFalse(file_exists($this->outputDirname . '/bug70752.txt')); static::assertFileNotExists($this->outputDirname . '/bug70752.txt');
$zipFile->close(); $zipFile->close();
throw $exception; 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 * @see https://github.com/php/php-src/blob/master/ext/zip/tests/pecl12414.phpt
*
* @throws ZipException * @throws ZipException
*/ */
public function testPecl12414() 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 = new ZipFile();
$zipFile->openFile($filename); $zipFile->openFile($filename);
$info = $zipFile->getEntryInfo($entryName);
$this->assertTrue($info->getSize() > 0);
$contents = $zipFile[$entryName];
$this->assertEquals(strlen($contents), $info->getSize());
$zipFile->close();
} }
} }

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,32 +2,31 @@
namespace PhpZip; namespace PhpZip;
use PHPUnit\Framework\TestCase;
use PhpZip\Model\EndOfCentralDirectory; use PhpZip\Model\EndOfCentralDirectory;
use PhpZip\Util\FilesUtil; use PhpZip\Util\FilesUtil;
/** /**
* PHPUnit test case and helper methods. * PHPUnit test case and helper methods.
*/ */
class ZipTestCase extends \PHPUnit_Framework_TestCase abstract class ZipTestCase extends TestCase
{ {
/** /** @var string */
* @var string
*/
protected $outputFilename; protected $outputFilename;
/**
* @var string /** @var string */
*/
protected $outputDirname; protected $outputDirname;
/** /**
* Before test * Before test.
*
* @noinspection PhpMissingParentCallCommonInspection
*/ */
protected function setUp() protected function setUp()
{ {
parent::setUp();
$id = uniqid('phpzip', true); $id = uniqid('phpzip', true);
$tempDir = sys_get_temp_dir() . '/phpunit-phpzip'; $tempDir = sys_get_temp_dir() . '/phpunit-phpzip';
if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) { if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) {
throw new \RuntimeException('Dir ' . $tempDir . " can't created"); 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() protected function tearDown()
{ {
@@ -45,6 +44,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
if ($this->outputFilename !== null && file_exists($this->outputFilename)) { if ($this->outputFilename !== null && file_exists($this->outputFilename)) {
unlink($this->outputFilename); unlink($this->outputFilename);
} }
if ($this->outputDirname !== null && is_dir($this->outputDirname)) { if ($this->outputDirname !== null && is_dir($this->outputDirname)) {
FilesUtil::removeDir($this->outputDirname); FilesUtil::removeDir($this->outputDirname);
} }
@@ -60,18 +60,23 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
{ {
if (self::existsProgram('unzip')) { if (self::existsProgram('unzip')) {
$command = 'unzip'; $command = 'unzip';
if ($password !== null) { if ($password !== null) {
$command .= ' -P ' . escapeshellarg($password); $command .= ' -P ' . escapeshellarg($password);
} }
$command .= ' -t ' . escapeshellarg($filename); $command .= ' -t ' . escapeshellarg($filename);
$command .= ' 2>&1';
exec($command, $output, $returnCode); exec($command, $output, $returnCode);
$output = implode(PHP_EOL, $output); $output = implode(\PHP_EOL, $output);
if ($password !== null && $returnCode === 81) { if ($password !== null && $returnCode === 81) {
if (self::existsProgram('7z')) { 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); $password = substr($password, 0, 99);
$command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename); $command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename);
@@ -79,31 +84,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
/** /**
* @var array $output * @var array $output
*/ */
$output = implode(PHP_EOL, $output); $output = implode(\PHP_EOL, $output);
self::assertEquals($returnCode, 0); static::assertSame($returnCode, 0);
self::assertNotContains(' Errors', $output); static::assertNotContains(' Errors', $output);
self::assertContains(' Ok', $output); static::assertContains(' Ok', $output);
} else { } else {
fwrite(STDERR, 'Program unzip cannot support this function.' . 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); fwrite(\STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . \PHP_EOL);
} }
} else { } else {
self::assertEquals($returnCode, 0, $output); static::assertSame($returnCode, 0, $output);
self::assertNotContains('incorrect password', $output); static::assertNotContains('incorrect password', $output);
self::assertContains(' OK', $output); static::assertContains(' OK', $output);
self::assertContains('No errors', $output); static::assertContains('No errors', $output);
} }
} }
} }
/** /**
* @param string $program * @param string $program
*
* @return bool * @return bool
*/ */
private static function existsProgram($program){ private static function existsProgram($program)
if (DIRECTORY_SEPARATOR !== '\\') { {
if (\DIRECTORY_SEPARATOR !== '\\') {
exec('which ' . escapeshellarg($program), $output, $returnCode); exec('which ' . escapeshellarg($program), $output, $returnCode);
return $returnCode === 0; return $returnCode === 0;
} }
// false for Windows // false for Windows
@@ -120,30 +128,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
if (self::existsProgram('zipinfo')) { if (self::existsProgram('zipinfo')) {
exec('zipinfo ' . escapeshellarg($filename), $output, $returnCode); 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); $actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CD_SIG, 0, 0, 0, 0, 0);
self::assertStringEqualsFile($filename, $actualEmptyZipData); static::assertStringEqualsFile($filename, $actualEmptyZipData);
} }
/** /**
* @param string $filename * @param string $filename
* @param bool $showErrors * @param bool $showErrors
* @return bool|null If null - can not install zipalign *
* @return bool|null If null returned, then the zipalign program is not installed
*/ */
public static function assertVerifyZipAlign($filename, $showErrors = false) public static function assertVerifyZipAlign($filename, $showErrors = false)
{ {
if (self::existsProgram('zipalign')) { if (self::existsProgram('zipalign')) {
exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode); exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode);
if ($showErrors && $returnCode !== 0) { if ($showErrors && $returnCode !== 0) {
fwrite(STDERR, implode(PHP_EOL, $output)); fwrite(\STDERR, implode(\PHP_EOL, $output));
} }
return $returnCode === 0; 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; return null;
} }
} }