mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-07-20 07:21:17 +02:00
Merge branch 'hotfix/3.1.13'
This commit is contained in:
.php_cs.travis.ymlcomposer.json
src/PhpZip
Crypto
Exception
Crc32Exception.phpZipEntryNotFoundException.phpZipException.phpZipNotFoundEntry.phpZipUnsupportMethod.phpZipUnsupportMethodException.php
Extra
Mapper
Model
EndOfCentralDirectory.php
Entry
OutputOffsetEntry.phpZipAbstractEntry.phpZipChangesEntry.phpZipNewEntry.phpZipNewFileEntry.phpZipSourceEntry.php
ZipEntry.phpZipEntryMatcher.phpZipInfo.phpZipModel.phpStream
ResponseStream.phpZipInputStream.phpZipInputStreamInterface.phpZipOutputStream.phpZipOutputStreamInterface.php
Util
ZipFile.phpZipFileInterface.phptests/PhpZip
@@ -1,3 +1,5 @@
|
||||
dist: trusty
|
||||
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
@@ -6,12 +8,7 @@ php:
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
- '7.3'
|
||||
|
||||
# cache vendor dirs
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
- '7.4'
|
||||
|
||||
install:
|
||||
- travis_retry composer self-update && composer --version
|
||||
|
@@ -21,12 +21,13 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"ext-zlib": "*",
|
||||
"php": "^5.5 || ^7.0",
|
||||
"psr/http-message": "^1.0"
|
||||
"ext-zlib": "*",
|
||||
"psr/http-message": "^1.0",
|
||||
"paragonie/random_compat": ">=1 <9.99"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8|~5.7",
|
||||
"phpunit/phpunit": "^4.8|^5.7",
|
||||
"zendframework/zend-diactoros": "^1.4"
|
||||
},
|
||||
"autoload": {
|
||||
@@ -45,5 +46,15 @@
|
||||
"ext-bz2": "Needed to support BZIP2 compression",
|
||||
"ext-fileinfo": "Needed to get mime-type file"
|
||||
},
|
||||
"minimum-stability": "stable"
|
||||
"minimum-stability": "stable",
|
||||
"scripts": {
|
||||
"php:fix": "php .php_cs --force",
|
||||
"php:fix:debug": "php .php_cs"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.1.x-dev",
|
||||
"dev-develop": "3.2.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,75 +2,297 @@
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* Traditional PKWARE Encryption Engine.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
{
|
||||
/**
|
||||
* Encryption header size
|
||||
*/
|
||||
/** Encryption header size */
|
||||
const STD_DEC_HDR_SIZE = 12;
|
||||
|
||||
/**
|
||||
* Crc table
|
||||
* Crc table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $CRC_TABLE = [
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
0x00000000,
|
||||
0x77073096,
|
||||
0xee0e612c,
|
||||
0x990951ba,
|
||||
0x076dc419,
|
||||
0x706af48f,
|
||||
0xe963a535,
|
||||
0x9e6495a3,
|
||||
0x0edb8832,
|
||||
0x79dcb8a4,
|
||||
0xe0d5e91e,
|
||||
0x97d2d988,
|
||||
0x09b64c2b,
|
||||
0x7eb17cbd,
|
||||
0xe7b82d07,
|
||||
0x90bf1d91,
|
||||
0x1db71064,
|
||||
0x6ab020f2,
|
||||
0xf3b97148,
|
||||
0x84be41de,
|
||||
0x1adad47d,
|
||||
0x6ddde4eb,
|
||||
0xf4d4b551,
|
||||
0x83d385c7,
|
||||
0x136c9856,
|
||||
0x646ba8c0,
|
||||
0xfd62f97a,
|
||||
0x8a65c9ec,
|
||||
0x14015c4f,
|
||||
0x63066cd9,
|
||||
0xfa0f3d63,
|
||||
0x8d080df5,
|
||||
0x3b6e20c8,
|
||||
0x4c69105e,
|
||||
0xd56041e4,
|
||||
0xa2677172,
|
||||
0x3c03e4d1,
|
||||
0x4b04d447,
|
||||
0xd20d85fd,
|
||||
0xa50ab56b,
|
||||
0x35b5a8fa,
|
||||
0x42b2986c,
|
||||
0xdbbbc9d6,
|
||||
0xacbcf940,
|
||||
0x32d86ce3,
|
||||
0x45df5c75,
|
||||
0xdcd60dcf,
|
||||
0xabd13d59,
|
||||
0x26d930ac,
|
||||
0x51de003a,
|
||||
0xc8d75180,
|
||||
0xbfd06116,
|
||||
0x21b4f4b5,
|
||||
0x56b3c423,
|
||||
0xcfba9599,
|
||||
0xb8bda50f,
|
||||
0x2802b89e,
|
||||
0x5f058808,
|
||||
0xc60cd9b2,
|
||||
0xb10be924,
|
||||
0x2f6f7c87,
|
||||
0x58684c11,
|
||||
0xc1611dab,
|
||||
0xb6662d3d,
|
||||
0x76dc4190,
|
||||
0x01db7106,
|
||||
0x98d220bc,
|
||||
0xefd5102a,
|
||||
0x71b18589,
|
||||
0x06b6b51f,
|
||||
0x9fbfe4a5,
|
||||
0xe8b8d433,
|
||||
0x7807c9a2,
|
||||
0x0f00f934,
|
||||
0x9609a88e,
|
||||
0xe10e9818,
|
||||
0x7f6a0dbb,
|
||||
0x086d3d2d,
|
||||
0x91646c97,
|
||||
0xe6635c01,
|
||||
0x6b6b51f4,
|
||||
0x1c6c6162,
|
||||
0x856530d8,
|
||||
0xf262004e,
|
||||
0x6c0695ed,
|
||||
0x1b01a57b,
|
||||
0x8208f4c1,
|
||||
0xf50fc457,
|
||||
0x65b0d9c6,
|
||||
0x12b7e950,
|
||||
0x8bbeb8ea,
|
||||
0xfcb9887c,
|
||||
0x62dd1ddf,
|
||||
0x15da2d49,
|
||||
0x8cd37cf3,
|
||||
0xfbd44c65,
|
||||
0x4db26158,
|
||||
0x3ab551ce,
|
||||
0xa3bc0074,
|
||||
0xd4bb30e2,
|
||||
0x4adfa541,
|
||||
0x3dd895d7,
|
||||
0xa4d1c46d,
|
||||
0xd3d6f4fb,
|
||||
0x4369e96a,
|
||||
0x346ed9fc,
|
||||
0xad678846,
|
||||
0xda60b8d0,
|
||||
0x44042d73,
|
||||
0x33031de5,
|
||||
0xaa0a4c5f,
|
||||
0xdd0d7cc9,
|
||||
0x5005713c,
|
||||
0x270241aa,
|
||||
0xbe0b1010,
|
||||
0xc90c2086,
|
||||
0x5768b525,
|
||||
0x206f85b3,
|
||||
0xb966d409,
|
||||
0xce61e49f,
|
||||
0x5edef90e,
|
||||
0x29d9c998,
|
||||
0xb0d09822,
|
||||
0xc7d7a8b4,
|
||||
0x59b33d17,
|
||||
0x2eb40d81,
|
||||
0xb7bd5c3b,
|
||||
0xc0ba6cad,
|
||||
0xedb88320,
|
||||
0x9abfb3b6,
|
||||
0x03b6e20c,
|
||||
0x74b1d29a,
|
||||
0xead54739,
|
||||
0x9dd277af,
|
||||
0x04db2615,
|
||||
0x73dc1683,
|
||||
0xe3630b12,
|
||||
0x94643b84,
|
||||
0x0d6d6a3e,
|
||||
0x7a6a5aa8,
|
||||
0xe40ecf0b,
|
||||
0x9309ff9d,
|
||||
0x0a00ae27,
|
||||
0x7d079eb1,
|
||||
0xf00f9344,
|
||||
0x8708a3d2,
|
||||
0x1e01f268,
|
||||
0x6906c2fe,
|
||||
0xf762575d,
|
||||
0x806567cb,
|
||||
0x196c3671,
|
||||
0x6e6b06e7,
|
||||
0xfed41b76,
|
||||
0x89d32be0,
|
||||
0x10da7a5a,
|
||||
0x67dd4acc,
|
||||
0xf9b9df6f,
|
||||
0x8ebeeff9,
|
||||
0x17b7be43,
|
||||
0x60b08ed5,
|
||||
0xd6d6a3e8,
|
||||
0xa1d1937e,
|
||||
0x38d8c2c4,
|
||||
0x4fdff252,
|
||||
0xd1bb67f1,
|
||||
0xa6bc5767,
|
||||
0x3fb506dd,
|
||||
0x48b2364b,
|
||||
0xd80d2bda,
|
||||
0xaf0a1b4c,
|
||||
0x36034af6,
|
||||
0x41047a60,
|
||||
0xdf60efc3,
|
||||
0xa867df55,
|
||||
0x316e8eef,
|
||||
0x4669be79,
|
||||
0xcb61b38c,
|
||||
0xbc66831a,
|
||||
0x256fd2a0,
|
||||
0x5268e236,
|
||||
0xcc0c7795,
|
||||
0xbb0b4703,
|
||||
0x220216b9,
|
||||
0x5505262f,
|
||||
0xc5ba3bbe,
|
||||
0xb2bd0b28,
|
||||
0x2bb45a92,
|
||||
0x5cb36a04,
|
||||
0xc2d7ffa7,
|
||||
0xb5d0cf31,
|
||||
0x2cd99e8b,
|
||||
0x5bdeae1d,
|
||||
0x9b64c2b0,
|
||||
0xec63f226,
|
||||
0x756aa39c,
|
||||
0x026d930a,
|
||||
0x9c0906a9,
|
||||
0xeb0e363f,
|
||||
0x72076785,
|
||||
0x05005713,
|
||||
0x95bf4a82,
|
||||
0xe2b87a14,
|
||||
0x7bb12bae,
|
||||
0x0cb61b38,
|
||||
0x92d28e9b,
|
||||
0xe5d5be0d,
|
||||
0x7cdcefb7,
|
||||
0x0bdbdf21,
|
||||
0x86d3d2d4,
|
||||
0xf1d4e242,
|
||||
0x68ddb3f8,
|
||||
0x1fda836e,
|
||||
0x81be16cd,
|
||||
0xf6b9265b,
|
||||
0x6fb077e1,
|
||||
0x18b74777,
|
||||
0x88085ae6,
|
||||
0xff0f6a70,
|
||||
0x66063bca,
|
||||
0x11010b5c,
|
||||
0x8f659eff,
|
||||
0xf862ae69,
|
||||
0x616bffd3,
|
||||
0x166ccf45,
|
||||
0xa00ae278,
|
||||
0xd70dd2ee,
|
||||
0x4e048354,
|
||||
0x3903b3c2,
|
||||
0xa7672661,
|
||||
0xd06016f7,
|
||||
0x4969474d,
|
||||
0x3e6e77db,
|
||||
0xaed16a4a,
|
||||
0xd9d65adc,
|
||||
0x40df0b66,
|
||||
0x37d83bf0,
|
||||
0xa9bcae53,
|
||||
0xdebb9ec5,
|
||||
0x47b2cf7f,
|
||||
0x30b5ffe9,
|
||||
0xbdbdf21c,
|
||||
0xcabac28a,
|
||||
0x53b39330,
|
||||
0x24b4a3a6,
|
||||
0xbad03605,
|
||||
0xcdd70693,
|
||||
0x54de5729,
|
||||
0x23d967bf,
|
||||
0xb3667a2e,
|
||||
0xc4614ab8,
|
||||
0x5d681b02,
|
||||
0x2a6f2b94,
|
||||
0xb40bbe37,
|
||||
0xc30c8ea1,
|
||||
0x5a05df1b,
|
||||
0x2d02ef8d,
|
||||
];
|
||||
|
||||
/**
|
||||
* Encryption keys
|
||||
* Encryption keys.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $keys = [];
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
|
||||
/** @var ZipEntry */
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
@@ -84,7 +306,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial keys
|
||||
* Initial keys.
|
||||
*
|
||||
* @param string $password
|
||||
*/
|
||||
@@ -93,6 +315,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
$this->keys[0] = 305419896;
|
||||
$this->keys[1] = 591751049;
|
||||
$this->keys[2] = 878082192;
|
||||
|
||||
foreach (unpack('C*', $password) as $b) {
|
||||
$this->updateKeys($b);
|
||||
}
|
||||
@@ -101,21 +324,22 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
/**
|
||||
* Update keys.
|
||||
*
|
||||
* @param string $charAt
|
||||
* @param int $charAt
|
||||
*/
|
||||
private function updateKeys($charAt)
|
||||
{
|
||||
$this->keys[0] = self::crc32($this->keys[0], $charAt);
|
||||
$this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff);
|
||||
$this->keys[0] = $this->crc32($this->keys[0], $charAt);
|
||||
$this->keys[1] += ($this->keys[0] & 0xff);
|
||||
$this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1);
|
||||
$this->keys[2] = PackUtil::toSignedInt32(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
|
||||
$this->keys[2] = PackUtil::toSignedInt32($this->crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update crc.
|
||||
*
|
||||
* @param int $oldCrc
|
||||
* @param string $charAt
|
||||
* @param int $charAt
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function crc32($oldCrc, $charAt)
|
||||
@@ -125,18 +349,25 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipAuthenticationException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
if (\PHP_INT_SIZE === 4) {
|
||||
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
|
||||
}
|
||||
|
||||
$password = $this->entry->getPassword();
|
||||
$this->initKeys($password);
|
||||
|
||||
$headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE)));
|
||||
$byte = 0;
|
||||
foreach ($headerBytes as &$byte) {
|
||||
$byte = ($byte ^ $this->decryptByte()) & 0xff;
|
||||
|
||||
for ($i = 0; $i < self::STD_DEC_HDR_SIZE; $i++) {
|
||||
$byte = ($headerBytes[$i] ^ $this->decryptByte()) & 0xff;
|
||||
$this->updateKeys($byte);
|
||||
}
|
||||
|
||||
@@ -147,19 +378,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
// compare against the CRC otherwise
|
||||
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
|
||||
}
|
||||
|
||||
if ($byte !== $checkByte) {
|
||||
throw new ZipAuthenticationException(sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$this->entry->getName()
|
||||
));
|
||||
throw new ZipAuthenticationException(
|
||||
sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$this->entry->getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$outputContent = "";
|
||||
$outputContent = '';
|
||||
|
||||
foreach (unpack('C*', substr($content, self::STD_DEC_HDR_SIZE)) as $val) {
|
||||
$val = ($val ^ $this->decryptByte()) & 0xff;
|
||||
$this->updateKeys($val);
|
||||
$outputContent .= pack('c', $val);
|
||||
}
|
||||
|
||||
return $outputContent;
|
||||
}
|
||||
|
||||
@@ -171,22 +407,34 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
private function decryptByte()
|
||||
{
|
||||
$temp = $this->keys[2] | 2;
|
||||
|
||||
return (($temp * ($temp ^ 1)) >> 8) & 0xffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption data
|
||||
* Encryption data.
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipCryptoException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($data)
|
||||
{
|
||||
if (\PHP_INT_SIZE === 4) {
|
||||
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
|
||||
}
|
||||
|
||||
$crc = $this->entry->isDataDescriptorRequired() ?
|
||||
($this->entry->getDosTime() & 0x0000ffff) << 16 :
|
||||
$this->entry->getCrc();
|
||||
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
|
||||
|
||||
try {
|
||||
$headerBytes = random_bytes(self::STD_DEC_HDR_SIZE);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
|
||||
}
|
||||
|
||||
// Initialize again since the generated bytes were encrypted.
|
||||
$password = $this->entry->getPassword();
|
||||
@@ -196,13 +444,16 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
$headerBytes[self::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
|
||||
|
||||
$headerBytes = $this->encryptData($headerBytes);
|
||||
|
||||
return $headerBytes . $this->encryptData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipCryptoException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encryptData($content)
|
||||
{
|
||||
@@ -210,20 +461,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
|
||||
throw new ZipCryptoException('content is null');
|
||||
}
|
||||
$buff = '';
|
||||
|
||||
foreach (unpack('C*', $content) as $val) {
|
||||
$buff .= pack('c', $this->encryptByte($val));
|
||||
}
|
||||
|
||||
return $buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $byte
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function encryptByte($byte)
|
||||
{
|
||||
$tempVal = $byte ^ $this->decryptByte() & 0xff;
|
||||
$this->updateKeys($byte);
|
||||
|
||||
return $tempVal;
|
||||
}
|
||||
}
|
||||
|
@@ -8,12 +8,12 @@ use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* WinZip Aes Encryption Engine.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -24,18 +24,18 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
* in bits (AES_BLOCK_SIZE_BITS).
|
||||
*/
|
||||
const AES_BLOCK_SIZE_BITS = 128;
|
||||
|
||||
const PWD_VERIFIER_BITS = 16;
|
||||
/**
|
||||
* The iteration count for the derived keys of the cipher, KLAC and MAC.
|
||||
*/
|
||||
|
||||
/** The iteration count for the derived keys of the cipher, KLAC and MAC. */
|
||||
const ITERATION_COUNT = 1000;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
|
||||
/** @var ZipEntry */
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* WinZipAesEngine constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
@@ -47,17 +47,19 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
* Decrypt from stream resource.
|
||||
*
|
||||
* @param string $content Input stream buffer
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws ZipAuthenticationException
|
||||
* @throws ZipCryptoException
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
$extraFieldsCollection = $this->entry->getExtraFieldsCollection();
|
||||
|
||||
if (!isset($extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()])) {
|
||||
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
|
||||
throw new ZipCryptoException($this->entry->getName() . ' (missing extra field for WinZip AES entry)');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,32 +80,39 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
|
||||
// Init start, end and size of encrypted data.
|
||||
$start = $pos;
|
||||
$endPos = strlen($content);
|
||||
$endPos = \strlen($content);
|
||||
$footerSize = $sha1Size / 2;
|
||||
$end = $endPos - $footerSize;
|
||||
$size = $end - $start;
|
||||
|
||||
if (0 > $size) {
|
||||
throw new ZipCryptoException($this->entry->getName() . " (false positive WinZip AES entry is too short)");
|
||||
if ($size < 0) {
|
||||
throw new ZipCryptoException($this->entry->getName() . ' (false positive WinZip AES entry is too short)');
|
||||
}
|
||||
|
||||
// Load authentication code.
|
||||
$authenticationCode = substr($content, $end, $footerSize);
|
||||
|
||||
if ($end + $footerSize !== $endPos) {
|
||||
// This should never happen unless someone is writing to the
|
||||
// end of the file concurrently!
|
||||
throw new ZipCryptoException("Expected end of file after WinZip AES authentication code!");
|
||||
throw new ZipCryptoException('Expected end of file after WinZip AES authentication code!');
|
||||
}
|
||||
|
||||
$password = $this->entry->getPassword();
|
||||
assert($password !== null);
|
||||
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
|
||||
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
if ($password === null) {
|
||||
throw new ZipException(sprintf('Password not set for entry %s', $this->entry->getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* WinZip 99-character limit.
|
||||
*
|
||||
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
*/
|
||||
$password = substr($password, 0, 99);
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$iv = str_repeat(chr(0), $ctrIvSize);
|
||||
$iv = str_repeat(\chr(0), $ctrIvSize);
|
||||
|
||||
do {
|
||||
// Here comes the strange part about WinZip AES encryption:
|
||||
// Its unorthodox use of the Password-Based Key Derivation
|
||||
@@ -111,7 +120,7 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
// Yes, the password verifier is only a 16 bit value.
|
||||
// So we must use the MAC for password verification, too.
|
||||
$keyParam = hash_pbkdf2(
|
||||
"sha1",
|
||||
'sha1',
|
||||
$password,
|
||||
$salt,
|
||||
self::ITERATION_COUNT,
|
||||
@@ -126,9 +135,11 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
$content = substr($content, $start, $size);
|
||||
$mac = hash_hmac('sha1', $content, $sha1MacParam, true);
|
||||
|
||||
if (substr($mac, 0, 10) !== $authenticationCode) {
|
||||
throw new ZipAuthenticationException($this->entry->getName() .
|
||||
" (authenticated WinZip AES entry content has been tampered with)");
|
||||
if (strpos($mac, $authenticationCode) !== 0) {
|
||||
throw new ZipAuthenticationException(
|
||||
$this->entry->getName() .
|
||||
' (authenticated WinZip AES entry content has been tampered with)'
|
||||
);
|
||||
}
|
||||
|
||||
return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false);
|
||||
@@ -137,25 +148,27 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
/**
|
||||
* Decryption or encryption AES-CTR with Segment Integer Count (SIC).
|
||||
*
|
||||
* @param string $str Data
|
||||
* @param string $key Key
|
||||
* @param string $iv IV
|
||||
* @param bool $encrypted If true encryption else decryption
|
||||
* @param string $str Data
|
||||
* @param string $key Key
|
||||
* @param string $iv IV
|
||||
* @param bool $encrypted If true encryption else decryption
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function aesCtrSegmentIntegerCounter($str, $key, $iv, $encrypted = true)
|
||||
{
|
||||
$numOfBlocks = ceil(strlen($str) / 16);
|
||||
$numOfBlocks = ceil(\strlen($str) / 16);
|
||||
$ctrStr = '';
|
||||
for ($i = 0; $i < $numOfBlocks; ++$i) {
|
||||
for ($j = 0; $j < 16; ++$j) {
|
||||
$n = ord($iv[$j]);
|
||||
$n = \ord($iv[$j]);
|
||||
|
||||
if (++$n === 0x100) {
|
||||
// overflow, set this one to 0, increment next
|
||||
$iv[$j] = chr(0);
|
||||
$iv[$j] = \chr(0);
|
||||
} else {
|
||||
// no overflow, just write incremented number back and abort
|
||||
$iv[$j] = chr($n);
|
||||
$iv[$j] = \chr($n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -164,6 +177,7 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
self::encryptCtr($data, $key, $iv) :
|
||||
self::decryptCtr($data, $key, $iv);
|
||||
}
|
||||
|
||||
return $ctrStr;
|
||||
}
|
||||
|
||||
@@ -171,78 +185,101 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
* Encrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Raw data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
*
|
||||
* @return string Encrypted data
|
||||
*/
|
||||
private static function encryptCtr($data, $key, $iv)
|
||||
{
|
||||
if (extension_loaded("openssl")) {
|
||||
$numBits = strlen($key) * 8;
|
||||
if (\extension_loaded('openssl')) {
|
||||
$numBits = \strlen($key) * 8;
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
|
||||
if (\extension_loaded('mcrypt')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Encrypted data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
*
|
||||
* @return string Raw data
|
||||
*/
|
||||
private static function decryptCtr($data, $key, $iv)
|
||||
{
|
||||
if (extension_loaded("openssl")) {
|
||||
$numBits = strlen($key) * 8;
|
||||
if (\extension_loaded('openssl')) {
|
||||
$numBits = \strlen($key) * 8;
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
|
||||
if (\extension_loaded('mcrypt')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption string.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
public function encrypt($content)
|
||||
{
|
||||
// Init key strength.
|
||||
$password = $this->entry->getPassword();
|
||||
|
||||
if ($password === null) {
|
||||
throw new ZipException('No password was set for the entry "'.$this->entry->getName().'"');
|
||||
throw new ZipException('No password was set for the entry "' . $this->entry->getName() . '"');
|
||||
}
|
||||
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
/**
|
||||
* WinZip 99-character limit.
|
||||
*
|
||||
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
*/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($this->entry->getEncryptionMethod());
|
||||
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
|
||||
$this->entry->getEncryptionMethod()
|
||||
);
|
||||
$keyStrengthBytes = $keyStrengthBits / 8;
|
||||
|
||||
$salt = CryptoUtil::randomBytes($keyStrengthBytes / 2);
|
||||
try {
|
||||
$salt = random_bytes($keyStrengthBytes / 2);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
|
||||
}
|
||||
|
||||
$keyParam = hash_pbkdf2("sha1", $password, $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true);
|
||||
$keyParam = hash_pbkdf2(
|
||||
'sha1',
|
||||
$password,
|
||||
$salt,
|
||||
self::ITERATION_COUNT,
|
||||
(2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8,
|
||||
true
|
||||
);
|
||||
$sha1HMacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
|
||||
|
||||
// Can you believe they "forgot" the nonce in the CTR mode IV?! :-(
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$iv = str_repeat(chr(0), $ctrIvSize);
|
||||
$iv = str_repeat(\chr(0), $ctrIvSize);
|
||||
|
||||
$key = substr($keyParam, 0, $keyStrengthBytes);
|
||||
|
||||
@@ -250,10 +287,9 @@ class WinZipAesEngine implements ZipEncryptionEngine
|
||||
|
||||
$mac = hash_hmac('sha1', $content, $sha1HMacParam, true);
|
||||
|
||||
return ($salt .
|
||||
return $salt .
|
||||
substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) .
|
||||
$content .
|
||||
substr($mac, 0, 10)
|
||||
);
|
||||
substr($mac, 0, 10);
|
||||
}
|
||||
}
|
||||
|
@@ -5,9 +5,10 @@ namespace PhpZip\Crypto;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
|
||||
/**
|
||||
* Encryption Engine
|
||||
* Encryption Engine.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -17,8 +18,10 @@ interface ZipEncryptionEngine
|
||||
* Decryption string.
|
||||
*
|
||||
* @param string $encryptionContent
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipAuthenticationException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decrypt($encryptionContent);
|
||||
|
||||
@@ -26,6 +29,7 @@ interface ZipEncryptionEngine
|
||||
* Encryption string.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($content);
|
||||
|
@@ -32,20 +32,19 @@ class Crc32Exception extends ZipException
|
||||
* Crc32Exception constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $expected
|
||||
* @param int $actual
|
||||
* @param int $expected
|
||||
* @param int $actual
|
||||
*/
|
||||
public function __construct($name, $expected, $actual)
|
||||
{
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
"%s (expected CRC32 value 0x%x, but is actually 0x%x)",
|
||||
'%s (expected CRC32 value 0x%x, but is actually 0x%x)',
|
||||
$name,
|
||||
$expected,
|
||||
$actual
|
||||
)
|
||||
);
|
||||
assert($expected != $actual);
|
||||
$this->expectedCrc = $expected;
|
||||
$this->actualCrc = $actual;
|
||||
}
|
||||
|
@@ -10,21 +10,22 @@ namespace PhpZip\Exception;
|
||||
*/
|
||||
class ZipEntryNotFoundException extends ZipException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $entryName;
|
||||
|
||||
/**
|
||||
* ZipEntryNotFoundException constructor.
|
||||
*
|
||||
* @param string $entryName
|
||||
*/
|
||||
public function __construct($entryName)
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
"Zip Entry \"%s\" was not found in the archive.",
|
||||
$entryName
|
||||
));
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
'Zip Entry "%s" was not found in the archive.',
|
||||
$entryName
|
||||
)
|
||||
);
|
||||
$this->entryName = $entryName;
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ namespace PhpZip\Exception;
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*
|
||||
* @see \Exception
|
||||
*/
|
||||
class ZipException extends \Exception
|
||||
|
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if entry not found.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @deprecated Rename class exception, using ZipEntryNotFoundException
|
||||
*/
|
||||
class ZipNotFoundEntry extends ZipEntryNotFoundException
|
||||
{
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if entry unsupport compression method.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @deprecated Rename exception class, using ZipUnsupportMethodException
|
||||
*/
|
||||
class ZipUnsupportMethod extends ZipUnsupportMethodException
|
||||
{
|
||||
}
|
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Class ZipUnsupportMethodException.
|
||||
*/
|
||||
class ZipUnsupportMethodException extends RuntimeException
|
||||
{
|
||||
}
|
||||
|
@@ -23,12 +23,14 @@ interface ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize();
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data);
|
||||
|
@@ -31,51 +31,60 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return sizeof($this->collection);
|
||||
return \count($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
* @param int $headerId the requested Header ID
|
||||
*
|
||||
* @throws ZipException if headerId is out of range
|
||||
*
|
||||
* @return ExtraField|null the Extra Field with the given Header ID or
|
||||
* if no such Extra Field exists
|
||||
*/
|
||||
public function get($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
if ($headerId < 0x0000 || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
|
||||
if (isset($this->collection[$headerId])) {
|
||||
return $this->collection[$headerId];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given Extra Field in this collection.
|
||||
*
|
||||
* @param ExtraField $extraField The Extra Field to store in this collection.
|
||||
* @return ExtraField The Extra Field previously associated with the Header ID of
|
||||
* of the given Extra Field or null if no such Extra Field existed.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
* @param ExtraField $extraField the Extra Field to store in this collection
|
||||
*
|
||||
* @throws ZipException if headerId is out of range
|
||||
*
|
||||
* @return ExtraField the Extra Field previously associated with the Header ID of
|
||||
* of the given Extra Field or null if no such Extra Field existed
|
||||
*/
|
||||
public function add(ExtraField $extraField)
|
||||
{
|
||||
$headerId = $extraField::getHeaderId();
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
|
||||
if ($headerId < 0x0000 || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
$this->collection[$headerId] = $extraField;
|
||||
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Extra Field exists
|
||||
* Returns Extra Field exists.
|
||||
*
|
||||
* @param int $headerId the requested Header ID
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function has($headerId)
|
||||
@@ -86,34 +95,43 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
/**
|
||||
* Removes the Extra Field with the given Header ID.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range or extra field not found.
|
||||
* @param int $headerId the requested Header ID
|
||||
*
|
||||
* @throws ZipException if headerId is out of range or extra field not found
|
||||
*
|
||||
* @return ExtraField the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists
|
||||
*/
|
||||
public function remove($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
if ($headerId < 0x0000 || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
|
||||
if (isset($this->collection[$headerId])) {
|
||||
$ef = $this->collection[$headerId];
|
||||
unset($this->collection[$headerId]);
|
||||
|
||||
return $ef;
|
||||
}
|
||||
|
||||
throw new ZipException('ExtraField not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a offset exists
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* Whether a offset exists.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
*
|
||||
* @param mixed $offset <p>
|
||||
* An offset to check for.
|
||||
* </p>
|
||||
* @return boolean true on success or false on failure.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
* An offset to check for.
|
||||
* </p>
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
@@ -122,14 +140,19 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* Offset to retrieve.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
*
|
||||
* @param mixed $offset <p>
|
||||
* The offset to retrieve.
|
||||
* </p>
|
||||
* @return mixed Can return all value types.
|
||||
* @since 5.0.0
|
||||
* The offset to retrieve.
|
||||
* </p>
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return mixed can return all value types
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
@@ -137,23 +160,26 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* Offset to set.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
*
|
||||
* @param mixed $offset <p>
|
||||
* The offset to assign the value to.
|
||||
* </p>
|
||||
* @param mixed $value <p>
|
||||
* The value to set.
|
||||
* </p>
|
||||
* @return void
|
||||
* The offset to assign the value to.
|
||||
* </p>
|
||||
* @param mixed $value <p>
|
||||
* The value to set.
|
||||
* </p>
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($value instanceof ExtraField) {
|
||||
if ($offset !== $value::getHeaderId()) {
|
||||
throw new InvalidArgumentException("Value header id !== array access key");
|
||||
throw new InvalidArgumentException('Value header id !== array access key');
|
||||
}
|
||||
$this->add($value);
|
||||
} else {
|
||||
@@ -162,13 +188,16 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* Offset to unset.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
*
|
||||
* @param mixed $offset <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
* @return void
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
@@ -177,9 +206,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
* Return the current element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.current.php
|
||||
*
|
||||
* @return mixed can return any type
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function current()
|
||||
@@ -188,9 +220,9 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return void Any returned value is ignored.
|
||||
* Move forward to next element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.next.php
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function next()
|
||||
@@ -199,9 +231,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
* Return the key of the current element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.key.php
|
||||
*
|
||||
* @return mixed scalar on success, or null on failure
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function key()
|
||||
@@ -210,10 +245,13 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
* Checks if current position is valid.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.valid.php
|
||||
*
|
||||
* @return bool The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function valid()
|
||||
@@ -222,9 +260,9 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
* Rewind the Iterator to the first element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.rewind.php
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function rewind()
|
||||
|
@@ -13,16 +13,14 @@ use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Extra Fields Factory
|
||||
* Extra Fields Factory.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFieldsFactory
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
/** @var array|null */
|
||||
protected static $registry;
|
||||
|
||||
private function __construct()
|
||||
@@ -30,18 +28,22 @@ class ExtraFieldsFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @param string $extra
|
||||
* @param ZipEntry|null $entry
|
||||
* @return ExtraFieldsCollection
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ExtraFieldsCollection
|
||||
*/
|
||||
public static function createExtraFieldCollections($extra, ZipEntry $entry = null)
|
||||
{
|
||||
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||
|
||||
if ($extra !== null) {
|
||||
$extraLength = strlen($extra);
|
||||
$extraLength = \strlen($extra);
|
||||
|
||||
if ($extraLength > 0xffff) {
|
||||
throw new ZipException("Extra Fields too large: " . $extraLength);
|
||||
throw new ZipException('Extra Fields too large: ' . $extraLength);
|
||||
}
|
||||
$pos = 0;
|
||||
$endPos = $extraLength;
|
||||
@@ -49,38 +51,53 @@ class ExtraFieldsFactory
|
||||
while ($endPos - $pos >= 4) {
|
||||
$unpack = unpack('vheaderId/vdataSize', substr($extra, $pos, 4));
|
||||
$pos += 4;
|
||||
$headerId = (int)$unpack['headerId'];
|
||||
$dataSize = (int)$unpack['dataSize'];
|
||||
$extraField = ExtraFieldsFactory::create($headerId);
|
||||
$headerId = (int) $unpack['headerId'];
|
||||
$dataSize = (int) $unpack['dataSize'];
|
||||
$extraField = self::create($headerId);
|
||||
|
||||
if ($extraField instanceof Zip64ExtraField && $entry !== null) {
|
||||
$extraField->setEntry($entry);
|
||||
}
|
||||
$extraField->deserialize(substr($extra, $pos, $dataSize));
|
||||
|
||||
if ($dataSize > 0) {
|
||||
$content = substr($extra, $pos, $dataSize);
|
||||
|
||||
if ($content !== false) {
|
||||
$extraField->deserialize($content);
|
||||
$extraFieldsCollection[$headerId] = $extraField;
|
||||
}
|
||||
}
|
||||
|
||||
$pos += $dataSize;
|
||||
$extraFieldsCollection[$headerId] = $extraField;
|
||||
}
|
||||
}
|
||||
|
||||
return $extraFieldsCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtraFieldsCollection $extraFieldsCollection
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function createSerializedData(ExtraFieldsCollection $extraFieldsCollection)
|
||||
{
|
||||
$extraData = '';
|
||||
|
||||
foreach ($extraFieldsCollection as $extraField) {
|
||||
$data = $extraField->serialize();
|
||||
$extraData .= pack('vv', $extraField::getHeaderId(), strlen($data));
|
||||
$extraData .= pack('vv', $extraField::getHeaderId(), \strlen($data));
|
||||
$extraData .= $data;
|
||||
}
|
||||
|
||||
$size = strlen($extraData);
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
$size = \strlen($extraData);
|
||||
|
||||
if ($size < 0x0000 || $size > 0xffff) {
|
||||
throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData);
|
||||
}
|
||||
|
||||
return $extraData;
|
||||
}
|
||||
|
||||
@@ -90,29 +107,31 @@ class ExtraFieldsFactory
|
||||
* The returned Extra Field still requires proper initialization, for
|
||||
* example by calling ExtraField::readFrom.
|
||||
*
|
||||
* @param int $headerId An unsigned short integer (two bytes) which indicates
|
||||
* the type of the returned Extra Field.
|
||||
* @return ExtraField A new Extra Field or null if not support header id.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
* @param int $headerId an unsigned short integer (two bytes) which indicates
|
||||
* the type of the returned Extra Field
|
||||
*
|
||||
* @throws ZipException if headerId is out of range
|
||||
*
|
||||
* @return ExtraField a new Extra Field or null if not support header id
|
||||
*/
|
||||
public static function create($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
if ($headerId < 0x0000 || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ExtraField $extraField
|
||||
*/
|
||||
if (isset(self::getRegistry()[$headerId])) {
|
||||
$extraClassName = self::getRegistry()[$headerId];
|
||||
$extraField = new $extraClassName;
|
||||
/** @var ExtraField $extraField */
|
||||
$extraField = new $extraClassName();
|
||||
|
||||
if ($headerId !== $extraField::getHeaderId()) {
|
||||
throw new ZipException('Runtime error support headerId ' . $headerId);
|
||||
}
|
||||
} else {
|
||||
$extraField = new DefaultExtraField($headerId);
|
||||
}
|
||||
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
@@ -130,6 +149,7 @@ class ExtraFieldsFactory
|
||||
self::$registry[ApkAlignmentExtraField::getHeaderId()] = ApkAlignmentExtraField::class;
|
||||
self::$registry[JarMarkerExtraField::getHeaderId()] = JarMarkerExtraField::class;
|
||||
}
|
||||
|
||||
return self::$registry;
|
||||
}
|
||||
|
||||
@@ -151,6 +171,7 @@ class ExtraFieldsFactory
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @return Zip64ExtraField
|
||||
*/
|
||||
public static function createZip64Extra(ZipEntry $entry)
|
||||
@@ -160,19 +181,22 @@ class ExtraFieldsFactory
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param int $padding
|
||||
* @param int $padding
|
||||
*
|
||||
* @return ApkAlignmentExtraField
|
||||
*/
|
||||
public static function createApkAlignExtra(ZipEntry $entry, $padding)
|
||||
{
|
||||
$padding = (int)$padding;
|
||||
$padding = (int) $padding;
|
||||
$multiple = 4;
|
||||
|
||||
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||
$multiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
}
|
||||
$extraField = new ApkAlignmentExtraField();
|
||||
$extraField->setMultiple($multiple);
|
||||
$extraField->setPadding($padding);
|
||||
|
||||
return $extraField;
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
|
||||
/**
|
||||
* Apk Alignment Extra Field
|
||||
* Apk Alignment Extra Field.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
@@ -21,13 +21,10 @@ class ApkAlignmentExtraField implements ExtraField
|
||||
|
||||
const ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
private $multiple;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $padding;
|
||||
|
||||
/**
|
||||
@@ -44,6 +41,7 @@ class ApkAlignmentExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
@@ -53,25 +51,31 @@ class ApkAlignmentExtraField implements ExtraField
|
||||
['vc*', $this->multiple],
|
||||
array_fill(2, $this->padding, 0)
|
||||
);
|
||||
return call_user_func_array('pack', $args);
|
||||
|
||||
return \call_user_func_array('pack', $args);
|
||||
}
|
||||
|
||||
return pack('v', $this->multiple);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$length = strlen($data);
|
||||
$length = \strlen($data);
|
||||
|
||||
if ($length < 2) {
|
||||
// This is APK alignment field.
|
||||
// FORMAT:
|
||||
// * uint16 alignment multiple (in bytes)
|
||||
// * remaining bytes -- padding to achieve alignment of data which starts after
|
||||
// the extra field
|
||||
throw new InvalidArgumentException("Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.");
|
||||
throw new InvalidArgumentException(
|
||||
'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.'
|
||||
);
|
||||
}
|
||||
$this->multiple = unpack('v', $data)[1];
|
||||
$this->padding = $length - 2;
|
||||
|
@@ -14,26 +14,23 @@ use PhpZip\Extra\ExtraField;
|
||||
*/
|
||||
class DefaultExtraField implements ExtraField
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
private static $headerId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Constructs a new Extra Field.
|
||||
*
|
||||
* @param int $headerId an unsigned short integer (two bytes) indicating the
|
||||
* type of the Extra Field.
|
||||
* type of the Extra Field
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
if ($headerId < 0x0000 || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
self::$headerId = $headerId;
|
||||
@@ -53,6 +50,7 @@ class DefaultExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
@@ -62,6 +60,7 @@ class DefaultExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
|
@@ -30,6 +30,7 @@ class JarMarkerExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
@@ -39,12 +40,14 @@ class JarMarkerExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
if (strlen($data) !== 0) {
|
||||
if ($data !== '') {
|
||||
throw new ZipException("JarMarker doesn't expect any data");
|
||||
}
|
||||
}
|
||||
|
@@ -6,31 +6,31 @@ use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* NTFS Extra Field
|
||||
* NTFS Extra Field.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class NtfsExtraField implements ExtraField
|
||||
{
|
||||
|
||||
/**
|
||||
* Modify time
|
||||
* Modify time.
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* Access Time
|
||||
* Access Time.
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* Create Time
|
||||
* Create Time.
|
||||
*
|
||||
* @var int Unix Time
|
||||
*/
|
||||
@@ -50,11 +50,13 @@ class NtfsExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$unpack = unpack('vtag/vsizeAttr', substr($data, 0, 4));
|
||||
|
||||
if ($unpack['sizeAttr'] === 24) {
|
||||
$tagData = substr($data, 4, $unpack['sizeAttr']);
|
||||
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
|
||||
@@ -65,11 +67,13 @@ class NtfsExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
$serialize = '';
|
||||
|
||||
if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) {
|
||||
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
|
||||
$atimeLong = ($this->atime + 11644473600) * 10000000;
|
||||
@@ -80,6 +84,7 @@ class NtfsExtraField implements ExtraField
|
||||
. PackUtil::packLongLE($atimeLong)
|
||||
. PackUtil::packLongLE($ctimeLong);
|
||||
}
|
||||
|
||||
return $serialize;
|
||||
}
|
||||
|
||||
@@ -96,7 +101,7 @@ class NtfsExtraField implements ExtraField
|
||||
*/
|
||||
public function setMtime($mtime)
|
||||
{
|
||||
$this->mtime = (int)$mtime;
|
||||
$this->mtime = (int) $mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +117,7 @@ class NtfsExtraField implements ExtraField
|
||||
*/
|
||||
public function setAtime($atime)
|
||||
{
|
||||
$this->atime = (int)$atime;
|
||||
$this->atime = (int) $atime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +133,6 @@ class NtfsExtraField implements ExtraField
|
||||
*/
|
||||
public function setCtime($ctime)
|
||||
{
|
||||
$this->ctime = (int)$ctime;
|
||||
$this->ctime = (int) $ctime;
|
||||
}
|
||||
}
|
||||
|
@@ -4,47 +4,54 @@ namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* WinZip AES Extra Field.
|
||||
*
|
||||
* @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2 (WinZip Computing, S.L.)
|
||||
* @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2
|
||||
* (WinZip Computing, S.L.)
|
||||
* @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers (WinZip Computing, S.L.)
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class WinZipAesEntryExtraField implements ExtraField
|
||||
{
|
||||
const DATA_SIZE = 7;
|
||||
|
||||
const VENDOR_ID = 17729; // 'A' | ('E' << 8);
|
||||
|
||||
/**
|
||||
* Entries of this type <em>do</em> include the standard ZIP CRC-32 value.
|
||||
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion().
|
||||
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see
|
||||
* WinZipAesEntryExtraField::getVendorVersion().
|
||||
*/
|
||||
const VV_AE_1 = 1;
|
||||
|
||||
/**
|
||||
* Entries of this type do <em>not</em> include the standard ZIP CRC-32 value.
|
||||
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion().
|
||||
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see
|
||||
* WinZipAesEntryExtraField::getVendorVersion().
|
||||
*/
|
||||
const VV_AE_2 = 2;
|
||||
|
||||
const KEY_STRENGTH_128BIT = 128;
|
||||
|
||||
const KEY_STRENGTH_192BIT = 192;
|
||||
|
||||
const KEY_STRENGTH_256BIT = 256;
|
||||
|
||||
protected static $keyStrengths = [
|
||||
self::KEY_STRENGTH_128BIT => 0x01,
|
||||
self::KEY_STRENGTH_192BIT => 0x02,
|
||||
self::KEY_STRENGTH_256BIT => 0x03
|
||||
self::KEY_STRENGTH_256BIT => 0x03,
|
||||
];
|
||||
|
||||
protected static $encryptionMethods = [
|
||||
self::KEY_STRENGTH_128BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128,
|
||||
self::KEY_STRENGTH_192BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192,
|
||||
self::KEY_STRENGTH_256BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
self::KEY_STRENGTH_128BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128,
|
||||
self::KEY_STRENGTH_192BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192,
|
||||
self::KEY_STRENGTH_256BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -94,14 +101,16 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
/**
|
||||
* Sets the vendor version.
|
||||
*
|
||||
* @param int $vendorVersion the vendor version
|
||||
*
|
||||
* @throws ZipException unsupport vendor version
|
||||
*
|
||||
* @see WinZipAesEntryExtraField::VV_AE_1
|
||||
* @see WinZipAesEntryExtraField::VV_AE_2
|
||||
* @param int $vendorVersion the vendor version.
|
||||
* @throws ZipException Unsupport vendor version.
|
||||
*/
|
||||
public function setVendorVersion($vendorVersion)
|
||||
{
|
||||
if ($vendorVersion < self::VV_AE_1 || self::VV_AE_2 < $vendorVersion) {
|
||||
if ($vendorVersion < self::VV_AE_1 || $vendorVersion > self::VV_AE_2) {
|
||||
throw new ZipException($vendorVersion);
|
||||
}
|
||||
$this->vendorVersion = $vendorVersion;
|
||||
@@ -118,8 +127,9 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|int
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function getKeyStrength()
|
||||
{
|
||||
@@ -127,16 +137,20 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionStrength Encryption strength as bits.
|
||||
* @param int $encryptionStrength encryption strength as bits
|
||||
*
|
||||
* @throws ZipException if unsupport encryption strength
|
||||
*
|
||||
* @return int
|
||||
* @throws ZipException If unsupport encryption strength.
|
||||
*/
|
||||
public static function keyStrength($encryptionStrength)
|
||||
{
|
||||
$flipKeyStrength = array_flip(self::$keyStrengths);
|
||||
|
||||
if (!isset($flipKeyStrength[$encryptionStrength])) {
|
||||
throw new ZipException("Unsupport encryption strength " . $encryptionStrength);
|
||||
throw new ZipException('Unsupport encryption strength ' . $encryptionStrength);
|
||||
}
|
||||
|
||||
return $flipKeyStrength[$encryptionStrength];
|
||||
}
|
||||
|
||||
@@ -153,8 +167,9 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
/**
|
||||
* Internal encryption method.
|
||||
*
|
||||
* @return int
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
@@ -165,15 +180,19 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
* @return int
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getKeyStrangeFromEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
$flipKey = array_flip(self::$encryptionMethods);
|
||||
|
||||
if (!isset($flipKey[$encryptionMethod])) {
|
||||
throw new ZipException("Unsupport encryption method " . $encryptionMethod);
|
||||
throw new ZipException('Unsupport encryption method ' . $encryptionMethod);
|
||||
}
|
||||
|
||||
return $flipKey[$encryptionMethod];
|
||||
}
|
||||
|
||||
@@ -181,11 +200,12 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
* Sets compression method.
|
||||
*
|
||||
* @param int $compressionMethod Compression method
|
||||
* @throws ZipException Compression method out of range.
|
||||
*
|
||||
* @throws ZipException compression method out of range
|
||||
*/
|
||||
public function setMethod($compressionMethod)
|
||||
{
|
||||
if (0x0000 > $compressionMethod || $compressionMethod > 0xffff) {
|
||||
if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
|
||||
throw new ZipException('Compression method out of range');
|
||||
}
|
||||
$this->method = $compressionMethod;
|
||||
@@ -204,7 +224,8 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
/**
|
||||
* Returns encryption strength.
|
||||
*
|
||||
* @param int $keyStrength Key strength in bits.
|
||||
* @param int $keyStrength key strength in bits
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function encryptionStrength($keyStrength)
|
||||
@@ -216,6 +237,7 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
@@ -231,13 +253,16 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$size = strlen($data);
|
||||
if (self::DATA_SIZE !== $size) {
|
||||
$size = \strlen($data);
|
||||
|
||||
if ($size !== self::DATA_SIZE) {
|
||||
throw new ZipException('WinZip AES Extra data invalid size: ' . $size . '. Must be ' . self::DATA_SIZE);
|
||||
}
|
||||
|
||||
@@ -249,7 +274,8 @@ class WinZipAesEntryExtraField implements ExtraField
|
||||
*/
|
||||
$unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', $data);
|
||||
$this->setVendorVersion($unpack['vendorVersion']);
|
||||
if (self::VENDOR_ID !== $unpack['vendorId']) {
|
||||
|
||||
if ($unpack['vendorId'] !== self::VENDOR_ID) {
|
||||
throw new ZipException('Vendor id invalid: ' . $unpack['vendorId'] . '. Must be ' . self::VENDOR_ID);
|
||||
}
|
||||
$this->setKeyStrength(self::keyStrength($unpack['keyStrength'])); // checked
|
||||
|
@@ -3,14 +3,16 @@
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* ZIP64 Extra Field
|
||||
* ZIP64 Extra Field.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -18,13 +20,13 @@ class Zip64ExtraField implements ExtraField
|
||||
{
|
||||
/** The Header ID for a ZIP64 Extended Information Extra Field. */
|
||||
const ZIP64_HEADER_ID = 0x0001;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
|
||||
/** @var ZipEntry */
|
||||
protected $entry;
|
||||
|
||||
/**
|
||||
* Zip64ExtraField constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry = null)
|
||||
@@ -56,61 +58,65 @@ class Zip64ExtraField implements ExtraField
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
if ($this->entry === null) {
|
||||
throw new RuntimeException("entry is null");
|
||||
throw new RuntimeException('entry is null');
|
||||
}
|
||||
$data = '';
|
||||
// Write out Uncompressed Size.
|
||||
$size = $this->entry->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
|
||||
if ($size >= 0xffffffff) {
|
||||
$data .= PackUtil::packLongLE($size);
|
||||
}
|
||||
// Write out Compressed Size.
|
||||
$compressedSize = $this->entry->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
|
||||
if ($compressedSize >= 0xffffffff) {
|
||||
$data .= PackUtil::packLongLE($compressedSize);
|
||||
}
|
||||
// Write out Relative Header Offset.
|
||||
$offset = $this->entry->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
|
||||
if ($offset >= 0xffffffff) {
|
||||
$data .= PackUtil::packLongLE($offset);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
*
|
||||
* @param string $data
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
if ($this->entry === null) {
|
||||
throw new RuntimeException("entry is null");
|
||||
throw new RuntimeException('entry is null');
|
||||
}
|
||||
$off = 0;
|
||||
|
||||
// Read in Uncompressed Size.
|
||||
$size = $this->entry->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
assert(0xffffffff === $size);
|
||||
if ($this->entry->getSize() === 0xffffffff) {
|
||||
$this->entry->setSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
|
||||
// Read in Compressed Size.
|
||||
$compressedSize = $this->entry->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
assert(0xffffffff === $compressedSize);
|
||||
if ($this->entry->getCompressedSize() === 0xffffffff) {
|
||||
$this->entry->setCompressedSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
|
||||
// Read in Relative Header Offset.
|
||||
$offset = $this->entry->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
assert(0xffffffff, $offset);
|
||||
if ($this->entry->getOffset() === 0xffffffff) {
|
||||
$this->entry->setOffset(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
}
|
||||
}
|
||||
|
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Mapper;
|
||||
|
||||
/**
|
||||
* Adds a offset value to the given position.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class OffsetPositionMapper extends PositionMapper
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offset;
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
*/
|
||||
public function __construct($offset)
|
||||
{
|
||||
$this->offset = (int)$offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function map($position)
|
||||
{
|
||||
return parent::map($position) + $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function unmap($position)
|
||||
{
|
||||
return parent::unmap($position) - $this->offset;
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Mapper;
|
||||
|
||||
/**
|
||||
* Maps a given position.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class PositionMapper
|
||||
{
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function map($position)
|
||||
{
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function unmap($position)
|
||||
{
|
||||
return $position;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
namespace PhpZip\Model;
|
||||
|
||||
/**
|
||||
* Read End of Central Directory
|
||||
* End of Central Directory.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
@@ -11,11 +11,14 @@ namespace PhpZip\Model;
|
||||
class EndOfCentralDirectory
|
||||
{
|
||||
/** Zip64 End Of Central Directory Record. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
|
||||
const ZIP64_END_OF_CD_RECORD_SIG = 0x06064B50;
|
||||
|
||||
/** Zip64 End Of Central Directory Locator. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
|
||||
const ZIP64_END_OF_CD_LOCATOR_SIG = 0x07064B50;
|
||||
|
||||
/** End Of Central Directory Record signature. */
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
|
||||
const END_OF_CD_SIG = 0x06054B50;
|
||||
|
||||
/**
|
||||
* The minimum length of the End Of Central Directory Record.
|
||||
*
|
||||
@@ -34,6 +37,7 @@ class EndOfCentralDirectory
|
||||
* zipfile comment length 2
|
||||
*/
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
|
||||
|
||||
/**
|
||||
* The length of the Zip64 End Of Central Directory Locator.
|
||||
* zip64 end of central dir locator
|
||||
@@ -43,9 +47,10 @@ class EndOfCentralDirectory
|
||||
* central directory 4
|
||||
* relative offset of the zip64
|
||||
* end of central directory record 8
|
||||
* total number of disks 4
|
||||
* total number of disks 4.
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
|
||||
const ZIP64_END_OF_CD_LOCATOR_LEN = 20;
|
||||
|
||||
/**
|
||||
* The minimum length of the Zip64 End Of Central Directory Record.
|
||||
*
|
||||
@@ -68,38 +73,46 @@ class EndOfCentralDirectory
|
||||
* the starting disk number 8
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
|
||||
/**
|
||||
* @var string|null The archive comment.
|
||||
*/
|
||||
private $comment;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int Count files. */
|
||||
private $entryCount;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $zip64 = false;
|
||||
|
||||
/** @var int Central Directory Offset. */
|
||||
private $cdOffset;
|
||||
|
||||
/** @var int */
|
||||
private $cdSize;
|
||||
|
||||
/** @var string|null The archive comment. */
|
||||
private $comment;
|
||||
|
||||
/** @var bool Zip64 extension */
|
||||
private $zip64;
|
||||
|
||||
/**
|
||||
* EndOfCentralDirectory constructor.
|
||||
* @param int $entryCount
|
||||
* @param null|string $comment
|
||||
* @param bool $zip64
|
||||
*
|
||||
* @param int $entryCount
|
||||
* @param int $cdOffset
|
||||
* @param int $cdSize
|
||||
* @param bool $zip64
|
||||
* @param mixed|null $comment
|
||||
*/
|
||||
public function __construct($entryCount, $comment, $zip64 = false)
|
||||
public function __construct($entryCount, $cdOffset, $cdSize, $zip64, $comment = null)
|
||||
{
|
||||
$this->entryCount = $entryCount;
|
||||
$this->comment = $comment;
|
||||
$this->cdOffset = $cdOffset;
|
||||
$this->cdSize = $cdSize;
|
||||
$this->zip64 = $zip64;
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* @param string|null $comment
|
||||
*/
|
||||
public function getComment()
|
||||
public function setComment($comment)
|
||||
{
|
||||
return $this->comment;
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +123,30 @@ class EndOfCentralDirectory
|
||||
return $this->entryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCdOffset()
|
||||
{
|
||||
return $this->cdOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCdSize()
|
||||
{
|
||||
return $this->cdSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
@@ -9,20 +9,19 @@ use PhpZip\Model\ZipEntry;
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OutputOffsetEntry
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
private $offset;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
|
||||
/** @var ZipEntry */
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* @param int $pos
|
||||
* @param int $pos
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct($pos, ZipEntry $entry)
|
||||
|
@@ -10,65 +10,60 @@ use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\DateTimeConverter;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Abstract ZIP entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class ZipAbstractEntry implements ZipEntry
|
||||
{
|
||||
/**
|
||||
* @var int Bit flags for init state.
|
||||
*/
|
||||
private $init;
|
||||
/**
|
||||
* @var string Entry name (filename in archive)
|
||||
*/
|
||||
/** @var string Entry name (filename in archive) */
|
||||
private $name;
|
||||
/**
|
||||
* @var int Made by platform
|
||||
*/
|
||||
private $platform;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $versionNeededToExtract = 20;
|
||||
/**
|
||||
* @var int Compression method
|
||||
*/
|
||||
private $method;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $general;
|
||||
/**
|
||||
* @var int Dos time
|
||||
*/
|
||||
private $dosTime;
|
||||
/**
|
||||
* @var int Crc32
|
||||
*/
|
||||
private $crc;
|
||||
/**
|
||||
* @var int Compressed size
|
||||
*/
|
||||
|
||||
/** @var int Made by platform */
|
||||
private $createdOS = self::UNKNOWN;
|
||||
|
||||
/** @var int Extracted by platform */
|
||||
private $extractedOS = self::UNKNOWN;
|
||||
|
||||
/** @var int */
|
||||
private $softwareVersion = self::UNKNOWN;
|
||||
|
||||
/** @var int */
|
||||
private $versionNeededToExtract = self::UNKNOWN;
|
||||
|
||||
/** @var int Compression method */
|
||||
private $method = self::UNKNOWN;
|
||||
|
||||
/** @var int */
|
||||
private $generalPurposeBitFlags = 0;
|
||||
|
||||
/** @var int Dos time */
|
||||
private $dosTime = self::UNKNOWN;
|
||||
|
||||
/** @var int Crc32 */
|
||||
private $crc = self::UNKNOWN;
|
||||
|
||||
/** @var int Compressed size */
|
||||
private $compressedSize = self::UNKNOWN;
|
||||
/**
|
||||
* @var int Uncompressed size
|
||||
*/
|
||||
|
||||
/** @var int Uncompressed size */
|
||||
private $size = self::UNKNOWN;
|
||||
/**
|
||||
* @var int External attributes
|
||||
*/
|
||||
private $externalAttributes;
|
||||
/**
|
||||
* @var int Relative Offset Of Local File Header.
|
||||
*/
|
||||
private $offset = self::UNKNOWN;
|
||||
|
||||
/** @var int Internal attributes */
|
||||
private $internalAttributes = 0;
|
||||
|
||||
/** @var int External attributes */
|
||||
private $externalAttributes = 0;
|
||||
|
||||
/** @var int relative Offset Of Local File Header */
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Collections of Extra Fields.
|
||||
* Keys from Header ID [int] and value Extra Field [ExtraField].
|
||||
@@ -77,27 +72,27 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* @var ExtraFieldsCollection
|
||||
*/
|
||||
private $extraFieldsCollection;
|
||||
/**
|
||||
* @var string Comment field.
|
||||
*/
|
||||
|
||||
/** @var string|null comment field */
|
||||
private $comment;
|
||||
/**
|
||||
* @var string Entry password for read or write encryption data.
|
||||
*/
|
||||
|
||||
/** @var string entry password for read or write encryption data */
|
||||
private $password;
|
||||
|
||||
/**
|
||||
* Encryption method.
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
|
||||
private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;
|
||||
|
||||
/** @var int */
|
||||
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
|
||||
|
||||
/**
|
||||
* ZipAbstractEntry constructor.
|
||||
@@ -109,12 +104,15 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEntry(ZipEntry $entry)
|
||||
{
|
||||
$this->setName($entry->getName());
|
||||
$this->setPlatform($entry->getPlatform());
|
||||
$this->setSoftwareVersion($entry->getSoftwareVersion());
|
||||
$this->setCreatedOS($entry->getCreatedOS());
|
||||
$this->setExtractedOS($entry->getExtractedOS());
|
||||
$this->setVersionNeededToExtract($entry->getVersionNeededToExtract());
|
||||
$this->setMethod($entry->getMethod());
|
||||
$this->setGeneralPurposeBitFlags($entry->getGeneralPurposeBitFlags());
|
||||
@@ -122,6 +120,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
$this->setCrc($entry->getCrc());
|
||||
$this->setCompressedSize($entry->getCompressedSize());
|
||||
$this->setSize($entry->getSize());
|
||||
$this->setInternalAttributes($entry->getInternalAttributes());
|
||||
$this->setExternalAttributes($entry->getExternalAttributes());
|
||||
$this->setOffset($entry->getOffset());
|
||||
$this->setExtra($entry->getExtra());
|
||||
@@ -146,87 +145,150 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* Set entry name.
|
||||
*
|
||||
* @param string $name New entry name
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$length = strlen($name);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
$length = \strlen($name);
|
||||
|
||||
if ($length < 0x0000 || $length > 0xffff) {
|
||||
throw new ZipException('Illegal zip entry name parameter');
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
$this->name = $name;
|
||||
$this->externalAttributes = $this->isDirectory() ? 0x10 : 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit)
|
||||
{
|
||||
if ($bit) {
|
||||
$this->general |= $mask;
|
||||
$this->generalPurposeBitFlags |= $mask;
|
||||
} else {
|
||||
$this->general &= ~$mask;
|
||||
$this->generalPurposeBitFlags &= ~$mask;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*
|
||||
* @deprecated Use {@see ZipEntry::getCreatedOS()}
|
||||
* @noinspection PhpUsageOfSilenceOperatorInspection
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->isInit(self::BIT_PLATFORM) ? $this->platform & 0xffff : self::UNKNOWN;
|
||||
@trigger_error('ZipEntry::getPlatform() is deprecated. Use ZipEntry::getCreatedOS()', \E_USER_DEPRECATED);
|
||||
|
||||
return $this->getCreatedOS();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set platform
|
||||
*
|
||||
* @param int $platform
|
||||
*
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @deprecated Use {@see ZipEntry::setCreatedOS()}
|
||||
* @noinspection PhpUsageOfSilenceOperatorInspection
|
||||
*/
|
||||
public function setPlatform($platform)
|
||||
{
|
||||
$known = self::UNKNOWN !== $platform;
|
||||
if ($known) {
|
||||
if (0x00 > $platform || $platform > 0xff) {
|
||||
throw new ZipException("Platform out of range");
|
||||
}
|
||||
$this->platform = $platform;
|
||||
} else {
|
||||
$this->platform = 0;
|
||||
@trigger_error('ZipEntry::setPlatform() is deprecated. Use ZipEntry::setCreatedOS()', \E_USER_DEPRECATED);
|
||||
|
||||
return $this->setCreatedOS($platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int platform
|
||||
*/
|
||||
public function getCreatedOS()
|
||||
{
|
||||
return $this->createdOS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set platform.
|
||||
*
|
||||
* @param int $platform
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCreatedOS($platform)
|
||||
{
|
||||
$platform = (int) $platform;
|
||||
|
||||
if ($platform < 0x00 || $platform > 0xff) {
|
||||
throw new ZipException('Platform out of range');
|
||||
}
|
||||
$this->setInit(self::BIT_PLATFORM, $known);
|
||||
$this->createdOS = $platform;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
* @return int
|
||||
*/
|
||||
protected function isInit($mask)
|
||||
public function getExtractedOS()
|
||||
{
|
||||
return ($this->init & $mask) !== 0;
|
||||
return $this->extractedOS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @param bool $init
|
||||
* Set extracted OS.
|
||||
*
|
||||
* @param int $platform
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
protected function setInit($mask, $init)
|
||||
public function setExtractedOS($platform)
|
||||
{
|
||||
if ($init) {
|
||||
$this->init |= $mask;
|
||||
} else {
|
||||
$this->init &= ~$mask;
|
||||
$platform = (int) $platform;
|
||||
|
||||
if ($platform < 0x00 || $platform > 0xff) {
|
||||
throw new ZipException('Platform out of range');
|
||||
}
|
||||
$this->extractedOS = $platform;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSoftwareVersion()
|
||||
{
|
||||
return $this->softwareVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $softwareVersion
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setSoftwareVersion($softwareVersion)
|
||||
{
|
||||
$this->softwareVersion = (int) $softwareVersion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,6 +298,24 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function getVersionNeededToExtract()
|
||||
{
|
||||
if ($this->versionNeededToExtract === self::UNKNOWN) {
|
||||
$method = $this->getMethod();
|
||||
|
||||
if ($method === self::METHOD_WINZIP_AES) {
|
||||
return 51;
|
||||
}
|
||||
|
||||
if ($method === ZipFile::METHOD_BZIP2) {
|
||||
return 46;
|
||||
}
|
||||
|
||||
if ($this->isZip64ExtensionsRequired()) {
|
||||
return 45;
|
||||
}
|
||||
|
||||
return $method === ZipFile::METHOD_DEFLATED || $this->isDirectory() ? 20 : 10;
|
||||
}
|
||||
|
||||
return $this->versionNeededToExtract;
|
||||
}
|
||||
|
||||
@@ -243,11 +323,13 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* Set version needed to extract.
|
||||
*
|
||||
* @param int $version
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setVersionNeededToExtract($version)
|
||||
{
|
||||
$this->versionNeededToExtract = $version;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -256,8 +338,8 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function isZip64ExtensionsRequired()
|
||||
{
|
||||
return 0xffffffff <= $this->getCompressedSize()
|
||||
|| 0xffffffff <= $this->getSize();
|
||||
return $this->getCompressedSize() >= 0xffffffff
|
||||
|| $this->getSize() >= 0xffffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,12 +355,14 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
/**
|
||||
* Sets the compressed size of this entry.
|
||||
*
|
||||
* @param int $compressedSize The Compressed Size.
|
||||
* @param int $compressedSize the Compressed Size
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressedSize($compressedSize)
|
||||
{
|
||||
$this->compressedSize = $compressedSize;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -295,12 +379,14 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
/**
|
||||
* Sets the uncompressed size of this entry.
|
||||
*
|
||||
* @param int $size The (Uncompressed) Size.
|
||||
* @param int $size the (Uncompressed) Size
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setSize($size)
|
||||
{
|
||||
$this->size = $size;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -316,49 +402,59 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setOffset($offset)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
$this->offset = (int) $offset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags()
|
||||
{
|
||||
return $this->general & 0xffff;
|
||||
return $this->generalPurposeBitFlags & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the General Purpose Bit Flags.
|
||||
*
|
||||
* @var int general
|
||||
* @return ZipEntry
|
||||
* @param mixed $general
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @var int general
|
||||
*/
|
||||
public function setGeneralPurposeBitFlags($general)
|
||||
{
|
||||
if (0x0000 > $general || $general > 0xffff) {
|
||||
if ($general < 0x0000 || $general > 0xffff) {
|
||||
throw new ZipException('general out of range');
|
||||
}
|
||||
$this->general = $general;
|
||||
if ($this->method === ZipFileInterface::METHOD_DEFLATED) {
|
||||
$this->generalPurposeBitFlags = $general;
|
||||
|
||||
if ($this->method === ZipFile::METHOD_DEFLATED) {
|
||||
$bit1 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG1);
|
||||
$bit2 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG2);
|
||||
|
||||
if ($bit1 && !$bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_BEST_COMPRESSION;
|
||||
$this->compressionLevel = ZipFile::LEVEL_BEST_COMPRESSION;
|
||||
} elseif (!$bit1 && $bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_FAST;
|
||||
$this->compressionLevel = ZipFile::LEVEL_FAST;
|
||||
} elseif ($bit1 && $bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_SUPER_FAST;
|
||||
$this->compressionLevel = ZipFile::LEVEL_SUPER_FAST;
|
||||
} else {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
|
||||
$this->compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -376,35 +472,38 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask)
|
||||
{
|
||||
return ($this->general & $mask) !== 0;
|
||||
return ($this->generalPurposeBitFlags & $mask) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function disableEncryption()
|
||||
{
|
||||
$this->setEncrypted(false);
|
||||
$headerId = WinZipAesEntryExtraField::getHeaderId();
|
||||
|
||||
if (isset($this->extraFieldsCollection[$headerId])) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
/** @var WinZipAesEntryExtraField $field */
|
||||
$field = $this->extraFieldsCollection[$headerId];
|
||||
if (self::METHOD_WINZIP_AES === $this->getMethod()) {
|
||||
|
||||
if ($this->getMethod() === self::METHOD_WINZIP_AES) {
|
||||
$this->setMethod($field === null ? self::UNKNOWN : $field->getMethod());
|
||||
}
|
||||
unset($this->extraFieldsCollection[$headerId]);
|
||||
}
|
||||
$this->password = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -412,12 +511,14 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
* @param bool $encrypted
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted)
|
||||
{
|
||||
$encrypted = (bool)$encrypted;
|
||||
$encrypted = (bool) $encrypted;
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -428,59 +529,60 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
$isInit = $this->isInit(self::BIT_METHOD);
|
||||
return $isInit ?
|
||||
$this->method & 0xffff :
|
||||
self::UNKNOWN;
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression method for this entry.
|
||||
*
|
||||
* @param int $method
|
||||
*
|
||||
* @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
|
||||
*
|
||||
* @return ZipEntry
|
||||
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
|
||||
*/
|
||||
public function setMethod($method)
|
||||
{
|
||||
if ($method === self::UNKNOWN) {
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, false);
|
||||
|
||||
return $this;
|
||||
}
|
||||
if (0x0000 > $method || $method > 0xffff) {
|
||||
|
||||
if ($method < 0x0000 || $method > 0xffff) {
|
||||
throw new ZipException('method out of range: ' . $method);
|
||||
}
|
||||
switch ($method) {
|
||||
case self::METHOD_WINZIP_AES:
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
case ZipFile::METHOD_STORED:
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($this->name . " (unsupported compression method $method)");
|
||||
throw new ZipException($this->name . " (unsupported compression method {$method})");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unix Timestamp
|
||||
* Get Unix Timestamp.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTime()
|
||||
{
|
||||
if (!$this->isInit(self::BIT_DATE_TIME)) {
|
||||
if ($this->getDosTime() === self::UNKNOWN) {
|
||||
return self::UNKNOWN;
|
||||
}
|
||||
|
||||
return DateTimeConverter::toUnixTimestamp($this->getDosTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
* Get Dos Time.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
@@ -490,70 +592,96 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Dos Time
|
||||
* Set Dos Time.
|
||||
*
|
||||
* @param int $dosTime
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setDosTime($dosTime)
|
||||
{
|
||||
$dosTime = sprintf('%u', $dosTime);
|
||||
if (0x00000000 > $dosTime || $dosTime > 0xffffffff) {
|
||||
$dosTime = (int) $dosTime;
|
||||
|
||||
if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
|
||||
throw new ZipException('DosTime out of range');
|
||||
}
|
||||
$this->dosTime = $dosTime;
|
||||
$this->setInit(self::BIT_DATE_TIME, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp)
|
||||
{
|
||||
$known = self::UNKNOWN != $unixTimestamp;
|
||||
$known = $unixTimestamp !== self::UNKNOWN;
|
||||
|
||||
if ($known) {
|
||||
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
|
||||
} else {
|
||||
$this->dosTime = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_DATE_TIME, $known);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the external file attributes.
|
||||
*
|
||||
* @return int The external file attributes.
|
||||
* @return int the external file attributes
|
||||
*/
|
||||
public function getExternalAttributes()
|
||||
{
|
||||
if (!$this->isInit(self::BIT_EXTERNAL_ATTR)) {
|
||||
return $this->isDirectory() ? 0x10 : 0;
|
||||
}
|
||||
return $this->externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the external file attributes.
|
||||
*
|
||||
* @param int $externalAttributes the external file attributes.
|
||||
* @param int $externalAttributes the external file attributes
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes)
|
||||
{
|
||||
$known = self::UNKNOWN != $externalAttributes;
|
||||
if ($known) {
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
} else {
|
||||
$this->externalAttributes = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_EXTERNAL_ATTR, $known);
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal file attributes.
|
||||
*
|
||||
* @param int $attributes the internal file attributes
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setInternalAttributes($attributes)
|
||||
{
|
||||
$this->internalAttributes = (int) $attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal file attributes.
|
||||
*
|
||||
* @return int the internal file attributes
|
||||
*/
|
||||
public function getInternalAttributes()
|
||||
{
|
||||
return $this->internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
@@ -575,8 +703,10 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the serialized Extra Fields.
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
@@ -591,41 +721,50 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* (application) data.
|
||||
* Consider storing such data in a separate entry instead.
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @param string $data the byte array holding the serialized Extra Fields
|
||||
*
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setExtra($data)
|
||||
{
|
||||
$this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comment entry
|
||||
* Returns comment entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return null !== $this->comment ? $this->comment : "";
|
||||
return $this->comment !== null ? $this->comment : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param $comment
|
||||
* @return ZipEntry
|
||||
* @param string|null $comment
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
if ($comment !== null) {
|
||||
$commentLength = strlen($comment);
|
||||
if (0x0000 > $commentLength || $commentLength > 0xffff) {
|
||||
throw new ZipException("Comment too long");
|
||||
$commentLength = \strlen($comment);
|
||||
|
||||
if ($commentLength < 0x0000 || $commentLength > 0xffff) {
|
||||
throw new ZipException('Comment too long');
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -634,11 +773,11 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function isDataDescriptorRequired()
|
||||
{
|
||||
return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) == self::UNKNOWN;
|
||||
return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) === self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return crc32 content or 0 for WinZip AES v2
|
||||
* Return crc32 content or 0 for WinZip AES v2.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
@@ -651,12 +790,13 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
* Set crc32 content.
|
||||
*
|
||||
* @param int $crc
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCrc($crc)
|
||||
{
|
||||
$this->crc = $crc;
|
||||
$this->setInit(self::BIT_CRC, true);
|
||||
$this->crc = (int) $crc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -669,24 +809,29 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password and encryption method from entry
|
||||
* Set password and encryption method from entry.
|
||||
*
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod
|
||||
*
|
||||
* @param string $password
|
||||
* @param null|int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
if ($encryptionMethod !== null) {
|
||||
$this->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
|
||||
if (!empty($this->password)) {
|
||||
$this->setEncrypted(true);
|
||||
} else {
|
||||
$this->disableEncryption();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -699,30 +844,33 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
}
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
* Set encryption method.
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
if (null !== $encryptionMethod) {
|
||||
if ($encryptionMethod !== null) {
|
||||
if (
|
||||
$encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
$encryptionMethod !== ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
) {
|
||||
throw new ZipException('Invalid encryption method');
|
||||
}
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -736,22 +884,26 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
|
||||
/**
|
||||
* @param int $compressionLevel
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION)
|
||||
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
|
||||
{
|
||||
if ($compressionLevel < ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ||
|
||||
$compressionLevel > ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
|
||||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
|
||||
) {
|
||||
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
|
||||
ZipFileInterface::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFileInterface::LEVEL_BEST_COMPRESSION);
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid compression level. Minimum level ' .
|
||||
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION
|
||||
);
|
||||
}
|
||||
$this->compressionLevel = $compressionLevel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
* Clone extra fields.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
|
@@ -2,26 +2,29 @@
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Source Entry Changes
|
||||
* Source Entry Changes.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ZipChangesEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* @var ZipSourceEntry
|
||||
*/
|
||||
/** @var ZipSourceEntry */
|
||||
protected $entry;
|
||||
|
||||
/**
|
||||
* ZipChangesEntry constructor.
|
||||
*
|
||||
* @param ZipSourceEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws \PhpZip\Exception\InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(ZipSourceEntry $entry)
|
||||
{
|
||||
@@ -47,8 +50,9 @@ class ZipChangesEntry extends ZipAbstractEntry
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
|
@@ -3,7 +3,6 @@
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
@@ -11,23 +10,22 @@ use PhpZip\ZipFileInterface;
|
||||
*/
|
||||
class ZipNewEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* @var resource|string|null
|
||||
*/
|
||||
/** @var resource|string|null */
|
||||
protected $content;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $clone = false;
|
||||
|
||||
/**
|
||||
* ZipNewEntry constructor.
|
||||
*
|
||||
* @param string|resource|null $content
|
||||
*/
|
||||
public function __construct($content = null)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($content !== null && !is_string($content) && !is_resource($content)) {
|
||||
|
||||
if ($content !== null && !\is_string($content) && !\is_resource($content)) {
|
||||
throw new InvalidArgumentException('invalid content');
|
||||
}
|
||||
$this->content = $content;
|
||||
@@ -36,39 +34,23 @@ class ZipNewEntry extends ZipAbstractEntry
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (is_resource($this->content)) {
|
||||
if (\is_resource($this->content)) {
|
||||
if (stream_get_meta_data($this->content)['seekable']) {
|
||||
rewind($this->content);
|
||||
}
|
||||
|
||||
return stream_get_contents($this->content);
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionNeededToExtract()
|
||||
{
|
||||
$method = $this->getMethod();
|
||||
return self::METHOD_WINZIP_AES === $method ? 51 :
|
||||
(
|
||||
ZipFileInterface::METHOD_BZIP2 === $method ? 46 :
|
||||
(
|
||||
$this->isZip64ExtensionsRequired() ? 45 :
|
||||
(ZipFileInterface::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
* Clone extra fields.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
@@ -78,7 +60,7 @@ class ZipNewEntry extends ZipAbstractEntry
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->clone && $this->content !== null && is_resource($this->content)) {
|
||||
if (!$this->clone && $this->content !== null && \is_resource($this->content)) {
|
||||
fclose($this->content);
|
||||
$this->content = null;
|
||||
}
|
||||
|
@@ -12,28 +12,31 @@ use PhpZip\Exception\ZipException;
|
||||
*/
|
||||
class ZipNewFileEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* @var string Filename
|
||||
*/
|
||||
/** @var string Filename */
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* ZipNewEntry constructor.
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct($file)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if ($file === null) {
|
||||
throw new InvalidArgumentException("file is null");
|
||||
throw new InvalidArgumentException('file is null');
|
||||
}
|
||||
$file = (string)$file;
|
||||
$file = (string) $file;
|
||||
|
||||
if (!is_file($file)) {
|
||||
throw new ZipException("File $file does not exist.");
|
||||
throw new ZipException("File {$file} does not exist.");
|
||||
}
|
||||
|
||||
if (!is_readable($file)) {
|
||||
throw new ZipException("The '$file' file could not be read. Check permissions.");
|
||||
throw new ZipException("The '{$file}' file could not be read. Check permissions.");
|
||||
}
|
||||
$this->file = $file;
|
||||
}
|
||||
@@ -41,13 +44,14 @@ class ZipNewFileEntry extends ZipAbstractEntry
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (!is_file($this->file)) {
|
||||
throw new RuntimeException("File {$this->file} does not exist.");
|
||||
}
|
||||
|
||||
return file_get_contents($this->file);
|
||||
}
|
||||
}
|
||||
|
@@ -9,34 +9,27 @@ use PhpZip\Stream\ZipInputStreamInterface;
|
||||
* This class is used to represent a ZIP file entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipSourceEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* Max size cached content in memory.
|
||||
*/
|
||||
/** Max size cached content in memory. */
|
||||
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 524288; // 512 kb
|
||||
/**
|
||||
* @var ZipInputStreamInterface
|
||||
*/
|
||||
|
||||
/** @var ZipInputStreamInterface */
|
||||
protected $inputStream;
|
||||
/**
|
||||
* @var string|resource Cached entry content.
|
||||
*/
|
||||
|
||||
/** @var string|resource cached entry content */
|
||||
protected $entryContent;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $readPassword;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $clone = false;
|
||||
|
||||
/**
|
||||
* ZipSourceEntry constructor.
|
||||
*
|
||||
* @param ZipInputStreamInterface $inputStream
|
||||
*/
|
||||
public function __construct(ZipInputStreamInterface $inputStream)
|
||||
@@ -56,6 +49,8 @@ class ZipSourceEntry extends ZipAbstractEntry
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
@@ -63,23 +58,28 @@ class ZipSourceEntry extends ZipAbstractEntry
|
||||
if ($this->entryContent === null) {
|
||||
// In order not to unpack again, we cache the content in memory or on disk
|
||||
$content = $this->inputStream->readEntryContent($this);
|
||||
|
||||
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
|
||||
$this->entryContent = $content;
|
||||
} else {
|
||||
$this->entryContent = fopen('php://temp', 'r+b');
|
||||
fwrite($this->entryContent, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
if (is_resource($this->entryContent)) {
|
||||
|
||||
if (\is_resource($this->entryContent)) {
|
||||
rewind($this->entryContent);
|
||||
|
||||
return stream_get_contents($this->entryContent);
|
||||
}
|
||||
|
||||
return $this->entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
* Clone extra fields.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
@@ -89,7 +89,7 @@ class ZipSourceEntry extends ZipAbstractEntry
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->clone && $this->entryContent !== null && is_resource($this->entryContent)) {
|
||||
if (!$this->clone && $this->entryContent !== null && \is_resource($this->entryContent)) {
|
||||
fclose($this->entryContent);
|
||||
}
|
||||
}
|
||||
|
@@ -4,32 +4,27 @@ namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* ZIP file entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipEntry
|
||||
{
|
||||
// Bit masks for initialized fields.
|
||||
const BIT_PLATFORM = 1,
|
||||
BIT_METHOD = 2 /* 1 << 1 */,
|
||||
BIT_CRC = 4 /* 1 << 2 */,
|
||||
BIT_DATE_TIME = 64 /* 1 << 6 */,
|
||||
BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/
|
||||
;
|
||||
|
||||
/** The unknown value for numeric properties. */
|
||||
const UNKNOWN = -1;
|
||||
|
||||
/** Windows platform. */
|
||||
const PLATFORM_FAT = 0;
|
||||
|
||||
/** Unix platform. */
|
||||
const PLATFORM_UNIX = 3;
|
||||
|
||||
/** MacOS platform */
|
||||
const PLATFORM_OS_X = 19;
|
||||
|
||||
@@ -40,26 +35,33 @@ interface ZipEntry
|
||||
const METHOD_WINZIP_AES = 99;
|
||||
|
||||
/** General Purpose Bit Flag mask for encrypted data. */
|
||||
const GPBF_ENCRYPTED = 1; // 1 << 0
|
||||
// (For Methods 8 and 9 - Deflating)
|
||||
// Bit 2 Bit 1
|
||||
// 0 0 Normal compression
|
||||
// 0 1 Maximum compression
|
||||
// 1 0 Fast compression
|
||||
// 1 1 Super Fast compression
|
||||
const GPBF_ENCRYPTED = 1;
|
||||
|
||||
// (For Methods 8 and 9 - Deflating)
|
||||
// Bit 2 Bit 1
|
||||
// 0 0 Normal compression
|
||||
// 0 1 Maximum compression
|
||||
// 1 0 Fast compression
|
||||
// 1 1 Super Fast compression
|
||||
const GPBF_COMPRESSION_FLAG1 = 2; // 1 << 1
|
||||
|
||||
const GPBF_COMPRESSION_FLAG2 = 4; // 1 << 2
|
||||
|
||||
/** General Purpose Bit Flag mask for data descriptor. */
|
||||
const GPBF_DATA_DESCRIPTOR = 8; // 1 << 3
|
||||
|
||||
/** General Purpose Bit Flag mask for strong encryption. */
|
||||
const GPBF_STRONG_ENCRYPTION = 64; // 1 << 6
|
||||
|
||||
/** General Purpose Bit Flag mask for UTF-8. */
|
||||
const GPBF_UTF8 = 2048; // 1 << 11
|
||||
|
||||
/** Local File Header signature. */
|
||||
const LOCAL_FILE_HEADER_SIG = 0x04034B50;
|
||||
|
||||
/** Data Descriptor signature. */
|
||||
const DATA_DESCRIPTOR_SIG = 0x08074B50;
|
||||
|
||||
/**
|
||||
* The minimum length of the Local File Header record.
|
||||
*
|
||||
@@ -76,6 +78,7 @@ interface ZipEntry
|
||||
* extra field length 2
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_MIN_LEN = 30;
|
||||
|
||||
/**
|
||||
* Local File Header signature 4
|
||||
* Version Needed To Extract 2
|
||||
@@ -85,12 +88,11 @@ interface ZipEntry
|
||||
* Last Mod File Date 2
|
||||
* CRC-32 4
|
||||
* Compressed Size 4
|
||||
* Uncompressed Size 4
|
||||
* Uncompressed Size 4.
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26;
|
||||
/**
|
||||
* Default compression level for bzip2
|
||||
*/
|
||||
|
||||
/** Default compression level for bzip2 */
|
||||
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
|
||||
|
||||
/**
|
||||
@@ -104,25 +106,77 @@ interface ZipEntry
|
||||
* Set entry name.
|
||||
*
|
||||
* @param string $name New entry name
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*
|
||||
* @deprecated Use {@see ZipEntry::getCreatedOS()}
|
||||
*/
|
||||
public function getPlatform();
|
||||
|
||||
/**
|
||||
* Set platform
|
||||
*
|
||||
* @param int $platform
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @deprecated Use {@see ZipEntry::setCreatedOS()}
|
||||
*/
|
||||
public function setPlatform($platform);
|
||||
|
||||
/**
|
||||
* Returns created OS.
|
||||
*
|
||||
* @return int Get platform
|
||||
*/
|
||||
public function getCreatedOS();
|
||||
|
||||
/**
|
||||
* Set created OS.
|
||||
*
|
||||
* @param int $platform
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCreatedOS($platform);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExtractedOS();
|
||||
|
||||
/**
|
||||
* Set extracted OS.
|
||||
*
|
||||
* @param int $platform
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setExtractedOS($platform);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSoftwareVersion();
|
||||
|
||||
/**
|
||||
* @param int $softwareVersion
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setSoftwareVersion($softwareVersion);
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
@@ -134,6 +188,7 @@ interface ZipEntry
|
||||
* Set version needed to extract.
|
||||
*
|
||||
* @param int $version
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setVersionNeededToExtract($version);
|
||||
@@ -153,9 +208,11 @@ interface ZipEntry
|
||||
/**
|
||||
* Sets the compressed size of this entry.
|
||||
*
|
||||
* @param int $compressedSize The Compressed Size.
|
||||
* @return ZipEntry
|
||||
* @param int $compressedSize the Compressed Size
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressedSize($compressedSize);
|
||||
|
||||
@@ -169,9 +226,11 @@ interface ZipEntry
|
||||
/**
|
||||
* Sets the uncompressed size of this entry.
|
||||
*
|
||||
* @param int $size The (Uncompressed) Size.
|
||||
* @return ZipEntry
|
||||
* @param int $size the (Uncompressed) Size
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setSize($size);
|
||||
|
||||
@@ -184,8 +243,10 @@ interface ZipEntry
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setOffset($offset);
|
||||
|
||||
@@ -207,9 +268,13 @@ interface ZipEntry
|
||||
/**
|
||||
* Sets the General Purpose Bit Flags.
|
||||
*
|
||||
* @var int general
|
||||
* @return ZipEntry
|
||||
* @param mixed $general
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @var int general
|
||||
*/
|
||||
public function setGeneralPurposeBitFlags($general);
|
||||
|
||||
@@ -217,6 +282,7 @@ interface ZipEntry
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask);
|
||||
@@ -224,8 +290,9 @@ interface ZipEntry
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit);
|
||||
@@ -241,6 +308,7 @@ interface ZipEntry
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
* @param bool $encrypted
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted);
|
||||
@@ -264,13 +332,15 @@ interface ZipEntry
|
||||
* Sets the compression method for this entry.
|
||||
*
|
||||
* @param int $method
|
||||
*
|
||||
* @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
|
||||
*
|
||||
* @return ZipEntry
|
||||
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
|
||||
*/
|
||||
public function setMethod($method);
|
||||
|
||||
/**
|
||||
* Get Unix Timestamp
|
||||
* Get Unix Timestamp.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
@@ -280,37 +350,62 @@ interface ZipEntry
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp);
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
* Get Dos Time.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDosTime();
|
||||
|
||||
/**
|
||||
* Set Dos Time
|
||||
* Set Dos Time.
|
||||
*
|
||||
* @param int $dosTime
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setDosTime($dosTime);
|
||||
|
||||
/**
|
||||
* Returns the external file attributes.
|
||||
*
|
||||
* @return int The external file attributes.
|
||||
* @return int the external file attributes
|
||||
*/
|
||||
public function getExternalAttributes();
|
||||
|
||||
/**
|
||||
* Sets the internal file attributes.
|
||||
*
|
||||
* @param int $attributes the internal file attributes
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setInternalAttributes($attributes);
|
||||
|
||||
/**
|
||||
* Returns the internal file attributes.
|
||||
*
|
||||
* @return int the internal file attributes
|
||||
*/
|
||||
public function getInternalAttributes();
|
||||
|
||||
/**
|
||||
* Sets the external file attributes.
|
||||
*
|
||||
* @param int $externalAttributes the external file attributes.
|
||||
* @return ZipEntry
|
||||
* @param int $externalAttributes the external file attributes
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes);
|
||||
|
||||
@@ -335,13 +430,16 @@ interface ZipEntry
|
||||
* (application) data.
|
||||
* Consider storing such data in a separate entry instead.
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @param string $data the byte array holding the serialized Extra Fields
|
||||
*
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setExtra($data);
|
||||
|
||||
/**
|
||||
* Returns comment entry
|
||||
* Returns comment entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -351,6 +449,7 @@ interface ZipEntry
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param $comment
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setComment($comment);
|
||||
@@ -361,7 +460,7 @@ interface ZipEntry
|
||||
public function isDataDescriptorRequired();
|
||||
|
||||
/**
|
||||
* Return crc32 content or 0 for WinZip AES v2
|
||||
* Return crc32 content or 0 for WinZip AES v2.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
@@ -371,8 +470,10 @@ interface ZipEntry
|
||||
* Set crc32 content.
|
||||
*
|
||||
* @param int $crc
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCrc($crc);
|
||||
|
||||
@@ -382,10 +483,11 @@ interface ZipEntry
|
||||
public function getPassword();
|
||||
|
||||
/**
|
||||
* Set password and encryption method from entry
|
||||
* Set password and encryption method from entry.
|
||||
*
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod
|
||||
*
|
||||
* @param string $password
|
||||
* @param null|int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null);
|
||||
@@ -396,32 +498,36 @@ interface ZipEntry
|
||||
public function getEncryptionMethod();
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
* Set encryption method.
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod);
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEntryContent();
|
||||
|
||||
/**
|
||||
* @param int $compressionLevel
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION);
|
||||
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
|
@@ -8,18 +8,15 @@ namespace PhpZip\Model;
|
||||
*/
|
||||
class ZipEntryMatcher implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
/** @var ZipModel */
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var array */
|
||||
protected $matches = [];
|
||||
|
||||
/**
|
||||
* ZipEntryMatcher constructor.
|
||||
*
|
||||
* @param ZipModel $zipModel
|
||||
*/
|
||||
public function __construct(ZipModel $zipModel)
|
||||
@@ -29,44 +26,59 @@ class ZipEntryMatcher implements \Countable
|
||||
|
||||
/**
|
||||
* @param string|array $entries
|
||||
*
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function add($entries)
|
||||
{
|
||||
$entries = (array)$entries;
|
||||
$entries = array_map(function ($entry) {
|
||||
return $entry instanceof ZipEntry ? $entry->getName() : $entry;
|
||||
}, $entries);
|
||||
$this->matches = array_unique(
|
||||
array_merge(
|
||||
$this->matches,
|
||||
array_keys(
|
||||
array_intersect_key(
|
||||
$this->zipModel->getEntries(),
|
||||
array_flip($entries)
|
||||
$entries = (array) $entries;
|
||||
$entries = array_map(
|
||||
static function ($entry) {
|
||||
return $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
|
||||
},
|
||||
$entries
|
||||
);
|
||||
$this->matches = array_values(
|
||||
array_map(
|
||||
'strval',
|
||||
array_unique(
|
||||
array_merge(
|
||||
$this->matches,
|
||||
array_keys(
|
||||
array_intersect_key(
|
||||
$this->zipModel->getEntries(),
|
||||
array_flip($entries)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $regexp
|
||||
*
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function match($regexp)
|
||||
{
|
||||
array_walk($this->zipModel->getEntries(), function (
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
$entry,
|
||||
$entryName
|
||||
) use ($regexp) {
|
||||
if (preg_match($regexp, $entryName)) {
|
||||
$this->matches[] = $entryName;
|
||||
array_walk(
|
||||
$this->zipModel->getEntries(),
|
||||
function (
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
$entry,
|
||||
$entryName
|
||||
) use ($regexp) {
|
||||
if (preg_match($regexp, $entryName)) {
|
||||
$this->matches[] = (string) $entryName;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
$this->matches = array_unique($this->matches);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -76,6 +88,7 @@ class ZipEntryMatcher implements \Countable
|
||||
public function all()
|
||||
{
|
||||
$this->matches = array_keys($this->zipModel->getEntries());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -90,9 +103,12 @@ class ZipEntryMatcher implements \Countable
|
||||
public function invoke(callable $callable)
|
||||
{
|
||||
if (!empty($this->matches)) {
|
||||
array_walk($this->matches, function ($entryName) use ($callable) {
|
||||
call_user_func($callable, $entryName);
|
||||
});
|
||||
array_walk(
|
||||
$this->matches,
|
||||
static function ($entryName) use ($callable) {
|
||||
$callable($entryName);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,24 +122,31 @@ class ZipEntryMatcher implements \Countable
|
||||
|
||||
public function delete()
|
||||
{
|
||||
array_walk($this->matches, function ($entry) {
|
||||
$this->zipModel->deleteEntry($entry);
|
||||
});
|
||||
array_walk(
|
||||
$this->matches,
|
||||
function ($entry) {
|
||||
$this->zipModel->deleteEntry($entry);
|
||||
}
|
||||
);
|
||||
$this->matches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $password
|
||||
* @param int|null $encryptionMethod
|
||||
* @param int|null $encryptionMethod
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
array_walk($this->matches, function ($entry) use ($password, $encryptionMethod) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod);
|
||||
array_walk(
|
||||
$this->matches,
|
||||
function ($entry) use ($password, $encryptionMethod) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
|
||||
if (!$entry->isDirectory()) {
|
||||
$this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,36 +154,47 @@ class ZipEntryMatcher implements \Countable
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
array_walk($this->matches, function ($entry) use ($encryptionMethod) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod);
|
||||
array_walk(
|
||||
$this->matches,
|
||||
function ($entry) use ($encryptionMethod) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
|
||||
if (!$entry->isDirectory()) {
|
||||
$this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
public function disableEncryption()
|
||||
{
|
||||
array_walk($this->matches, function ($entry) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry = $this->zipModel->getEntryForChanges($entry);
|
||||
$entry->disableEncryption();
|
||||
array_walk(
|
||||
$this->matches,
|
||||
function ($entry) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry = $this->zipModel->getEntryForChanges($entry);
|
||||
$entry->disableEncryption();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* Count elements of an object.
|
||||
*
|
||||
* @see http://php.net/manual/en/countable.count.php
|
||||
*
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->matches);
|
||||
return \count($this->matches);
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
<?php /** @noinspection PhpMissingBreakStatementInspection */
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Zip info
|
||||
* Zip info.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
@@ -17,53 +18,96 @@ class ZipInfo
|
||||
{
|
||||
// made by constants
|
||||
const MADE_BY_MS_DOS = 0;
|
||||
|
||||
const MADE_BY_AMIGA = 1;
|
||||
|
||||
const MADE_BY_OPEN_VMS = 2;
|
||||
|
||||
const MADE_BY_UNIX = 3;
|
||||
|
||||
const MADE_BY_VM_CMS = 4;
|
||||
|
||||
const MADE_BY_ATARI = 5;
|
||||
|
||||
const MADE_BY_OS_2 = 6;
|
||||
|
||||
const MADE_BY_MACINTOSH = 7;
|
||||
|
||||
const MADE_BY_Z_SYSTEM = 8;
|
||||
|
||||
const MADE_BY_CP_M = 9;
|
||||
|
||||
const MADE_BY_WINDOWS_NTFS = 10;
|
||||
|
||||
const MADE_BY_MVS = 11;
|
||||
|
||||
const MADE_BY_VSE = 12;
|
||||
|
||||
const MADE_BY_ACORN_RISC = 13;
|
||||
|
||||
const MADE_BY_VFAT = 14;
|
||||
|
||||
const MADE_BY_ALTERNATE_MVS = 15;
|
||||
|
||||
const MADE_BY_BEOS = 16;
|
||||
|
||||
const MADE_BY_TANDEM = 17;
|
||||
|
||||
const MADE_BY_OS_400 = 18;
|
||||
|
||||
const MADE_BY_OS_X = 19;
|
||||
|
||||
const MADE_BY_UNKNOWN = 20;
|
||||
|
||||
const UNX_IFMT = 0170000; /* Unix file type mask */
|
||||
const UNX_IFREG = 0100000; /* Unix regular file */
|
||||
const UNX_IFSOCK = 0140000; /* Unix socket (BSD, not SysV or Amiga) */
|
||||
const UNX_IFLNK = 0120000; /* Unix symbolic link (not SysV, Amiga) */
|
||||
const UNX_IFBLK = 0060000; /* Unix block special (not Amiga) */
|
||||
const UNX_IFDIR = 0040000; /* Unix directory */
|
||||
const UNX_IFCHR = 0020000; /* Unix character special (not Amiga) */
|
||||
const UNX_IFIFO = 0010000; /* Unix fifo (BCC, not MSC or Amiga) */
|
||||
const UNX_ISUID = 04000; /* Unix set user id on execution */
|
||||
const UNX_ISGID = 02000; /* Unix set group id on execution */
|
||||
const UNX_ISVTX = 01000; /* Unix directory permissions control */
|
||||
const UNX_ENFMT = self::UNX_ISGID; /* Unix record locking enforcement flag */
|
||||
const UNX_IRWXU = 00700; /* Unix read, write, execute: owner */
|
||||
const UNX_IRUSR = 00400; /* Unix read permission: owner */
|
||||
const UNX_IWUSR = 00200; /* Unix write permission: owner */
|
||||
const UNX_IXUSR = 00100; /* Unix execute permission: owner */
|
||||
const UNX_IRWXG = 00070; /* Unix read, write, execute: group */
|
||||
const UNX_IRGRP = 00040; /* Unix read permission: group */
|
||||
const UNX_IWGRP = 00020; /* Unix write permission: group */
|
||||
const UNX_IXGRP = 00010; /* Unix execute permission: group */
|
||||
const UNX_IRWXO = 00007; /* Unix read, write, execute: other */
|
||||
const UNX_IROTH = 00004; /* Unix read permission: other */
|
||||
const UNX_IWOTH = 00002; /* Unix write permission: other */
|
||||
const UNX_IXOTH = 00001; /* Unix execute permission: other */
|
||||
const UNX_IFMT = 0170000; // Unix file type mask
|
||||
|
||||
private static $valuesMadeBy = [
|
||||
const UNX_IFREG = 0100000; // Unix regular file
|
||||
|
||||
const UNX_IFSOCK = 0140000; // Unix socket (BSD, not SysV or Amiga)
|
||||
|
||||
const UNX_IFLNK = 0120000; // Unix symbolic link (not SysV, Amiga)
|
||||
|
||||
const UNX_IFBLK = 0060000; // Unix block special (not Amiga)
|
||||
|
||||
const UNX_IFDIR = 0040000; // Unix directory
|
||||
|
||||
const UNX_IFCHR = 0020000; // Unix character special (not Amiga)
|
||||
|
||||
const UNX_IFIFO = 0010000; // Unix fifo (BCC, not MSC or Amiga)
|
||||
|
||||
const UNX_ISUID = 04000; // Unix set user id on execution
|
||||
|
||||
const UNX_ISGID = 02000; // Unix set group id on execution
|
||||
|
||||
const UNX_ISVTX = 01000; // Unix directory permissions control
|
||||
|
||||
const UNX_ENFMT = self::UNX_ISGID; // Unix record locking enforcement flag
|
||||
|
||||
const UNX_IRWXU = 00700; // Unix read, write, execute: owner
|
||||
|
||||
const UNX_IRUSR = 00400; // Unix read permission: owner
|
||||
|
||||
const UNX_IWUSR = 00200; // Unix write permission: owner
|
||||
|
||||
const UNX_IXUSR = 00100; // Unix execute permission: owner
|
||||
|
||||
const UNX_IRWXG = 00070; // Unix read, write, execute: group
|
||||
|
||||
const UNX_IRGRP = 00040; // Unix read permission: group
|
||||
|
||||
const UNX_IWGRP = 00020; // Unix write permission: group
|
||||
|
||||
const UNX_IXGRP = 00010; // Unix execute permission: group
|
||||
|
||||
const UNX_IRWXO = 00007; // Unix read, write, execute: other
|
||||
|
||||
const UNX_IROTH = 00004; // Unix read permission: other
|
||||
|
||||
const UNX_IWOTH = 00002; // Unix write permission: other
|
||||
|
||||
const UNX_IXOTH = 00001; // Unix execute permission: other
|
||||
|
||||
private static $platformNames = [
|
||||
self::MADE_BY_MS_DOS => 'FAT',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
self::MADE_BY_OPEN_VMS => 'OpenVMS',
|
||||
@@ -86,9 +130,9 @@ class ZipInfo
|
||||
self::MADE_BY_OS_X => 'Mac OS X',
|
||||
];
|
||||
|
||||
private static $valuesCompressionMethod = [
|
||||
private static $compressionMethodNames = [
|
||||
ZipEntry::UNKNOWN => 'unknown',
|
||||
ZipFileInterface::METHOD_STORED => 'no compression',
|
||||
ZipFile::METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
@@ -96,7 +140,7 @@ class ZipInfo
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
ZipFileInterface::METHOD_DEFLATED => 'deflate',
|
||||
ZipFile::METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
@@ -113,80 +157,64 @@ class ZipInfo
|
||||
ZipEntry::METHOD_WINZIP_AES => 'WinZip AES',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $name;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $folder;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $size;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $compressedSize;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $mtime;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
|
||||
/** @var int|null */
|
||||
private $ctime;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
|
||||
/** @var int|null */
|
||||
private $atime;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $encrypted;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
|
||||
/** @var string|null */
|
||||
private $comment;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $crc;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
/** @var string */
|
||||
private $methodName;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $compressionMethod;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
/** @var string */
|
||||
private $platform;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $version;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
/** @var string */
|
||||
private $attributes;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
|
||||
/** @var int|null */
|
||||
private $encryptionMethod;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
|
||||
/** @var int|null */
|
||||
private $compressionLevel;
|
||||
|
||||
/**
|
||||
* ZipInfo constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*
|
||||
* @throws ZipException
|
||||
* @noinspection PhpMissingBreakStatementInspection
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
@@ -195,7 +223,8 @@ class ZipInfo
|
||||
$ctime = null;
|
||||
|
||||
$field = $entry->getExtraFieldsCollection()->get(NtfsExtraField::getHeaderId());
|
||||
if (null !== $field && $field instanceof NtfsExtraField) {
|
||||
|
||||
if ($field instanceof NtfsExtraField) {
|
||||
/**
|
||||
* @var NtfsExtraField $field
|
||||
*/
|
||||
@@ -206,12 +235,8 @@ class ZipInfo
|
||||
|
||||
$this->name = $entry->getName();
|
||||
$this->folder = $entry->isDirectory();
|
||||
$this->size = PHP_INT_SIZE === 4 ?
|
||||
sprintf('%u', $entry->getSize()) :
|
||||
$entry->getSize();
|
||||
$this->compressedSize = PHP_INT_SIZE === 4 ?
|
||||
sprintf('%u', $entry->getCompressedSize()) :
|
||||
$entry->getCompressedSize();
|
||||
$this->size = $entry->getSize();
|
||||
$this->compressedSize = $entry->getCompressedSize();
|
||||
$this->mtime = $mtime;
|
||||
$this->ctime = $ctime;
|
||||
$this->atime = $atime;
|
||||
@@ -225,65 +250,71 @@ class ZipInfo
|
||||
$this->version = $entry->getVersionNeededToExtract();
|
||||
$this->compressionLevel = $entry->getCompressionLevel();
|
||||
|
||||
$attributes = str_repeat(" ", 12);
|
||||
$attributes = str_repeat(' ', 12);
|
||||
$externalAttributes = $entry->getExternalAttributes();
|
||||
$externalAttributes = PHP_INT_SIZE === 4 ?
|
||||
sprintf('%u', $externalAttributes) :
|
||||
$externalAttributes;
|
||||
$xattr = (($externalAttributes >> 16) & 0xFFFF);
|
||||
switch ($entry->getPlatform()) {
|
||||
switch ($entry->getCreatedOS()) {
|
||||
case self::MADE_BY_MS_DOS:
|
||||
case self::MADE_BY_WINDOWS_NTFS:
|
||||
if ($entry->getPlatform() != self::MADE_BY_MS_DOS ||
|
||||
($xattr & 0700) !=
|
||||
(0400 |
|
||||
if ($entry->getCreatedOS() !== self::MADE_BY_MS_DOS ||
|
||||
($xattr & self::UNX_IRWXU) !==
|
||||
(self::UNX_IRUSR |
|
||||
(!($externalAttributes & 1) << 7) |
|
||||
(($externalAttributes & 0x10) << 2))
|
||||
) {
|
||||
$xattr = $externalAttributes & 0xFF;
|
||||
$attributes = ".r.-... ";
|
||||
$attributes = '.r.-... ';
|
||||
$attributes[2] = ($xattr & 0x01) ? '-' : 'w';
|
||||
$attributes[5] = ($xattr & 0x02) ? 'h' : '-';
|
||||
$attributes[6] = ($xattr & 0x04) ? 's' : '-';
|
||||
$attributes[4] = ($xattr & 0x20) ? 'a' : '-';
|
||||
|
||||
if ($xattr & 0x10) {
|
||||
$attributes[0] = 'd';
|
||||
$attributes[3] = 'x';
|
||||
} else {
|
||||
$attributes[0] = '-';
|
||||
}
|
||||
|
||||
if ($xattr & 0x08) {
|
||||
$attributes[0] = 'V';
|
||||
} else {
|
||||
$ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) {
|
||||
$ext = strtolower(pathinfo($entry->getName(), \PATHINFO_EXTENSION));
|
||||
|
||||
if (\in_array($ext, ['com', 'exe', 'btm', 'cmd', 'bat'])) {
|
||||
$attributes[3] = 'x';
|
||||
}
|
||||
}
|
||||
break;
|
||||
} /* else: fall through! */
|
||||
} // else: fall through!
|
||||
|
||||
// no break
|
||||
default: /* assume Unix-like */
|
||||
default: // assume Unix-like
|
||||
switch ($xattr & self::UNX_IFMT) {
|
||||
case self::UNX_IFDIR:
|
||||
$attributes[0] = 'd';
|
||||
break;
|
||||
|
||||
case self::UNX_IFREG:
|
||||
$attributes[0] = '-';
|
||||
break;
|
||||
|
||||
case self::UNX_IFLNK:
|
||||
$attributes[0] = 'l';
|
||||
break;
|
||||
|
||||
case self::UNX_IFBLK:
|
||||
$attributes[0] = 'b';
|
||||
break;
|
||||
|
||||
case self::UNX_IFCHR:
|
||||
$attributes[0] = 'c';
|
||||
break;
|
||||
|
||||
case self::UNX_IFIFO:
|
||||
$attributes[0] = 'p';
|
||||
break;
|
||||
|
||||
case self::UNX_IFSOCK:
|
||||
$attributes[0] = 's';
|
||||
break;
|
||||
@@ -302,91 +333,97 @@ class ZipInfo
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
|
||||
} else {
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-';
|
||||
} /* S==undefined */
|
||||
} // S==undefined
|
||||
if ($xattr & self::UNX_IXGRP) {
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x';
|
||||
} /* == UNX_ENFMT */
|
||||
} // == UNX_ENFMT
|
||||
else {
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-';
|
||||
} /* SunOS 4.1.x */
|
||||
} // SunOS 4.1.x
|
||||
if ($xattr & self::UNX_IXOTH) {
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x';
|
||||
} /* "sticky bit" */
|
||||
} // "sticky bit"
|
||||
else {
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-';
|
||||
} /* T==undefined */
|
||||
} // T==undefined
|
||||
}
|
||||
$this->attributes = trim($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return int
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
private static function getMethodId(ZipEntry $entry)
|
||||
{
|
||||
$method = $entry->getMethod();
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$method = $field->getMethod();
|
||||
}
|
||||
|
||||
if ($entry->isEncrypted() && $entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
|
||||
if ($field !== null) {
|
||||
/** @var WinZipAesEntryExtraField $field */
|
||||
$method = $field->getMethod();
|
||||
}
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
private static function getEntryMethodName(ZipEntry $entry)
|
||||
{
|
||||
$return = '';
|
||||
|
||||
$compressionMethod = $entry->getMethod();
|
||||
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
$return .= ucfirst(self::$compressionMethodNames[$entry->getMethod()]);
|
||||
/** @var WinZipAesEntryExtraField|null $field */
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
|
||||
if ($field !== null) {
|
||||
$return .= '-' . $field->getKeyStrength();
|
||||
if (isset(self::$valuesCompressionMethod[$field->getMethod()])) {
|
||||
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$field->getMethod()]);
|
||||
}
|
||||
$compressionMethod = $field->getMethod();
|
||||
}
|
||||
} else {
|
||||
$return .= 'ZipCrypto';
|
||||
if (isset(self::$valuesCompressionMethod[$entry->getMethod()])) {
|
||||
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
}
|
||||
}
|
||||
} elseif (isset(self::$valuesCompressionMethod[$entry->getMethod()])) {
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
} else {
|
||||
$return = 'unknown';
|
||||
|
||||
$return .= ' ';
|
||||
}
|
||||
|
||||
if (isset(self::$compressionMethodNames[$compressionMethod])) {
|
||||
$return .= ucfirst(self::$compressionMethodNames[$compressionMethod]);
|
||||
} else {
|
||||
$return .= 'unknown';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPlatformName(ZipEntry $entry)
|
||||
{
|
||||
if (isset(self::$valuesMadeBy[$entry->getPlatform()])) {
|
||||
return self::$valuesMadeBy[$entry->getPlatform()];
|
||||
} else {
|
||||
return 'unknown';
|
||||
if (isset(self::$platformNames[$entry->getCreatedOS()])) {
|
||||
return self::$platformNames[$entry->getCreatedOS()];
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,6 +436,7 @@ class ZipInfo
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @deprecated use \PhpZip\Model\ZipInfo::getName()
|
||||
*/
|
||||
public function getPath()
|
||||
@@ -407,7 +445,7 @@ class ZipInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @return bool
|
||||
*/
|
||||
public function isFolder()
|
||||
{
|
||||
@@ -463,7 +501,7 @@ class ZipInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
@@ -471,7 +509,7 @@ class ZipInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
@@ -488,6 +526,7 @@ class ZipInfo
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
|
||||
*/
|
||||
public function getMethod()
|
||||
@@ -566,7 +605,7 @@ class ZipInfo
|
||||
'method_name' => $this->getMethodName(),
|
||||
'compression_method' => $this->getCompressionMethod(),
|
||||
'platform' => $this->getPlatform(),
|
||||
'version' => $this->getVersion()
|
||||
'version' => $this->getVersion(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -580,9 +619,9 @@ class ZipInfo
|
||||
. ($this->isFolder() ? 'Folder, ' : '')
|
||||
. 'Size="' . FilesUtil::humanSize($this->getSize()) . '"'
|
||||
. ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()) . '"'
|
||||
. ', Modified time="' . date(DATE_W3C, $this->getMtime()) . '", '
|
||||
. ($this->getCtime() !== null ? 'Created time="' . date(DATE_W3C, $this->getCtime()) . '", ' : '')
|
||||
. ($this->getAtime() !== null ? 'Accessed time="' . date(DATE_W3C, $this->getAtime()) . '", ' : '')
|
||||
. ', Modified time="' . date(\DATE_W3C, $this->getMtime()) . '", '
|
||||
. ($this->getCtime() !== null ? 'Created time="' . date(\DATE_W3C, $this->getCtime()) . '", ' : '')
|
||||
. ($this->getAtime() !== null ? 'Accessed time="' . date(\DATE_W3C, $this->getAtime()) . '", ' : '')
|
||||
. ($this->isEncrypted() ? 'Encrypted, ' : '')
|
||||
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
|
||||
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')
|
||||
|
@@ -7,68 +7,63 @@ use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Entry\ZipChangesEntry;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Zip Model
|
||||
* Zip Model.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipModel implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var ZipSourceEntry[]
|
||||
*/
|
||||
/** @var ZipSourceEntry[] */
|
||||
protected $inputEntries = [];
|
||||
/**
|
||||
* @var ZipEntry[]
|
||||
*/
|
||||
|
||||
/** @var ZipEntry[] */
|
||||
protected $outEntries = [];
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
|
||||
/** @var string|null */
|
||||
protected $archiveComment;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
|
||||
/** @var string|null */
|
||||
protected $archiveCommentChanges;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
protected $archiveCommentChanged = false;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
|
||||
/** @var int|null */
|
||||
protected $zipAlign;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $zip64;
|
||||
|
||||
/**
|
||||
* @param ZipSourceEntry[] $entries
|
||||
* @param ZipSourceEntry[] $entries
|
||||
* @param EndOfCentralDirectory $endOfCentralDirectory
|
||||
*
|
||||
* @return ZipModel
|
||||
*/
|
||||
public static function newSourceModel(array $entries, EndOfCentralDirectory $endOfCentralDirectory)
|
||||
{
|
||||
$model = new self;
|
||||
$model = new self();
|
||||
$model->inputEntries = $entries;
|
||||
$model->outEntries = $entries;
|
||||
$model->archiveComment = $endOfCentralDirectory->getComment();
|
||||
$model->zip64 = $endOfCentralDirectory->isZip64();
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getArchiveComment()
|
||||
{
|
||||
if ($this->archiveCommentChanged) {
|
||||
return $this->archiveCommentChanges;
|
||||
}
|
||||
|
||||
return $this->archiveComment;
|
||||
}
|
||||
|
||||
@@ -77,13 +72,15 @@ class ZipModel implements \Countable
|
||||
*/
|
||||
public function setArchiveComment($comment)
|
||||
{
|
||||
if ($comment !== null && strlen($comment) !== 0) {
|
||||
$comment = (string)$comment;
|
||||
$length = strlen($comment);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
if ($comment !== null && $comment !== '') {
|
||||
$comment = (string) $comment;
|
||||
$length = \strlen($comment);
|
||||
|
||||
if ($length > 0xffff) {
|
||||
throw new InvalidArgumentException('Length comment out of range');
|
||||
}
|
||||
}
|
||||
|
||||
if ($comment !== $this->archiveComment) {
|
||||
$this->archiveCommentChanges = $comment;
|
||||
$this->archiveCommentChanged = true;
|
||||
@@ -95,7 +92,8 @@ class ZipModel implements \Countable
|
||||
/**
|
||||
* Specify a password for extracting files.
|
||||
*
|
||||
* @param null|string $password
|
||||
* @param string|null $password
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setReadPassword($password)
|
||||
@@ -110,6 +108,7 @@ class ZipModel implements \Countable
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*/
|
||||
@@ -118,6 +117,7 @@ class ZipModel implements \Countable
|
||||
if (!isset($this->inputEntries[$entryName])) {
|
||||
throw new ZipEntryNotFoundException($entryName);
|
||||
}
|
||||
|
||||
if ($this->inputEntries[$entryName]->isEncrypted()) {
|
||||
$this->inputEntries[$entryName]->setPassword($password);
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class ZipModel implements \Countable
|
||||
*/
|
||||
public function setZipAlign($zipAlign)
|
||||
{
|
||||
$this->zipAlign = $zipAlign === null ? null : (int)$zipAlign;
|
||||
$this->zipAlign = $zipAlign === null ? null : (int) $zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,11 +144,11 @@ class ZipModel implements \Countable
|
||||
*/
|
||||
public function isZipAlign()
|
||||
{
|
||||
return $this->zipAlign != null;
|
||||
return $this->zipAlign !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $writePassword
|
||||
* @param string|null $writePassword
|
||||
*/
|
||||
public function setWritePassword($writePassword)
|
||||
{
|
||||
@@ -156,7 +156,7 @@ class ZipModel implements \Countable
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove password
|
||||
* Remove password.
|
||||
*/
|
||||
public function removePassword()
|
||||
{
|
||||
@@ -182,15 +182,16 @@ class ZipModel implements \Countable
|
||||
/**
|
||||
* @param string|ZipEntry $old
|
||||
* @param string|ZipEntry $new
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function renameEntry($old, $new)
|
||||
{
|
||||
$old = $old instanceof ZipEntry ? $old->getName() : (string)$old;
|
||||
$new = $new instanceof ZipEntry ? $new->getName() : (string)$new;
|
||||
$old = $old instanceof ZipEntry ? $old->getName() : (string) $old;
|
||||
$new = $new instanceof ZipEntry ? $new->getName() : (string) $new;
|
||||
|
||||
if (isset($this->outEntries[$new])) {
|
||||
throw new InvalidArgumentException("New entry name " . $new . ' is exists.');
|
||||
throw new InvalidArgumentException('New entry name ' . $new . ' is exists.');
|
||||
}
|
||||
|
||||
$entry = $this->getEntryForChanges($old);
|
||||
@@ -201,45 +202,57 @@ class ZipModel implements \Countable
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
* @return ZipChangesEntry|ZipEntry
|
||||
* @throws ZipException
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipChangesEntry|ZipEntry
|
||||
*/
|
||||
public function getEntryForChanges($entry)
|
||||
{
|
||||
$entry = $this->getEntry($entry);
|
||||
|
||||
if ($entry instanceof ZipSourceEntry) {
|
||||
$entry = new ZipChangesEntry($entry);
|
||||
$this->addEntry($entry);
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return ZipEntry
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function getEntry($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
|
||||
|
||||
if (isset($this->outEntries[$entryName])) {
|
||||
return $this->outEntries[$entryName];
|
||||
}
|
||||
|
||||
throw new ZipEntryNotFoundException($entryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteEntry($entry)
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
|
||||
|
||||
if (isset($this->outEntries[$entry])) {
|
||||
unset($this->outEntries[$entry]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -263,11 +276,13 @@ class ZipModel implements \Countable
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
|
||||
|
||||
return isset($this->outEntries[$entryName]);
|
||||
}
|
||||
|
||||
@@ -280,21 +295,24 @@ class ZipModel implements \Countable
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* Count elements of an object.
|
||||
*
|
||||
* @see http://php.net/manual/en/countable.count.php
|
||||
*
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return sizeof($this->outEntries);
|
||||
return \count($this->outEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive
|
||||
* Undo all changes done in the archive.
|
||||
*/
|
||||
public function unchangeAll()
|
||||
{
|
||||
@@ -303,7 +321,7 @@ class ZipModel implements \Countable
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo change archive comment
|
||||
* Undo change archive comment.
|
||||
*/
|
||||
public function unchangeArchiveComment()
|
||||
{
|
||||
@@ -315,22 +333,26 @@ class ZipModel implements \Countable
|
||||
* Revert all changes done to an entry with the given name.
|
||||
*
|
||||
* @param string|ZipEntry $entry Entry name or ZipEntry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unchangeEntry($entry)
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
|
||||
if (isset($this->outEntries[$entry]) && isset($this->inputEntries[$entry])) {
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
|
||||
|
||||
if (isset($this->outEntries[$entry], $this->inputEntries[$entry])) {
|
||||
$this->outEntries[$entry] = $this->inputEntries[$entry];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256)
|
||||
public function setEncryptionMethod($encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256)
|
||||
{
|
||||
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
|
@@ -5,59 +5,77 @@ namespace PhpZip\Stream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Implement PSR Message Stream
|
||||
* Implement PSR Message Stream.
|
||||
*/
|
||||
class ResponseStream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var array */
|
||||
private static $readWriteHash = [
|
||||
'read' => [
|
||||
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
|
||||
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
|
||||
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
|
||||
'x+t' => true, 'c+t' => true, 'a+' => true,
|
||||
'r' => true,
|
||||
'w+' => true,
|
||||
'r+' => true,
|
||||
'x+' => true,
|
||||
'c+' => true,
|
||||
'rb' => true,
|
||||
'w+b' => true,
|
||||
'r+b' => true,
|
||||
'x+b' => true,
|
||||
'c+b' => true,
|
||||
'rt' => true,
|
||||
'w+t' => true,
|
||||
'r+t' => true,
|
||||
'x+t' => true,
|
||||
'c+t' => true,
|
||||
'a+' => true,
|
||||
],
|
||||
'write' => [
|
||||
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
|
||||
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
|
||||
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
|
||||
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
|
||||
'w' => true,
|
||||
'w+' => true,
|
||||
'rw' => true,
|
||||
'r+' => true,
|
||||
'x+' => true,
|
||||
'c+' => true,
|
||||
'wb' => true,
|
||||
'w+b' => true,
|
||||
'r+b' => true,
|
||||
'x+b' => true,
|
||||
'c+b' => true,
|
||||
'w+t' => true,
|
||||
'r+t' => true,
|
||||
'x+t' => true,
|
||||
'c+t' => true,
|
||||
'a' => true,
|
||||
'a+' => true,
|
||||
],
|
||||
];
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
|
||||
/** @var resource */
|
||||
private $stream;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
||||
/** @var int */
|
||||
private $size;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $seekable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $readable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
||||
/** @var bool */
|
||||
private $writable;
|
||||
/**
|
||||
* @var array|mixed|null
|
||||
*/
|
||||
|
||||
/** @var array|mixed|null */
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @param resource $stream Stream resource to wrap.
|
||||
* @param resource $stream stream resource to wrap
|
||||
*
|
||||
* @throws \InvalidArgumentException if the stream is not a stream resource
|
||||
*/
|
||||
public function __construct($stream)
|
||||
{
|
||||
if (!is_resource($stream)) {
|
||||
if (!\is_resource($stream)) {
|
||||
throw new \InvalidArgumentException('Stream must be a resource');
|
||||
}
|
||||
$this->stream = $stream;
|
||||
@@ -74,11 +92,13 @@ class ResponseStream implements StreamInterface
|
||||
* The keys returned are identical to the keys returned from PHP's
|
||||
* stream_get_meta_data() function.
|
||||
*
|
||||
* @link http://php.net/manual/en/function.stream-get-meta-data.php
|
||||
* @param string $key Specific metadata to retrieve.
|
||||
* @see http://php.net/manual/en/function.stream-get-meta-data.php
|
||||
*
|
||||
* @param string $key specific metadata to retrieve
|
||||
*
|
||||
* @return array|mixed|null Returns an associative array if no key is
|
||||
* provided. Returns a specific key value if a key is provided and the
|
||||
* value is found, or null if the key is not found.
|
||||
* provided. Returns a specific key value if a key is provided and the
|
||||
* value is found, or null if the key is not found.
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
@@ -86,6 +106,7 @@ class ResponseStream implements StreamInterface
|
||||
return $key ? null : [];
|
||||
}
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
|
||||
return isset($meta[$key]) ? $meta[$key] : null;
|
||||
}
|
||||
|
||||
@@ -101,6 +122,7 @@ class ResponseStream implements StreamInterface
|
||||
* string casting operations.
|
||||
*
|
||||
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
@@ -109,7 +131,8 @@ class ResponseStream implements StreamInterface
|
||||
return '';
|
||||
}
|
||||
$this->rewind();
|
||||
return (string)stream_get_contents($this->stream);
|
||||
|
||||
return (string) stream_get_contents($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,9 +141,10 @@ class ResponseStream implements StreamInterface
|
||||
* If the stream is not seekable, this method will raise an exception;
|
||||
* otherwise, it will perform a seek(0).
|
||||
*
|
||||
* @throws \RuntimeException on failure
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.fseek.php
|
||||
* @see seek()
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @throws \RuntimeException on failure.
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
@@ -130,13 +154,14 @@ class ResponseStream implements StreamInterface
|
||||
/**
|
||||
* Get the size of the stream if known.
|
||||
*
|
||||
* @return int|null Returns the size in bytes if known, or null if unknown.
|
||||
* @return int|null returns the size in bytes if known, or null if unknown
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
if ($this->size !== null) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
if (!$this->stream) {
|
||||
return null;
|
||||
}
|
||||
@@ -145,18 +170,22 @@ class ResponseStream implements StreamInterface
|
||||
clearstatcache(true, $this->uri);
|
||||
}
|
||||
$stats = fstat($this->stream);
|
||||
|
||||
if (isset($stats['size'])) {
|
||||
$this->size = $stats['size'];
|
||||
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current position of the file read/write pointer
|
||||
* Returns the current position of the file read/write pointer.
|
||||
*
|
||||
* @throws \RuntimeException on error
|
||||
*
|
||||
* @return int Position of the file pointer
|
||||
* @throws \RuntimeException on error.
|
||||
*/
|
||||
public function tell()
|
||||
{
|
||||
@@ -186,16 +215,18 @@ class ResponseStream implements StreamInterface
|
||||
/**
|
||||
* Seek to a position in the stream.
|
||||
*
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @see http://www.php.net/manual/en/function.fseek.php
|
||||
*
|
||||
* @param int $offset Stream offset
|
||||
* @param int $whence Specifies how the cursor position will be calculated
|
||||
* based on the seek offset. Valid values are identical to the built-in
|
||||
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
|
||||
* offset bytes SEEK_CUR: Set position to current location plus offset
|
||||
* SEEK_END: Set position to end-of-stream plus offset.
|
||||
* @throws \RuntimeException on failure.
|
||||
* based on the seek offset. Valid values are identical to the built-in
|
||||
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
|
||||
* offset bytes SEEK_CUR: Set position to current location plus offset
|
||||
* SEEK_END: Set position to end-of-stream plus offset.
|
||||
*
|
||||
* @throws \RuntimeException on failure
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
public function seek($offset, $whence = \SEEK_SET)
|
||||
{
|
||||
$this->seekable && fseek($this->stream, $offset, $whence);
|
||||
}
|
||||
@@ -213,13 +244,16 @@ class ResponseStream implements StreamInterface
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* @param string $string The string that is to be written.
|
||||
* @return int Returns the number of bytes written to the stream.
|
||||
* @throws \RuntimeException on failure.
|
||||
* @param string $string the string that is to be written
|
||||
*
|
||||
* @throws \RuntimeException on failure
|
||||
*
|
||||
* @return int returns the number of bytes written to the stream
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
$this->size = null;
|
||||
|
||||
return $this->writable ? fwrite($this->stream, $string) : false;
|
||||
}
|
||||
|
||||
@@ -237,23 +271,26 @@ class ResponseStream implements StreamInterface
|
||||
* Read data from the stream.
|
||||
*
|
||||
* @param int $length Read up to $length bytes from the object and return
|
||||
* them. Fewer than $length bytes may be returned if underlying stream
|
||||
* call returns fewer bytes.
|
||||
* @return string Returns the data read from the stream, or an empty string
|
||||
* if no bytes are available.
|
||||
* @throws \RuntimeException if an error occurs.
|
||||
* them. Fewer than $length bytes may be returned if underlying stream
|
||||
* call returns fewer bytes.
|
||||
*
|
||||
* @throws \RuntimeException if an error occurs
|
||||
*
|
||||
* @return string returns the data read from the stream, or an empty string
|
||||
* if no bytes are available
|
||||
*/
|
||||
public function read($length)
|
||||
{
|
||||
return $this->readable ? fread($this->stream, $length) : "";
|
||||
return $this->readable ? fread($this->stream, $length) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining contents in a string
|
||||
* Returns the remaining contents in a string.
|
||||
*
|
||||
* @throws \RuntimeException if unable to read or an error occurs while
|
||||
* reading
|
||||
*
|
||||
* @return string
|
||||
* @throws \RuntimeException if unable to read or an error occurs while
|
||||
* reading.
|
||||
*/
|
||||
public function getContents()
|
||||
{
|
||||
@@ -261,7 +298,7 @@ class ResponseStream implements StreamInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream when the destructed
|
||||
* Closes the stream when the destructed.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
@@ -270,12 +307,10 @@ class ResponseStream implements StreamInterface
|
||||
|
||||
/**
|
||||
* Closes the stream and any underlying resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (is_resource($this->stream)) {
|
||||
if (\is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
$this->detach();
|
||||
@@ -293,6 +328,7 @@ class ResponseStream implements StreamInterface
|
||||
$result = $this->stream;
|
||||
$this->stream = $this->size = $this->uri = null;
|
||||
$this->readable = $this->writable = $this->seekable = false;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@@ -14,61 +14,45 @@ use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Mapper\OffsetPositionMapper;
|
||||
use PhpZip\Mapper\PositionMapper;
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Read zip file
|
||||
* Read zip file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipInputStream implements ZipInputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
/** @var resource */
|
||||
protected $in;
|
||||
/**
|
||||
* @var PositionMapper
|
||||
*/
|
||||
protected $mapper;
|
||||
/**
|
||||
* @var int The number of bytes in the preamble of this ZIP file.
|
||||
*/
|
||||
protected $preamble = 0;
|
||||
/**
|
||||
* @var int The number of bytes in the postamble of this ZIP file.
|
||||
*/
|
||||
protected $postamble = 0;
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
|
||||
/** @var ZipModel */
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* ZipInputStream constructor.
|
||||
*
|
||||
* @param resource $in
|
||||
*/
|
||||
public function __construct($in)
|
||||
{
|
||||
if (!is_resource($in)) {
|
||||
if (!\is_resource($in)) {
|
||||
throw new RuntimeException('$in must be resource');
|
||||
}
|
||||
$this->in = $in;
|
||||
$this->mapper = new PositionMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipModel
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipModel
|
||||
*/
|
||||
public function readZip()
|
||||
{
|
||||
@@ -76,11 +60,12 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
$endOfCentralDirectory = $this->readEndOfCentralDirectory();
|
||||
$entries = $this->mountCentralDirectory($endOfCentralDirectory);
|
||||
$this->zipModel = ZipModel::newSourceModel($entries, $endOfCentralDirectory);
|
||||
|
||||
return $this->zipModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check zip file signature
|
||||
* Check zip file signature.
|
||||
*
|
||||
* @throws ZipException if this not .ZIP file.
|
||||
*/
|
||||
@@ -90,152 +75,203 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
// Constraint: A ZIP file must start with a Local File Header
|
||||
// or a (ZIP64) End Of Central Directory Record if it's empty.
|
||||
$signatureBytes = fread($this->in, 4);
|
||||
if (strlen($signatureBytes) < 4) {
|
||||
throw new ZipException("Invalid zip file.");
|
||||
|
||||
if (\strlen($signatureBytes) < 4) {
|
||||
throw new ZipException('Invalid zip file.');
|
||||
}
|
||||
$signature = unpack('V', $signatureBytes)[1];
|
||||
|
||||
if (
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature
|
||||
&& EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
&& EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
$signature !== ZipEntry::LOCAL_FILE_HEADER_SIG
|
||||
&& $signature !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG
|
||||
&& $signature !== EndOfCentralDirectory::END_OF_CD_SIG
|
||||
) {
|
||||
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
|
||||
throw new ZipException(
|
||||
'Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: ' . $signature
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EndOfCentralDirectory
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return EndOfCentralDirectory
|
||||
*/
|
||||
protected function readEndOfCentralDirectory()
|
||||
{
|
||||
if (!$this->findEndOfCentralDirectory()) {
|
||||
throw new ZipException('Invalid zip file. The end of the central directory could not be found.');
|
||||
}
|
||||
|
||||
$positionECD = ftell($this->in) - 4;
|
||||
$buffer = fread($this->in, fstat($this->in)['size'] - $positionECD);
|
||||
|
||||
$unpack = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/' .
|
||||
'vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
substr($buffer, 0, 18)
|
||||
);
|
||||
|
||||
if (
|
||||
$unpack['diskNo'] !== 0 ||
|
||||
$unpack['cdDiskNo'] !== 0 ||
|
||||
$unpack['cdEntriesDisk'] !== $unpack['cdEntries']
|
||||
) {
|
||||
throw new ZipException(
|
||||
'ZIP file spanning/splitting is not supported!'
|
||||
);
|
||||
}
|
||||
// .ZIP file comment (variable sizeECD)
|
||||
$comment = null;
|
||||
// Search for End of central directory record.
|
||||
$stats = fstat($this->in);
|
||||
$size = $stats['size'];
|
||||
$max = $size - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
|
||||
|
||||
if ($unpack['commentLength'] > 0) {
|
||||
$comment = substr($buffer, 18, $unpack['commentLength']);
|
||||
}
|
||||
|
||||
// Check for ZIP64 End Of Central Directory Locator exists.
|
||||
$zip64ECDLocatorPosition = $positionECD - EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_LEN;
|
||||
fseek($this->in, $zip64ECDLocatorPosition);
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
if ($zip64ECDLocatorPosition > 0 && unpack(
|
||||
'V',
|
||||
fread($this->in, 4)
|
||||
)[1] === EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG) {
|
||||
$positionECD = $this->findZip64ECDPosition();
|
||||
$endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD);
|
||||
$endCentralDirectory->setComment($comment);
|
||||
} else {
|
||||
$endCentralDirectory = new EndOfCentralDirectory(
|
||||
$unpack['cdEntries'],
|
||||
$unpack['cdPos'],
|
||||
$unpack['cdSize'],
|
||||
false,
|
||||
$comment
|
||||
);
|
||||
}
|
||||
|
||||
return $endCentralDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function findEndOfCentralDirectory()
|
||||
{
|
||||
$max = fstat($this->in)['size'] - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
|
||||
|
||||
if ($max < 0) {
|
||||
throw new ZipException('Too short to be a zip file');
|
||||
}
|
||||
$min = $max >= 0xffff ? $max - 0xffff : 0;
|
||||
for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) {
|
||||
fseek($this->in, $endOfCentralDirRecordPos, SEEK_SET);
|
||||
// Search for End of central directory record.
|
||||
for ($position = $max; $position >= $min; $position--) {
|
||||
fseek($this->in, $position);
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
if (EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($this->in, 4))[1]) {
|
||||
if (unpack('V', fread($this->in, 4))[1] !== EndOfCentralDirectory::END_OF_CD_SIG) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// number of this disk - 2 bytes
|
||||
// number of the disk with the start of the
|
||||
// central directory - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory on this disk - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory - 2 bytes
|
||||
// size of the central directory - 4 bytes
|
||||
// offset of start of central directory with
|
||||
// respect to the starting disk number - 4 bytes
|
||||
// ZIP file comment length - 2 bytes
|
||||
$data = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
fread($this->in, 18)
|
||||
);
|
||||
|
||||
if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) {
|
||||
throw new ZipException(
|
||||
"ZIP file spanning/splitting is not supported!"
|
||||
);
|
||||
}
|
||||
// .ZIP file comment (variable size)
|
||||
if ($data['commentLength'] > 0) {
|
||||
$comment = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['commentLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['commentLength'] - $offset);
|
||||
$comment .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
}
|
||||
$this->preamble = $endOfCentralDirRecordPos;
|
||||
$this->postamble = $size - ftell($this->in);
|
||||
|
||||
// Check for ZIP64 End Of Central Directory Locator.
|
||||
$endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN;
|
||||
|
||||
fseek($this->in, $endOfCentralDirLocatorPos, SEEK_SET);
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
if (
|
||||
0 > $endOfCentralDirLocatorPos ||
|
||||
ftell($this->in) === $size ||
|
||||
EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($this->in, 4))[1]
|
||||
) {
|
||||
// Seek and check first CFH, probably requiring an offset mapper.
|
||||
$offset = $endOfCentralDirRecordPos - $data['cdSize'];
|
||||
fseek($this->in, $offset, SEEK_SET);
|
||||
$offset -= $data['cdPos'];
|
||||
if ($offset !== 0) {
|
||||
$this->mapper = new OffsetPositionMapper($offset);
|
||||
}
|
||||
$entryCount = $data['cdEntries'];
|
||||
return new EndOfCentralDirectory($entryCount, $comment);
|
||||
}
|
||||
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
$zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($this->in, 4))[1];
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
$zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// total number of disks 4 bytes
|
||||
$totalDisks = unpack('V', fread($this->in, 4))[1];
|
||||
if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
fseek($this->in, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET);
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
$zip64EndOfCentralDirSig = unpack('V', fread($this->in, 4))[1];
|
||||
if (EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) {
|
||||
throw new ZipException("Expected ZIP64 End Of Central Directory Record!");
|
||||
}
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
fseek($this->in, 12, SEEK_CUR);
|
||||
// number of this disk 4 bytes
|
||||
$diskNo = unpack('V', fread($this->in, 4))[1];
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
$cdDiskNo = unpack('V', fread($this->in, 4))[1];
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
$cdEntriesDisk = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
$cdEntries = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
if ($cdEntries < 0 || 0x7fffffff < $cdEntries) {
|
||||
throw new ZipException("Total Number Of Entries In The Central Directory out of range!");
|
||||
}
|
||||
// size of the central directory 8 bytes
|
||||
fseek($this->in, 8, SEEK_CUR);
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
$cdPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// zip64 extensible data sector (variable size)
|
||||
fseek($this->in, $cdPos, SEEK_SET);
|
||||
$this->preamble = $zip64EndOfCentralDirectoryRecordPos;
|
||||
$entryCount = $cdEntries;
|
||||
$zip64 = true;
|
||||
return new EndOfCentralDirectory($entryCount, $comment, $zip64);
|
||||
return true;
|
||||
}
|
||||
// Start recovering file entries from min.
|
||||
$this->preamble = $min;
|
||||
$this->postamble = $size - $min;
|
||||
return new EndOfCentralDirectory(0, $comment);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Zip64 end of central directory locator and returns
|
||||
* Zip64 end of central directory position.
|
||||
*
|
||||
* number of the disk with the
|
||||
* start of the zip64 end of
|
||||
* central directory 4 bytes
|
||||
* relative offset of the zip64
|
||||
* end of central directory record 8 bytes
|
||||
* total number of disks 4 bytes
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return int Zip64 End Of Central Directory position
|
||||
*/
|
||||
protected function findZip64ECDPosition()
|
||||
{
|
||||
$diskNo = unpack('V', fread($this->in, 4))[1];
|
||||
$zip64ECDPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
$totalDisks = unpack('V', fread($this->in, 4))[1];
|
||||
|
||||
if ($diskNo !== 0 || $totalDisks > 1) {
|
||||
throw new ZipException('ZIP file spanning/splitting is not supported!');
|
||||
}
|
||||
|
||||
return $zip64ECDPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read zip64 end of central directory locator and zip64 end
|
||||
* of central directory record.
|
||||
*
|
||||
* zip64 end of central dir
|
||||
* signature 4 bytes (0x06064b50)
|
||||
* size of zip64 end of central
|
||||
* directory record 8 bytes
|
||||
* version made by 2 bytes
|
||||
* version needed to extract 2 bytes
|
||||
* number of this disk 4 bytes
|
||||
* number of the disk with the
|
||||
* start of the central directory 4 bytes
|
||||
* total number of entries in the
|
||||
* central directory on this disk 8 bytes
|
||||
* total number of entries in the
|
||||
* central directory 8 bytes
|
||||
* size of the central directory 8 bytes
|
||||
* offset of start of central
|
||||
* directory with respect to
|
||||
* the starting disk number 8 bytes
|
||||
* zip64 extensible data sector (variable size)
|
||||
*
|
||||
* @param int $zip64ECDPosition
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return EndOfCentralDirectory
|
||||
*/
|
||||
protected function readZip64EndOfCentralDirectory($zip64ECDPosition)
|
||||
{
|
||||
fseek($this->in, $zip64ECDPosition);
|
||||
|
||||
$buffer = fread($this->in, 56 /* zip64 end of cd rec length */);
|
||||
|
||||
if (unpack('V', $buffer)[1] !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG) {
|
||||
throw new ZipException('Expected ZIP64 End Of Central Directory Record!');
|
||||
}
|
||||
|
||||
$data = unpack(
|
||||
'VdiskNo/VcdDiskNo',
|
||||
substr($buffer, 16)
|
||||
);
|
||||
$cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8));
|
||||
$entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8));
|
||||
$cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8));
|
||||
$cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8));
|
||||
|
||||
if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) {
|
||||
throw new ZipException('ZIP file spanning/splitting is not supported!');
|
||||
}
|
||||
|
||||
if ($entryCount < 0 || $entryCount > 0x7fffffff) {
|
||||
throw new ZipException('Total Number Of Entries In The Central Directory out of range!');
|
||||
}
|
||||
|
||||
// skip zip64 extensible data sector (variable sizeEndCD)
|
||||
|
||||
return new EndOfCentralDirectory(
|
||||
$entryCount,
|
||||
$cdPos,
|
||||
$cdSize,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,151 +283,148 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
* file header or additional data to be read.
|
||||
*
|
||||
* @param EndOfCentralDirectory $endOfCentralDirectory
|
||||
* @return ZipEntry[]
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
protected function mountCentralDirectory(EndOfCentralDirectory $endOfCentralDirectory)
|
||||
{
|
||||
$numEntries = $endOfCentralDirectory->getEntryCount();
|
||||
$entries = [];
|
||||
|
||||
for (; $numEntries > 0; $numEntries--) {
|
||||
$entry = $this->readEntry();
|
||||
// Re-load virtual offset after ZIP64 Extended Information
|
||||
// Extra Field may have been parsed, map it to the real
|
||||
// offset and conditionally update the preamble size from it.
|
||||
$lfhOff = $this->mapper->map($entry->getOffset());
|
||||
$lfhOff = PHP_INT_SIZE === 4 ? sprintf('%u', $lfhOff) : $lfhOff;
|
||||
if ($lfhOff < $this->preamble) {
|
||||
$this->preamble = $lfhOff;
|
||||
}
|
||||
fseek($this->in, $endOfCentralDirectory->getCdOffset());
|
||||
|
||||
if (!($cdStream = fopen('php://temp', 'w+b'))) {
|
||||
throw new ZipException('Temp resource can not open from write');
|
||||
}
|
||||
stream_copy_to_stream($this->in, $cdStream, $endOfCentralDirectory->getCdSize());
|
||||
rewind($cdStream);
|
||||
for ($numEntries = $endOfCentralDirectory->getEntryCount(); $numEntries > 0; $numEntries--) {
|
||||
$entry = $this->readCentralDirectoryEntry($cdStream);
|
||||
$entries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
if (($numEntries % 0x10000) !== 0) {
|
||||
throw new ZipException("Expected " . abs($numEntries) .
|
||||
($numEntries > 0 ? " more" : " less") .
|
||||
" entries in the Central Directory!");
|
||||
}
|
||||
|
||||
if ($this->preamble + $this->postamble >= fstat($this->in)['size']) {
|
||||
$this->checkZipFileSignature();
|
||||
}
|
||||
fclose($cdStream);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
* Read central directory entry.
|
||||
*
|
||||
* central file header signature 4 bytes (0x02014b50)
|
||||
* version made by 2 bytes
|
||||
* version needed to extract 2 bytes
|
||||
* general purpose bit flag 2 bytes
|
||||
* compression method 2 bytes
|
||||
* last mod file time 2 bytes
|
||||
* last mod file date 2 bytes
|
||||
* crc-32 4 bytes
|
||||
* compressed size 4 bytes
|
||||
* uncompressed size 4 bytes
|
||||
* file name length 2 bytes
|
||||
* extra field length 2 bytes
|
||||
* file comment length 2 bytes
|
||||
* disk number start 2 bytes
|
||||
* internal file attributes 2 bytes
|
||||
* external file attributes 4 bytes
|
||||
* relative offset of local header 4 bytes
|
||||
*
|
||||
* file name (variable size)
|
||||
* extra field (variable size)
|
||||
* file comment (variable size)
|
||||
*
|
||||
* @param resource $stream
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function readEntry()
|
||||
public function readCentralDirectoryEntry($stream)
|
||||
{
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
$fileHeaderSig = unpack('V', fread($this->in, 4))[1];
|
||||
if ($fileHeaderSig !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
|
||||
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
|
||||
if (unpack('V', fread($stream, 4))[1] !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
|
||||
throw new ZipException('Corrupt zip file. Cannot read central dir entry.');
|
||||
}
|
||||
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// general purpose bit flag 2 bytes
|
||||
// compression method 2 bytes
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
// crc-32 4 bytes
|
||||
// compressed size 4 bytes
|
||||
// uncompressed size 4 bytes
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
// file comment length 2 bytes
|
||||
// disk number start 2 bytes
|
||||
// internal file attributes 2 bytes
|
||||
// external file attributes 4 bytes
|
||||
// relative offset of local header 4 bytes
|
||||
$data = unpack(
|
||||
'vversionMadeBy/vversionNeededToExtract/vgpbf/' .
|
||||
'vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' .
|
||||
'VrawSize/vfileLength/vextraLength/vcommentLength/' .
|
||||
'VrawInternalAttributes/VrawExternalAttributes/VlfhOff',
|
||||
fread($this->in, 42)
|
||||
'vversionMadeBy/vversionNeededToExtract/' .
|
||||
'vgeneralPurposeBitFlag/vcompressionMethod/' .
|
||||
'VlastModFile/Vcrc/VcompressedSize/' .
|
||||
'VuncompressedSize/vfileNameLength/vextraFieldLength/' .
|
||||
'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' .
|
||||
'VexternalFileAttributes/VoffsetLocalHeader',
|
||||
fread($stream, 42)
|
||||
);
|
||||
|
||||
// $utf8 = ($data['gpbf'] & ZipEntry::GPBF_UTF8) !== 0;
|
||||
$createdOS = ($data['versionMadeBy'] & 0xFF00) >> 8;
|
||||
$softwareVersion = $data['versionMadeBy'] & 0x00FF;
|
||||
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$name = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['fileLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['fileLength'] - $offset);
|
||||
$name .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
$extractOS = ($data['versionNeededToExtract'] & 0xFF00) >> 8;
|
||||
$extractVersion = $data['versionNeededToExtract'] & 0x00FF;
|
||||
|
||||
$name = fread($stream, $data['fileNameLength']);
|
||||
|
||||
$extra = '';
|
||||
|
||||
if ($data['extraFieldLength'] > 0) {
|
||||
$extra = fread($stream, $data['extraFieldLength']);
|
||||
}
|
||||
|
||||
$comment = null;
|
||||
|
||||
if ($data['fileCommentLength'] > 0) {
|
||||
$comment = fread($stream, $data['fileCommentLength']);
|
||||
}
|
||||
|
||||
$entry = new ZipSourceEntry($this);
|
||||
$entry->setName($name);
|
||||
$entry->setVersionNeededToExtract($data['versionNeededToExtract']);
|
||||
$entry->setPlatform($data['versionMadeBy'] >> 8);
|
||||
$entry->setMethod($data['rawMethod']);
|
||||
$entry->setGeneralPurposeBitFlags($data['gpbf']);
|
||||
$entry->setDosTime($data['rawTime']);
|
||||
$entry->setCrc($data['rawCrc']);
|
||||
$entry->setCompressedSize($data['rawCompressedSize']);
|
||||
$entry->setSize($data['rawSize']);
|
||||
$entry->setExternalAttributes($data['rawExternalAttributes']);
|
||||
$entry->setOffset($data['lfhOff']); // must be unmapped!
|
||||
if ($data['extraLength'] > 0) {
|
||||
$extra = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['extraLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['extraLength'] - $offset);
|
||||
$extra .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
$entry->setExtra($extra);
|
||||
}
|
||||
if ($data['commentLength'] > 0) {
|
||||
$comment = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['commentLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['commentLength'] - $offset);
|
||||
$comment .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
$entry->setComment($comment);
|
||||
}
|
||||
$entry->setCreatedOS($createdOS);
|
||||
$entry->setSoftwareVersion($softwareVersion);
|
||||
$entry->setVersionNeededToExtract($extractVersion);
|
||||
$entry->setExtractedOS($extractOS);
|
||||
$entry->setMethod($data['compressionMethod']);
|
||||
$entry->setGeneralPurposeBitFlags($data['generalPurposeBitFlag']);
|
||||
$entry->setDosTime($data['lastModFile']);
|
||||
$entry->setCrc($data['crc']);
|
||||
$entry->setCompressedSize($data['compressedSize']);
|
||||
$entry->setSize($data['uncompressedSize']);
|
||||
$entry->setInternalAttributes($data['internalFileAttributes']);
|
||||
$entry->setExternalAttributes($data['externalFileAttributes']);
|
||||
$entry->setOffset($data['offsetLocalHeader']);
|
||||
$entry->setComment($comment);
|
||||
$entry->setExtra($extra);
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function readEntryContent(ZipEntry $entry)
|
||||
{
|
||||
if ($entry->isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!($entry instanceof ZipSourceEntry)) {
|
||||
throw new InvalidArgumentException('entry must be ' . ZipSourceEntry::class);
|
||||
}
|
||||
$isEncrypted = $entry->isEncrypted();
|
||||
|
||||
if ($isEncrypted && $entry->getPassword() === null) {
|
||||
throw new ZipException("Can not password from entry " . $entry->getName());
|
||||
throw new ZipException('Can not password from entry ' . $entry->getName());
|
||||
}
|
||||
|
||||
$pos = $entry->getOffset();
|
||||
$pos = PHP_INT_SIZE === 4
|
||||
? sprintf('%u', $pos) // PHP 32-Bit
|
||||
: $pos; // PHP 64-Bit
|
||||
$startPos = $pos = $entry->getOffset();
|
||||
|
||||
$startPos = $pos = $this->mapper->map($pos);
|
||||
fseek($this->in, $startPos);
|
||||
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
if (unpack('V', fread($this->in, 4))[1] !== ZipEntry::LOCAL_FILE_HEADER_SIG) {
|
||||
throw new ZipException($entry->getName() . " (expected Local File Header)");
|
||||
throw new ZipException($entry->getName() . ' (expected Local File Header)');
|
||||
}
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS);
|
||||
// file name length 2 bytes
|
||||
@@ -399,7 +432,9 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
$data = unpack('vfileLength/vextraLength', fread($this->in, 4));
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
|
||||
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
|
||||
if ($entry->getCrc() === ZipEntry::UNKNOWN) {
|
||||
throw new ZipException(sprintf('Missing crc for entry %s', $entry->getName()));
|
||||
}
|
||||
|
||||
$method = $entry->getMethod();
|
||||
|
||||
@@ -407,10 +442,11 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
|
||||
// Get raw entry content
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$compressedSize = PHP_INT_SIZE === 4 ? sprintf('%u', $compressedSize) : $compressedSize;
|
||||
$content = '';
|
||||
|
||||
if ($compressedSize > 0) {
|
||||
$offset = 0;
|
||||
|
||||
while ($offset < $compressedSize) {
|
||||
$read = min(8192 /* chunk size */, $compressedSize - $offset);
|
||||
$content .= fread($this->in, $read);
|
||||
@@ -419,6 +455,7 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
}
|
||||
|
||||
$skipCheckCrc = false;
|
||||
|
||||
if ($isEncrypted) {
|
||||
if ($method === ZipEntry::METHOD_WINZIP_AES) {
|
||||
// Strong Encryption Specification - WinZip AES
|
||||
@@ -435,12 +472,13 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
// Traditional PKWARE Decryption
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
|
||||
$content = $zipCryptoEngine->decrypt($content);
|
||||
$entry->setEncryptionMethod(ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$entry->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
}
|
||||
|
||||
if (!$skipCheckCrc) {
|
||||
// Check CRC32 in the Local File Header or Data Descriptor.
|
||||
$localCrc = null;
|
||||
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// The CRC32 is in the Data Descriptor after the compressed size.
|
||||
// Note the Data Descriptor's Signature is optional:
|
||||
@@ -448,72 +486,88 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
// but older apps might not.
|
||||
fseek($this->in, $pos + $compressedSize);
|
||||
$localCrc = unpack('V', fread($this->in, 4))[1];
|
||||
|
||||
if ($localCrc === ZipEntry::DATA_DESCRIPTOR_SIG) {
|
||||
$localCrc = unpack('V', fread($this->in, 4))[1];
|
||||
}
|
||||
} else {
|
||||
fseek($this->in, $startPos + 14);
|
||||
// The CRC32 in the Local File Header.
|
||||
$localCrc = sprintf('%u', fread($this->in, 4)[1]);
|
||||
$localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc;
|
||||
$localCrc = fread($this->in, 4)[1];
|
||||
}
|
||||
|
||||
$crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc();
|
||||
|
||||
if ($crc != $localCrc) {
|
||||
throw new Crc32Exception($entry->getName(), $crc, $localCrc);
|
||||
if (\PHP_INT_SIZE === 4) {
|
||||
if (sprintf('%u', $entry->getCrc()) === sprintf('%u', $localCrc)) {
|
||||
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
|
||||
}
|
||||
} elseif ($localCrc !== $entry->getCrc()) {
|
||||
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
case ZipFile::METHOD_STORED:
|
||||
break;
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
/** @noinspection PhpUsageOfSilenceOperatorInspection */
|
||||
$content = @gzinflate($content);
|
||||
break;
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
if (!extension_loaded('bz2')) {
|
||||
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
if (!\extension_loaded('bz2')) {
|
||||
throw new ZipException('Extension bzip2 not install');
|
||||
}
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
$content = bzdecompress($content);
|
||||
if (is_int($content)) { // decompress error
|
||||
|
||||
if (\is_int($content)) { // decompress error
|
||||
$content = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ZipUnsupportMethodException($entry->getName() .
|
||||
" (compression method " . $method . " is not supported)");
|
||||
throw new ZipUnsupportMethodException(
|
||||
$entry->getName() .
|
||||
' (compression method ' . $method . ' is not supported)'
|
||||
);
|
||||
}
|
||||
|
||||
if ($content === false) {
|
||||
if ($isEncrypted) {
|
||||
throw new ZipAuthenticationException(sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$entry->getName()
|
||||
));
|
||||
throw new ZipAuthenticationException(
|
||||
sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$entry->getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
throw new ZipException(sprintf(
|
||||
'Failed to get the contents of the zip entry "%s"',
|
||||
$entry->getName()
|
||||
));
|
||||
|
||||
throw new ZipException(
|
||||
sprintf(
|
||||
'Failed to get the contents of the zip entry "%s"',
|
||||
$entry->getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$skipCheckCrc) {
|
||||
$localCrc = crc32($content);
|
||||
$localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc;
|
||||
$crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc();
|
||||
if ($crc != $localCrc) {
|
||||
|
||||
if (sprintf('%u', $entry->getCrc()) !== sprintf('%u', $localCrc)) {
|
||||
if ($isEncrypted) {
|
||||
throw new ZipAuthenticationException(sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$entry->getName()
|
||||
));
|
||||
throw new ZipAuthenticationException(
|
||||
sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$entry->getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
throw new Crc32Exception($entry->getName(), $crc, $localCrc);
|
||||
|
||||
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
@@ -529,36 +583,40 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
* Copy the input stream of the LOC entry zip and the data into
|
||||
* the output stream and zip the alignment if necessary.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out)
|
||||
{
|
||||
$pos = $entry->getOffset();
|
||||
assert(ZipEntry::UNKNOWN !== $pos);
|
||||
$pos = PHP_INT_SIZE === 4 ? sprintf('%u', $pos) : $pos;
|
||||
$pos = $this->mapper->map($pos);
|
||||
|
||||
$nameLength = strlen($entry->getName());
|
||||
if ($pos === ZipEntry::UNKNOWN) {
|
||||
throw new ZipException(sprintf('Missing local header offset for entry %s', $entry->getName()));
|
||||
}
|
||||
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
|
||||
$nameLength = \strlen($entry->getName());
|
||||
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, \SEEK_SET);
|
||||
$sourceExtraLength = $destExtraLength = unpack('v', fread($this->in, 2))[1];
|
||||
|
||||
if ($sourceExtraLength > 0) {
|
||||
// read Local File Header extra fields
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, SEEK_SET);
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, \SEEK_SET);
|
||||
$extra = '';
|
||||
$offset = 0;
|
||||
|
||||
while ($offset < $sourceExtraLength) {
|
||||
$read = min(8192 /* chunk size */, $sourceExtraLength - $offset);
|
||||
$extra .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
$extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($extra, $entry);
|
||||
|
||||
if (isset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]) && $this->zipModel->isZipAlign()) {
|
||||
unset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]);
|
||||
$destExtraLength = strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection));
|
||||
$destExtraLength = \strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection));
|
||||
}
|
||||
} else {
|
||||
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||
@@ -567,12 +625,12 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||
$copyInToOutLength = $entry->getCompressedSize();
|
||||
|
||||
fseek($this->in, $pos, SEEK_SET);
|
||||
fseek($this->in, $pos, \SEEK_SET);
|
||||
|
||||
if (
|
||||
$this->zipModel->isZipAlign() &&
|
||||
!$entry->isEncrypted() &&
|
||||
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||
$entry->getMethod() === ZipFile::METHOD_STORED
|
||||
) {
|
||||
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
@@ -599,23 +657,25 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
// from input stream to output stream
|
||||
stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2);
|
||||
// write new extra field length (2 bytes) to output stream
|
||||
fwrite($out->getStream(), pack('v', strlen($extra)));
|
||||
fwrite($out->getStream(), pack('v', \strlen($extra)));
|
||||
// skip 2 bytes to input stream
|
||||
fseek($this->in, 2, SEEK_CUR);
|
||||
fseek($this->in, 2, \SEEK_CUR);
|
||||
// copy name from input stream to output stream
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $nameLength);
|
||||
// write extra field to output stream
|
||||
fwrite($out->getStream(), $extra);
|
||||
// skip source extraLength from input stream
|
||||
fseek($this->in, $sourceExtraLength, SEEK_CUR);
|
||||
fseek($this->in, $sourceExtraLength, \SEEK_CUR);
|
||||
} else {
|
||||
$copyInToOutLength += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $sourceExtraLength + $nameLength;
|
||||
}
|
||||
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// crc-32 4 bytes
|
||||
// compressed size 4 bytes
|
||||
// uncompressed size 4 bytes
|
||||
$copyInToOutLength += 12;
|
||||
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
// compressed size +4 bytes
|
||||
// uncompressed size +4 bytes
|
||||
@@ -627,20 +687,18 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out)
|
||||
{
|
||||
$offset = $entry->getOffset();
|
||||
$offset = PHP_INT_SIZE === 4 ? sprintf('%u', $offset) : $offset;
|
||||
$offset = $this->mapper->map($offset);
|
||||
$nameLength = strlen($entry->getName());
|
||||
$nameLength = \strlen($entry->getName());
|
||||
|
||||
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
|
||||
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, \SEEK_SET);
|
||||
$extraLength = unpack('v', fread($this->in, 2))[1];
|
||||
|
||||
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, SEEK_SET);
|
||||
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, \SEEK_SET);
|
||||
// copy raw data from input stream to output stream
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
|
||||
/**
|
||||
* Read zip file
|
||||
* Read zip file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
@@ -20,14 +20,22 @@ interface ZipInputStreamInterface
|
||||
public function readZip();
|
||||
|
||||
/**
|
||||
* Read central directory entry.
|
||||
*
|
||||
* @param resource $stream
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function readEntry();
|
||||
public function readCentralDirectoryEntry($stream);
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function readEntryContent(ZipEntry $entry);
|
||||
|
||||
@@ -40,13 +48,13 @@ interface ZipInputStreamInterface
|
||||
* Copy the input stream of the LOC entry zip and the data into
|
||||
* the output stream and zip the alignment if necessary.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out);
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out);
|
||||
|
@@ -19,33 +19,31 @@ use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Write zip file
|
||||
* Write zip file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputStream implements ZipOutputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
/** @var resource */
|
||||
protected $out;
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
|
||||
/** @var ZipModel */
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* ZipOutputStream constructor.
|
||||
*
|
||||
* @param resource $out
|
||||
* @param ZipModel $zipModel
|
||||
*/
|
||||
public function __construct($out, ZipModel $zipModel)
|
||||
{
|
||||
if (!is_resource($out)) {
|
||||
if (!\is_resource($out)) {
|
||||
throw new InvalidArgumentException('$out must be resource');
|
||||
}
|
||||
$this->out = $out;
|
||||
@@ -59,11 +57,13 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
{
|
||||
$entries = $this->zipModel->getEntries();
|
||||
$outPosEntries = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$outPosEntries[] = new OutputOffsetEntry(ftell($this->out), $entry);
|
||||
$this->writeEntry($entry);
|
||||
}
|
||||
$centralDirectoryOffset = ftell($this->out);
|
||||
|
||||
foreach ($outPosEntries as $outputEntry) {
|
||||
$this->writeCentralDirectoryHeader($outputEntry);
|
||||
}
|
||||
@@ -72,12 +72,14 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeEntry(ZipEntry $entry)
|
||||
{
|
||||
if ($entry instanceof ZipSourceEntry) {
|
||||
$entry->getInputStream()->copyEntry($entry, $this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,16 +90,17 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
|
||||
$extra = $entry->getExtra();
|
||||
|
||||
$nameLength = strlen($entry->getName());
|
||||
$extraLength = strlen($extra);
|
||||
$nameLength = \strlen($entry->getName());
|
||||
$extraLength = \strlen($extra);
|
||||
|
||||
// zip align
|
||||
if (
|
||||
$this->zipModel->isZipAlign() &&
|
||||
!$entry->isEncrypted() &&
|
||||
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||
$entry->getMethod() === ZipFile::METHOD_STORED
|
||||
) {
|
||||
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||
|
||||
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
}
|
||||
@@ -120,15 +123,16 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
$extraFieldsCollection->add($alignExtra);
|
||||
|
||||
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
|
||||
$extraLength = strlen($extra);
|
||||
$extraLength = \strlen($extra);
|
||||
}
|
||||
|
||||
$size = $nameLength + $extraLength;
|
||||
|
||||
if ($size > 0xffff) {
|
||||
throw new ZipException(
|
||||
$entry->getName() . " (the total size of " . $size .
|
||||
" bytes for the name, extra fields and comment " .
|
||||
"exceeds the maximum size of " . 0xffff . " bytes)"
|
||||
$entry->getName() . ' (the total size of ' . $size .
|
||||
' bytes for the name, extra fields and comment ' .
|
||||
'exceeds the maximum size of ' . 0xffff . ' bytes)'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,7 +144,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
($entry->getExtractedOS() << 8) | $entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
@@ -160,9 +164,11 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
$extraLength
|
||||
)
|
||||
);
|
||||
|
||||
if ($nameLength > 0) {
|
||||
fwrite($this->out, $entry->getName());
|
||||
}
|
||||
|
||||
if ($extraLength > 0) {
|
||||
fwrite($this->out, $extra);
|
||||
}
|
||||
@@ -173,8 +179,18 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
fwrite($this->out, $entryContent);
|
||||
}
|
||||
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getSize());
|
||||
if ($entry->getCrc() === ZipEntry::UNKNOWN) {
|
||||
throw new ZipException(sprintf('No crc for entry %s', $entry->getName()));
|
||||
}
|
||||
|
||||
if ($entry->getSize() === ZipEntry::UNKNOWN) {
|
||||
throw new ZipException(sprintf('No uncompressed size for entry %s', $entry->getName()));
|
||||
}
|
||||
|
||||
if ($entry->getCompressedSize() === ZipEntry::UNKNOWN) {
|
||||
throw new ZipException(sprintf('No compressed size for entry %s', $entry->getName()));
|
||||
}
|
||||
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
// crc-32 4 bytes
|
||||
@@ -187,25 +203,32 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
} else {
|
||||
fwrite($this->out, pack('VV', $entry->getCompressedSize(), $entry->getSize()));
|
||||
}
|
||||
} elseif ($compressedSize != $entry->getCompressedSize()) {
|
||||
} elseif ($compressedSize !== $entry->getCompressedSize()) {
|
||||
throw new ZipException(
|
||||
$entry->getName() . " (expected compressed entry size of "
|
||||
. $entry->getCompressedSize() . " bytes, " .
|
||||
"but is actually " . $compressedSize . " bytes)"
|
||||
$entry->getName() . ' (expected compressed entry size of '
|
||||
. $entry->getCompressedSize() . ' bytes, ' .
|
||||
'but is actually ' . $compressedSize . ' bytes)'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return null|string
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function entryCommitChangesAndReturnContent(ZipEntry $entry)
|
||||
{
|
||||
if ($entry->getPlatform() === ZipEntry::UNKNOWN) {
|
||||
$entry->setPlatform(ZipEntry::PLATFORM_UNIX);
|
||||
if ($entry->getCreatedOS() === ZipEntry::UNKNOWN) {
|
||||
$entry->setCreatedOS(ZipEntry::PLATFORM_UNIX);
|
||||
}
|
||||
|
||||
if ($entry->getExtractedOS() === ZipEntry::UNKNOWN) {
|
||||
$entry->setExtractedOS(ZipEntry::PLATFORM_UNIX);
|
||||
}
|
||||
|
||||
if ($entry->getTime() === ZipEntry::UNKNOWN) {
|
||||
$entry->setTime(time());
|
||||
}
|
||||
@@ -216,7 +239,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
$utf8 = true;
|
||||
|
||||
if ($encrypted && $entry->getPassword() === null) {
|
||||
throw new ZipException("Can not password from entry " . $entry->getName());
|
||||
throw new ZipException(sprintf('Password not set for entry %s', $entry->getName()));
|
||||
}
|
||||
|
||||
// Compose General Purpose Bit Flag.
|
||||
@@ -226,11 +249,12 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
|
||||
$entryContent = null;
|
||||
$extraFieldsCollection = $entry->getExtraFieldsCollection();
|
||||
|
||||
if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) {
|
||||
$entryContent = $entry->getEntryContent();
|
||||
|
||||
if ($entryContent !== null) {
|
||||
$entry->setSize(strlen($entryContent));
|
||||
$entry->setSize(\strlen($entryContent));
|
||||
$entry->setCrc(crc32($entryContent));
|
||||
|
||||
if ($encrypted && $method === ZipEntry::METHOD_WINZIP_AES) {
|
||||
@@ -238,26 +262,28 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
|
||||
if ($field !== null) {
|
||||
$method = $field->getMethod();
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
case ZipFile::METHOD_STORED:
|
||||
break;
|
||||
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
$entryContent = gzdeflate($entryContent, $entry->getCompressionLevel());
|
||||
break;
|
||||
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
$compressionLevel = $entry->getCompressionLevel() === ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ?
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
$compressionLevel = $entry->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ?
|
||||
ZipEntry::LEVEL_DEFAULT_BZIP2_COMPRESSION :
|
||||
$entry->getCompressionLevel();
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
$entryContent = bzcompress($entryContent, $compressionLevel);
|
||||
if (is_int($entryContent)) {
|
||||
|
||||
if (\is_int($entryContent)) {
|
||||
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
|
||||
}
|
||||
break;
|
||||
@@ -268,22 +294,22 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($entry->getName() . " (unsupported compression method " . $method . ")");
|
||||
throw new ZipException($entry->getName() . ' (unsupported compression method ' . $method . ')');
|
||||
}
|
||||
|
||||
if ($method === ZipFileInterface::METHOD_DEFLATED) {
|
||||
if ($method === ZipFile::METHOD_DEFLATED) {
|
||||
$bit1 = false;
|
||||
$bit2 = false;
|
||||
switch ($entry->getCompressionLevel()) {
|
||||
case ZipFileInterface::LEVEL_BEST_COMPRESSION:
|
||||
case ZipFile::LEVEL_BEST_COMPRESSION:
|
||||
$bit1 = true;
|
||||
break;
|
||||
|
||||
case ZipFileInterface::LEVEL_FAST:
|
||||
case ZipFile::LEVEL_FAST:
|
||||
$bit2 = true;
|
||||
break;
|
||||
|
||||
case ZipFileInterface::LEVEL_SUPER_FAST:
|
||||
case ZipFile::LEVEL_SUPER_FAST:
|
||||
$bit1 = true;
|
||||
$bit2 = true;
|
||||
break;
|
||||
@@ -294,17 +320,24 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
}
|
||||
|
||||
if ($encrypted) {
|
||||
if (in_array($entry->getEncryptionMethod(), [
|
||||
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128,
|
||||
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192,
|
||||
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256,
|
||||
], true)) {
|
||||
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits
|
||||
if (\in_array(
|
||||
$entry->getEncryptionMethod(),
|
||||
[
|
||||
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128,
|
||||
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192,
|
||||
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256,
|
||||
],
|
||||
true
|
||||
)) {
|
||||
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
|
||||
$entry->getEncryptionMethod()
|
||||
); // size bits
|
||||
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
|
||||
$field->setKeyStrength($keyStrength);
|
||||
$field->setMethod($method);
|
||||
$size = $entry->getSize();
|
||||
if ($size >= 20 && $method !== ZipFileInterface::METHOD_BZIP2) {
|
||||
|
||||
if ($size >= 20 && $method !== ZipFile::METHOD_BZIP2) {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
||||
} else {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
||||
@@ -315,13 +348,13 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
|
||||
$winZipAesEngine = new WinZipAesEngine($entry);
|
||||
$entryContent = $winZipAesEngine->encrypt($entryContent);
|
||||
} elseif ($entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL) {
|
||||
} elseif ($entry->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_TRADITIONAL) {
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
|
||||
$entryContent = $zipCryptoEngine->encrypt($entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
$compressedSize = strlen($entryContent);
|
||||
$compressedSize = \strlen($entryContent);
|
||||
$entry->setCompressedSize($compressedSize);
|
||||
}
|
||||
}
|
||||
@@ -334,25 +367,31 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
} elseif ($extraFieldsCollection->has(Zip64ExtraField::getHeaderId())) {
|
||||
$extraFieldsCollection->remove(Zip64ExtraField::getHeaderId());
|
||||
}
|
||||
|
||||
return $entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @param string $content
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
|
||||
{
|
||||
if ($content !== null) {
|
||||
$entryContent = gzdeflate($content, $entry->getCompressionLevel());
|
||||
if (strlen($entryContent) < strlen($content)) {
|
||||
$entry->setMethod(ZipFileInterface::METHOD_DEFLATED);
|
||||
|
||||
if (\strlen($entryContent) < \strlen($content)) {
|
||||
$entry->setMethod(ZipFile::METHOD_DEFLATED);
|
||||
|
||||
return $entryContent;
|
||||
}
|
||||
$entry->setMethod(ZipFileInterface::METHOD_STORED);
|
||||
$entry->setMethod(ZipFile::METHOD_STORED);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
@@ -369,12 +408,12 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
|
||||
// UNKNOWN!
|
||||
if (($compressedSize | $size) === ZipEntry::UNKNOWN) {
|
||||
throw new RuntimeException("invalid entry");
|
||||
throw new RuntimeException('invalid entry');
|
||||
}
|
||||
$extra = $entry->getExtra();
|
||||
$extraSize = strlen($extra);
|
||||
$extraSize = \strlen($extra);
|
||||
|
||||
$commentLength = strlen($entry->getComment());
|
||||
$commentLength = \strlen($entry->getComment());
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
@@ -382,9 +421,9 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
self::CENTRAL_FILE_HEADER_SIG,
|
||||
// version made by 2 bytes
|
||||
($entry->getPlatform() << 8) | 63,
|
||||
($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
($entry->getExtractedOS() << 8) | $entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
@@ -398,7 +437,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
strlen($entry->getName()),
|
||||
\strlen($entry->getName()),
|
||||
// extra field length 2 bytes
|
||||
$extraSize,
|
||||
// file comment length 2 bytes
|
||||
@@ -406,7 +445,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
0,
|
||||
$entry->getInternalAttributes(),
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
@@ -415,82 +454,116 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
);
|
||||
// file name (variable size)
|
||||
fwrite($this->out, $entry->getName());
|
||||
|
||||
if ($extraSize > 0) {
|
||||
// extra field (variable size)
|
||||
fwrite($this->out, $extra);
|
||||
}
|
||||
|
||||
if ($commentLength > 0) {
|
||||
// file comment (variable size)
|
||||
fwrite($this->out, $entry->getComment());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $centralDirectoryOffset
|
||||
*/
|
||||
protected function writeEndOfCentralDirectoryRecord($centralDirectoryOffset)
|
||||
{
|
||||
$centralDirectoryEntriesCount = count($this->zipModel);
|
||||
$cdEntriesCount = \count($this->zipModel);
|
||||
|
||||
$position = ftell($this->out);
|
||||
$centralDirectorySize = $position - $centralDirectoryOffset;
|
||||
$centralDirectoryEntriesZip64 = $centralDirectoryEntriesCount > 0xffff;
|
||||
$centralDirectorySizeZip64 = $centralDirectorySize > 0xffffffff;
|
||||
$centralDirectoryOffsetZip64 = $centralDirectoryOffset > 0xffffffff;
|
||||
$centralDirectoryEntries16 = $centralDirectoryEntriesZip64 ? 0xffff : (int)$centralDirectoryEntriesCount;
|
||||
$centralDirectorySize32 = $centralDirectorySizeZip64 ? 0xffffffff : $centralDirectorySize;
|
||||
$centralDirectoryOffset32 = $centralDirectoryOffsetZip64 ? 0xffffffff : $centralDirectoryOffset;
|
||||
$zip64 // ZIP64 extensions?
|
||||
= $centralDirectoryEntriesZip64
|
||||
|| $centralDirectorySizeZip64
|
||||
|| $centralDirectoryOffsetZip64;
|
||||
if ($zip64) {
|
||||
// [zip64 end of central directory record]
|
||||
// relative offset of the zip64 end of central directory record
|
||||
$zip64EndOfCentralDirectoryOffset = $position;
|
||||
// zip64 end of central dir
|
||||
|
||||
$cdEntriesZip64 = $cdEntriesCount > 0xFFFF;
|
||||
$cdSizeZip64 = $centralDirectorySize > 0xFFFFFFFF;
|
||||
$cdOffsetZip64 = $centralDirectoryOffset > 0xFFFFFFFF;
|
||||
|
||||
$zip64Required = $cdEntriesZip64 || $cdSizeZip64 || $cdOffsetZip64;
|
||||
|
||||
if ($zip64Required) {
|
||||
$zip64EndOfCentralDirectoryOffset = ftell($this->out);
|
||||
|
||||
// find max software version, version needed to extract and most common platform
|
||||
list($softwareVersion, $versionNeededToExtract) = array_reduce(
|
||||
$this->zipModel->getEntries(),
|
||||
static function (array $carry, ZipEntry $entry) {
|
||||
$carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
|
||||
$carry[1] = max($carry[1], $entry->getVersionNeededToExtract() & 0xFF);
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[10 /* simple file min ver */, 45 /* zip64 ext min ver */]
|
||||
);
|
||||
|
||||
$createdOS = $extractedOS = ZipEntry::PLATFORM_FAT;
|
||||
$versionMadeBy = ($createdOS << 8) | max($softwareVersion, 45 /* zip64 ext min ver */);
|
||||
$versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, 45 /* zip64 ext min ver */);
|
||||
|
||||
// signature 4 bytes (0x06064b50)
|
||||
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG));
|
||||
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG));
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE(EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN - 12));
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// due to potential use of BZIP2 compression
|
||||
// number of this disk 4 bytes
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
fwrite($this->out, pack('vvVV', 63, 46, 0, 0));
|
||||
fwrite($this->out, PackUtil::packLongLE(44));
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'vvVV',
|
||||
// version made by 2 bytes
|
||||
$versionMadeBy & 0xFFFF,
|
||||
// version needed to extract 2 bytes
|
||||
$versionExtractedBy & 0xFFFF,
|
||||
// number of this disk 4 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
0
|
||||
)
|
||||
);
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
|
||||
fwrite($this->out, PackUtil::packLongLE($cdEntriesCount));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
|
||||
fwrite($this->out, PackUtil::packLongLE($cdEntriesCount));
|
||||
// size of the central directory 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectorySize));
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryOffset));
|
||||
// zip64 extensible data sector (variable size)
|
||||
|
||||
// [zip64 end of central directory locator]
|
||||
// signature 4 bytes (0x07064b50)
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
fwrite($this->out, pack('VV', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG, 0));
|
||||
// write zip64 end of central directory locator
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VV',
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG,
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
0
|
||||
)
|
||||
);
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset));
|
||||
// total number of disks 4 bytes
|
||||
fwrite($this->out, pack('V', 1));
|
||||
}
|
||||
|
||||
$comment = $this->zipModel->getArchiveComment();
|
||||
$commentLength = strlen($comment);
|
||||
$commentLength = $comment !== null ? \strlen($comment) : 0;
|
||||
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG,
|
||||
EndOfCentralDirectory::END_OF_CD_SIG,
|
||||
// number of this disk 2 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
@@ -498,20 +571,21 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
|
||||
// size of the central directory 4 bytes
|
||||
$centralDirectorySize32,
|
||||
$cdSizeZip64 ? 0xFFFFFFFF : $centralDirectorySize,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$centralDirectoryOffset32,
|
||||
$cdOffsetZip64 ? 0xFFFFFFFF : $centralDirectoryOffset,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
|
||||
if ($commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($this->out, $comment);
|
||||
|
@@ -5,7 +5,7 @@ namespace PhpZip\Stream;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Write zip file
|
||||
* Write zip file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
|
@@ -2,38 +2,26 @@
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Crypto Utils
|
||||
* Crypto Utils.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class CryptoUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns random bytes.
|
||||
*
|
||||
* @param int $length
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated Use random_bytes()
|
||||
*/
|
||||
final public static function randomBytes($length)
|
||||
{
|
||||
$length = (int)$length;
|
||||
if (function_exists('random_bytes')) {
|
||||
try {
|
||||
return random_bytes($length);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException("Could not generate a random string.");
|
||||
}
|
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_random_pseudo_bytes($length);
|
||||
} elseif (function_exists('mcrypt_create_iv')) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_create_iv($length);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
return random_bytes($length);
|
||||
}
|
||||
}
|
||||
|
@@ -28,13 +28,14 @@ class DateTimeConverter
|
||||
* Convert a 32 bit integer DOS date/time value to a UNIX timestamp value.
|
||||
*
|
||||
* @param int $dosTime Dos date/time
|
||||
*
|
||||
* @return int Unix timestamp
|
||||
*/
|
||||
public static function toUnixTimestamp($dosTime)
|
||||
{
|
||||
if (self::MIN_DOS_TIME > $dosTime) {
|
||||
if ($dosTime < self::MIN_DOS_TIME) {
|
||||
$dosTime = self::MIN_DOS_TIME;
|
||||
} elseif (self::MAX_DOS_TIME < $dosTime) {
|
||||
} elseif ($dosTime > self::MAX_DOS_TIME) {
|
||||
$dosTime = self::MAX_DOS_TIME;
|
||||
}
|
||||
|
||||
@@ -51,17 +52,19 @@ class DateTimeConverter
|
||||
/**
|
||||
* Converts a UNIX timestamp value to a DOS date/time value.
|
||||
*
|
||||
* @param int $unixTimestamp The number of seconds since midnight, January 1st,
|
||||
* 1970 AD UTC.
|
||||
* @return int A DOS date/time value reflecting the local time zone and
|
||||
* rounded down to even seconds
|
||||
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME.
|
||||
* @throws ZipException If unix timestamp is negative.
|
||||
* @param int $unixTimestamp the number of seconds since midnight, January 1st,
|
||||
* 1970 AD UTC
|
||||
*
|
||||
* @throws ZipException if unix timestamp is negative
|
||||
*
|
||||
* @return int a DOS date/time value reflecting the local time zone and
|
||||
* rounded down to even seconds
|
||||
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME
|
||||
*/
|
||||
public static function toDosTime($unixTimestamp)
|
||||
{
|
||||
if (0 > $unixTimestamp) {
|
||||
throw new ZipException("Negative unix timestamp: " . $unixTimestamp);
|
||||
if ($unixTimestamp < 0) {
|
||||
throw new ZipException('Negative unix timestamp: ' . $unixTimestamp);
|
||||
}
|
||||
|
||||
$date = getdate($unixTimestamp);
|
||||
@@ -71,8 +74,9 @@ class DateTimeConverter
|
||||
}
|
||||
|
||||
$date['year'] -= 1980;
|
||||
return ($date['year'] << 25 | $date['mon'] << 21 |
|
||||
|
||||
return $date['year'] << 25 | $date['mon'] << 21 |
|
||||
$date['mday'] << 16 | $date['hours'] << 11 |
|
||||
$date['minutes'] << 5 | $date['seconds'] >> 1);
|
||||
$date['minutes'] << 5 | $date['seconds'] >> 1;
|
||||
}
|
||||
}
|
||||
|
@@ -10,14 +10,16 @@ use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FilesUtil
|
||||
final class FilesUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Is empty directory
|
||||
* Is empty directory.
|
||||
*
|
||||
* @param string $dir Directory
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEmptyDir($dir)
|
||||
@@ -25,13 +27,14 @@ class FilesUtil
|
||||
if (!is_readable($dir)) {
|
||||
return false;
|
||||
}
|
||||
return count(scandir($dir)) === 2;
|
||||
|
||||
return \count(scandir($dir)) === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove recursive directory.
|
||||
*
|
||||
* @param string $dir Directory path.
|
||||
* @param string $dir directory path
|
||||
*/
|
||||
public static function removeDir($dir)
|
||||
{
|
||||
@@ -39,6 +42,7 @@ class FilesUtil
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($files as $fileInfo) {
|
||||
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$function($fileInfo->getRealPath());
|
||||
@@ -46,11 +50,11 @@ class FilesUtil
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert glob pattern to regex pattern.
|
||||
*
|
||||
* @param string $globPattern
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function convertGlobToRegEx($globPattern)
|
||||
@@ -61,16 +65,19 @@ class FilesUtil
|
||||
$inCurrent = 0;
|
||||
$chars = str_split($globPattern);
|
||||
$regexPattern = '';
|
||||
|
||||
foreach ($chars as $currentChar) {
|
||||
switch ($currentChar) {
|
||||
case '*':
|
||||
$regexPattern .= ($escaping ? "\\*" : '.*');
|
||||
$regexPattern .= ($escaping ? '\\*' : '.*');
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
$regexPattern .= ($escaping ? "\\?" : '.');
|
||||
$regexPattern .= ($escaping ? '\\?' : '.');
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '.':
|
||||
case '(':
|
||||
case ')':
|
||||
@@ -83,41 +90,45 @@ class FilesUtil
|
||||
$regexPattern .= '\\' . $currentChar;
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
if ($escaping) {
|
||||
$regexPattern .= "\\\\";
|
||||
$regexPattern .= '\\\\';
|
||||
$escaping = false;
|
||||
} else {
|
||||
$escaping = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case '{':
|
||||
if ($escaping) {
|
||||
$regexPattern .= "\\{";
|
||||
$regexPattern .= '\\{';
|
||||
} else {
|
||||
$regexPattern = '(';
|
||||
$inCurrent++;
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= ')';
|
||||
$inCurrent--;
|
||||
} elseif ($escaping) {
|
||||
$regexPattern = "\\}";
|
||||
$regexPattern = '\\}';
|
||||
} else {
|
||||
$regexPattern = "}";
|
||||
$regexPattern = '}';
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= '|';
|
||||
} elseif ($escaping) {
|
||||
$regexPattern .= "\\,";
|
||||
$regexPattern .= '\\,';
|
||||
} else {
|
||||
$regexPattern = ",";
|
||||
$regexPattern = ',';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -125,6 +136,7 @@ class FilesUtil
|
||||
$regexPattern .= $currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
return $regexPattern;
|
||||
}
|
||||
|
||||
@@ -132,8 +144,9 @@ class FilesUtil
|
||||
* Search files.
|
||||
*
|
||||
* @param string $inputDir
|
||||
* @param bool $recursive
|
||||
* @param array $ignoreFiles
|
||||
* @param bool $recursive
|
||||
* @param array $ignoreFiles
|
||||
*
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
|
||||
@@ -153,11 +166,13 @@ class FilesUtil
|
||||
new \IteratorIterator($directoryIterator);
|
||||
|
||||
$fileList = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
@@ -165,21 +180,27 @@ class FilesUtil
|
||||
* Search files from glob pattern.
|
||||
*
|
||||
* @param string $globPattern
|
||||
* @param int $flags
|
||||
* @param bool $recursive
|
||||
* @param int $flags
|
||||
* @param bool $recursive
|
||||
*
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
|
||||
{
|
||||
$flags = (int)$flags;
|
||||
$recursive = (bool)$recursive;
|
||||
$flags = (int) $flags;
|
||||
$recursive = (bool) $recursive;
|
||||
$files = glob($globPattern, $flags);
|
||||
|
||||
if (!$recursive) {
|
||||
return $files;
|
||||
}
|
||||
foreach (glob(dirname($globPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
|
||||
|
||||
foreach (glob(\dirname($globPattern) . '/*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) {
|
||||
// Unpacking the argument via ... is supported starting from php 5.6 only
|
||||
/** @noinspection SlowArrayOperationsInLoopInspection */
|
||||
$files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
@@ -188,48 +209,58 @@ class FilesUtil
|
||||
*
|
||||
* @param string $folder
|
||||
* @param string $pattern
|
||||
* @param bool $recursive
|
||||
* @param bool $recursive
|
||||
*
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function regexFileSearch($folder, $pattern, $recursive = true)
|
||||
{
|
||||
$directoryIterator = $recursive ? new \RecursiveDirectoryIterator($folder) : new \DirectoryIterator($folder);
|
||||
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator($directoryIterator);
|
||||
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator(
|
||||
$directoryIterator
|
||||
);
|
||||
$regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
|
||||
$fileList = [];
|
||||
|
||||
foreach ($regexIterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to human size.
|
||||
*
|
||||
* @param int $size Size bytes
|
||||
* @param int $size Size bytes
|
||||
* @param string|null $unit Unit support 'GB', 'MB', 'KB'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function humanSize($size, $unit = null)
|
||||
{
|
||||
if (($unit === null && $size >= 1 << 30) || $unit === "GB") {
|
||||
return number_format($size / (1 << 30), 2) . "GB";
|
||||
if (($unit === null && $size >= 1 << 30) || $unit === 'GB') {
|
||||
return number_format($size / (1 << 30), 2) . 'GB';
|
||||
}
|
||||
if (($unit === null && $size >= 1 << 20) || $unit === "MB") {
|
||||
return number_format($size / (1 << 20), 2) . "MB";
|
||||
|
||||
if (($unit === null && $size >= 1 << 20) || $unit === 'MB') {
|
||||
return number_format($size / (1 << 20), 2) . 'MB';
|
||||
}
|
||||
if (($unit === null && $size >= 1 << 10) || $unit === "KB") {
|
||||
return number_format($size / (1 << 10), 2) . "KB";
|
||||
|
||||
if (($unit === null && $size >= 1 << 10) || $unit === 'KB') {
|
||||
return number_format($size / (1 << 10), 2) . 'KB';
|
||||
}
|
||||
return number_format($size) . " bytes";
|
||||
|
||||
return number_format($size) . ' bytes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes zip path.
|
||||
*
|
||||
* @param string $path Zip path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function normalizeZipPath($path)
|
||||
@@ -237,7 +268,7 @@ class FilesUtil
|
||||
return implode(
|
||||
'/',
|
||||
array_filter(
|
||||
explode('/', (string)$path),
|
||||
explode('/', (string) $path),
|
||||
static function ($part) {
|
||||
return $part !== '.' && $part !== '..';
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ use PhpZip\Util\StringUtil;
|
||||
class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
{
|
||||
/**
|
||||
* Ignore list files
|
||||
* Ignore list files.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
|
||||
/**
|
||||
* @param \Iterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\Iterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
@@ -30,9 +30,12 @@ class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
* Check whether the current element of the iterator is acceptable.
|
||||
*
|
||||
* @see http://php.net/manual/en/filteriterator.accept.php
|
||||
*
|
||||
* @return bool true if the current element is acceptable, otherwise false
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function accept()
|
||||
@@ -42,6 +45,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
@@ -56,6 +60,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ use PhpZip\Util\StringUtil;
|
||||
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
/**
|
||||
* Ignore list files
|
||||
* Ignore list files.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
|
||||
/**
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
@@ -30,9 +30,12 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
* Check whether the current element of the iterator is acceptable.
|
||||
*
|
||||
* @see http://php.net/manual/en/filteriterator.accept.php
|
||||
*
|
||||
* @return bool true if the current element is acceptable, otherwise false
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function accept()
|
||||
@@ -42,10 +45,11 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
|
||||
&& $ignoreFile[\strlen($ignoreFile) - 1] === '/'
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
@@ -56,15 +60,16 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IgnoreFilesRecursiveFilterIterator
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
|
||||
}
|
||||
}
|
||||
|
@@ -3,22 +3,24 @@
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* Pack util
|
||||
* Pack util.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PackUtil
|
||||
final class PackUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int|string $longValue
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function packLongLE($longValue)
|
||||
{
|
||||
if (PHP_INT_SIZE === 8 && PHP_VERSION_ID >= 506030) {
|
||||
return pack("P", $longValue);
|
||||
if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) {
|
||||
return pack('P', $longValue);
|
||||
}
|
||||
|
||||
$left = 0xffffffff00000000;
|
||||
@@ -32,31 +34,36 @@ class PackUtil
|
||||
|
||||
/**
|
||||
* @param string|int $value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function unpackLongLE($value)
|
||||
{
|
||||
if (PHP_INT_SIZE === 8 && PHP_VERSION_ID >= 506030) {
|
||||
if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) {
|
||||
return unpack('P', $value)[1];
|
||||
}
|
||||
$unpack = unpack('Va/Vb', $value);
|
||||
|
||||
return $unpack['a'] + ($unpack['b'] << 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast to signed int 32-bit
|
||||
* Cast to signed int 32-bit.
|
||||
*
|
||||
* @param int $int
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function toSignedInt32($int)
|
||||
{
|
||||
if (PHP_INT_SIZE === 8) {
|
||||
$int = $int & 0xffffffff;
|
||||
if (\PHP_INT_SIZE === 8) {
|
||||
$int &= 0xffffffff;
|
||||
|
||||
if ($int & 0x80000000) {
|
||||
return $int - 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
return $int;
|
||||
}
|
||||
}
|
||||
|
@@ -3,54 +3,32 @@
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* String Util
|
||||
* String Util.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StringUtil
|
||||
final class StringUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
|
||||
return $needle === '' || strrpos($haystack, $needle, -\strlen($haystack)) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0
|
||||
return $needle === '' || (($temp = \strlen($haystack) - \strlen($needle)) >= 0
|
||||
&& strpos($haystack, $needle, $temp) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public static function cp866toUtf8($str)
|
||||
{
|
||||
if (function_exists('iconv')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return iconv('CP866', 'UTF-8//IGNORE', $str);
|
||||
} elseif (function_exists('mb_convert_encoding')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mb_convert_encoding($str, 'UTF-8', 'CP866');
|
||||
} elseif (class_exists('UConverter')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
$converter = new \UConverter('UTF-8', 'CP866');
|
||||
return $converter->convert($str, false);
|
||||
} else {
|
||||
static $cp866Utf8Pairs;
|
||||
if (empty($cp866Utf8Pairs)) {
|
||||
$cp866Utf8Pairs = require __DIR__ . '/encodings/cp866-utf8.php';
|
||||
}
|
||||
return strtr($str, $cp866Utf8Pairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ use Psr\Http\Message\ResponseInterface;
|
||||
* Support ZipAlign functional.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -25,67 +26,66 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
{
|
||||
/**
|
||||
* Method for Stored (uncompressed) entries.
|
||||
*
|
||||
* @see ZipEntry::setMethod()
|
||||
*/
|
||||
const METHOD_STORED = 0;
|
||||
|
||||
/**
|
||||
* Method for Deflated compressed entries.
|
||||
*
|
||||
* @see ZipEntry::setMethod()
|
||||
*/
|
||||
const METHOD_DEFLATED = 8;
|
||||
|
||||
/**
|
||||
* Method for BZIP2 compressed entries.
|
||||
* Require php extension bz2.
|
||||
*
|
||||
* @see ZipEntry::setMethod()
|
||||
*/
|
||||
const METHOD_BZIP2 = 12;
|
||||
|
||||
/**
|
||||
* Default compression level.
|
||||
*/
|
||||
/** Default compression level. */
|
||||
const LEVEL_DEFAULT_COMPRESSION = -1;
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*/
|
||||
|
||||
/** Compression level for fastest compression. */
|
||||
const LEVEL_FAST = 2;
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*/
|
||||
|
||||
/** Compression level for fastest compression. */
|
||||
const LEVEL_BEST_SPEED = 1;
|
||||
|
||||
const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED;
|
||||
/**
|
||||
* Compression level for best compression.
|
||||
*/
|
||||
|
||||
/** Compression level for best compression. */
|
||||
const LEVEL_BEST_COMPRESSION = 9;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to Traditional PKWARE encryption.
|
||||
*/
|
||||
/** No specified method for set encryption method to Traditional PKWARE encryption. */
|
||||
const ENCRYPTION_METHOD_TRADITIONAL = 0;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption.
|
||||
* Default value 256 bit
|
||||
* Default value 256 bit.
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES = self::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 128 bit.
|
||||
*/
|
||||
|
||||
/** No specified method for set encryption method to WinZip AES encryption 128 bit. */
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_128 = 2;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 194 bit.
|
||||
*/
|
||||
|
||||
/** No specified method for set encryption method to WinZip AES encryption 194 bit. */
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_192 = 3;
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 256 bit.
|
||||
*/
|
||||
|
||||
/** No specified method for set encryption method to WinZip AES encryption 256 bit. */
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_256 = 1;
|
||||
|
||||
/**
|
||||
* Open zip archive from file
|
||||
* Open zip archive from file.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @throws ZipException if can't open file
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException if can't open file.
|
||||
*/
|
||||
public function openFile($filename);
|
||||
|
||||
@@ -93,35 +93,39 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Open zip archive from raw string data.
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @throws ZipException if can't open temp stream
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException if can't open temp stream.
|
||||
*/
|
||||
public function openFromString($data);
|
||||
|
||||
/**
|
||||
* Open zip archive from stream resource
|
||||
* Open zip archive from stream resource.
|
||||
*
|
||||
* @param resource $handle
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function openFromStream($handle);
|
||||
|
||||
/**
|
||||
* @return string[] Returns the list files.
|
||||
* @return string[] returns the list files
|
||||
*/
|
||||
public function getListFiles();
|
||||
|
||||
/**
|
||||
* Returns the file comment.
|
||||
*
|
||||
* @return string The file comment.
|
||||
* @return string the file comment
|
||||
*/
|
||||
public function getArchiveComment();
|
||||
|
||||
/**
|
||||
* Set archive comment.
|
||||
*
|
||||
* @param null|string $comment
|
||||
* @param string|null $comment
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setArchiveComment($comment = null);
|
||||
@@ -132,8 +136,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return bool
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory($entryName);
|
||||
|
||||
@@ -141,18 +147,22 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Returns entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return string
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryComment($entryName);
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $entryName
|
||||
* @param string|null $comment
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setEntryComment($entryName, $comment = null);
|
||||
|
||||
@@ -160,6 +170,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Returns the entry contents.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContents($entryName);
|
||||
@@ -168,6 +179,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Checks if there is an entry in the archive.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName);
|
||||
@@ -176,8 +188,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Get info by entry.
|
||||
*
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return ZipInfo
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return ZipInfo
|
||||
*/
|
||||
public function getEntryInfo($entryName);
|
||||
|
||||
@@ -194,60 +208,68 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
public function matcher();
|
||||
|
||||
/**
|
||||
* Extract the archive contents
|
||||
* Extract the archive contents.
|
||||
*
|
||||
* Extract the complete archive or the given files to the specified destination.
|
||||
*
|
||||
* @param string $destination Location where to extract the files.
|
||||
* @param array|string|null $entries The entries to extract. It accepts either
|
||||
* a single entry name or an array of names.
|
||||
* @return ZipFileInterface
|
||||
* @param string $destination location where to extract the files
|
||||
* @param array|string|null $entries The entries to extract. It accepts either
|
||||
* a single entry name or an array of names.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function extractTo($destination, $entries = null);
|
||||
|
||||
/**
|
||||
* Add entry from the string.
|
||||
*
|
||||
* @param string $localName Zip entry name.
|
||||
* @param string $contents String contents.
|
||||
* @param string $localName zip entry name
|
||||
* @param string $contents string contents
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*
|
||||
* @see ZipFile::METHOD_STORED
|
||||
* @see ZipFile::METHOD_DEFLATED
|
||||
* @see ZipFile::METHOD_BZIP2
|
||||
*/
|
||||
public function addFromString($localName, $contents, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add entry from the file.
|
||||
*
|
||||
* @param string $filename Destination file.
|
||||
* @param string|null $localName Zip Entry name.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @param string $filename destination file
|
||||
* @param string|null $localName zip Entry name
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
|
||||
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*
|
||||
* @see ZipFile::METHOD_STORED
|
||||
* @see ZipFile::METHOD_DEFLATED
|
||||
* @see ZipFile::METHOD_BZIP2
|
||||
*/
|
||||
public function addFile($filename, $localName = null, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add entry from the stream.
|
||||
*
|
||||
* @param resource $stream Stream resource.
|
||||
* @param string $localName Zip Entry name.
|
||||
* @param resource $stream stream resource
|
||||
* @param string $localName zip Entry name
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*
|
||||
* @see ZipFile::METHOD_STORED
|
||||
* @see ZipFile::METHOD_DEFLATED
|
||||
* @see ZipFile::METHOD_BZIP2
|
||||
*/
|
||||
public function addFromStream($stream, $localName, $compressionMethod = null);
|
||||
|
||||
@@ -255,6 +277,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Add an empty directory in the zip archive.
|
||||
*
|
||||
* @param string $dirName
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function addEmptyDir($dirName);
|
||||
@@ -262,54 +285,60 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
/**
|
||||
* Add directory not recursively to the zip archive.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $localPath Add files to this directory, or the root.
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function addDir($inputDir, $localPath = "/", $compressionMethod = null);
|
||||
public function addDir($inputDir, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add recursive directory to the zip archive.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $localPath Add files to this directory, or the root.
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*
|
||||
* @see ZipFile::METHOD_STORED
|
||||
* @see ZipFile::METHOD_DEFLATED
|
||||
* @see ZipFile::METHOD_BZIP2
|
||||
*/
|
||||
public function addDirRecursive($inputDir, $localPath = "/", $compressionMethod = null);
|
||||
public function addDirRecursive($inputDir, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add directories from directory iterator.
|
||||
*
|
||||
* @param \Iterator $iterator Directory iterator.
|
||||
* @param string $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @param \Iterator $iterator directory iterator
|
||||
* @param string $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
|
||||
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*
|
||||
* @see ZipFile::METHOD_STORED
|
||||
* @see ZipFile::METHOD_DEFLATED
|
||||
* @see ZipFile::METHOD_BZIP2
|
||||
*/
|
||||
public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add files from glob pattern.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $globPattern Glob pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $globPattern glob pattern
|
||||
* @param string|null $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
|
||||
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
@@ -318,12 +347,13 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
/**
|
||||
* Add files recursively from glob pattern.
|
||||
*
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $globPattern Glob pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @param string $inputDir Input directory
|
||||
* @param string $globPattern glob pattern
|
||||
* @param string|null $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
|
||||
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
@@ -332,56 +362,64 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
/**
|
||||
* Add files from regex pattern.
|
||||
*
|
||||
* @param string $inputDir Search files in this directory.
|
||||
* @param string $regexPattern Regex pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @param string $inputDir search files in this directory
|
||||
* @param string $regexPattern regex pattern
|
||||
* @param string|null $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
|
||||
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @internal param bool $recursive Recursive search.
|
||||
*
|
||||
* @internal param bool $recursive Recursive search
|
||||
*/
|
||||
public function addFilesFromRegex($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null);
|
||||
public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add files recursively from regex pattern.
|
||||
*
|
||||
* @param string $inputDir Search files in this directory.
|
||||
* @param string $regexPattern Regex pattern.
|
||||
* @param string|null $localPath Add files to this directory, or the root.
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @param string $inputDir search files in this directory
|
||||
* @param string $regexPattern regex pattern
|
||||
* @param string|null $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
|
||||
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @internal param bool $recursive Recursive search.
|
||||
*
|
||||
* @internal param bool $recursive Recursive search
|
||||
*/
|
||||
public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null);
|
||||
public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add array data to archive.
|
||||
* Keys is local names.
|
||||
* Values is contents.
|
||||
*
|
||||
* @param array $mapData Associative array for added to zip.
|
||||
* @param array $mapData associative array for added to zip
|
||||
*/
|
||||
public function addAll(array $mapData);
|
||||
|
||||
/**
|
||||
* Rename the entry.
|
||||
*
|
||||
* @param string $oldName Old entry name.
|
||||
* @param string $newName New entry name.
|
||||
* @return ZipFileInterface
|
||||
* @param string $oldName old entry name
|
||||
* @param string $newName new entry name
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function rename($oldName, $newName);
|
||||
|
||||
/**
|
||||
* Delete entry by name.
|
||||
*
|
||||
* @param string $entryName Zip Entry name.
|
||||
* @param string $entryName zip Entry name
|
||||
*
|
||||
* @throws ZipEntryNotFoundException if entry not found
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipEntryNotFoundException If entry not found.
|
||||
*/
|
||||
public function deleteFromName($entryName);
|
||||
|
||||
@@ -389,6 +427,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Delete entries by glob pattern.
|
||||
*
|
||||
* @param string $globPattern Glob pattern
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
@@ -398,12 +437,14 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Delete entries by regex pattern.
|
||||
*
|
||||
* @param string $regexPattern Regex pattern
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function deleteFromRegex($regexPattern);
|
||||
|
||||
/**
|
||||
* Delete all entries
|
||||
* Delete all entries.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function deleteAll();
|
||||
@@ -412,34 +453,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Set compression level for new entries.
|
||||
*
|
||||
* @param int $compressionLevel
|
||||
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFileInterface::LEVEL_SUPER_FAST
|
||||
* @see ZipFileInterface::LEVEL_FAST
|
||||
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @see ZipFile::LEVEL_SUPER_FAST
|
||||
* @see ZipFile::LEVEL_FAST
|
||||
* @see ZipFile::LEVEL_BEST_COMPRESSION
|
||||
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionLevel
|
||||
* @return ZipFileInterface
|
||||
* @param int $compressionLevel
|
||||
*
|
||||
* @throws ZipException
|
||||
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFileInterface::LEVEL_SUPER_FAST
|
||||
* @see ZipFileInterface::LEVEL_FAST
|
||||
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFile::LEVEL_SUPER_FAST
|
||||
* @see ZipFile::LEVEL_FAST
|
||||
* @see ZipFile::LEVEL_BEST_COMPRESSION
|
||||
*/
|
||||
public function setCompressionLevelEntry($entryName, $compressionLevel);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionMethod
|
||||
* @return ZipFileInterface
|
||||
* @param int $compressionMethod
|
||||
*
|
||||
* @throws ZipException
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @see ZipFile::METHOD_STORED
|
||||
* @see ZipFile::METHOD_DEFLATED
|
||||
* @see ZipFile::METHOD_BZIP2
|
||||
*/
|
||||
public function setCompressionMethodEntry($entryName, $compressionMethod);
|
||||
|
||||
@@ -447,8 +496,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* zipalign is optimization to Android application (APK) files.
|
||||
*
|
||||
* @param int|null $align
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @link https://developer.android.com/studio/command-line/zipalign.html
|
||||
*
|
||||
* @see https://developer.android.com/studio/command-line/zipalign.html
|
||||
*/
|
||||
public function setZipAlign($align = null);
|
||||
|
||||
@@ -456,8 +507,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Set password to all input encrypted entries.
|
||||
*
|
||||
* @param string $password Password
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::setReadPassword()
|
||||
*
|
||||
* @deprecated using ZipFile::setReadPassword()
|
||||
*/
|
||||
public function withReadPassword($password);
|
||||
|
||||
@@ -465,6 +518,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Set password to all input encrypted entries.
|
||||
*
|
||||
* @param string $password Password
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setReadPassword($password);
|
||||
@@ -473,7 +527,8 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Set password to concrete input entry.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $password Password
|
||||
* @param string $password Password
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setReadPasswordEntry($entryName, $password);
|
||||
@@ -481,18 +536,21 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
/**
|
||||
* Set password for all entries for update.
|
||||
*
|
||||
* @param string $password If password null then encryption clear
|
||||
* @param string $password If password null then encryption clear
|
||||
* @param int|null $encryptionMethod Encryption method
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::setPassword()
|
||||
*
|
||||
* @deprecated using ZipFile::setPassword()
|
||||
*/
|
||||
public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
|
||||
|
||||
/**
|
||||
* Sets a new password for all files in the archive.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod Encryption method
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
|
||||
@@ -500,41 +558,49 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
/**
|
||||
* Sets a new password of an entry defined by its name.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setPasswordEntry($entryName, $password, $encryptionMethod = null);
|
||||
|
||||
/**
|
||||
* Remove password for all entries for update.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::disableEncryption()
|
||||
*
|
||||
* @deprecated using ZipFile::disableEncryption()
|
||||
*/
|
||||
public function withoutPassword();
|
||||
|
||||
/**
|
||||
* Disable encryption for all entries that are already in the archive.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function disableEncryption();
|
||||
|
||||
/**
|
||||
* Disable encryption of an entry defined by its name.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function disableEncryptionEntry($entryName);
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive
|
||||
* Undo all changes done in the archive.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeAll();
|
||||
|
||||
/**
|
||||
* Undo change archive comment
|
||||
* Undo change archive comment.
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeArchiveComment();
|
||||
@@ -543,6 +609,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Revert all changes done to an entry with the given name.
|
||||
*
|
||||
* @param string|ZipEntry $entry Entry name or ZipEntry
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeEntry($entry);
|
||||
@@ -551,8 +618,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Save as file.
|
||||
*
|
||||
* @param string $filename Output filename
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function saveAsFile($filename);
|
||||
|
||||
@@ -560,8 +629,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Save as stream.
|
||||
*
|
||||
* @param resource $handle Output stream resource
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function saveAsStream($handle);
|
||||
|
||||
@@ -569,33 +640,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
* Output .ZIP archive as attachment.
|
||||
* Die after output.
|
||||
*
|
||||
* @param string $outputFilename Output filename
|
||||
* @param string|null $mimeType Mime-Type
|
||||
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
|
||||
* @param string $outputFilename Output filename
|
||||
* @param string|null $mimeType Mime-Type
|
||||
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
|
||||
*/
|
||||
public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true);
|
||||
|
||||
/**
|
||||
* Output .ZIP archive as PSR-7 Response.
|
||||
*
|
||||
* @param ResponseInterface $response Instance PSR-7 Response
|
||||
* @param string $outputFilename Output filename
|
||||
* @param string|null $mimeType Mime-Type
|
||||
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
|
||||
* @param ResponseInterface $response Instance PSR-7 Response
|
||||
* @param string $outputFilename Output filename
|
||||
* @param string|null $mimeType Mime-Type
|
||||
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true);
|
||||
public function outputAsResponse(
|
||||
ResponseInterface $response,
|
||||
$outputFilename,
|
||||
$mimeType = null,
|
||||
$attachment = true
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the zip archive as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function outputAsString();
|
||||
|
||||
/**
|
||||
* Save and reopen zip archive.
|
||||
* @return ZipFileInterface
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function rewrite();
|
||||
|
||||
|
72
tests/PhpZip/Internal/DummyFileSystemStream.php
Normal file
72
tests/PhpZip/Internal/DummyFileSystemStream.php
Normal 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);
|
||||
}
|
||||
}
|
18
tests/PhpZip/Internal/ZipFileExtended.php
Normal file
18
tests/PhpZip/Internal/ZipFileExtended.php
Normal 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');
|
||||
}
|
||||
}
|
@@ -3,24 +3,31 @@
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class Issue24Test extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* This method is called before the first test of this test class is run.
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
stream_wrapper_register("dummyfs", DummyFileSystemStream::class);
|
||||
stream_wrapper_register('dummyfs', Internal\DummyFileSystemStream::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDummyFS()
|
||||
{
|
||||
$fileContents = str_repeat(base64_encode(CryptoUtil::randomBytes(12000)), 100);
|
||||
$fileContents = str_repeat(base64_encode(random_bytes(12000)), 100);
|
||||
|
||||
// create zip file
|
||||
$zip = new ZipFile();
|
||||
@@ -32,73 +39,13 @@ class Issue24Test extends ZipTestCase
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb');
|
||||
$this->assertNotFalse($stream);
|
||||
static::assertNotFalse($stream);
|
||||
$zip->openFromStream($stream);
|
||||
$this->assertEquals($zip->getListFiles(), ['file.txt']);
|
||||
$this->assertEquals($zip['file.txt'], $fileContents);
|
||||
static::assertSame($zip->getListFiles(), ['file.txt']);
|
||||
static::assertSame($zip['file.txt'], $fileContents);
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load using dummy stream
|
||||
*/
|
||||
class DummyFileSystemStream
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $fp;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_open($path, $mode, $options)" . PHP_EOL;
|
||||
|
||||
$parsedUrl = parse_url($path);
|
||||
$path = $parsedUrl['path'];
|
||||
$this->fp = fopen($path, $mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_read($count)" . PHP_EOL;
|
||||
$position = ftell($this->fp);
|
||||
|
||||
// echo "Loading chunk " . $position . " to " . ($position + $count - 1) . PHP_EOL;
|
||||
$ret = fread($this->fp, $count);
|
||||
|
||||
// echo "String length: " . strlen($ret) . PHP_EOL;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_tell()" . PHP_EOL;
|
||||
return ftell($this->fp);
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_eof()" . PHP_EOL;
|
||||
$isfeof = feof($this->fp);
|
||||
return $isfeof;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_seek($offset, $whence)" . PHP_EOL;
|
||||
fseek($this->fp, $offset, $whence);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_stat()" . PHP_EOL;
|
||||
return fstat($this->fp);
|
||||
}
|
||||
}
|
||||
|
@@ -2,16 +2,25 @@
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Some tests from the official extension of php-zip.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class PhpZipExtResourceTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* Bug #7214 (zip_entry_read() binary safe)
|
||||
* Bug #7214 (zip_entry_read() binary safe).
|
||||
*
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug7214.phpt
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBinaryNull()
|
||||
@@ -20,18 +29,21 @@ class PhpZipExtResourceTest extends ZipTestCase
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
|
||||
foreach ($zipFile as $name => $contents) {
|
||||
$info = $zipFile->getEntryInfo($name);
|
||||
$this->assertEquals(strlen($contents), $info->getSize());
|
||||
static::assertSame(\strlen($contents), $info->getSize());
|
||||
}
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($filename);
|
||||
static::assertCorrectZipArchive($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #8009 (cannot add again same entry to an archive)
|
||||
* Bug #8009 (cannot add again same entry to an archive).
|
||||
*
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug8009.phpt
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBug8009()
|
||||
@@ -44,36 +56,42 @@ class PhpZipExtResourceTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertCount(2, $zipFile);
|
||||
$this->assertTrue(isset($zipFile['1.txt']));
|
||||
$this->assertTrue(isset($zipFile['2.txt']));
|
||||
$this->assertEquals($zipFile['2.txt'], $zipFile['1.txt']);
|
||||
static::assertCount(2, $zipFile);
|
||||
static::assertTrue(isset($zipFile['1.txt']));
|
||||
static::assertTrue(isset($zipFile['2.txt']));
|
||||
static::assertSame($zipFile['2.txt'], $zipFile['1.txt']);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #40228 (extractTo does not create recursive empty path)
|
||||
* Bug #40228 (extractTo does not create recursive empty path).
|
||||
*
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228.phpt
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228-mb.phpt
|
||||
* @dataProvider provideBug40228
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBug40228($filename)
|
||||
{
|
||||
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
static::assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->extractTo($this->outputDirname);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertTrue(is_dir($this->outputDirname . '/test/empty'));
|
||||
static::assertTrue(is_dir($this->outputDirname . '/test/empty'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function provideBug40228()
|
||||
{
|
||||
return [
|
||||
@@ -82,14 +100,16 @@ class PhpZipExtResourceTest extends ZipTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #49072 (feof never returns true for damaged file in zip)
|
||||
* Bug #49072 (feof never returns true for damaged file in zip).
|
||||
*
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug49072.phpt
|
||||
* @expectedException \PhpZip\Exception\Crc32Exception
|
||||
* @expectedExceptionMessage file1
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBug49072()
|
||||
{
|
||||
$this->setExpectedException(Crc32Exception::class, 'file1');
|
||||
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/bug49072.zip';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
@@ -98,51 +118,59 @@ class PhpZipExtResourceTest extends ZipTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #70752 (Depacking with wrong password leaves 0 length files)
|
||||
* Bug #70752 (Depacking with wrong password leaves 0 length files).
|
||||
*
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug70752.phpt
|
||||
* @expectedException \PhpZip\Exception\ZipAuthenticationException
|
||||
* @expectedExceptionMessage nvalid password for zip entry "bug70752.txt"
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBug70752()
|
||||
{
|
||||
if (\PHP_INT_SIZE === 4) { // php 32 bit
|
||||
$this->setExpectedException(
|
||||
RuntimeException::class,
|
||||
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
|
||||
);
|
||||
} else { // php 64 bit
|
||||
$this->setExpectedException(
|
||||
ZipAuthenticationException::class,
|
||||
'nvalid password for zip entry "bug70752.txt"'
|
||||
);
|
||||
}
|
||||
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/bug70752.zip';
|
||||
|
||||
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
static::assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->setReadPassword('bar');
|
||||
|
||||
try {
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->setReadPassword('bar');
|
||||
$zipFile->extractTo($this->outputDirname);
|
||||
$this->markTestIncomplete('failed test');
|
||||
static::markTestIncomplete('failed test');
|
||||
} catch (ZipException $exception) {
|
||||
$this->assertFalse(file_exists($this->outputDirname . '/bug70752.txt'));
|
||||
static::assertFileNotExists($this->outputDirname . '/bug70752.txt');
|
||||
$zipFile->close();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #12414 ( extracting files from damaged archives)
|
||||
* Bug #12414 ( extracting files from damaged archives).
|
||||
*
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/pecl12414.phpt
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testPecl12414()
|
||||
{
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/pecl12414.zip';
|
||||
$this->setExpectedException(ZipException::class, 'Corrupt zip file. Cannot read central dir entry.');
|
||||
|
||||
$entryName = 'MYLOGOV2.GFX';
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/pecl12414.zip';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
|
||||
$info = $zipFile->getEntryInfo($entryName);
|
||||
$this->assertTrue($info->getSize() > 0);
|
||||
|
||||
$contents = $zipFile[$entryName];
|
||||
$this->assertEquals(strlen($contents), $info->getSize());
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
||||
|
44
tests/PhpZip/Zip64Test.php
Normal file
44
tests/PhpZip/Zip64Test.php
Normal 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();
|
||||
}
|
||||
}
|
@@ -3,10 +3,13 @@
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* Test ZipAlign
|
||||
* Test ZipAlign.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipAlignTest extends ZipTestCase
|
||||
{
|
||||
@@ -17,10 +20,11 @@ class ZipAlignTest extends ZipTestCase
|
||||
{
|
||||
$filename = __DIR__ . '/resources/apk.zip';
|
||||
|
||||
$this->assertCorrectZipArchive($filename);
|
||||
$result = $this->assertVerifyZipAlign($filename);
|
||||
if (null !== $result) {
|
||||
$this->assertTrue($result);
|
||||
static::assertCorrectZipArchive($filename);
|
||||
$result = static::assertVerifyZipAlign($filename);
|
||||
|
||||
if ($result !== null) {
|
||||
static::assertTrue($result);
|
||||
}
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
@@ -29,16 +33,19 @@ class ZipAlignTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
|
||||
if (null !== $result) {
|
||||
$this->assertTrue($result);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename, true);
|
||||
|
||||
if ($result !== null) {
|
||||
static::assertTrue($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test zip alignment.
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testZipAlignSourceZip()
|
||||
{
|
||||
@@ -46,39 +53,41 @@ class ZipAlignTest extends ZipTestCase
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile->addFromString(
|
||||
'entry' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
ZipFileInterface::METHOD_STORED
|
||||
random_bytes(mt_rand(100, 4096)),
|
||||
ZipFile::METHOD_STORED
|
||||
);
|
||||
}
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if ($result === null) {
|
||||
return;
|
||||
} // zip align not installed
|
||||
|
||||
// check not zip align
|
||||
$this->assertFalse($result);
|
||||
static::assertFalse($result);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
|
||||
$this->assertNotNull($result);
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename, true);
|
||||
static::assertNotNull($result);
|
||||
|
||||
// check zip align
|
||||
$this->assertTrue($result);
|
||||
static::assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testZipAlignNewFiles()
|
||||
{
|
||||
@@ -86,26 +95,28 @@ class ZipAlignTest extends ZipTestCase
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile->addFromString(
|
||||
'entry' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
ZipFileInterface::METHOD_STORED
|
||||
random_bytes(mt_rand(100, 4096)),
|
||||
ZipFile::METHOD_STORED
|
||||
);
|
||||
}
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if ($result === null) {
|
||||
return;
|
||||
} // zip align not installed
|
||||
// check not zip align
|
||||
$this->assertTrue($result);
|
||||
static::assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testZipAlignFromModifiedZipArchive()
|
||||
{
|
||||
@@ -113,46 +124,47 @@ class ZipAlignTest extends ZipTestCase
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile->addFromString(
|
||||
'entry' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
ZipFileInterface::METHOD_STORED
|
||||
random_bytes(mt_rand(100, 4096)),
|
||||
ZipFile::METHOD_STORED
|
||||
);
|
||||
}
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if ($result === null) {
|
||||
return;
|
||||
} // zip align not installed
|
||||
|
||||
// check not zip align
|
||||
$this->assertFalse($result);
|
||||
static::assertFalse($result);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->deleteFromRegex("~entry2[\d]+\.txt$~s");
|
||||
$zipFile->deleteFromRegex('~entry2[\\d]+\\.txt$~s');
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$isStored = (bool)mt_rand(0, 1);
|
||||
$isStored = (bool) mt_rand(0, 1);
|
||||
|
||||
$zipFile->addFromString(
|
||||
'entry_new_' . ($isStored ? 'stored' : 'deflated') . '_' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
random_bytes(mt_rand(100, 4096)),
|
||||
$isStored ?
|
||||
ZipFileInterface::METHOD_STORED :
|
||||
ZipFileInterface::METHOD_DEFLATED
|
||||
ZipFile::METHOD_STORED :
|
||||
ZipFile::METHOD_DEFLATED
|
||||
);
|
||||
}
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
|
||||
$this->assertNotNull($result);
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename, true);
|
||||
static::assertNotNull($result);
|
||||
|
||||
// check zip align
|
||||
$this->assertTrue($result);
|
||||
static::assertTrue($result);
|
||||
}
|
||||
}
|
||||
|
@@ -4,16 +4,11 @@ namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
class ZipFileExtended extends ZipFile
|
||||
{
|
||||
protected function onBeforeSave()
|
||||
{
|
||||
parent::onBeforeSave();
|
||||
$this->setZipAlign(4);
|
||||
$this->deleteFromRegex('~^META\-INF/~i');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipEventTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
@@ -21,27 +16,28 @@ class ZipEventTest extends ZipTestCase
|
||||
*/
|
||||
public function testBeforeSave()
|
||||
{
|
||||
$zipFile = new ZipFileExtended();
|
||||
$zipFile = new Internal\ZipFileExtended();
|
||||
$zipFile->openFile(__DIR__ . '/resources/apk.zip');
|
||||
$this->assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
$this->assertTrue(isset($zipFile['META-INF/CERT.SF']));
|
||||
$this->assertTrue(isset($zipFile['META-INF/CERT.RSA']));
|
||||
static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
static::assertTrue(isset($zipFile['META-INF/CERT.SF']));
|
||||
static::assertTrue(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if (null !== $result) {
|
||||
$this->assertTrue($result);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
$result = static::assertVerifyZipAlign($this->outputFilename);
|
||||
|
||||
if ($result !== null) {
|
||||
static::assertTrue($result);
|
||||
}
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,10 @@ use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Test add directory to zip archive.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipFileAddDirTest extends ZipTestCase
|
||||
{
|
||||
@@ -28,7 +32,7 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
];
|
||||
|
||||
/**
|
||||
* Before test
|
||||
* Before test.
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
@@ -40,12 +44,14 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
{
|
||||
foreach (self::$files as $name => $content) {
|
||||
$fullName = $this->outputDirname . '/' . $name;
|
||||
|
||||
if ($content === null) {
|
||||
if (!is_dir($fullName)) {
|
||||
mkdir($fullName, 0755, true);
|
||||
}
|
||||
} else {
|
||||
$dirname = dirname($fullName);
|
||||
$dirname = \dirname($fullName);
|
||||
|
||||
if (!is_dir($dirname)) {
|
||||
mkdir($dirname, 0755, true);
|
||||
}
|
||||
@@ -54,23 +60,33 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
}
|
||||
}
|
||||
|
||||
protected static function assertFilesResult(ZipFileInterface $zipFile, array $actualResultFiles = [], $localPath = '/')
|
||||
{
|
||||
/**
|
||||
* @param ZipFileInterface $zipFile
|
||||
* @param array $actualResultFiles
|
||||
* @param string $localPath
|
||||
*/
|
||||
protected static function assertFilesResult(
|
||||
ZipFileInterface $zipFile,
|
||||
array $actualResultFiles = [],
|
||||
$localPath = '/'
|
||||
) {
|
||||
$localPath = rtrim($localPath, '/');
|
||||
$localPath = empty($localPath) ? "" : $localPath . '/';
|
||||
self::assertEquals(sizeof($zipFile), sizeof($actualResultFiles));
|
||||
$localPath = empty($localPath) ? '' : $localPath . '/';
|
||||
static::assertCount(\count($zipFile), $actualResultFiles);
|
||||
$actualResultFiles = array_flip($actualResultFiles);
|
||||
|
||||
foreach (self::$files as $file => $content) {
|
||||
$zipEntryName = $localPath . $file;
|
||||
|
||||
if (isset($actualResultFiles[$file])) {
|
||||
self::assertTrue(isset($zipFile[$zipEntryName]));
|
||||
self::assertEquals($zipFile[$zipEntryName], $content);
|
||||
static::assertTrue(isset($zipFile[$zipEntryName]));
|
||||
static::assertSame($zipFile[$zipEntryName], $content);
|
||||
unset($actualResultFiles[$file]);
|
||||
} else {
|
||||
self::assertFalse(isset($zipFile[$zipEntryName]));
|
||||
static::assertFalse(isset($zipFile[$zipEntryName]));
|
||||
}
|
||||
}
|
||||
self::assertEmpty($actualResultFiles);
|
||||
static::assertEmpty($actualResultFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,15 +101,19 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -107,15 +127,18 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -133,15 +156,19 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -159,15 +186,18 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -185,10 +215,10 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -204,10 +234,10 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -221,10 +251,10 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files));
|
||||
static::assertFilesResult($zipFile, array_keys(self::$files));
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -236,7 +266,7 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$localPath = 'to/project';
|
||||
$ignoreFiles = [
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/'
|
||||
'empty dir/',
|
||||
];
|
||||
|
||||
$directoryIterator = new \DirectoryIterator($this->outputDirname);
|
||||
@@ -247,13 +277,17 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -278,24 +312,29 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
'catalog/New File',
|
||||
'catalog/New File 2',
|
||||
'catalog/Empty Dir/',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
'catalog/New File',
|
||||
'catalog/New File 2',
|
||||
'catalog/Empty Dir/',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files from glob pattern
|
||||
* Create archive and add files from glob pattern.
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromGlob()
|
||||
@@ -307,18 +346,23 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add recursively files from glob pattern
|
||||
* Create archive and add recursively files from glob pattern.
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromGlobRecursive()
|
||||
@@ -330,23 +374,28 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files from regex pattern
|
||||
* Create archive and add files from regex pattern.
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromRegex()
|
||||
@@ -358,18 +407,23 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files recursively from regex pattern
|
||||
* Create archive and add files recursively from regex pattern.
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromRegexRecursive()
|
||||
@@ -381,18 +435,22 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
], $localPath);
|
||||
static::assertFilesResult(
|
||||
$zipFile,
|
||||
[
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
],
|
||||
$localPath
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -409,10 +467,10 @@ class ZipFileAddDirTest extends ZipTestCase
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,16 @@
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PhpZip\Model\ZipEntryMatcher;
|
||||
use PhpZip\Model\ZipInfo;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
class ZipMatcherTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipMatcherTest extends TestCase
|
||||
{
|
||||
public function testMatcher()
|
||||
{
|
||||
@@ -16,50 +21,65 @@ class ZipMatcherTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
$matcher = $zipFile->matcher();
|
||||
$this->assertInstanceOf(ZipEntryMatcher::class, $matcher);
|
||||
static::assertInstanceOf(ZipEntryMatcher::class, $matcher);
|
||||
|
||||
$this->assertTrue(is_array($matcher->getMatches()));
|
||||
$this->assertCount(0, $matcher);
|
||||
static::assertInternalType('array', $matcher->getMatches());
|
||||
static::assertCount(0, $matcher);
|
||||
|
||||
$matcher->add(1)->add(10)->add(20);
|
||||
$this->assertCount(3, $matcher);
|
||||
$this->assertEquals($matcher->getMatches(), ['1', '10', '20']);
|
||||
static::assertCount(3, $matcher);
|
||||
static::assertEquals($matcher->getMatches(), ['1', '10', '20']);
|
||||
|
||||
$matcher->delete();
|
||||
$this->assertCount(97, $zipFile);
|
||||
$this->assertCount(0, $matcher);
|
||||
static::assertCount(97, $zipFile);
|
||||
static::assertCount(0, $matcher);
|
||||
|
||||
$matcher->match('~^[2][1-5]|[3][6-9]|40$~s');
|
||||
$this->assertCount(10, $matcher);
|
||||
static::assertCount(10, $matcher);
|
||||
$actualMatches = [
|
||||
'21', '22', '23', '24', '25',
|
||||
'36', '37', '38', '39',
|
||||
'40'
|
||||
'21',
|
||||
'22',
|
||||
'23',
|
||||
'24',
|
||||
'25',
|
||||
'36',
|
||||
'37',
|
||||
'38',
|
||||
'39',
|
||||
'40',
|
||||
];
|
||||
$this->assertEquals($matcher->getMatches(), $actualMatches);
|
||||
static::assertSame($matcher->getMatches(), $actualMatches);
|
||||
$matcher->setPassword('qwerty');
|
||||
$info = $zipFile->getAllInfo();
|
||||
array_walk($info, function (ZipInfo $zipInfo) use ($actualMatches) {
|
||||
$this->assertEquals($zipInfo->isEncrypted(), in_array($zipInfo->getName(), $actualMatches));
|
||||
});
|
||||
array_walk(
|
||||
$info,
|
||||
function (ZipInfo $zipInfo) use ($actualMatches) {
|
||||
$this->assertSame($zipInfo->isEncrypted(), \in_array($zipInfo->getName(), $actualMatches, true));
|
||||
}
|
||||
);
|
||||
|
||||
$matcher->all();
|
||||
$this->assertCount(count($zipFile), $matcher);
|
||||
static::assertCount(\count($zipFile), $matcher);
|
||||
|
||||
$expectedNames = [];
|
||||
$matcher->invoke(function ($entryName) use (&$expectedNames) {
|
||||
$expectedNames[] = $entryName;
|
||||
});
|
||||
$this->assertEquals($expectedNames, $matcher->getMatches());
|
||||
$matcher->invoke(
|
||||
static function ($entryName) use (&$expectedNames) {
|
||||
$expectedNames[] = $entryName;
|
||||
}
|
||||
);
|
||||
static::assertSame($expectedNames, $matcher->getMatches());
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDocsExample()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile['file_'.$i.'.jpg'] = CryptoUtil::randomBytes(100);
|
||||
$zipFile['file_' . $i . '.jpg'] = random_bytes(100);
|
||||
}
|
||||
|
||||
$renameEntriesArray = [
|
||||
@@ -86,24 +106,26 @@ class ZipMatcherTest extends \PHPUnit_Framework_TestCase
|
||||
];
|
||||
|
||||
foreach ($renameEntriesArray as $name) {
|
||||
$this->assertTrue(isset($zipFile[$name]));
|
||||
static::assertTrue(isset($zipFile[$name]));
|
||||
}
|
||||
|
||||
$matcher = $zipFile->matcher();
|
||||
$matcher->match('~^file_(1|5)\d+~');
|
||||
$this->assertEquals($matcher->getMatches(), $renameEntriesArray);
|
||||
static::assertSame($matcher->getMatches(), $renameEntriesArray);
|
||||
|
||||
$matcher->invoke(function ($entryName) use ($zipFile) {
|
||||
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
|
||||
$zipFile->rename($entryName, $newName);
|
||||
});
|
||||
$matcher->invoke(
|
||||
static function ($entryName) use ($zipFile) {
|
||||
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
|
||||
$zipFile->rename($entryName, $newName);
|
||||
}
|
||||
);
|
||||
|
||||
foreach ($renameEntriesArray as $name) {
|
||||
$this->assertFalse(isset($zipFile[$name]));
|
||||
static::assertFalse(isset($zipFile[$name]));
|
||||
|
||||
$pathInfo = pathinfo($name);
|
||||
$newName = $pathInfo['filename'].'.no_optimize.'.$pathInfo['extension'];
|
||||
$this->assertTrue(isset($zipFile[$newName]));
|
||||
$newName = $pathInfo['filename'] . '.no_optimize.' . $pathInfo['extension'];
|
||||
static::assertTrue(isset($zipFile[$newName]));
|
||||
}
|
||||
|
||||
$zipFile->close();
|
||||
|
@@ -2,89 +2,103 @@
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipInfo;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* Tests with zip password.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipPasswordTest extends ZipFileAddDirTest
|
||||
{
|
||||
/**
|
||||
* Test archive password.
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function testSetPassword()
|
||||
{
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
|
||||
if (\PHP_INT_SIZE === 4) { // php 32 bit
|
||||
$this->setExpectedException(
|
||||
RuntimeException::class,
|
||||
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
|
||||
);
|
||||
}
|
||||
|
||||
$password = base64_encode(CryptoUtil::randomBytes(100));
|
||||
$badPassword = "bad password";
|
||||
$password = base64_encode(random_bytes(100));
|
||||
$badPassword = 'bad password';
|
||||
|
||||
// create encryption password with ZipCrypto
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDir(__DIR__);
|
||||
$zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zipFile->setPassword($password, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
static::assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
// check bad password for ZipCrypto
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->setReadPassword($badPassword);
|
||||
|
||||
foreach ($zipFile->getListFiles() as $entryName) {
|
||||
try {
|
||||
$zipFile[$entryName];
|
||||
$this->fail("Expected Exception has not been raised.");
|
||||
static::fail('Expected Exception has not been raised.');
|
||||
} catch (ZipAuthenticationException $ae) {
|
||||
$this->assertContains('Invalid password for zip entry', $ae->getMessage());
|
||||
static::assertContains('Invalid password for zip entry', $ae->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// check correct password for ZipCrypto
|
||||
$zipFile->setReadPassword($password);
|
||||
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('ZipCrypto', $info->getMethodName());
|
||||
$decryptContent = $zipFile[$info->getName()];
|
||||
$this->assertNotEmpty($decryptContent);
|
||||
$this->assertContains('<?php', $decryptContent);
|
||||
static::assertNotEmpty($decryptContent);
|
||||
static::assertContains('<?php', $decryptContent);
|
||||
}
|
||||
|
||||
// change encryption method to WinZip Aes and update file
|
||||
$zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zipFile->setPassword($password, ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
static::assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
// check from WinZip AES encryption
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
// set bad password WinZip AES
|
||||
$zipFile->setReadPassword($badPassword);
|
||||
|
||||
foreach ($zipFile->getListFiles() as $entryName) {
|
||||
try {
|
||||
$zipFile[$entryName];
|
||||
$this->fail("Expected Exception has not been raised.");
|
||||
static::fail('Expected Exception has not been raised.');
|
||||
} catch (ZipAuthenticationException $ae) {
|
||||
$this->assertNotNull($ae);
|
||||
static::assertNotNull($ae);
|
||||
}
|
||||
}
|
||||
|
||||
// set correct password WinZip AES
|
||||
$zipFile->setReadPassword($password);
|
||||
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('WinZip', $info->getMethodName());
|
||||
$decryptContent = $zipFile[$info->getName()];
|
||||
$this->assertNotEmpty($decryptContent);
|
||||
$this->assertContains('<?php', $decryptContent);
|
||||
static::assertNotEmpty($decryptContent);
|
||||
static::assertContains('<?php', $decryptContent);
|
||||
}
|
||||
|
||||
// clear password
|
||||
@@ -94,42 +108,48 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
// check remove password
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertFalse($info->isEncrypted());
|
||||
static::assertFalse($info->isEncrypted());
|
||||
}
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testTraditionalEncryption()
|
||||
{
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
|
||||
if (\PHP_INT_SIZE === 4) { // php 32 bit
|
||||
$this->setExpectedException(
|
||||
RuntimeException::class,
|
||||
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
|
||||
);
|
||||
}
|
||||
|
||||
$password = base64_encode(CryptoUtil::randomBytes(50));
|
||||
$password = base64_encode(random_bytes(50));
|
||||
|
||||
$zip = new ZipFile();
|
||||
$zip->addDirRecursive($this->outputDirname);
|
||||
$zip->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->setPassword($password, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
static::assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPassword($password);
|
||||
$this->assertFilesResult($zip, array_keys(self::$files));
|
||||
static::assertFilesResult($zip, array_keys(self::$files));
|
||||
|
||||
foreach ($zip->getAllInfo() as $info) {
|
||||
if (!$info->isFolder()) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('ZipCrypto', $info->getMethodName());
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
@@ -137,13 +157,16 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
|
||||
/**
|
||||
* @dataProvider winZipKeyStrengthProvider
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @param int $bitSize
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testWinZipAesEncryption($encryptionMethod, $bitSize)
|
||||
{
|
||||
$password = base64_encode(CryptoUtil::randomBytes(50));
|
||||
$password = base64_encode(random_bytes(50));
|
||||
|
||||
$zip = new ZipFile();
|
||||
$zip->addDirRecursive($this->outputDirname);
|
||||
@@ -151,16 +174,17 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
static::assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPassword($password);
|
||||
$this->assertFilesResult($zip, array_keys(self::$files));
|
||||
static::assertFilesResult($zip, array_keys(self::$files));
|
||||
|
||||
foreach ($zip->getAllInfo() as $info) {
|
||||
if (!$info->isFolder()) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertEquals($info->getEncryptionMethod(), $encryptionMethod);
|
||||
$this->assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertSame($info->getEncryptionMethod(), $encryptionMethod);
|
||||
static::assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
@@ -172,10 +196,10 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
public function winZipKeyStrengthProvider()
|
||||
{
|
||||
return [
|
||||
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, 128],
|
||||
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, 192],
|
||||
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES, 256],
|
||||
[ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256, 256],
|
||||
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128, 128],
|
||||
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192, 192],
|
||||
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES, 256],
|
||||
[ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256, 256],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -185,8 +209,11 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
*/
|
||||
public function testEncryptionEntries()
|
||||
{
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
|
||||
if (\PHP_INT_SIZE === 4) { // php 32 bit
|
||||
$this->setExpectedException(
|
||||
RuntimeException::class,
|
||||
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
|
||||
);
|
||||
}
|
||||
|
||||
$password1 = '353442434235424234';
|
||||
@@ -194,31 +221,34 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
|
||||
$zip = new ZipFile();
|
||||
$zip->addDir($this->outputDirname);
|
||||
$zip->setPasswordEntry('.hidden', $password1, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->setPasswordEntry('text file.txt', $password2, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zip->setPasswordEntry('.hidden', $password1, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->setPasswordEntry('text file.txt', $password2, ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPasswordEntry('.hidden', $password1);
|
||||
$zip->setReadPasswordEntry('text file.txt', $password2);
|
||||
$this->assertFilesResult($zip, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
static::assertFilesResult(
|
||||
$zip,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]
|
||||
);
|
||||
|
||||
$info = $zip->getEntryInfo('.hidden');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('ZipCrypto', $info->getMethodName());
|
||||
|
||||
$info = $zip->getEntryInfo('text file.txt');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip AES', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('WinZip AES', $info->getMethodName());
|
||||
|
||||
$this->assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
|
||||
$this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
|
||||
static::assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
|
||||
static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
|
||||
|
||||
$zip->close();
|
||||
}
|
||||
@@ -229,8 +259,11 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
*/
|
||||
public function testEncryptionEntriesWithDefaultPassword()
|
||||
{
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
|
||||
if (\PHP_INT_SIZE === 4) { // php 32 bit
|
||||
$this->setExpectedException(
|
||||
RuntimeException::class,
|
||||
'Traditional PKWARE Encryption is not supported in 32-bit PHP.'
|
||||
);
|
||||
}
|
||||
|
||||
$password1 = '353442434235424234';
|
||||
@@ -240,8 +273,8 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zip = new ZipFile();
|
||||
$zip->addDir($this->outputDirname);
|
||||
$zip->setPassword($defaultPassword);
|
||||
$zip->setPasswordEntry('.hidden', $password1, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->setPasswordEntry('text file.txt', $password2, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zip->setPasswordEntry('.hidden', $password1, ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->setPasswordEntry('text file.txt', $password2, ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
@@ -249,36 +282,40 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zip->setReadPassword($defaultPassword);
|
||||
$zip->setReadPasswordEntry('.hidden', $password1);
|
||||
$zip->setReadPasswordEntry('text file.txt', $password2);
|
||||
$this->assertFilesResult($zip, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
static::assertFilesResult(
|
||||
$zip,
|
||||
[
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]
|
||||
);
|
||||
|
||||
$info = $zip->getEntryInfo('.hidden');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('ZipCrypto', $info->getMethodName());
|
||||
|
||||
$info = $zip->getEntryInfo('text file.txt');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip AES', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('WinZip AES', $info->getMethodName());
|
||||
|
||||
$info = $zip->getEntryInfo('Текстовый документ.txt');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip AES', $info->getMethodName());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
static::assertContains('WinZip AES', $info->getMethodName());
|
||||
|
||||
$this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
|
||||
static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
|
||||
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \PhpZip\Exception\ZipException
|
||||
* @expectedExceptionMessage Invalid encryption method
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testSetEncryptionMethodInvalid()
|
||||
{
|
||||
$this->setExpectedException(ZipException::class, 'Invalid encryption method');
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$encryptionMethod = 9999;
|
||||
$zipFile->setPassword('pass', $encryptionMethod);
|
||||
@@ -295,35 +332,40 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->setPassword('pass');
|
||||
$zipFile['file'] = 'content';
|
||||
$this->assertFalse($zipFile->getEntryInfo('file')->isEncrypted());
|
||||
static::assertFalse($zipFile->getEntryInfo('file')->isEncrypted());
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$zipFile['file' . $i] = 'content';
|
||||
|
||||
if ($i < 6) {
|
||||
$zipFile->setPasswordEntry('file' . $i, 'pass');
|
||||
$this->assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
|
||||
static::assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
|
||||
} else {
|
||||
$this->assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
|
||||
static::assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
|
||||
}
|
||||
}
|
||||
$zipFile->disableEncryptionEntry('file3');
|
||||
$this->assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
|
||||
$this->asserttrue($zipFile->getEntryInfo('file2')->isEncrypted());
|
||||
static::assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
|
||||
static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
|
||||
$zipFile->disableEncryption();
|
||||
$infoList = $zipFile->getAllInfo();
|
||||
array_walk($infoList, function (ZipInfo $zipInfo) {
|
||||
$this->assertFalse($zipInfo->isEncrypted());
|
||||
});
|
||||
array_walk(
|
||||
$infoList,
|
||||
function (ZipInfo $zipInfo) {
|
||||
$this->assertFalse($zipInfo->isEncrypted());
|
||||
}
|
||||
);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \PhpZip\Exception\ZipException
|
||||
* @expectedExceptionMessage Invalid encryption method
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testInvalidEncryptionMethodEntry()
|
||||
{
|
||||
$this->setExpectedException(ZipException::class, 'Invalid encryption method');
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFromString('file', 'content', ZipFileInterface::METHOD_STORED);
|
||||
$zipFile->addFromString('file', 'content', ZipFile::METHOD_STORED);
|
||||
$zipFile->setPasswordEntry('file', 'pass', 99);
|
||||
}
|
||||
|
||||
@@ -341,43 +383,46 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, 'password');
|
||||
static::assertCorrectZipArchive($this->outputFilename, 'password');
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertCount(3, $zipFile);
|
||||
static::assertCount(3, $zipFile);
|
||||
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
static::assertTrue($info->isEncrypted());
|
||||
}
|
||||
unset($zipFile['file3']);
|
||||
$zipFile['file4'] = 'content';
|
||||
$zipFile->rewrite();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, 'password');
|
||||
static::assertCorrectZipArchive($this->outputFilename, 'password');
|
||||
|
||||
$this->assertCount(3, $zipFile);
|
||||
$this->assertFalse(isset($zipFile['file3']));
|
||||
$this->assertTrue(isset($zipFile['file4']));
|
||||
$this->assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
|
||||
$this->assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
|
||||
$this->assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
|
||||
$this->assertEquals($zipFile['file4'], 'content');
|
||||
static::assertCount(3, $zipFile);
|
||||
static::assertFalse(isset($zipFile['file3']));
|
||||
static::assertTrue(isset($zipFile['file4']));
|
||||
static::assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
|
||||
static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
|
||||
static::assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
|
||||
static::assertSame($zipFile['file4'], 'content');
|
||||
|
||||
$zipFile->extractTo($this->outputDirname, ['file4']);
|
||||
|
||||
$this->assertTrue(file_exists($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'));
|
||||
$this->assertEquals(file_get_contents($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'), $zipFile['file4']);
|
||||
static::assertFileExists($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4');
|
||||
static::assertStringEqualsFile($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4', $zipFile['file4']);
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/Ne-Lexa/php-zip/issues/9
|
||||
*
|
||||
* @throws ZipException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testIssues9()
|
||||
{
|
||||
$contents = str_pad('', 1000, 'test;test2;test3' . PHP_EOL, STR_PAD_RIGHT);
|
||||
$password = base64_encode(CryptoUtil::randomBytes(20));
|
||||
$contents = str_pad('', 1000, 'test;test2;test3' . \PHP_EOL, \STR_PAD_RIGHT);
|
||||
$password = base64_encode(random_bytes(20));
|
||||
|
||||
$encryptMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
$zipFile = new ZipFile();
|
||||
@@ -385,13 +430,14 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
->addFromString('codes.csv', $contents)
|
||||
->setPassword($password, $encryptMethod)
|
||||
->saveAsFile($this->outputFilename)
|
||||
->close();
|
||||
->close()
|
||||
;
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
static::assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->setReadPassword($password);
|
||||
$this->assertEquals($zipFile['codes.csv'], $contents);
|
||||
static::assertSame($zipFile['codes.csv'], $contents);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -413,12 +459,13 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
||||
$zipFile2 = new ZipFile();
|
||||
$zipFile2->openFile($this->outputFilename);
|
||||
$zipFile2->setReadPassword($password);
|
||||
$this->assertEquals($zipFile2->getListFiles(), $zipFile->getListFiles());
|
||||
static::assertSame($zipFile2->getListFiles(), $zipFile->getListFiles());
|
||||
|
||||
foreach ($zipFile as $name => $contents) {
|
||||
$this->assertNotEmpty($name);
|
||||
$this->assertNotEmpty($contents);
|
||||
$this->assertContains('test contents', $contents);
|
||||
$this->assertEquals($zipFile2[$name], $contents);
|
||||
static::assertNotEmpty($name);
|
||||
static::assertNotEmpty($contents);
|
||||
static::assertContains('test contents', $contents);
|
||||
static::assertSame($zipFile2[$name], $contents);
|
||||
}
|
||||
$zipFile2->close();
|
||||
|
||||
|
@@ -3,20 +3,16 @@
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Test add remote files to zip archive
|
||||
* Test add remote files to zip archive.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipRemoteFileTest extends ZipTestCase
|
||||
{
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
@@ -25,16 +21,28 @@ class ZipRemoteFileTest extends ZipTestCase
|
||||
$zipFile = new ZipFile();
|
||||
$outputZip = $this->outputFilename;
|
||||
$fileUrl = 'https://raw.githubusercontent.com/Ne-Lexa/php-zip/master/README.md';
|
||||
$fp = @fopen($fileUrl, 'rb', false, stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 3,
|
||||
]
|
||||
]));
|
||||
/** @noinspection PhpUsageOfSilenceOperatorInspection */
|
||||
$fp = @fopen(
|
||||
$fileUrl,
|
||||
'rb',
|
||||
false,
|
||||
stream_context_create(
|
||||
[
|
||||
'http' => [
|
||||
'timeout' => 3,
|
||||
],
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
if ($fp === false) {
|
||||
self::markTestSkipped(sprintf(
|
||||
"Could not fetch remote file: %s",
|
||||
$fileUrl
|
||||
));
|
||||
static::markTestSkipped(
|
||||
sprintf(
|
||||
'Could not fetch remote file: %s',
|
||||
$fileUrl
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,9 +55,8 @@ class ZipRemoteFileTest extends ZipTestCase
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($outputZip);
|
||||
$files = $zipFile->getListFiles();
|
||||
self::assertCount(1, $files);
|
||||
self::assertSame($fileName, $files[0]);
|
||||
static::assertCount(1, $files);
|
||||
static::assertSame($fileName, $files[0]);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,11 +3,14 @@
|
||||
namespace PhpZip;
|
||||
|
||||
/**
|
||||
* Class ZipSlipVulnerabilityTest
|
||||
* Class ZipSlipVulnerabilityTest.
|
||||
*
|
||||
* @package PhpZip
|
||||
* @see https://github.com/Ne-Lexa/php-zip/issues/39 Issue#31
|
||||
* @see https://snyk.io/research/zip-slip-vulnerability Zip Slip Vulnerability
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipSlipVulnerabilityTest extends ZipTestCase
|
||||
{
|
||||
@@ -19,7 +22,7 @@ class ZipSlipVulnerabilityTest extends ZipTestCase
|
||||
$localFile = '../dir/./../../file.txt';
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFromString($localFile, 'contents');
|
||||
self::assertContains($localFile, $zipFile->getListFiles());
|
||||
static::assertContains($localFile, $zipFile->getListFiles());
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
@@ -28,7 +31,7 @@ class ZipSlipVulnerabilityTest extends ZipTestCase
|
||||
*/
|
||||
public function testUnpack()
|
||||
{
|
||||
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
static::assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFromString('../dir/./../../file.txt', 'contents');
|
||||
@@ -36,6 +39,6 @@ class ZipSlipVulnerabilityTest extends ZipTestCase
|
||||
$zipFile->close();
|
||||
|
||||
$expectedExtractedFile = $this->outputDirname . '/dir/file.txt';
|
||||
self::assertTrue(is_file($expectedExtractedFile));
|
||||
static::assertTrue(is_file($expectedExtractedFile));
|
||||
}
|
||||
}
|
||||
|
@@ -2,32 +2,31 @@
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
|
||||
/**
|
||||
* PHPUnit test case and helper methods.
|
||||
*/
|
||||
class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
abstract class ZipTestCase extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $outputFilename;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
/** @var string */
|
||||
protected $outputDirname;
|
||||
|
||||
/**
|
||||
* Before test
|
||||
* Before test.
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$id = uniqid('phpzip', true);
|
||||
$tempDir = sys_get_temp_dir() . '/phpunit-phpzip';
|
||||
|
||||
if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) {
|
||||
throw new \RuntimeException('Dir ' . $tempDir . " can't created");
|
||||
}
|
||||
@@ -36,7 +35,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* After test
|
||||
* After test.
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
@@ -45,6 +44,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
if ($this->outputFilename !== null && file_exists($this->outputFilename)) {
|
||||
unlink($this->outputFilename);
|
||||
}
|
||||
|
||||
if ($this->outputDirname !== null && is_dir($this->outputDirname)) {
|
||||
FilesUtil::removeDir($this->outputDirname);
|
||||
}
|
||||
@@ -53,25 +53,30 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* Assert correct zip archive.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $filename
|
||||
* @param string|null $password
|
||||
*/
|
||||
public static function assertCorrectZipArchive($filename, $password = null)
|
||||
{
|
||||
if (self::existsProgram('unzip')) {
|
||||
$command = 'unzip';
|
||||
|
||||
if ($password !== null) {
|
||||
$command .= ' -P ' . escapeshellarg($password);
|
||||
}
|
||||
$command .= ' -t ' . escapeshellarg($filename);
|
||||
$command .= ' 2>&1';
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
$output = implode(\PHP_EOL, $output);
|
||||
|
||||
if ($password !== null && $returnCode === 81) {
|
||||
if (self::existsProgram('7z')) {
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
/**
|
||||
* WinZip 99-character limit.
|
||||
*
|
||||
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
*/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename);
|
||||
@@ -79,31 +84,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @var array $output
|
||||
*/
|
||||
$output = implode(PHP_EOL, $output);
|
||||
$output = implode(\PHP_EOL, $output);
|
||||
|
||||
self::assertEquals($returnCode, 0);
|
||||
self::assertNotContains(' Errors', $output);
|
||||
self::assertContains(' Ok', $output);
|
||||
static::assertSame($returnCode, 0);
|
||||
static::assertNotContains(' Errors', $output);
|
||||
static::assertContains(' Ok', $output);
|
||||
} else {
|
||||
fwrite(STDERR, 'Program unzip cannot support this function.' . PHP_EOL);
|
||||
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . PHP_EOL);
|
||||
fwrite(\STDERR, 'Program unzip cannot support this function.' . \PHP_EOL);
|
||||
fwrite(\STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . \PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
self::assertEquals($returnCode, 0, $output);
|
||||
self::assertNotContains('incorrect password', $output);
|
||||
self::assertContains(' OK', $output);
|
||||
self::assertContains('No errors', $output);
|
||||
static::assertSame($returnCode, 0, $output);
|
||||
static::assertNotContains('incorrect password', $output);
|
||||
static::assertContains(' OK', $output);
|
||||
static::assertContains('No errors', $output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $program
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function existsProgram($program){
|
||||
if (DIRECTORY_SEPARATOR !== '\\') {
|
||||
private static function existsProgram($program)
|
||||
{
|
||||
if (\DIRECTORY_SEPARATOR !== '\\') {
|
||||
exec('which ' . escapeshellarg($program), $output, $returnCode);
|
||||
|
||||
return $returnCode === 0;
|
||||
}
|
||||
// false for Windows
|
||||
@@ -120,30 +128,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
if (self::existsProgram('zipinfo')) {
|
||||
exec('zipinfo ' . escapeshellarg($filename), $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
$output = implode(\PHP_EOL, $output);
|
||||
|
||||
self::assertContains('Empty zipfile', $output);
|
||||
static::assertContains('Empty zipfile', $output);
|
||||
}
|
||||
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
|
||||
self::assertStringEqualsFile($filename, $actualEmptyZipData);
|
||||
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CD_SIG, 0, 0, 0, 0, 0);
|
||||
static::assertStringEqualsFile($filename, $actualEmptyZipData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param bool $showErrors
|
||||
* @return bool|null If null - can not install zipalign
|
||||
* @param bool $showErrors
|
||||
*
|
||||
* @return bool|null If null returned, then the zipalign program is not installed
|
||||
*/
|
||||
public static function assertVerifyZipAlign($filename, $showErrors = false)
|
||||
{
|
||||
if (self::existsProgram('zipalign')) {
|
||||
exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode);
|
||||
|
||||
if ($showErrors && $returnCode !== 0) {
|
||||
fwrite(STDERR, implode(PHP_EOL, $output));
|
||||
fwrite(\STDERR, implode(\PHP_EOL, $output));
|
||||
}
|
||||
|
||||
return $returnCode === 0;
|
||||
}
|
||||
|
||||
fwrite(STDERR, 'Can not find program "zipalign" for test' . PHP_EOL);
|
||||
fwrite(\STDERR, "Cannot find the program 'zipalign' for the test" . \PHP_EOL);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user