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

68 Commits

Author SHA1 Message Date
Ne-Lexa
3b769793fc Merge branch 'hotfix/3.1.15' 2019-12-11 15:26:23 +03:00
Ne-Lexa
7cbf992492 fix #42 2019-12-11 15:21:28 +03:00
Ne-Lexa
c420f191dd Merge branch 'hotfix/3.1.14' 2019-12-09 12:17:55 +03:00
Ne-Lexa
50015f25f3 fix unknown software version 2019-12-09 12:13:56 +03:00
Ne-Lexa
f2d2954bee Merge branch 'hotfix/3.1.13' 2019-12-09 11:20:56 +03:00
wapplay
725959d98f fix encryption 2019-12-07 20:16:40 +03:00
wapplay
f82a57e662 fix duplicates 2019-12-07 19:40:36 +03:00
wapplay
a20e9e054d use random_compat 2019-12-06 23:23:44 +03:00
Ne-Lexa
95e3312e60 refactoring zip64, add property softwareVersion, internal attrs, extracted os 2019-12-06 17:36:22 +03:00
Ne-Lexa
e2c058840c fixed for php 32-bit 2019-12-06 12:09:37 +03:00
wapplay
e03c963dc1 refactoring 2019-12-06 08:32:33 +03:00
wapplay
c0786f5947 set travis dist 2019-12-05 23:52:17 +03:00
Ne-Lexa
3bafe01ff0 php cs fix 2019-12-05 19:36:11 +03:00
wapplay
ca068fa78f Merge branch 'hotfix/3.1.12' 2019-07-25 23:08:42 +03:00
wapplay
a84d2f9eff fix issue #39 2019-07-25 23:03:28 +03:00
Ne-Lexa
97db60a52d Merge pull request #36 from chrisgraham/develop
Fixed slightly confusing language regarding setPassword
2019-04-04 11:37:51 +03:00
Chris Graham
4134ca8daa Fixed slightly confusing language regarding setPassword 2019-04-02 16:53:33 -05:00
Ne-Lexa
e7528b2974 Merge tag '3.1.11' into develop
Tagging hotfix 3.1.11 3.1.11
2019-03-13 15:41:56 +03:00
Ne-Lexa
19e17fb730 Merge branch 'hotfix/3.1.11' 2019-03-13 15:41:56 +03:00
Ne-Lexa
d8f913ac67 removed nightly PHP (8.0-dev) from travis 2019-03-13 15:37:01 +03:00
Ne-Lexa
1d1c8559cd update travis config 2019-03-13 15:29:40 +03:00
Ne-Lexa
e1108f9a24 Added .gitattributes to exclude tests from the release version #34 2019-03-13 15:28:38 +03:00
Ne-Lexa
96d269b4ca Merge tag '3.1.10' into develop
Tagging hotfix 3.1.10 3.1.10
2019-03-13 15:16:08 +03:00
Ne-Lexa
53da7053ba Merge branch 'hotfix/3.1.10' 2019-03-13 15:16:08 +03:00
Ne-Lexa
e8745e0379 fix #34 2019-03-13 15:15:52 +03:00
Ne-Lexa
b3277fcc5c Replaced the execution operator backticks with the function call exec in tests 2019-03-13 15:14:01 +03:00
Ne-Lexa
4aa9711e00 Merge tag '3.1.9' into develop
Tagging version 3.1.9 3.1.9
2018-11-02 15:15:08 +03:00
Ne-Lexa
c9871c9f80 Merge branch 'release-3.1.9' 2018-11-02 15:15:08 +03:00
Ne-Lexa
650fab4bad Merge branch 'master' into develop 2018-11-02 15:14:52 +03:00
Ne-Lexa
fd9750c4f3 Merge pull request #26 from belgattitude/fix/non-seekable-streams
Re-enable support for remote streams, fix #25
2018-11-02 15:11:54 +03:00
Sébastien Vanvelthem
e903642893 Re-enable support for remote streams, fix #25 2018-11-02 11:53:35 +01:00
wapplay
516d0c1e77 Merge tag '3.1.8' into develop
Tagging version 3.1.8 3.1.8
2018-10-21 19:31:21 +03:00
wapplay
9934a860c1 Merge branch 'release/3.1.8' 2018-10-21 19:31:21 +03:00
wapplay
c9f597308e cs fix 2018-10-21 19:30:45 +03:00
wapplay
837454ba7e Added additional check for correct decompression 2018-10-21 19:25:13 +03:00
wapplay
680a9d92c1 Merge branch 'hotfix/3.1.7' 2018-10-21 01:54:45 +03:00
wapplay
04a92e7904 Merge tag '3.1.7' into develop
Tagging hotfix 3.1.7 3.1.7
2018-10-21 01:54:45 +03:00
wapplay
e4e3a7504e Test streamWrapper extract (issue #24) 2018-10-21 01:54:30 +03:00
wapplay
d8bb1be43b Fix #24 Change fread for variable size reads 2018-10-21 01:32:01 +03:00
Ne-Lexa
c163f0583e update pull request template 2018-10-18 10:46:49 +03:00
Ne-Lexa
116a617744 added github templates 2018-10-18 10:42:57 +03:00
Ne-Lexa
e207086a75 Merge tag '3.1.6' into develop
Tagging version 3.1.6 3.1.6
2018-10-11 10:54:30 +03:00
Ne-Lexa
9bb20cc15e Merge branch 'release-3.1.6' 2018-10-11 10:54:30 +03:00
Ne-Lexa
7e84f97473 resolve merge conflict 2018-10-11 10:49:53 +03:00
Ne-Lexa
a13f4cc32f Merge branch 'master' into develop
# Conflicts:
#	src/PhpZip/ZipFile.php
#	tests/PhpZip/ZipFileTest.php
2018-10-11 10:39:05 +03:00
Ne-Lexa
c863c18869 Fix #22 Adding links to files, not open handles 2018-10-10 16:53:17 +03:00
wapplay
ad3bac6f96 cp866 to utf8 converter 2018-10-10 10:34:06 +03:00
wapplay
d2e94ac9cd update REAMDE 2018-10-10 07:43:29 +03:00
wapplay
3f0c6a7bd8 add php 7.2 in travis config 2018-10-10 07:41:43 +03:00
wapplay
062762ed09 update README 2018-10-10 07:41:19 +03:00
wapplay
e1866215a6 cs-fix 2018-10-09 10:17:54 +03:00
wapplay
f9e6a73587 PHP-doc updated (@throws added everywhere) and minor refactoring done. 2018-10-09 10:06:04 +03:00
wapplay
59773d62a8 Merge branch 'litlife-patch-1' into develop 2018-10-09 09:55:45 +03:00
wapplay
9417d7dc95 Merge branch 'patch-1' of https://github.com/litlife/php-zip into litlife-patch-1 2018-10-09 09:55:13 +03:00
Ne-Lexa
45905eacf0 Merge pull request #21 from MohannadNaj/rewording
Rewording some exceptions messages
2018-05-08 07:23:02 +03:00
MohannadNaj
aa8846b944 Rewording some exceptions messages 2018-05-08 03:40:39 +03:00
Ne-Lexa
6058c289a4 Merge tag '3.1.5' into develop
version 3.1.5
2018-04-25 12:44:40 +03:00
Ne-Lexa
251ce11bdc Merge branch 'hotfix/3.1.5' 2018-04-25 12:44:11 +03:00
Ne-Lexa
f969e59319 Updated ZipFile::outputAsResponse() to conform to PSR standard 2018-04-25 12:43:37 +03:00
Frederik Buus Sauer
6808e4ffdc Lowered requirement to support PHP 5.5 2018-04-12 11:25:12 +02:00
Frederik Buus Sauer
91f08b9f55 Updated composer.json regarding unit tests, and corrected PSR implementation 2018-04-12 11:10:45 +02:00
litlife
8de3a70571 Файлы при записывании в кеш не могут считаться
Привет! Спасибо за такой прекрасный модуль для работы с архивами!
Нашел такой глюк. При превышении размера 524288 файлы записывается в php://temp. При запросе данных из кеша stream_get_contents ничего не выводит. В fopen mode поменял на r+b, что включился режим записи. При запросе данных из stream нужно установить указатель в начало файла, поэтому перед ним вставил функцию rewind($this->entryContent);
2018-04-09 18:09:19 +03:00
Ne-Lexa
bdd5423f67 Merge tag '3.1.4' into develop
Tagging version 3.1.4 3.1.4
2018-02-01 09:52:40 +03:00
Ne-Lexa
e58cf0f337 Merge tag '3.1.3' into develop
Tagging version 3.1.3 3.1.3
2017-12-06 15:43:25 +03:00
Ne-Lexa
fb1a9ced88 Merge tag '3.1.2' into develop
Tagging version 3.1.2 3.1.2
2017-11-17 11:13:15 +03:00
Ne-Lexa
a1da1f0069 Added an event that runs before the archive is saved or output. 2017-11-15 16:24:14 +03:00
Ne-Lexa
f29fed2753 Merge tag '3.1.1' into develop
Tagging version 3.1.1 3.1.1
2017-11-15 10:48:35 +03:00
Ne-Lexa
03998d79a9 Merge tag '3.1.0' into develop
Tagging version 3.1.0 3.1.0
2017-11-14 15:27:09 +03:00
71 changed files with 7137 additions and 3246 deletions

6
.gitattributes vendored Normal file
View File

@@ -0,0 +1,6 @@
.gitattributes export-ignore
.github export-ignore
.gitignore export-ignore
.travis.yml export-ignore
phpunit.xml export-ignore
tests export-ignore

23
.github/ISSUE_TEMPLATE/1_Bug_report.md vendored Normal file
View File

@@ -0,0 +1,23 @@
---
name: 🔴 Bug Report
about: Report errors and problems
---
| Q | A
| -----------------------------| ---
| Library version(s) affected: | x.y.z
| PHP version(s): | x.y.z
| OS (with bit depth): | <!-- ex. Ubuntu 18.04 64-bit -->
**Description**
<!-- A clear and concise description of the problem. -->
**How to reproduce**
<!-- Code to reproduce the problem. -->
**Possible Solution**
<!--- Optional: only if you have suggestions on a fix/reason for the bug -->
**Additional context**
<!-- Optional: any other context about the problem: error messages, stack trace, zip files, etc. -->

View File

@@ -0,0 +1,12 @@
---
name: 🚀 Feature Request
about: Ideas for new features and improvements
---
**Description**
<!-- A clear and concise description of the new feature. -->
**Example**
<!-- A simple example of the new feature in action (include PHP code)
If the new feature changes an existing feature, include a simple before/after comparison. -->

12
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,12 @@
| Q | A
| ------------- | ---
| Bug fix? | yes/no
| New feature? | yes/no <!-- don't forget to update CHANGELOG.md file -->
<!--
Write a short README entry for your feature/bugfix here (replace this comment block.)
Do NOT send pull request to `master` branch.
Please send to `develop` branch instead.
Any PR to `master` branch will NOT be merged.
-->

1500
.php_cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,18 @@
dist: trusty
language: php
php:
- '5.5'
- '5.6'
- '7.0'
- '7.1'
- nightly
# cache vendor dirs
cache:
directories:
- vendor
- $HOME/.composer/cache
- '7.2'
- '7.3'
- '7.4'
install:
- travis_retry composer self-update && composer --version
- travis_retry composer install --prefer-dist --no-interaction
- travis_retry composer install --no-interaction
before_script:
- sudo apt-get install p7zip-full

View File

@@ -78,21 +78,29 @@
```php
// создание нового архива
$zipFile = new \PhpZip\ZipFile();
$zipFile
->addFromString("zip/entry/filename", "Is file content") // добавить запись из строки
->addFile("/path/to/file", "data/tofile") // добавить запись из файла
->addDir(__DIR__, "to/path/") // добавить файлы из директории
->saveAsFile($outputFilename) // сохранить архив в файл
->close(); // закрыть архив
// открытие архива, извлечение файлов, удаление файлов, добавление файлов, установка пароля и вывод архива в браузер.
$zipFile
->openFile($outputFilename) // открыть архив из файла
->extractTo($outputDirExtract) // извлечь файлы в заданную директорию
->deleteFromRegex('~^\.~') // удалить все скрытые (Unix) файлы
->addFromString('dir/file.txt', 'Test file') // добавить новую запись из строки
->setPassword('password') // установить пароль на все записи
->outputAsAttachment('library.jar'); // вывести в браузер без сохранения в файл
try{
$zipFile
->addFromString("zip/entry/filename", "Is file content") // добавить запись из строки
->addFile("/path/to/file", "data/tofile") // добавить запись из файла
->addDir(__DIR__, "to/path/") // добавить файлы из директории
->saveAsFile($outputFilename) // сохранить архив в файл
->close(); // закрыть архив
// открытие архива, извлечение файлов, удаление файлов, добавление файлов, установка пароля и вывод архива в браузер.
$zipFile
->openFile($outputFilename) // открыть архив из файла
->extractTo($outputDirExtract) // извлечь файлы в заданную директорию
->deleteFromRegex('~^\.~') // удалить все скрытые (Unix) файлы
->addFromString('dir/file.txt', 'Test file') // добавить новую запись из строки
->setPassword('password') // установить пароль на все записи
->outputAsAttachment('library.jar'); // вывести в браузер без сохранения в файл
}
catch(\PhpZip\Exception\ZipException $e){
// обработка исключения
}
finally{
$zipFile->close();
}
```
Другие примеры можно посмотреть в папке `tests/`.

View File

@@ -78,21 +78,29 @@ Latest stable version: [![Latest Stable Version](https://poser.pugx.org/nelexa/z
```php
// create new archive
$zipFile = new \PhpZip\ZipFile();
$zipFile
->addFromString("zip/entry/filename", "Is file content") // add an entry from the string
->addFile("/path/to/file", "data/tofile") // add an entry from the file
->addDir(__DIR__, "to/path/") // add files from the directory
->saveAsFile($outputFilename) // save the archive to a file
->close(); // close archive
// open archive, extract, add files, set password and output to browser.
$zipFile
->openFile($outputFilename) // open archive from file
->extractTo($outputDirExtract) // extract files to the specified directory
->deleteFromRegex('~^\.~') // delete all hidden (Unix) files
->addFromString('dir/file.txt', 'Test file') // add a new entry from the string
->setPassword('password') // set password for all entries
->outputAsAttachment('library.jar'); // output to the browser without saving to a file
try{
$zipFile
->addFromString("zip/entry/filename", "Is file content") // add an entry from the string
->addFile("/path/to/file", "data/tofile") // add an entry from the file
->addDir(__DIR__, "to/path/") // add files from the directory
->saveAsFile($outputFilename) // save the archive to a file
->close(); // close archive
// open archive, extract, add files, set password and output to browser.
$zipFile
->openFile($outputFilename) // open archive from file
->extractTo($outputDirExtract) // extract files to the specified directory
->deleteFromRegex('~^\.~') // delete all hidden (Unix) files
->addFromString('dir/file.txt', 'Test file') // add a new entry from the string
->setPassword('password') // set password for all entries
->outputAsAttachment('library.jar'); // output to the browser without saving to a file
}
catch(\PhpZip\Exception\ZipException $e){
// handle exception
}
finally{
$zipFile->close();
}
```
Other examples can be found in the `tests/` folder
@@ -559,7 +567,7 @@ $zipFile->rename($oldName, $newName);
```
<a name="Documentation-ZipFile-setCompressionLevel"></a> **ZipFile::setCompressionLevel** - set the compression level for all files in the archive.
> _Note that this method does not apply to entries that were added after this method was run._
> _Note that this method does not apply to entries that are added after this method is run._
By default, the compression level is -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) or the compression level specified in the archive for Deflate compression.
@@ -663,7 +671,7 @@ $zipFile->setReadPasswordEntry($entryName, $password);
```
<a name="Documentation-ZipFile-setPassword"></a> **ZipFile::setPassword** - sets a new password for all files in the archive.
> _Note that this method does not apply to entries that were added after this method was run._
> _Note that this method does not apply to entries that are added after this method is run._
```php
$zipFile->setPassword($password);
```
@@ -683,7 +691,7 @@ $zipFile->setPasswordEntry($entryName, $password, $encryptionMethod);
```
<a name="Documentation-ZipFile-disableEncryption"></a> **ZipFile::disableEncryption** - disable encryption for all entries that are already in the archive.
> _Note that this method does not apply to entries that were added after this method was run._
> _Note that this method does not apply to entries that are added after this method is run._
```php
$zipFile->disableEncryption();
```

View File

@@ -1,45 +1,60 @@
{
"name": "nelexa/zip",
"description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
"type": "library",
"keywords": [
"zip",
"unzip",
"archive",
"extract",
"winzip",
"zipalign",
"ziparchive"
],
"require-dev": {
"phpunit/phpunit": "4.8"
},
"license": "MIT",
"authors": [
{
"name": "Ne-Lexa",
"email": "alexey@nelexa.ru",
"role": "Developer"
"name": "nelexa/zip",
"type": "library",
"description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
"keywords": [
"zip",
"unzip",
"archive",
"extract",
"winzip",
"zipalign",
"ziparchive"
],
"homepage": "https://github.com/Ne-Lexa/php-zip",
"license": "MIT",
"authors": [
{
"name": "Ne-Lexa",
"email": "alexey@nelexa.ru",
"role": "Developer"
}
],
"require": {
"php": "^5.5 || ^7.0",
"ext-zlib": "*",
"psr/http-message": "^1.0",
"paragonie/random_compat": ">=1 <9.99"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.7",
"zendframework/zend-diactoros": "^1.4"
},
"autoload": {
"psr-4": {
"PhpZip\\": "src/PhpZip"
}
},
"autoload-dev": {
"psr-4": {
"PhpZip\\": "tests/PhpZip"
}
},
"suggest": {
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
"ext-bz2": "Needed to support BZIP2 compression",
"ext-fileinfo": "Needed to get mime-type file"
},
"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"
}
}
],
"minimum-stability": "stable",
"require": {
"php": "^5.5 || ^7.0",
"psr/http-message": "^1.0"
},
"autoload": {
"psr-4": {
"PhpZip\\": "src/PhpZip"
}
},
"autoload-dev": {
"psr-4": {
"PhpZip\\": "tests/PhpZip"
}
},
"suggest": {
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
"ext-bz2": "Needed to support BZIP2 compression"
}
}

View File

@@ -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,16 +378,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
// compare against the CRC otherwise
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
}
if ($byte !== $checkByte) {
throw new ZipAuthenticationException("Bad password for entry " . $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;
}
@@ -168,21 +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
*
* @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();
@@ -192,34 +444,41 @@ 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)
{
if (null === $content) {
if ($content === null) {
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;
}
}

View File

@@ -5,14 +5,15 @@ namespace PhpZip\Crypto;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
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
*/
@@ -23,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)
@@ -46,16 +47,19 @@ class WinZipAesEngine implements ZipEncryptionEngine
* Decrypt from stream resource.
*
* @param string $content Input stream buffer
* @return string
*
* @throws ZipException
* @throws ZipAuthenticationException
* @throws ZipCryptoException
*
* @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)');
}
/**
@@ -76,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
@@ -109,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,
@@ -124,36 +135,40 @@ 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(false, $content, $key, $iv);
return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false);
}
/**
* Decryption or encryption AES-CTR with Segment Integer Count (SIC).
*
* @param bool $encrypted If true encryption else decryption
* @param string $str Data
* @param string $key Key
* @param string $iv IV
* @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($encrypted = true, $str, $key, $iv)
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]);
if (0x100 === ++$n) {
$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;
}
}
@@ -162,6 +177,7 @@ class WinZipAesEngine implements ZipEncryptionEngine
self::encryptCtr($data, $key, $iv) :
self::decryptCtr($data, $key, $iv);
}
return $ctrStr;
}
@@ -169,84 +185,111 @@ 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
* @throws RuntimeException
*/
private static function encryptCtr($data, $key, $iv)
{
if (extension_loaded("openssl")) {
$numBits = strlen($key) * 8;
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
} elseif (extension_loaded("mcrypt")) {
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
} else {
throw new RuntimeException('Extension openssl or mcrypt not loaded');
if (\extension_loaded('openssl')) {
$numBits = \strlen($key) * 8;
/** @noinspection PhpComposerExtensionStubsInspection */
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
* @throws RuntimeException
*/
private static function decryptCtr($data, $key, $iv)
{
if (extension_loaded("openssl")) {
$numBits = strlen($key) * 8;
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
} elseif (extension_loaded("mcrypt")) {
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
} else {
throw new RuntimeException('Extension openssl or mcrypt not loaded');
if (\extension_loaded('openssl')) {
$numBits = \strlen($key) * 8;
/** @noinspection PhpComposerExtensionStubsInspection */
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
*/
public function encrypt($content)
{
// Init key strength.
$password = $this->entry->getPassword();
assert($password !== null);
// WinZip 99-character limit
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
if ($password === null) {
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/
*/
$password = substr($password, 0, 99);
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($this->entry->getEncryptionMethod());
$keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
$this->entry->getEncryptionMethod()
);
$keyStrengthBytes = $keyStrengthBits / 8;
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
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);
}
$salt = CryptoUtil::randomBytes($keyStrengthBytes / 2);
$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);
$content = self::aesCtrSegmentIntegerCounter(true, $content, $key, $iv);
$content = self::aesCtrSegmentIntegerCounter($content, $key, $iv, true);
$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);
}
}

View File

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

View File

@@ -32,14 +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($name . " (expected CRC32 value 0x" .
dechex($expected) . ", but is actually 0x" . dechex($actual) . ")");
assert($expected != $actual);
parent::__construct(
sprintf(
'%s (expected CRC32 value 0x%x, but is actually 0x%x)',
$name,
$expected,
$actual
)
);
$this->expectedCrc = $expected;
$this->actualCrc = $actual;
}

View File

@@ -4,10 +4,11 @@ namespace PhpZip\Exception;
/**
* Runtime exception.
* Exception thrown if an error which can only be found on runtime occurs.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class RuntimeException extends ZipException
class RuntimeException extends \RuntimeException
{
}

View File

@@ -0,0 +1,39 @@
<?php
namespace PhpZip\Exception;
/**
* Thrown if entry not found.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipEntryNotFoundException extends ZipException
{
/** @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
)
);
$this->entryName = $entryName;
}
/**
* @return string
*/
public function getEntryName()
{
return $this->entryName;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
<?php
namespace PhpZip\Exception;
/**
* Class ZipUnsupportMethodException.
*/
class ZipUnsupportMethodException extends RuntimeException
{
}

View File

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

View File

@@ -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,12 +140,18 @@ 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.
* The offset to retrieve.
* </p>
*
* @throws ZipException
*
* @return mixed can return all value types
*
* @since 5.0.0
*/
public function offsetGet($offset)
@@ -136,22 +160,27 @@ 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
* @throws InvalidArgumentException
* 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) {
assert($offset == $value::getHeaderId());
if ($offset !== $value::getHeaderId()) {
throw new InvalidArgumentException('Value header id !== array access key');
}
$this->add($value);
} else {
throw new InvalidArgumentException('value is not instanceof ' . ExtraField::class);
@@ -159,13 +188,17 @@ 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)
{
@@ -173,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()
@@ -184,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()
@@ -195,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()
@@ -206,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()
@@ -218,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()

View File

@@ -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 (null !== $extra) {
$extraLength = strlen($extra);
if ($extra !== null) {
$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,33 +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
*
* @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;
}
@@ -85,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;
if ($extraField::getHeaderId() !== $headerId) {
/** @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;
}
@@ -118,13 +142,14 @@ class ExtraFieldsFactory
*/
protected static function getRegistry()
{
if (null === self::$registry) {
if (self::$registry === null) {
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
self::$registry[Zip64ExtraField::getHeaderId()] = Zip64ExtraField::class;
self::$registry[ApkAlignmentExtraField::getHeaderId()] = ApkAlignmentExtraField::class;
self::$registry[JarMarkerExtraField::getHeaderId()] = JarMarkerExtraField::class;
}
return self::$registry;
}
@@ -146,6 +171,7 @@ class ExtraFieldsFactory
/**
* @param ZipEntry $entry
*
* @return Zip64ExtraField
*/
public static function createZip64Extra(ZipEntry $entry)
@@ -155,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;
}
}

View File

@@ -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,26 +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
* @throws InvalidArgumentException
*/
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;

View File

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

View File

@@ -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 (0 !== strlen($data)) {
if ($data !== '') {
throw new ZipException("JarMarker doesn't expect any data");
}
}

View File

@@ -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,12 +50,14 @@ 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 (24 === $unpack['sizeAttr']) {
if ($unpack['sizeAttr'] === 24) {
$tagData = substr($data, 4, $unpack['sizeAttr']);
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
$this->atime = PackUtil::unpackLongLE(substr($tagData, 8, 8)) / 10000000 - 11644473600;
@@ -65,12 +67,14 @@ class NtfsExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
{
$serialize = '';
if (null !== $this->mtime && null !== $this->atime && null !== $this->ctime) {
if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) {
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
$atimeLong = ($this->atime + 11644473600) * 10000000;
$ctimeLong = ($this->ctime + 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;
}
}

View File

@@ -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,6 +127,8 @@ class WinZipAesEntryExtraField implements ExtraField
}
/**
* @throws ZipException
*
* @return bool|int
*/
public function getKeyStrength()
@@ -126,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];
}
@@ -152,6 +167,8 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Internal encryption method.
*
* @throws ZipException
*
* @return int
*/
public function getEncryptionMethod()
@@ -163,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];
}
@@ -179,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;
@@ -202,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)
@@ -214,6 +237,7 @@ class WinZipAesEntryExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
*/
public function serialize()
@@ -229,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);
}
@@ -247,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

View File

@@ -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,18 +20,18 @@ 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)
{
if (null !== $entry) {
if ($entry !== null) {
$this->setEntry($entry);
}
}
@@ -56,62 +58,65 @@ class Zip64ExtraField implements ExtraField
/**
* Serializes a Data Block.
*
* @return string
* @throws RuntimeException
*/
public function serialize()
{
if (null === $this->entry) {
throw new RuntimeException("entry is null");
if ($this->entry === 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 RuntimeException
*
* @throws ZipException
*/
public function deserialize($data)
{
if (null === $this->entry) {
throw new RuntimeException("entry is null");
if ($this->entry === 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)));
}
}

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
namespace PhpZip\Model;
/**
* 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
*/

View File

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

View File

@@ -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,11 +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());
@@ -121,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());
@@ -145,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
*
* @return ZipEntry
*
* @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 0 !== ($this->init & $mask);
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;
}
/**
@@ -235,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;
}
@@ -242,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;
}
@@ -255,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;
}
/**
@@ -272,13 +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
* @throws ZipException
*/
public function setCompressedSize($compressedSize)
{
$this->compressedSize = $compressedSize;
return $this;
}
@@ -295,13 +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
* @throws ZipException
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
@@ -317,50 +402,59 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* @param int $offset
*
* @return ZipEntry
* @throws ZipException
*/
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;
}
@@ -378,34 +472,38 @@ abstract class ZipAbstractEntry implements ZipEntry
* Returns the indexed General Purpose Bit Flag.
*
* @param int $mask
*
* @return bool
*/
public function getGeneralPurposeBitFlag($mask)
{
return 0 !== ($this->general & $mask);
return ($this->generalPurposeBitFlags & $mask) !== 0;
}
/**
* Sets the encryption property to false and removes any other
* encryption artifacts.
*
* @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()) {
$this->setMethod(null === $field ? self::UNKNOWN : $field->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;
}
@@ -413,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;
}
@@ -429,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 (self::UNKNOWN === $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
*/
@@ -491,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
*
* @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
* @throws ZipException
*/
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 '/').
@@ -576,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()
{
@@ -592,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 (null !== $comment) {
$commentLength = strlen($comment);
if (0x0000 > $commentLength || $commentLength > 0xffff) {
throw new ZipException("Comment too long");
if ($comment !== null) {
$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;
}
@@ -635,11 +773,11 @@ abstract class ZipAbstractEntry implements ZipEntry
*/
public function isDataDescriptorRequired()
{
return self::UNKNOWN == ($this->getCrc() | $this->getCompressedSize() | $this->getSize());
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
*/
@@ -652,13 +790,13 @@ abstract class ZipAbstractEntry implements ZipEntry
* Set crc32 content.
*
* @param int $crc
*
* @return ZipEntry
* @throws ZipException
*/
public function setCrc($crc)
{
$this->crc = $crc;
$this->setInit(self::BIT_CRC, true);
$this->crc = (int) $crc;
return $this;
}
@@ -671,23 +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
*
* @throws ZipException
*
* @param string $password
* @param null|int $encryptionMethod
* @return ZipEntry
*/
public function setPassword($password, $encryptionMethod = null)
{
$this->password = $password;
if (null !== $encryptionMethod) {
if ($encryptionMethod !== null) {
$this->setEncryptionMethod($encryptionMethod);
}
if (!empty($this->password)) {
$this->setEncrypted(true);
} else {
$this->disableEncryption();
}
return $this;
}
@@ -700,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 (
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
$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;
}
@@ -737,23 +884,26 @@ abstract class ZipAbstractEntry implements ZipEntry
/**
* @param int $compressionLevel
*
* @return ZipEntry
* @throws InvalidArgumentException
*/
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()
{

View File

@@ -2,24 +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 InvalidArgumentException
*/
public function __construct(ZipSourceEntry $entry)
{
@@ -45,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()
{

View File

@@ -3,36 +3,29 @@
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\ZipFileInterface;
/**
* Abstract class for new zip entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
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
* @throws InvalidArgumentException
*/
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;
@@ -41,37 +34,23 @@ class ZipNewEntry extends ZipAbstractEntry
/**
* Returns an string content of the given entry.
*
* @return null|string
* @throws ZipException
* @return string|null
*/
public function getEntryContent()
{
if (is_resource($this->content)) {
return stream_get_contents($this->content, -1, 0);
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()
{
@@ -81,7 +60,7 @@ class ZipNewEntry extends ZipAbstractEntry
public function __destruct()
{
if (!$this->clone && null !== $this->content && is_resource($this->content)) {
if (!$this->clone && $this->content !== null && \is_resource($this->content)) {
fclose($this->content);
$this->content = null;
}

View File

@@ -0,0 +1,57 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipException;
/**
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipNewFileEntry extends ZipAbstractEntry
{
/** @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');
}
$file = (string) $file;
if (!is_file($file)) {
throw new ZipException("File {$file} does not exist.");
}
if (!is_readable($file)) {
throw new ZipException("The '{$file}' file could not be read. Check permissions.");
}
$this->file = $file;
}
/**
* Returns an string content of the given entry.
*
* @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);
}
}

View 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,29 +49,37 @@ class ZipSourceEntry extends ZipAbstractEntry
/**
* Returns an string content of the given entry.
*
* @return string
* @throws ZipException
*
* @return string
*/
public function getEntryContent()
{
if (null === $this->entryContent) {
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', 'rb');
$this->entryContent = fopen('php://temp', 'r+b');
fwrite($this->entryContent, $content);
}
return $content;
}
if (is_resource($this->entryContent)) {
return stream_get_contents($this->entryContent, -1, 0);
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()
{
@@ -88,7 +89,7 @@ class ZipSourceEntry extends ZipAbstractEntry
public function __destruct()
{
if (!$this->clone && null !== $this->entryContent && is_resource($this->entryContent)) {
if (!$this->clone && $this->entryContent !== null && \is_resource($this->entryContent)) {
fclose($this->entryContent);
}
}

View File

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

View File

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

View File

@@ -2,13 +2,14 @@
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,79 +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 ZipException
* @noinspection PhpMissingBreakStatementInspection
*/
public function __construct(ZipEntry $entry)
{
@@ -194,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
*/
@@ -205,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;
@@ -224,67 +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:
// no break
/** @noinspection PhpMissingBreakStatementInspection */
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;
@@ -303,89 +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
*/
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
*/
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';
}
/**
@@ -398,6 +436,7 @@ class ZipInfo
/**
* @return string
*
* @deprecated use \PhpZip\Model\ZipInfo::getName()
*/
public function getPath()
@@ -406,7 +445,7 @@ class ZipInfo
}
/**
* @return boolean
* @return bool
*/
public function isFolder()
{
@@ -462,7 +501,7 @@ class ZipInfo
}
/**
* @return boolean
* @return bool
*/
public function isEncrypted()
{
@@ -470,7 +509,7 @@ class ZipInfo
}
/**
* @return null|string
* @return string|null
*/
public function getComment()
{
@@ -487,6 +526,7 @@ class ZipInfo
/**
* @return string
*
* @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
*/
public function getMethod()
@@ -565,7 +605,7 @@ class ZipInfo
'method_name' => $this->getMethodName(),
'compression_method' => $this->getCompressionMethod(),
'platform' => $this->getPlatform(),
'version' => $this->getVersion()
'version' => $this->getVersion(),
];
}
@@ -579,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()) . ', ' : '')

View File

@@ -3,88 +3,84 @@
namespace PhpZip\Model;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipNotFoundEntry;
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;
}
/**
* @param string $comment
* @throws InvalidArgumentException
*/
public function setArchiveComment($comment)
{
if (null !== $comment && 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;
@@ -96,7 +92,9 @@ 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,13 +108,16 @@ class ZipModel implements \Countable
/**
* @param string $entryName
* @param string $password
* @throws ZipNotFoundEntry
*
* @throws ZipEntryNotFoundException
* @throws ZipException
*/
public function setReadPasswordEntry($entryName, $password)
{
if (!isset($this->inputEntries[$entryName])) {
throw new ZipNotFoundEntry('Not found entry ' . $entryName);
throw new ZipEntryNotFoundException($entryName);
}
if ($this->inputEntries[$entryName]->isEncrypted()) {
$this->inputEntries[$entryName]->setPassword($password);
}
@@ -135,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;
}
/**
@@ -143,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)
{
@@ -155,7 +156,7 @@ class ZipModel implements \Countable
}
/**
* Remove password
* Remove password.
*/
public function removePassword()
{
@@ -181,16 +182,16 @@ class ZipModel implements \Countable
/**
* @param string|ZipEntry $old
* @param string|ZipEntry $new
* @throws InvalidArgumentException
* @throws ZipNotFoundEntry
*
* @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,43 +202,57 @@ class ZipModel implements \Countable
/**
* @param string|ZipEntry $entry
*
* @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
*
* @throws ZipEntryNotFoundException
*
* @return ZipEntry
* @throws ZipNotFoundEntry
*/
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 ZipNotFoundEntry('Zip entry "' . $entryName . '" not found');
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;
}
@@ -261,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]);
}
@@ -278,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()
{
@@ -301,7 +321,7 @@ class ZipModel implements \Countable
}
/**
* Undo change archive comment
* Undo change archive comment.
*/
public function unchangeArchiveComment()
{
@@ -313,23 +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
* @throws ZipException
*/
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);
}

View File

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

View File

@@ -7,67 +7,51 @@ use PhpZip\Crypto\WinZipAesEngine;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipCryptoException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipUnsupportMethod;
use PhpZip\Exception\ZipUnsupportMethodException;
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
* @throws RuntimeException
*/
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();
}
/**
* @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,146 +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 (0 < $data['commentLength']) {
$comment = fread($this->in, $data['commentLength']);
}
$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 (0 !== $offset) {
$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
);
}
/**
@@ -241,131 +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 (0 !== $numEntries % 0x10000) {
throw new ZipException("Expected " . abs($numEntries) .
($numEntries > 0 ? " more" : " less") .
" entries in the Central Directory!");
}
if ($this->preamble + $this->postamble >= fstat($this->in)['size']) {
assert(0 === $numEntries);
$this->checkZipFileSignature();
}
fclose($cdStream);
return $entries;
}
/**
* 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
* @throws InvalidArgumentException
*/
public function readEntry()
public function readCentralDirectoryEntry($stream)
{
// central file header signature 4 bytes (0x02014b50)
$fileHeaderSig = unpack('V', fread($this->in, 4))[1];
if (ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG !== $fileHeaderSig) {
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 = 0 !== ($data['gpbf'] & self::GPBF_UTF8);
$createdOS = ($data['versionMadeBy'] & 0xFF00) >> 8;
$softwareVersion = $data['versionMadeBy'] & 0x00FF;
// See appendix D of PKWARE's ZIP File Format Specification.
$name = fread($this->in, $data['fileLength']);
$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 (0 < $data['extraLength']) {
$entry->setExtra(fread($this->in, $data['extraLength']));
}
if (0 < $data['commentLength']) {
$entry->setComment(fread($this->in, $data['commentLength']));
}
$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 && null === $entry->getPassword()) {
throw new ZipException("Can not password from entry " . $entry->getName());
if ($isEncrypted && $entry->getPassword() === null) {
throw new ZipException('Can not password from entry ' . $entry->getName());
}
$pos = $entry->getOffset();
assert(ZipEntry::UNKNOWN !== $pos);
$pos = PHP_INT_SIZE === 4 ? sprintf('%u', $pos) : $pos;
$startPos = $pos = $entry->getOffset();
$startPos = $pos = $this->mapper->map($pos);
fseek($this->in, $startPos);
// local file header signature 4 bytes (0x04034b50)
if (ZipEntry::LOCAL_FILE_HEADER_SIG !== unpack('V', fread($this->in, 4))[1]) {
throw new ZipException($entry->getName() . " (expected Local File Header)");
if (unpack('V', fread($this->in, 4))[1] !== ZipEntry::LOCAL_FILE_HEADER_SIG) {
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
@@ -373,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();
@@ -381,16 +442,22 @@ class ZipInputStream implements ZipInputStreamInterface
// Get raw entry content
$compressedSize = $entry->getCompressedSize();
$compressedSize = PHP_INT_SIZE === 4 ? sprintf('%u', $compressedSize) : $compressedSize;
$content = '';
if ($compressedSize > 0) {
$content = fread($this->in, $compressedSize);
} else {
$content = '';
$offset = 0;
while ($offset < $compressedSize) {
$read = min(8192 /* chunk size */, $compressedSize - $offset);
$content .= fread($this->in, $read);
$offset += $read;
}
}
$skipCheckCrc = false;
if ($isEncrypted) {
if (ZipEntry::METHOD_WINZIP_AES === $method) {
if ($method === ZipEntry::METHOD_WINZIP_AES) {
// Strong Encryption Specification - WinZip AES
$winZipAesEngine = new WinZipAesEngine($entry);
$content = $winZipAesEngine->decrypt($content);
@@ -405,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:
@@ -418,51 +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 (ZipEntry::DATA_DESCRIPTOR_SIG === $localCrc) {
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:
$content = gzinflate($content);
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
$content = false;
}
break;
default:
throw new ZipUnsupportMethod($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 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 ZipCryptoException("Wrong password");
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;
}
@@ -478,29 +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);
$extra = fread($this->in, $sourceExtraLength);
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();
@@ -509,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;
@@ -541,24 +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
@@ -570,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());
}
@@ -595,7 +710,7 @@ class ZipInputStream implements ZipInputStreamInterface
public function close()
{
if ($this->in != null) {
if ($this->in !== null) {
fclose($this->in);
$this->in = null;
}

View File

@@ -2,11 +2,12 @@
namespace PhpZip\Stream;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipModel;
/**
* Read zip file
* Read zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
@@ -19,12 +20,21 @@ 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
*
* @throws ZipException
*
* @return string
*/
public function readEntryContent(ZipEntry $entry);
@@ -38,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);

View File

@@ -19,50 +19,51 @@ use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipModel;
use PhpZip\Util\PackUtil;
use PhpZip\Util\StringUtil;
use PhpZip\ZipFileInterface;
use PhpZip\ZipFile;
/**
* Write
* ip 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
* @throws InvalidArgumentException
*/
public function __construct($out, ZipModel $zipModel)
{
if (!is_resource($out)) {
if (!\is_resource($out)) {
throw new InvalidArgumentException('$out must be resource');
}
$this->out = $out;
$this->zipModel = $zipModel;
}
/**
* @throws ZipException
*/
public function writeZip()
{
$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);
}
@@ -71,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;
}
@@ -87,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;
}
@@ -119,15 +123,16 @@ class ZipOutputStream implements ZipOutputStreamInterface
$extraFieldsCollection->add($alignExtra);
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
$extraLength = strlen($extra);
$extraLength = \strlen($extra);
}
$size = $nameLength + $extraLength;
if (0xffff < $size) {
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)'
);
}
@@ -139,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
@@ -159,21 +164,33 @@ class ZipOutputStream implements ZipOutputStreamInterface
$extraLength
)
);
if ($nameLength > 0) {
fwrite($this->out, $entry->getName());
}
if ($extraLength > 0) {
fwrite($this->out, $extra);
}
if ($entry instanceof ZipChangesEntry && !$entry->isChangedContent()) {
$entry->getSourceEntry()->getInputStream()->copyEntryData($entry->getSourceEntry(), $this);
} elseif (null !== $entryContent) {
} elseif ($entryContent !== null) {
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
@@ -186,26 +203,37 @@ class ZipOutputStream implements ZipOutputStreamInterface
} else {
fwrite($this->out, pack('VV', $entry->getCompressedSize(), $entry->getSize()));
}
} elseif ($entry->getCompressedSize() != $compressedSize) {
} 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 (ZipEntry::UNKNOWN === $entry->getPlatform()) {
$entry->setPlatform(ZipEntry::PLATFORM_UNIX);
if ($entry->getCreatedOS() === ZipEntry::UNKNOWN) {
$entry->setCreatedOS(ZipEntry::PLATFORM_UNIX);
}
if (ZipEntry::UNKNOWN === $entry->getTime()) {
if ($entry->getSoftwareVersion() === ZipEntry::UNKNOWN) {
$entry->setSoftwareVersion(63);
}
if ($entry->getExtractedOS() === ZipEntry::UNKNOWN) {
$entry->setExtractedOS(ZipEntry::PLATFORM_UNIX);
}
if ($entry->getTime() === ZipEntry::UNKNOWN) {
$entry->setTime(time());
}
$method = $entry->getMethod();
@@ -214,8 +242,8 @@ class ZipOutputStream implements ZipOutputStreamInterface
// See appendix D of PKWARE's ZIP File Format Specification.
$utf8 = true;
if ($encrypted && null === $entry->getPassword()) {
throw new ZipException("Can not password from entry " . $entry->getName());
if ($encrypted && $entry->getPassword() === null) {
throw new ZipException(sprintf('Password not set for entry %s', $entry->getName()));
}
// Compose General Purpose Bit Flag.
@@ -225,37 +253,41 @@ 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 && ZipEntry::METHOD_WINZIP_AES === $method) {
if ($encrypted && $method === ZipEntry::METHOD_WINZIP_AES) {
/**
* @var WinZipAesEntryExtraField $field
*/
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
if (null !== $field) {
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;
@@ -266,22 +298,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 (ZipFileInterface::METHOD_DEFLATED === $method) {
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;
@@ -292,17 +324,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 (20 <= $size && ZipFileInterface::METHOD_BZIP2 !== $method) {
if ($size >= 20 && $method !== ZipFile::METHOD_BZIP2) {
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
} else {
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
@@ -313,13 +352,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);
}
}
@@ -332,25 +371,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 (null !== $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;
}
@@ -358,8 +403,6 @@ class ZipOutputStream implements ZipOutputStreamInterface
* Writes a Central File Header record.
*
* @param OutputOffsetEntry $outEntry
* @throws RuntimeException
* @internal param OutPosEntry $entry
*/
protected function writeCentralDirectoryHeader(OutputOffsetEntry $outEntry)
{
@@ -368,13 +411,13 @@ class ZipOutputStream implements ZipOutputStreamInterface
$size = $entry->getSize();
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
// UNKNOWN!
if (ZipEntry::UNKNOWN === ($compressedSize | $size)) {
throw new RuntimeException("invalid entry");
if (($compressedSize | $size) === ZipEntry::UNKNOWN) {
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 +425,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 +441,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 +449,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 +458,116 @@ class ZipOutputStream implements ZipOutputStreamInterface
);
// file name (variable size)
fwrite($this->out, $entry->getName());
if (0 < $extraSize) {
if ($extraSize > 0) {
// extra field (variable size)
fwrite($this->out, $extra);
}
if (0 < $commentLength) {
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 +575,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);

View File

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

View File

@@ -2,32 +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
* @throws RuntimeException
*
* @deprecated Use random_bytes()
*/
final public static function randomBytes($length)
{
$length = (int)$length;
if (function_exists('random_bytes')) {
return random_bytes($length);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($length);
} elseif (function_exists('mcrypt_create_iv')) {
return mcrypt_create_iv($length);
} else {
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
return random_bytes($length);
}
}

View File

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

View File

@@ -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,41 +209,70 @@ 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)
{
return implode(
'/',
array_filter(
explode('/', (string) $path),
static function ($part) {
return $part !== '.' && $part !== '..';
}
)
);
}
}

View File

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

View File

@@ -12,9 +12,8 @@ use PhpZip\Util\StringUtil;
*/
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
{
/**
* Ignore list files
* Ignore list files.
*
* @var array
*/
@@ -22,7 +21,7 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
/**
* @param \RecursiveIterator $iterator
* @param array $ignoreFiles
* @param array $ignoreFiles
*/
public function __construct(\RecursiveIterator $iterator, array $ignoreFiles)
{
@@ -31,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()
@@ -43,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;
@@ -57,11 +60,13 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
return false;
}
}
return true;
}
/**
* @return IgnoreFilesRecursiveFilterIterator
* @noinspection PhpMissingParentCallCommonInspection
*/
public function getChildren()
{

View File

@@ -2,25 +2,25 @@
namespace PhpZip\Util;
use PhpZip\Exception\ZipException;
/**
* 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;
@@ -34,32 +34,36 @@ class PackUtil
/**
* @param string|int $value
*
* @return int
* @throws ZipException
*/
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;
}
}

View File

@@ -3,29 +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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,8 @@
namespace PhpZip;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipNotFoundEntry;
use PhpZip\Exception\ZipUnsupportMethod;
use PhpZip\Model\ZipEntry;
use PhpZip\Model\ZipEntryMatcher;
use PhpZip\Model\ZipInfo;
@@ -20,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
*/
@@ -27,68 +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 InvalidArgumentException if file doesn't exists.
* @throws ZipException if can't open file.
*/
public function openFile($filename);
@@ -96,40 +93,40 @@ 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 InvalidArgumentException if data not available.
* @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
* @throws InvalidArgumentException Invalid stream resource
* or resource cannot seekable stream
*/
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
* @throws InvalidArgumentException Length comment out of range
*/
public function setArchiveComment($comment = null);
@@ -139,8 +136,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* (i.e. end with '/').
*
* @param string $entryName
*
* @throws ZipEntryNotFoundException
*
* @return bool
* @throws ZipNotFoundEntry
*/
public function isDirectory($entryName);
@@ -148,18 +147,22 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Returns entry comment.
*
* @param string $entryName
*
* @throws ZipEntryNotFoundException
*
* @return string
* @throws ZipNotFoundEntry
*/
public function getEntryComment($entryName);
/**
* Set entry comment.
*
* @param string $entryName
* @param string $entryName
* @param string|null $comment
*
* @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
* @throws ZipNotFoundEntry
*/
public function setEntryComment($entryName, $comment = null);
@@ -167,6 +170,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Returns the entry contents.
*
* @param string $entryName
*
* @return string
*/
public function getEntryContents($entryName);
@@ -175,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);
@@ -183,8 +188,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Get info by entry.
*
* @param string|ZipEntry $entryName
*
* @throws ZipEntryNotFoundException
*
* @return ZipInfo
* @throws ZipNotFoundEntry
*/
public function getEntryInfo($entryName);
@@ -201,66 +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
* @throws InvalidArgumentException If incorrect data or entry name.
* @throws ZipUnsupportMethod
* @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
* @throws InvalidArgumentException
* @throws ZipUnsupportMethod
* @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
* @throws InvalidArgumentException
* @throws ZipUnsupportMethod
* @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);
@@ -268,69 +277,69 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Add an empty directory in the zip archive.
*
* @param string $dirName
*
* @return ZipFileInterface
* @throws InvalidArgumentException
*/
public function addEmptyDir($dirName);
/**
* 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
* @throws InvalidArgumentException
*/
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
* @throws InvalidArgumentException
* @throws ZipUnsupportMethod
* @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
* @throws InvalidArgumentException
* @throws ZipUnsupportMethod
* @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
* @throws InvalidArgumentException
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null);
@@ -338,14 +347,14 @@ 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
* @throws InvalidArgumentException
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null);
@@ -353,57 +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.
* @param string $oldName old entry name
* @param string $newName new entry name
*
* @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
* @throws InvalidArgumentException
* @throws ZipNotFoundEntry
*/
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 ZipNotFoundEntry If entry not found.
*/
public function deleteFromName($entryName);
@@ -411,8 +427,8 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Delete entries by glob pattern.
*
* @param string $globPattern Glob pattern
*
* @return ZipFileInterface
* @throws InvalidArgumentException
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/
public function deleteFromGlob($globPattern);
@@ -421,13 +437,14 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Delete entries by regex pattern.
*
* @param string $regexPattern Regex pattern
*
* @return ZipFileInterface
* @throws InvalidArgumentException
*/
public function deleteFromRegex($regexPattern);
/**
* Delete all entries
* Delete all entries.
*
* @return ZipFileInterface
*/
public function deleteAll();
@@ -436,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);
@@ -471,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);
@@ -480,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);
@@ -489,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);
@@ -497,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);
@@ -505,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);
@@ -524,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();
@@ -567,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);
@@ -575,9 +618,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
* Save as file.
*
* @param string $filename Output filename
* @return ZipFileInterface
* @throws InvalidArgumentException
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function saveAsFile($filename);
@@ -585,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);
@@ -594,35 +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
* @throws InvalidArgumentException
*/
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
* @throws InvalidArgumentException
*/
public function outputAsString();
/**
* Save and reopen zip archive.
* @return ZipFileInterface
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function rewrite();

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
<?php
namespace PhpZip;
use PhpZip\Exception\ZipException;
/**
* @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', Internal\DummyFileSystemStream::class);
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testDummyFS()
{
$fileContents = str_repeat(base64_encode(random_bytes(12000)), 100);
// create zip file
$zip = new ZipFile();
$zip->addFromString(
'file.txt',
$fileContents,
ZipFile::METHOD_DEFLATED
);
$zip->saveAsFile($this->outputFilename);
$zip->close();
static::assertCorrectZipArchive($this->outputFilename);
$stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb');
static::assertNotFalse($stream);
$zip->openFromStream($stream);
static::assertSame($zip->getListFiles(), ['file.txt']);
static::assertSame($zip['file.txt'], $fileContents);
$zip->close();
}
}

View File

@@ -2,16 +2,26 @@
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()
{
@@ -19,18 +29,22 @@ class PhpZipExtResourceTest extends ZipTestCase
$zipFile = new ZipFile();
$zipFile->openFile($filename);
foreach ($zipFile as $name => $contents) {
$info = $zipFile->getEntryInfo($name);
self::assertEquals(strlen($contents), $info->getSize());
static::assertSame(\strlen($contents), $info->getSize());
}
$zipFile->close();
self::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()
{
@@ -42,35 +56,42 @@ class PhpZipExtResourceTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertCount(2, $zipFile);
self::assertTrue(isset($zipFile['1.txt']));
self::assertTrue(isset($zipFile['2.txt']));
self::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)
{
self::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();
self::assertTrue(is_dir($this->outputDirname . '/test/empty'));
static::assertTrue(is_dir($this->outputDirname . '/test/empty'));
}
/**
* @return array
*/
public function provideBug40228()
{
return [
@@ -79,13 +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();
@@ -94,49 +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 Bad password for 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';
self::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);
self::markTestIncomplete('failed test');
} catch (ZipAuthenticationException $exception) {
self::assertFalse(file_exists($this->outputDirname . '/bug70752.txt'));
static::markTestIncomplete('failed test');
} catch (ZipException $exception) {
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);
self::assertTrue($info->getSize() > 0);
$contents = $zipFile[$entryName];
self::assertEquals(strlen($contents), $info->getSize());
$zipFile->close();
}
}

View File

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

View File

@@ -2,21 +2,29 @@
namespace PhpZip;
use PhpZip\Util\CryptoUtil;
use PhpZip\Exception\ZipException;
/**
* Test ZipAlign
* Test ZipAlign.
*
* @internal
*
* @small
*/
class ZipAlignTest extends ZipTestCase
{
/**
* @throws ZipException
*/
public function testApkAlignedAndSetZipAlignAndReSave()
{
$filename = __DIR__ . '/resources/test.apk';
$filename = __DIR__ . '/resources/apk.zip';
self::assertCorrectZipArchive($filename);
$result = self::doZipAlignVerify($filename);
if (null !== $result) {
self::assertTrue($result);
static::assertCorrectZipArchive($filename);
$result = static::assertVerifyZipAlign($filename);
if ($result !== null) {
static::assertTrue($result);
}
$zipFile = new ZipFile();
@@ -25,15 +33,19 @@ class ZipAlignTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$result = self::doZipAlignVerify($this->outputFilename, true);
if (null !== $result) {
self::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()
{
@@ -41,107 +53,118 @@ 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();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
$result = self::doZipAlignVerify($this->outputFilename);
if ($result === null) {
return;
} // zip align not installed
// check not zip align
self::assertFalse($result);
static::assertFalse($result);
$zipFile->openFile($this->outputFilename);
$zipFile->setZipAlign(4);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = self::doZipAlignVerify($this->outputFilename, true);
self::assertNotNull($result);
$result = static::assertVerifyZipAlign($this->outputFilename, true);
static::assertNotNull($result);
// check zip align
self::assertTrue($result);
static::assertTrue($result);
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testZipAlignNewFiles()
{
$zipFile = new ZipFile();
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();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
$result = self::doZipAlignVerify($this->outputFilename);
if ($result === null) {
return;
} // zip align not installed
// check not zip align
self::assertTrue($result);
static::assertTrue($result);
}
/**
* @throws ZipException
* @throws \Exception
*/
public function testZipAlignFromModifiedZipArchive()
{
$zipFile = new ZipFile();
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();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
$result = self::doZipAlignVerify($this->outputFilename);
if ($result === null) {
return;
} // zip align not installed
// check not zip align
self::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();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$result = self::doZipAlignVerify($this->outputFilename, true);
self::assertNotNull($result);
$result = static::assertVerifyZipAlign($this->outputFilename, true);
static::assertNotNull($result);
// check zip align
self::assertTrue($result);
static::assertTrue($result);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace PhpZip;
use PhpZip\Exception\ZipException;
/**
* @internal
*
* @small
*/
class ZipEventTest extends ZipTestCase
{
/**
* @throws ZipException
*/
public function testBeforeSave()
{
$zipFile = new Internal\ZipFileExtended();
$zipFile->openFile(__DIR__ . '/resources/apk.zip');
static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
static::assertTrue(isset($zipFile['META-INF/CERT.SF']));
static::assertTrue(isset($zipFile['META-INF/CERT.RSA']));
$zipFile->saveAsFile($this->outputFilename);
static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
$zipFile->close();
static::assertCorrectZipArchive($this->outputFilename);
$result = static::assertVerifyZipAlign($this->outputFilename);
if ($result !== null) {
static::assertTrue($result);
}
$zipFile->openFile($this->outputFilename);
static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
$zipFile->close();
}
}

View File

@@ -2,11 +2,16 @@
namespace PhpZip;
use PhpZip\Exception\ZipException;
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
/**
* Test add directory to zip archive.
*
* @internal
*
* @small
*/
class ZipFileAddDirTest extends ZipTestCase
{
@@ -27,7 +32,7 @@ class ZipFileAddDirTest extends ZipTestCase
];
/**
* Before test
* Before test.
*/
protected function setUp()
{
@@ -39,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);
}
@@ -53,25 +60,38 @@ class ZipFileAddDirTest extends ZipTestCase
}
}
protected static function assertFilesResult(ZipFileInterface $zipFile, array $actualResultFiles = [], $localPath = '/')
{
/**
* @param ZipFile $zipFile
* @param array $actualResultFiles
* @param string $localPath
*/
protected static function assertFilesResult(
ZipFile $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);
}
/**
* @throws ZipException
*/
public function testAddDirWithLocalPath()
{
$localPath = 'to/path';
@@ -81,18 +101,25 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
],
$localPath
);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddDirWithoutLocalPath()
{
$zipFile = new ZipFile();
@@ -100,18 +127,24 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddFilesFromIterator()
{
$localPath = 'to/project';
@@ -123,18 +156,25 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
],
$localPath
);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddFilesFromIteratorEmptyLocalPath()
{
$localPath = '';
@@ -146,18 +186,24 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddFilesFromRecursiveIterator()
{
$localPath = 'to/project';
@@ -169,13 +215,16 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddRecursiveDirWithLocalPath()
{
$localPath = 'to/path';
@@ -185,13 +234,16 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
static::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddRecursiveDirWithoutLocalPath()
{
$zipFile = new ZipFile();
@@ -199,19 +251,22 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files));
static::assertFilesResult($zipFile, array_keys(self::$files));
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddFilesFromIteratorWithIgnoreFiles()
{
$localPath = 'to/project';
$ignoreFiles = [
'Текстовый документ.txt',
'empty dir/'
'empty dir/',
];
$directoryIterator = new \DirectoryIterator($this->outputDirname);
@@ -222,16 +277,23 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
], $localPath);
static::assertFilesResult(
$zipFile,
[
'.hidden',
'text file.txt',
],
$localPath
);
$zipFile->close();
}
/**
* @throws ZipException
*/
public function testAddFilesFromRecursiveIteratorWithIgnoreFiles()
{
$localPath = 'to/project';
@@ -250,24 +312,30 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::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()
{
@@ -278,18 +346,24 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::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()
{
@@ -300,23 +374,29 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::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()
{
@@ -327,18 +407,24 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::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()
{
@@ -349,21 +435,28 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::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();
}
/**
* @throws ZipException
*/
public function testArrayAccessAddDir()
{
$localPath = 'path/to';
@@ -374,10 +467,10 @@ class ZipFileAddDirTest extends ZipTestCase
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::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

View File

@@ -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();
self::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) {
self::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) {
self::assertTrue(isset($zipFile[$name]));
static::assertTrue(isset($zipFile[$name]));
}
$matcher = $zipFile->matcher();
$matcher->match('~^file_(1|5)\d+~');
self::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) {
self::assertFalse(isset($zipFile[$name]));
static::assertFalse(isset($zipFile[$name]));
$pathInfo = pathinfo($name);
$newName = $pathInfo['filename'].'.no_optimize.'.$pathInfo['extension'];
self::assertTrue(isset($zipFile[$newName]));
$newName = $pathInfo['filename'] . '.no_optimize.' . $pathInfo['extension'];
static::assertTrue(isset($zipFile[$newName]));
}
$zipFile->close();

View File

@@ -2,86 +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();
self::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];
self::fail("Expected Exception has not been raised.");
static::fail('Expected Exception has not been raised.');
} catch (ZipAuthenticationException $ae) {
self::assertNotNull($ae);
static::assertContains('Invalid password for zip entry', $ae->getMessage());
}
}
// check correct password for ZipCrypto
$zipFile->setReadPassword($password);
foreach ($zipFile->getAllInfo() as $info) {
self::assertTrue($info->isEncrypted());
self::assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
$decryptContent = $zipFile[$info->getName()];
self::assertNotEmpty($decryptContent);
self::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();
self::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];
self::fail("Expected Exception has not been raised.");
static::fail('Expected Exception has not been raised.');
} catch (ZipAuthenticationException $ae) {
self::assertNotNull($ae);
static::assertNotNull($ae);
}
}
// set correct password WinZip AES
$zipFile->setReadPassword($password);
foreach ($zipFile->getAllInfo() as $info) {
self::assertTrue($info->isEncrypted());
self::assertContains('WinZip', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip', $info->getMethodName());
$decryptContent = $zipFile[$info->getName()];
self::assertNotEmpty($decryptContent);
self::assertContains('<?php', $decryptContent);
static::assertNotEmpty($decryptContent);
static::assertContains('<?php', $decryptContent);
}
// clear password
@@ -91,39 +108,48 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
static::assertCorrectZipArchive($this->outputFilename);
// check remove password
$zipFile->openFile($this->outputFilename);
foreach ($zipFile->getAllInfo() as $info) {
self::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();
self::assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
$zip->openFile($this->outputFilename);
$zip->setReadPassword($password);
self::assertFilesResult($zip, array_keys(self::$files));
static::assertFilesResult($zip, array_keys(self::$files));
foreach ($zip->getAllInfo() as $info) {
if (!$info->isFolder()) {
self::assertTrue($info->isEncrypted());
self::assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
}
}
$zip->close();
@@ -131,12 +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);
@@ -144,16 +174,17 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zip->saveAsFile($this->outputFilename);
$zip->close();
self::assertCorrectZipArchive($this->outputFilename, $password);
static::assertCorrectZipArchive($this->outputFilename, $password);
$zip->openFile($this->outputFilename);
$zip->setReadPassword($password);
self::assertFilesResult($zip, array_keys(self::$files));
static::assertFilesResult($zip, array_keys(self::$files));
foreach ($zip->getAllInfo() as $info) {
if (!$info->isFolder()) {
self::assertTrue($info->isEncrypted());
self::assertEquals($info->getEncryptionMethod(), $encryptionMethod);
self::assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertSame($info->getEncryptionMethod(), $encryptionMethod);
static::assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
}
}
$zip->close();
@@ -165,17 +196,24 @@ 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],
];
}
/**
* @throws Exception\ZipEntryNotFoundException
* @throws ZipException
*/
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';
@@ -183,39 +221,49 @@ 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);
self::assertFilesResult($zip, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zip,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$info = $zip->getEntryInfo('.hidden');
self::assertTrue($info->isEncrypted());
self::assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
$info = $zip->getEntryInfo('text file.txt');
self::assertTrue($info->isEncrypted());
self::assertContains('WinZip AES', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip AES', $info->getMethodName());
self::assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
self::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
static::assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
$zip->close();
}
/**
* @throws Exception\ZipEntryNotFoundException
* @throws ZipException
*/
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';
@@ -225,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();
@@ -234,36 +282,40 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zip->setReadPassword($defaultPassword);
$zip->setReadPasswordEntry('.hidden', $password1);
$zip->setReadPasswordEntry('text file.txt', $password2);
self::assertFilesResult($zip, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
static::assertFilesResult(
$zip,
[
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]
);
$info = $zip->getEntryInfo('.hidden');
self::assertTrue($info->isEncrypted());
self::assertContains('ZipCrypto', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('ZipCrypto', $info->getMethodName());
$info = $zip->getEntryInfo('text file.txt');
self::assertTrue($info->isEncrypted());
self::assertContains('WinZip AES', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip AES', $info->getMethodName());
$info = $zip->getEntryInfo('Текстовый документ.txt');
self::assertTrue($info->isEncrypted());
self::assertContains('WinZip AES', $info->getMethodName());
static::assertTrue($info->isEncrypted());
static::assertContains('WinZip AES', $info->getMethodName());
self::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);
@@ -271,43 +323,56 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile->outputAsString();
}
/**
* @throws Exception\ZipEntryNotFoundException
* @throws ZipException
*/
public function testEntryPassword()
{
$zipFile = new ZipFile();
$zipFile->setPassword('pass');
$zipFile['file'] = 'content';
self::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');
self::assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
static::assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
} else {
self::assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
static::assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
}
}
$zipFile->disableEncryptionEntry('file3');
self::assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
self::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) {
self::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);
}
/**
* @throws ZipEntryNotFoundException
* @throws ZipException
*/
public function testArchivePasswordUpdateWithoutSetReadPassword()
{
$zipFile = new ZipFile();
@@ -318,42 +383,46 @@ class ZipPasswordTest extends ZipFileAddDirTest
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename, 'password');
static::assertCorrectZipArchive($this->outputFilename, 'password');
$zipFile->openFile($this->outputFilename);
self::assertCount(3, $zipFile);
static::assertCount(3, $zipFile);
foreach ($zipFile->getAllInfo() as $info) {
self::assertTrue($info->isEncrypted());
static::assertTrue($info->isEncrypted());
}
unset($zipFile['file3']);
$zipFile['file4'] = 'content';
$zipFile->rewrite();
self::assertCorrectZipArchive($this->outputFilename, 'password');
static::assertCorrectZipArchive($this->outputFilename, 'password');
self::assertCount(3, $zipFile);
self::assertFalse(isset($zipFile['file3']));
self::assertTrue(isset($zipFile['file4']));
self::assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
self::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
self::assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
self::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']);
self::assertTrue(file_exists($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'));
self::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();
@@ -361,16 +430,21 @@ 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();
}
/**
* @throws ZipEntryNotFoundException
* @throws ZipException
*/
public function testReadAesEncryptedAndRewriteArchive()
{
$file = __DIR__ . '/resources/aes_password_archive.zip';
@@ -385,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();

View File

@@ -0,0 +1,62 @@
<?php
namespace PhpZip;
use PhpZip\Exception\ZipException;
/**
* Test add remote files to zip archive.
*
* @internal
*
* @small
*/
class ZipRemoteFileTest extends ZipTestCase
{
/**
* @throws ZipException
*/
public function testAddRemoteFileFromStream()
{
$zipFile = new ZipFile();
$outputZip = $this->outputFilename;
$fileUrl = 'https://raw.githubusercontent.com/Ne-Lexa/php-zip/master/README.md';
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$fp = @fopen(
$fileUrl,
'rb',
false,
stream_context_create(
[
'http' => [
'timeout' => 3,
],
]
)
);
if ($fp === false) {
static::markTestSkipped(
sprintf(
'Could not fetch remote file: %s',
$fileUrl
)
);
return;
}
$fileName = 'remote-file-from-http-stream.md';
$zipFile->addFromStream($fp, $fileName);
$zipFile->saveAsFile($outputZip);
$zipFile->close();
$zipFile = new ZipFile();
$zipFile->openFile($outputZip);
$files = $zipFile->getListFiles();
static::assertCount(1, $files);
static::assertSame($fileName, $files[0]);
$zipFile->close();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace PhpZip;
/**
* Class ZipSlipVulnerabilityTest.
*
* @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
{
/**
* @throws Exception\ZipException
*/
public function testCreateSlipVulnerabilityFile()
{
$localFile = '../dir/./../../file.txt';
$zipFile = new ZipFile();
$zipFile->addFromString($localFile, 'contents');
static::assertContains($localFile, $zipFile->getListFiles());
$zipFile->close();
}
/**
* @throws Exception\ZipException
*/
public function testUnpack()
{
static::assertTrue(mkdir($this->outputDirname, 0755, true));
$zipFile = new ZipFile();
$zipFile->addFromString('../dir/./../../file.txt', 'contents');
$zipFile->extractTo($this->outputDirname);
$zipFile->close();
$expectedExtractedFile = $this->outputDirname . '/dir/file.txt';
static::assertTrue(is_file($expectedExtractedFile));
}
}

View File

@@ -2,44 +2,40 @@
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');
$id = uniqid('phpzip', true);
$tempDir = sys_get_temp_dir() . '/phpunit-phpzip';
if (!is_dir($tempDir)) {
if (!mkdir($tempDir, 0755, true)) {
throw new \RuntimeException("Dir " . $tempDir . " can't created");
}
if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) {
throw new \RuntimeException('Dir ' . $tempDir . " can't created");
}
$this->outputFilename = $tempDir . '/' . $id . '.zip';
$this->outputDirname = $tempDir . '/' . $id;
}
/**
* After test
* After test.
*/
protected function tearDown()
{
@@ -48,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);
}
@@ -56,48 +53,71 @@ 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 (DIRECTORY_SEPARATOR !== '\\' && `which unzip`) {
$command = "unzip";
if (self::existsProgram('unzip')) {
$command = 'unzip';
if ($password !== null) {
$command .= " -P " . escapeshellarg($password);
$command .= ' -P ' . escapeshellarg($password);
}
$command .= " -t " . escapeshellarg($filename);
$command .= ' -t ' . escapeshellarg($filename);
$command .= ' 2>&1';
exec($command, $output, $returnCode);
$output = implode(PHP_EOL, $output);
$output = implode(\PHP_EOL, $output);
if ($password !== null && $returnCode === 81) {
if (`which 7z`) {
// WinZip 99-character limit
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
if (self::existsProgram('7z')) {
/**
* WinZip 99-character limit.
*
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
*/
$password = substr($password, 0, 99);
$command = "7z t -p" . escapeshellarg($password) . " " . escapeshellarg($filename);
$command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename);
exec($command, $output, $returnCode);
/**
* @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 !== '\\') {
exec('which ' . escapeshellarg($program), $output, $returnCode);
return $returnCode === 0;
}
// false for Windows
return false;
}
/**
* Assert correct empty zip archive.
*
@@ -105,33 +125,37 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
*/
public static function assertCorrectEmptyZip($filename)
{
if (DIRECTORY_SEPARATOR !== '\\' && `which zipinfo`) {
exec("zipinfo " . escapeshellarg($filename), $output, $returnCode);
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::assertEquals(file_get_contents($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 doZipAlignVerify($filename, $showErrors = false)
public static function assertVerifyZipAlign($filename, $showErrors = false)
{
if (DIRECTORY_SEPARATOR !== '\\' && `which zipalign`) {
exec("zipalign -c -v 4 " . escapeshellarg($filename), $output, $returnCode);
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;
} else {
fwrite(STDERR, 'Can not find program "zipalign" for test' . PHP_EOL);
return null;
}
fwrite(\STDERR, "Cannot find the program 'zipalign' for the test" . \PHP_EOL);
return null;
}
}

Binary file not shown.

Binary file not shown.