mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-08-15 11:44:56 +02:00
Compare commits
134 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ca068fa78f | ||
|
a84d2f9eff | ||
|
97db60a52d | ||
|
4134ca8daa | ||
|
e7528b2974 | ||
|
19e17fb730 | ||
|
d8f913ac67 | ||
|
1d1c8559cd | ||
|
e1108f9a24 | ||
|
96d269b4ca | ||
|
53da7053ba | ||
|
e8745e0379 | ||
|
b3277fcc5c | ||
|
4aa9711e00 | ||
|
c9871c9f80 | ||
|
650fab4bad | ||
|
fd9750c4f3 | ||
|
e903642893 | ||
|
516d0c1e77 | ||
|
9934a860c1 | ||
|
c9f597308e | ||
|
837454ba7e | ||
|
680a9d92c1 | ||
|
04a92e7904 | ||
|
e4e3a7504e | ||
|
d8bb1be43b | ||
|
c163f0583e | ||
|
116a617744 | ||
|
e207086a75 | ||
|
9bb20cc15e | ||
|
7e84f97473 | ||
|
a13f4cc32f | ||
|
c863c18869 | ||
|
ad3bac6f96 | ||
|
d2e94ac9cd | ||
|
3f0c6a7bd8 | ||
|
062762ed09 | ||
|
e1866215a6 | ||
|
f9e6a73587 | ||
|
59773d62a8 | ||
|
9417d7dc95 | ||
|
45905eacf0 | ||
|
aa8846b944 | ||
|
6058c289a4 | ||
|
251ce11bdc | ||
|
f969e59319 | ||
|
6808e4ffdc | ||
|
91f08b9f55 | ||
|
8de3a70571 | ||
|
bdd5423f67 | ||
|
d0cf7f7d1d | ||
|
9f0d151f5e | ||
|
e58cf0f337 | ||
|
171d4a8e4c | ||
|
aa09b24d02 | ||
|
c34f90ac18 | ||
|
fb1a9ced88 | ||
|
7d73ac417f | ||
|
4979829fad | ||
|
a3083b821c | ||
|
a1da1f0069 | ||
|
d67fc4db7d | ||
|
f29fed2753 | ||
|
ba2e314ca2 | ||
|
0788892831 | ||
|
03998d79a9 | ||
|
ab41e70d5c | ||
|
23addc5695 | ||
|
d32b000855 | ||
|
e62e51efb5 | ||
|
02afaae56c | ||
|
0d4b101510 | ||
|
1b1495eee8 | ||
|
129e69c293 | ||
|
ec919808d0 | ||
|
88802754b3 | ||
|
2f87d4f5ab | ||
|
452c5920dd | ||
|
42c0fc59df | ||
|
dcd6ab933b | ||
|
6688f474b5 | ||
|
a72db0aa7d | ||
|
810b7ca741 | ||
|
115dfd3b52 | ||
|
183274d6da | ||
|
6812423c89 | ||
|
f99c0278fd | ||
|
1b065c4cca | ||
|
c49ec503c8 | ||
|
67fc1ea7ad | ||
|
9716976002 | ||
|
4ca1717979 | ||
|
560649b1e8 | ||
|
3ab98532a0 | ||
|
0dbdc0faeb | ||
|
1e4b14177a | ||
|
eb183c9da0 | ||
|
72ecdca941 | ||
|
b958cb7e19 | ||
|
e4650b7de2 | ||
|
f6fc289102 | ||
|
af4b66bb6e | ||
|
bcd7949efe | ||
|
3d8be5c339 | ||
|
46654e3e8d | ||
|
16c214b8a4 | ||
|
db50dd8e46 | ||
|
08c890ba24 | ||
|
6691858b95 | ||
|
f802861d86 | ||
|
d8e40ee3f1 | ||
|
cc75f44949 | ||
|
58e9f4bf73 | ||
|
39f8616336 | ||
|
8d140cc1a1 | ||
|
11a7c16f1b | ||
|
f2ffdae0c2 | ||
|
676eca7f87 | ||
|
2c402157ca | ||
|
464050ff85 | ||
|
294e3d54ef | ||
|
015166d165 | ||
|
47d308605e | ||
|
951433d0b7 | ||
|
9370f353c6 | ||
|
ac20d6fbf3 | ||
|
2729d8cbec | ||
|
4c6f27c269 | ||
|
f0d90da75c | ||
|
6e5d6f48e6 | ||
|
cf7f98b7c9 | ||
|
560a94c910 | ||
|
5c23e588ff | ||
|
fe38b442a0 |
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal 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
23
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal 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. -->
|
12
.github/ISSUE_TEMPLATE/2_Feature_request.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/2_Feature_request.md
vendored
Normal 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
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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.
|
||||
-->
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
/vendor/
|
||||
/vendor
|
||||
*.iml
|
||||
/.idea/
|
||||
/.idea
|
||||
/composer.lock
|
||||
/.php_cs.cache
|
25
.travis.yml
Normal file
25
.travis.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0'
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
- '7.3'
|
||||
|
||||
# cache vendor dirs
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
|
||||
install:
|
||||
- travis_retry composer self-update && composer --version
|
||||
- travis_retry composer install --no-interaction
|
||||
|
||||
before_script:
|
||||
- sudo apt-get install p7zip-full
|
||||
|
||||
script:
|
||||
- composer validate --no-check-lock
|
||||
- vendor/bin/phpunit -v -c phpunit.xml
|
42
CHANGELOG.md
Normal file
42
CHANGELOG.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Changelog
|
||||
|
||||
# 3.1.2 (2017-11-17)
|
||||
- Changed the algorithm for adding paddings to zipalign.
|
||||
Now we will use the special field ExtraField c ID 0xD935,
|
||||
which was implemented by Google in the apksigner library.
|
||||
Now this field corresponds to the ZIP standard for storing
|
||||
ExtraField records, and not just filling with zero bytes,
|
||||
as in the zipalign console utility.
|
||||
|
||||
## 3.1.1 (2017-11-15)
|
||||
- Fix resave zip aligned archive
|
||||
|
||||
## 3.1.0 (2017-11-14)
|
||||
- Added class `ZipModel` for all changes.
|
||||
- All manipulations with incoming and outgoing streams are in separate files: `ZipInputStream` and `ZipOutputStream`.
|
||||
- Removed class `CentralDirectory`.
|
||||
- Optimized extra fields classes.
|
||||
- Fixed issue #4 (`count()` returns 0 when files are added in directories).
|
||||
- Implemented issue #8 - support inline Content-Disposition and empty output filename.
|
||||
- Optimized and tested on a php 32-bit platform (issue #5).
|
||||
- Added output as PSR-7 Response.
|
||||
- Added methods for canceling changes.
|
||||
- Added [russian documentation](README.RU.md).
|
||||
- Updated [documentation](README.md).
|
||||
- Declared deprecated methods:
|
||||
+ rename `ZipFile::withReadPassword` to `ZipFile::setReadPassword`
|
||||
+ rename `ZipFile::withNewPassword` to `ZipFile::setPassword`
|
||||
+ rename `ZipFile::withoutPassword` to `ZipFile::disableEncryption`
|
||||
|
||||
## 3.0.3 (2017-11-11)
|
||||
Fix bug issue #8 - Error if the file is empty.
|
||||
|
||||
## 3.0.0 (2017-03-15)
|
||||
Merge `ZipOutputFile` with ZipFile and optimize the zip archive update.
|
||||
|
||||
See the update instructions in README.md.
|
||||
|
||||
## 2.2.0 (2017-03-02)
|
||||
Features:
|
||||
- create output object `ZipOutputFile` from `ZipFile` in method `ZipFile::edit()`.
|
||||
- create output object `ZipOutputFile` from filename in static method `ZipOutputFile::openFromFile(string $filename)`.
|
829
README.RU.md
Normal file
829
README.RU.md
Normal file
@@ -0,0 +1,829 @@
|
||||
`PhpZip`
|
||||
========
|
||||
`PhpZip` - php библиотека для продвинутой работы с ZIP-архивами.
|
||||
|
||||
[](https://travis-ci.org/Ne-Lexa/php-zip)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
[](https://php.net/)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
|
||||
[English Documentation](README.md)
|
||||
|
||||
Содержание
|
||||
----------
|
||||
- [Функционал](#Features)
|
||||
- [Требования](#Requirements)
|
||||
- [Установка](#Installation)
|
||||
- [Примеры](#Examples)
|
||||
- [Глоссарий](#Glossary)
|
||||
- [Документация](#Documentation)
|
||||
+ [Обзор методов класса `\PhpZip\ZipFile`](#Documentation-Overview)
|
||||
+ [Создание/Открытие ZIP-архива](#Documentation-Open-Zip-Archive)
|
||||
+ [Чтение записей из архива](#Documentation-Open-Zip-Entries)
|
||||
+ [Перебор записей/Итератор](#Documentation-Zip-Iterate)
|
||||
+ [Получение информации о записях](#Documentation-Zip-Info)
|
||||
+ [Добавление записей в архив](#Documentation-Add-Zip-Entries)
|
||||
+ [Удаление записей из архива](#Documentation-Remove-Zip-Entries)
|
||||
+ [Работа с записями и с архивом](#Documentation-Entries)
|
||||
+ [Работа с паролями](#Documentation-Password)
|
||||
+ [zipalign - выравнивание архива для оптимизации Android пакетов (APK)](#Documentation-ZipAlign-Usage)
|
||||
+ [Отмена изменений](#Documentation-Unchanged)
|
||||
+ [Сохранение файла или вывод в браузер](#Documentation-Save-Or-Output-Entries)
|
||||
+ [Закрытие архива](#Documentation-Close-Zip-Archive)
|
||||
- [Запуск тестов](#Running-Tests)
|
||||
- [История изменений](#Changelog)
|
||||
- [Обновление версий](#Upgrade)
|
||||
+ [Обновление с версии 2 до версии 3.0](#Upgrade-v2-to-v3)
|
||||
|
||||
### <a name="Features"></a> Функционал
|
||||
- Открытие и разархивирование ZIP-архивов.
|
||||
- Создание ZIP-архивов.
|
||||
- Модификация ZIP-архивов.
|
||||
- Чистый php (не требуется расширение `php-zip` и класс `\ZipArchive`).
|
||||
- Поддерживается сохранение архива в файл, вывод архива в браузер или вывод в виде строки, без сохранения в файл.
|
||||
- Поддерживаются комментарии архива и комментарии отдельных записей.
|
||||
- Получение подробной информации о каждой записи в архиве.
|
||||
- Поддерживаются только следующие методы сжатия:
|
||||
+ Без сжатия (Stored).
|
||||
+ Deflate сжатие.
|
||||
+ BZIP2 сжатие при наличии расширения `php-bz2`.
|
||||
- Поддержка `ZIP64` (размер файла более 4 GB или количество записей в архиве более 65535).
|
||||
- Встроенная поддержка выравнивания архива для оптимизации Android пакетов (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
||||
- Работа с паролями для PHP 5.5
|
||||
> **Внимание!**
|
||||
>
|
||||
> Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`.
|
||||
> Используйте метод шифрования `WinZIP AES Encryption`, когда это возможно.
|
||||
+ Установка пароля для чтения архива глобально или для некоторых записей.
|
||||
+ Изменение пароля архива, в том числе и для отдельных записей.
|
||||
+ Удаление пароля архива глобально или для отдельных записей.
|
||||
+ Установка пароля и/или метода шифрования, как для всех, так и для отдельных записей в архиве.
|
||||
+ Установка разных паролей и методов шифрования для разных записей.
|
||||
+ Удаление пароля для всех или для некоторых записей.
|
||||
+ Поддержка методов шифрования `Traditional PKWARE Encryption (ZipCrypto)` и `WinZIP AES Encryption (128, 192 или 256 bit)`.
|
||||
+ Установка метода шифрования для всех или для отдельных записей в архиве.
|
||||
|
||||
### <a name="Requirements"></a> Требования
|
||||
- `PHP` >= 5.5 (предпочтительно 64-bit).
|
||||
- Опционально php-расширение `bzip2` для поддержки BZIP2 компрессии.
|
||||
- Опционально php-расширение `openssl` или `mcrypt` для `WinZip Aes Encryption` шифрования.
|
||||
|
||||
### <a name="Installation"></a> Установка
|
||||
`composer require nelexa/zip`
|
||||
|
||||
Последняя стабильная версия: [](https://packagist.org/packages/nelexa/zip)
|
||||
|
||||
### <a name="Examples"></a> Примеры
|
||||
```php
|
||||
// создание нового архива
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
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/`.
|
||||
|
||||
### <a name="Glossary"></a> Глоссарий
|
||||
**Запись в ZIP-архиве (Zip Entry)** - файл или папка в ZIP-архиве. У каждой записи в архиве есть определённые свойства, например: имя файла, метод сжатия, метод шифрования, размер файла до сжатия, размер файла после сжатия, CRC32 и другие.
|
||||
|
||||
### <a name="Documentation"></a> Документация
|
||||
#### <a name="Documentation-Overview"></a> Обзор методов класса `\PhpZip\ZipFile`
|
||||
- [ZipFile::__construct](#Documentation-ZipFile-__construct) - инициализацирует ZIP-архив.
|
||||
- [ZipFile::addAll](#Documentation-ZipFile-addAll) - добавляет все записи из массива.
|
||||
- [ZipFile::addDir](#Documentation-ZipFile-addDir) - добавляет файлы из директории по указанному пути без вложенных директорий.
|
||||
- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - добавляет файлы из директории по указанному пути c вложенными директориями.
|
||||
- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - добавляет в ZIP-архив новую директорию.
|
||||
- [ZipFile::addFile](#Documentation-ZipFile-addFile) - добавляет в ZIP-архив файл по указанному пути.
|
||||
- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - добавляет файлы из итератора директорий.
|
||||
- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - добавляет файлы из директории в соответствии с glob шаблоном без вложенных директорий.
|
||||
- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - добавляет файлы из директории в соответствии с glob шаблоном c вложенными директориями.
|
||||
- [ZipFile::addFilesFromRegex](#Documentation-ZipFile-addFilesFromRegex) - добавляет файлы из директории в соответствии с регулярным выражением без вложенных директорий.
|
||||
- [ZipFile::addFilesFromRegexRecursive](#Documentation-ZipFile-addFilesFromRegexRecursive) - добавляет файлы из директории в соответствии с регулярным выражением c вложенными директориями.
|
||||
- [ZipFile::addFromStream](#Documentation-ZipFile-addFromStream) - добавляет в ZIP-архив запись из потока.
|
||||
- [ZipFile::addFromString](#Documentation-ZipFile-addFromString) - добавляет файл в ZIP-архив, используя его содержимое в виде строки.
|
||||
- [ZipFile::close](#Documentation-ZipFile-close) - закрывает ZIP-архив.
|
||||
- [ZipFile::count](#Documentation-ZipFile-count) - возвращает количество записей в архиве.
|
||||
- [ZipFile::deleteFromName](#Documentation-ZipFile-deleteFromName) - удаляет запись по имени.
|
||||
- [ZipFile::deleteFromGlob](#Documentation-ZipFile-deleteFromGlob) - удаляет записи в соответствии с glob шаблоном.
|
||||
- [ZipFile::deleteFromRegex](#Documentation-ZipFile-deleteFromRegex) - удаляет записи в соответствии с регулярным выражением.
|
||||
- [ZipFile::deleteAll](#Documentation-ZipFile-deleteAll) - удаляет все записи в ZIP-архиве.
|
||||
- [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) - отключает шифрования всех записей, находящихся в архиве.
|
||||
- [ZipFile::disableEncryptionEntry](#Documentation-ZipFile-disableEncryptionEntry) - отключает шифрование записи по её имени.
|
||||
- [ZipFile::extractTo](#Documentation-ZipFile-extractTo) - извлекает содержимое архива в заданную директорию.
|
||||
- [ZipFile::getAllInfo](#Documentation-ZipFile-getAllInfo) - возвращает подробную информацию обо всех записях в архиве.
|
||||
- [ZipFile::getArchiveComment](#Documentation-ZipFile-getArchiveComment) - возвращает комментарий ZIP-архива.
|
||||
- [ZipFile::getEntryComment](#Documentation-ZipFile-getEntryComment) - возвращает комментарий к записи, используя её имя.
|
||||
- [ZipFile::getEntryContent](#Documentation-ZipFile-getEntryContent) - возвращает содержимое записи.
|
||||
- [ZipFile::getEntryInfo](#Documentation-ZipFile-getEntryInfo) - возвращает подробную информацию о записи в архиве.
|
||||
- [ZipFile::getListFiles](#Documentation-ZipFile-getListFiles) - возвращает список файлов архива.
|
||||
- [ZipFile::hasEntry](#Documentation-ZipFile-hasEntry) - проверяет, присутствует ли запись в архиве.
|
||||
- [ZipFile::isDirectory](#Documentation-ZipFile-isDirectory) - проверяет, является ли запись в архиве директорией.
|
||||
- [ZipFile::matcher](#Documentation-ZipFile-matcher) - выборка записей в архиве для проведения операций над выбранными записями.
|
||||
- [ZipFile::openFile](#Documentation-ZipFile-openFile) - открывает ZIP-архив из файла.
|
||||
- [ZipFile::openFromString](#Documentation-ZipFile-openFromString) - открывает ZIP-архив из строки.
|
||||
- [ZipFile::openFromStream](#Documentation-ZipFile-openFromStream) - открывает ZIP-архив из потока.
|
||||
- [ZipFile::outputAsAttachment](#Documentation-ZipFile-outputAsAttachment) - выводит ZIP-архив в браузер.
|
||||
- [ZipFile::outputAsResponse](#Documentation-ZipFile-outputAsResponse) - выводит ZIP-архив, как Response PSR-7.
|
||||
- [ZipFile::outputAsString](#Documentation-ZipFile-outputAsString) - выводит ZIP-архив в виде строки.
|
||||
- [ZipFile::rename](#Documentation-ZipFile-rename) - переименовывает запись по имени.
|
||||
- [ZipFile::rewrite](#Documentation-ZipFile-rewrite) - сохраняет изменения и заново открывает изменившийся архив.
|
||||
- [ZipFile::saveAsFile](#Documentation-ZipFile-saveAsFile) - сохраняет архив в файл.
|
||||
- [ZipFile::saveAsStream](#Documentation-ZipFile-saveAsStream) - записывает архив в поток.
|
||||
- [ZipFile::setArchiveComment](#Documentation-ZipFile-setArchiveComment) - устанавливает комментарий к ZIP-архиву.
|
||||
- [ZipFile::setCompressionLevel](#Documentation-ZipFile-setCompressionLevel) - устанавливает уровень сжатия для всех файлов, находящихся в архиве.
|
||||
- [ZipFile::setCompressionLevelEntry](#Documentation-ZipFile-setCompressionLevelEntry) - устанавливает уровень сжатия для определённой записи в архиве.
|
||||
- [ZipFile::setCompressionMethodEntry](#Documentation-ZipFile-setCompressionMethodEntry) - устанавливает метод сжатия для определённой записи в архиве.
|
||||
- [ZipFile::setEntryComment](#Documentation-ZipFile-setEntryComment) - устанавливает комментарий к записи, используя её имя.
|
||||
- [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей.
|
||||
- [ZipFile::setReadPasswordEntry](#Documentation-ZipFile-setReadPasswordEntry) - устанавливает пароль на чтение конкретной зашифрованной записи открытого запароленного архива.
|
||||
- ~~ZipFile::withNewPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::setPassword](#Documentation-ZipFile-setPassword).
|
||||
- [ZipFile::setPassword](#Documentation-ZipFile-setPassword) - устанавливает новый пароль для всех файлов, находящихся в архиве.
|
||||
- [ZipFile::setPasswordEntry](#Documentation-ZipFile-setPasswordEntry) - устанавливает новый пароль для конкретного файла.
|
||||
- [ZipFile::setZipAlign](#Documentation-ZipFile-setZipAlign) - устанавливает выравнивание архива для оптимизации APK файлов (Android packages).
|
||||
- [ZipFile::unchangeAll](#Documentation-ZipFile-unchangeAll) - отменяет все изменения, сделанные в архиве.
|
||||
- [ZipFile::unchangeArchiveComment](#Documentation-ZipFile-unchangeArchiveComment) - отменяет изменения в комментарии к архиву.
|
||||
- [ZipFile::unchangeEntry](#Documentation-ZipFile-unchangeEntry) - отменяет изменения для конкретной записи архива.
|
||||
- ~~ZipFile::withoutPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption).
|
||||
- ~~ZipFile::withReadPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword).
|
||||
|
||||
#### <a name="Documentation-Open-Zip-Archive"></a> Создание/Открытие ZIP-архива
|
||||
<a name="Documentation-ZipFile-__construct"></a>**ZipFile::__construct** - Инициализацирует ZIP-архив.
|
||||
```php
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
```
|
||||
<a name="Documentation-ZipFile-openFile"></a> **ZipFile::openFile** - открывает ZIP-архив из файла.
|
||||
```php
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFile('file.zip');
|
||||
```
|
||||
<a name="Documentation-ZipFile-openFromString"></a> **ZipFile::openFromString** - открывает ZIP-архив из строки.
|
||||
```php
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFromString($stringContents);
|
||||
```
|
||||
<a name="Documentation-ZipFile-openFromStream"></a> **ZipFile::openFromStream** - открывает ZIP-архив из потока.
|
||||
```php
|
||||
$stream = fopen('file.zip', 'rb');
|
||||
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFromStream($stream);
|
||||
```
|
||||
#### <a name="Documentation-Open-Zip-Entries"></a> Чтение записей из архива
|
||||
<a name="Documentation-ZipFile-count"></a> **ZipFile::count** - возвращает количество записей в архиве.
|
||||
```php
|
||||
$count = count($zipFile);
|
||||
// или
|
||||
$count = $zipFile->count();
|
||||
```
|
||||
<a name="Documentation-ZipFile-getListFiles"></a> **ZipFile::getListFiles** - возвращает список файлов архива.
|
||||
```php
|
||||
$listFiles = $zipFile->getListFiles();
|
||||
|
||||
// Пример содержимого массива:
|
||||
// array (
|
||||
// 0 => 'info.txt',
|
||||
// 1 => 'path/to/file.jpg',
|
||||
// 2 => 'another path/',
|
||||
// )
|
||||
```
|
||||
<a name="Documentation-ZipFile-getEntryContent"></a> **ZipFile::getEntryContent** - возвращает содержимое записи.
|
||||
```php
|
||||
// $entryName = 'path/to/example-entry-name.txt';
|
||||
|
||||
$contents = $zipFile[$entryName];
|
||||
// или
|
||||
$contents = $zipFile->getEntryContents($entryName);
|
||||
```
|
||||
<a name="Documentation-ZipFile-hasEntry"></a> **ZipFile::hasEntry** - проверяет, присутствует ли запись в архиве.
|
||||
```php
|
||||
// $entryName = 'path/to/example-entry-name.txt';
|
||||
|
||||
$hasEntry = isset($zipFile[$entryName]);
|
||||
// или
|
||||
$hasEntry = $zipFile->hasEntry($entryName);
|
||||
```
|
||||
<a name="Documentation-ZipFile-isDirectory"></a> **ZipFile::isDirectory** - проверяет, является ли запись в архиве директорией.
|
||||
```php
|
||||
// $entryName = 'path/to/';
|
||||
|
||||
$isDirectory = $zipFile->isDirectory($entryName);
|
||||
```
|
||||
<a name="Documentation-ZipFile-extractTo"></a> **ZipFile::extractTo** - извлекает содержимое архива в заданную директорию.
|
||||
Директория должна существовать.
|
||||
```php
|
||||
$zipFile->extractTo($directory);
|
||||
```
|
||||
Можно извлечь только некоторые записи в заданную директорию.
|
||||
Директория должна существовать.
|
||||
```php
|
||||
$extractOnlyFiles = [
|
||||
"filename1",
|
||||
"filename2",
|
||||
"dir/dir/dir/"
|
||||
];
|
||||
$zipFile->extractTo($directory, $extractOnlyFiles);
|
||||
```
|
||||
#### <a name="Documentation-Zip-Iterate"></a> Перебор записей/Итератор
|
||||
`ZipFile` является итератором.
|
||||
Можно перебрать все записи, через цикл `foreach`.
|
||||
```php
|
||||
foreach($zipFile as $entryName => $contents){
|
||||
echo "Файл: $entryName" . PHP_EOL;
|
||||
echo "Содержимое: $contents" . PHP_EOL;
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
}
|
||||
```
|
||||
Можно использовать паттерн `Iterator`.
|
||||
```php
|
||||
$iterator = new \ArrayIterator($zipFile);
|
||||
while ($iterator->valid())
|
||||
{
|
||||
$entryName = $iterator->key();
|
||||
$contents = $iterator->current();
|
||||
|
||||
echo "Файл: $entryName" . PHP_EOL;
|
||||
echo "Содержимое: $contents" . PHP_EOL;
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
```
|
||||
#### <a name="Documentation-Zip-Info"></a> Получение информации о записях
|
||||
<a name="Documentation-ZipFile-getArchiveComment"></a> **ZipFile::getArchiveComment** - возвращает комментарий ZIP-архива.
|
||||
```php
|
||||
$commentArchive = $zipFile->getArchiveComment();
|
||||
```
|
||||
<a name="Documentation-ZipFile-getEntryComment"></a> **ZipFile::getEntryComment** - возвращает комментарий к записи, используя её имя.
|
||||
```php
|
||||
$commentEntry = $zipFile->getEntryComment($entryName);
|
||||
```
|
||||
<a name="Documentation-ZipFile-getEntryInfo"></a> **ZipFile::getEntryInfo** - возвращает подробную информацию о записи в архиве.
|
||||
```php
|
||||
$zipInfo = $zipFile->getEntryInfo('file.txt');
|
||||
|
||||
$arrayInfo = $zipInfo->toArray();
|
||||
// Пример содержимого массива:
|
||||
// array (
|
||||
// 'name' => 'file.gif',
|
||||
// 'folder' => false,
|
||||
// 'size' => '43',
|
||||
// 'compressed_size' => '43',
|
||||
// 'modified' => 1510489440,
|
||||
// 'created' => null,
|
||||
// 'accessed' => null,
|
||||
// 'attributes' => '-rw-r--r--',
|
||||
// 'encrypted' => false,
|
||||
// 'encryption_method' => 0,
|
||||
// 'comment' => '',
|
||||
// 'crc' => 782934147,
|
||||
// 'method_name' => 'No compression',
|
||||
// 'compression_method' => 0,
|
||||
// 'platform' => 'UNIX',
|
||||
// 'version' => 10,
|
||||
// )
|
||||
|
||||
print_r($zipInfo);
|
||||
// Вывод:
|
||||
//PhpZip\Model\ZipInfo Object
|
||||
//(
|
||||
// [name:PhpZip\Model\ZipInfo:private] => file.gif
|
||||
// [folder:PhpZip\Model\ZipInfo:private] =>
|
||||
// [size:PhpZip\Model\ZipInfo:private] => 43
|
||||
// [compressedSize:PhpZip\Model\ZipInfo:private] => 43
|
||||
// [mtime:PhpZip\Model\ZipInfo:private] => 1510489324
|
||||
// [ctime:PhpZip\Model\ZipInfo:private] =>
|
||||
// [atime:PhpZip\Model\ZipInfo:private] =>
|
||||
// [encrypted:PhpZip\Model\ZipInfo:private] =>
|
||||
// [comment:PhpZip\Model\ZipInfo:private] =>
|
||||
// [crc:PhpZip\Model\ZipInfo:private] => 782934147
|
||||
// [methodName:PhpZip\Model\ZipInfo:private] => No compression
|
||||
// [compressionMethod:PhpZip\Model\ZipInfo:private] => 0
|
||||
// [platform:PhpZip\Model\ZipInfo:private] => UNIX
|
||||
// [version:PhpZip\Model\ZipInfo:private] => 10
|
||||
// [attributes:PhpZip\Model\ZipInfo:private] => -rw-r--r--
|
||||
// [encryptionMethod:PhpZip\Model\ZipInfo:private] => 0
|
||||
// [compressionLevel:PhpZip\Model\ZipInfo:private] => -1
|
||||
//)
|
||||
|
||||
echo $zipInfo;
|
||||
// Вывод:
|
||||
// PhpZip\Model\ZipInfo {Name="file.gif", Size="43 bytes", Compressed size="43 bytes", Modified time="2017-11-12T15:22:04+03:00", Crc=0x2eaaa083, Method name="No compression", Attributes="-rw-r--r--", Platform="UNIX", Version=10}
|
||||
```
|
||||
<a name="Documentation-ZipFile-getAllInfo"></a> **ZipFile::getAllInfo** - возвращает подробную информацию обо всех записях в архиве.
|
||||
```php
|
||||
$zipAllInfo = $zipFile->getAllInfo();
|
||||
|
||||
print_r($zipAllInfo);
|
||||
//Array
|
||||
//(
|
||||
// [file.txt] => PhpZip\Model\ZipInfo Object
|
||||
// (
|
||||
// ...
|
||||
// )
|
||||
//
|
||||
// [file2.txt] => PhpZip\Model\ZipInfo Object
|
||||
// (
|
||||
// ...
|
||||
// )
|
||||
//
|
||||
// ...
|
||||
//)
|
||||
```
|
||||
#### <a name="Documentation-Add-Zip-Entries"></a> Добавление записей в архив
|
||||
|
||||
Все методы добавления записей в ZIP-архив позволяют указать метод сжатия содержимого.
|
||||
|
||||
Доступны следующие методы сжатия:
|
||||
- `\PhpZip\ZipFile::METHOD_STORED` - без сжатия
|
||||
- `\PhpZip\ZipFile::METHOD_DEFLATED` - Deflate сжатие
|
||||
- `\PhpZip\ZipFile::METHOD_BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
|
||||
|
||||
<a name="Documentation-ZipFile-addFile"></a> **ZipFile::addFile** - добавляет в ZIP-архив файл по указанному пути из файловой системы.
|
||||
```php
|
||||
// $file = '...../file.ext';
|
||||
$zipFile->addFile($file);
|
||||
|
||||
// можно указать имя записи в архиве (если null, то используется последний компонент из имени файла)
|
||||
$zipFile->addFile($file, $entryName);
|
||||
// или
|
||||
$zipFile[$entryName] = new \SplFileInfo($file);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFile($file, $entryName, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFile($file, $entryName, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFile($file, $entryName, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFromString"></a> **ZipFile::addFromString** - добавляет файл в ZIP-архив, используя его содержимое в виде строки.
|
||||
```php
|
||||
$zipFile[$entryName] = $contents;
|
||||
// или
|
||||
$zipFile->addFromString($entryName, $contents);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFromString($entryName, $contents, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFromString($entryName, $contents, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFromString($entryName, $contents, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFromStream"></a> **ZipFile::addFromStream** - добавляет в ZIP-архив запись из потока.
|
||||
```php
|
||||
// $stream = fopen(..., 'rb');
|
||||
|
||||
$zipFile->addFromStream($stream, $entryName);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addEmptyDir"></a> **ZipFile::addEmptyDir** - добавляет в ZIP-архив новую (пустую) директорию.
|
||||
```php
|
||||
// $path = "path/to/";
|
||||
|
||||
$zipFile->addEmptyDir($path);
|
||||
// или
|
||||
$zipFile[$path] = null;
|
||||
```
|
||||
<a name="Documentation-ZipFile-addAll"></a> **ZipFile::addAll** - добавляет все записи из массива.
|
||||
```php
|
||||
$entries = [
|
||||
'file.txt' => 'file contents', // запись из строки данных
|
||||
'empty dir/' => null, // пустой каталог
|
||||
'path/to/file.jpg' => fopen('..../filename', 'r'), // запись из потока
|
||||
'path/to/file.dat' => new \SplFileInfo('..../filename'), // запись из файла
|
||||
];
|
||||
|
||||
$zipFile->addAll($entries);
|
||||
```
|
||||
<a name="Documentation-ZipFile-addDir"></a> **ZipFile::addDir** - добавляет файлы из директории по указанному пути без вложенных директорий.
|
||||
```php
|
||||
$zipFile->addDir($dirName);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addDir($dirName, $localPath);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addDirRecursive"></a> **ZipFile::addDirRecursive** - добавляет файлы из директории по указанному пути c вложенными директориями.
|
||||
```php
|
||||
$zipFile->addDirRecursive($dirName);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addDirRecursive($dirName, $localPath);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFilesFromIterator"></a> **ZipFile::addFilesFromIterator** - добавляет файлы из итератора директорий.
|
||||
```php
|
||||
// $directoryIterator = new \DirectoryIterator($dir); // без вложенных директорий
|
||||
// $directoryIterator = new \RecursiveDirectoryIterator($dir); // с вложенными директориями
|
||||
|
||||
$zipFile->addFilesFromIterator($directoryIterator);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
// или
|
||||
$zipFile[$localPath] = $directoryIterator;
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
Пример добавления файлов из директории в архив с игнорированием некоторых файлов при помощи итератора директорий.
|
||||
```php
|
||||
$ignoreFiles = [
|
||||
"file_ignore.txt",
|
||||
"dir_ignore/sub dir ignore/"
|
||||
];
|
||||
|
||||
// $directoryIterator = new \DirectoryIterator($dir); // без вложенных директорий
|
||||
// $directoryIterator = new \RecursiveDirectoryIterator($dir); // с вложенными директориями
|
||||
|
||||
// используйте \PhpZip\Util\Iterator\IgnoreFilesFilterIterator для не рекурсивного поиска
|
||||
$ignoreIterator = new \PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator(
|
||||
$directoryIterator,
|
||||
$ignoreFiles
|
||||
);
|
||||
|
||||
$zipFile->addFilesFromIterator($ignoreIterator);
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFilesFromGlob"></a> **ZipFile::addFilesFromGlob** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) без вложенных директорий.
|
||||
```php
|
||||
$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> добавить все .jpg, .jpeg, .png и .gif файлы
|
||||
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFilesFromGlobRecursive"></a> **ZipFile::addFilesFromGlobRecursive** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) c вложенными директориями.
|
||||
```php
|
||||
$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> добавить все .jpg, .jpeg, .png и .gif файлы
|
||||
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFilesFromRegex"></a> **ZipFile::addFilesFromRegex** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) без вложенных директорий.
|
||||
```php
|
||||
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярного выражения -> добавить все .jpg, .jpeg, .png и .gif файлы
|
||||
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
<a name="Documentation-ZipFile-addFilesFromRegexRecursive"></a> **ZipFile::addFilesFromRegexRecursive** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) с вложенными директориями.
|
||||
```php
|
||||
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярного выражения -> добавить все .jpg, .jpeg, .png и .gif файлы
|
||||
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern);
|
||||
|
||||
// можно указать путь в архиве в который необходимо поместить записи
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath);
|
||||
|
||||
// можно указать метод сжатия
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // Без сжатия
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate сжатие
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 сжатие
|
||||
```
|
||||
#### <a name="Documentation-Remove-Zip-Entries"></a> Удаление записей из архива
|
||||
<a name="Documentation-ZipFile-deleteFromName"></a> **ZipFile::deleteFromName** - удаляет запись по имени.
|
||||
```php
|
||||
$zipFile->deleteFromName($entryName);
|
||||
```
|
||||
<a name="Documentation-ZipFile-deleteFromGlob"></a> **ZipFile::deleteFromGlob** - удаляет записи в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)).
|
||||
```php
|
||||
$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> удалить все .jpg, .jpeg, .png и .gif файлы
|
||||
|
||||
$zipFile->deleteFromGlob($globPattern);
|
||||
```
|
||||
<a name="Documentation-ZipFile-deleteFromRegex"></a> **ZipFile::deleteFromRegex** - удаляет записи в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression).
|
||||
```php
|
||||
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярному выражения -> удалить все .jpg, .jpeg, .png и .gif файлы
|
||||
|
||||
$zipFile->deleteFromRegex($regexPattern);
|
||||
```
|
||||
<a name="Documentation-ZipFile-deleteAll"></a> **ZipFile::deleteAll** - удаляет все записи в ZIP-архиве.
|
||||
```php
|
||||
$zipFile->deleteAll();
|
||||
```
|
||||
#### <a name="Documentation-Entries"></a> Работа с записями и с архивом
|
||||
<a name="Documentation-ZipFile-rename"></a> **ZipFile::rename** - переименовывает запись по имени.
|
||||
```php
|
||||
$zipFile->rename($oldName, $newName);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setCompressionLevel"></a> **ZipFile::setCompressionLevel** - устанавливает уровень сжатия для всех файлов, находящихся в архиве.
|
||||
|
||||
> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
|
||||
|
||||
По умолчанию используется уровень сжатия -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) или уровень сжатия, определённый в архиве для Deflate сжатия.
|
||||
|
||||
Поддерживаются значения -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) и диапазон от 1 (`\PhpZip\ZipFile::LEVEL_BEST_SPEED`) до 9 (`\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION`). Чем выше число, тем лучше и дольше сжатие.
|
||||
```php
|
||||
$zipFile->setCompressionLevel(\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setCompressionLevelEntry"></a> **ZipFile::setCompressionLevelEntry** - устанавливает уровень сжатия для определённой записи в архиве.
|
||||
|
||||
Поддерживаются значения -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) и диапазон от 1 (`\PhpZip\ZipFile::LEVEL_BEST_SPEED`) до 9 (`\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION`). Чем выше число, тем лучше и дольше сжатие.
|
||||
```php
|
||||
$zipFile->setCompressionLevelEntry($entryName, \PhpZip\ZipFile::LEVEL_BEST_COMPRESSION);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setCompressionMethodEntry"></a> **ZipFile::setCompressionMethodEntry** - устанавливает метод сжатия для определённой записи в архиве.
|
||||
|
||||
Доступны следующие методы сжатия:
|
||||
- `\PhpZip\ZipFile::METHOD_STORED` - без сжатия
|
||||
- `\PhpZip\ZipFile::METHOD_DEFLATED` - Deflate сжатие
|
||||
- `\PhpZip\ZipFile::METHOD_BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
|
||||
```php
|
||||
$zipFile->setCompressionMethodEntry($entryName, ZipFile::METHOD_DEFLATED);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setArchiveComment"></a> **ZipFile::setArchiveComment** - устанавливает комментарий к ZIP-архиву.
|
||||
```php
|
||||
$zipFile->setArchiveComment($commentArchive);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setEntryComment"></a> **ZipFile::setEntryComment** - устанавливает комментарий к записи, используя её имя.
|
||||
```php
|
||||
$zipFile->setEntryComment($entryName, $comment);
|
||||
```
|
||||
<a name="Documentation-ZipFile-matcher"></a> **ZipFile::matcher** - выборка записей в архиве для проведения операций над выбранными записями.
|
||||
```php
|
||||
$matcher = $zipFile->matcher();
|
||||
```
|
||||
Выбор файлов из архива по одному:
|
||||
```php
|
||||
$matcher
|
||||
->add('entry name')
|
||||
->add('another entry');
|
||||
```
|
||||
Выбор нескольких файлов в архиве:
|
||||
```php
|
||||
$matcher->add([
|
||||
'entry name',
|
||||
'another entry name',
|
||||
'path/'
|
||||
]);
|
||||
```
|
||||
Выбор файлов по регулярному выражению:
|
||||
```php
|
||||
$matcher->match('~\.jpe?g$~i');
|
||||
```
|
||||
Выбор всех файлов в архиве:
|
||||
```php
|
||||
$matcher->all();
|
||||
```
|
||||
count() - получает количество выбранных записей:
|
||||
```php
|
||||
$count = count($matcher);
|
||||
// или
|
||||
$count = $matcher->count();
|
||||
```
|
||||
getMatches() - получает список выбранных записей:
|
||||
```php
|
||||
$entries = $matcher->getMatches();
|
||||
// пример содержимого: ['entry name', 'another entry name'];
|
||||
```
|
||||
invoke() - выполняет пользовательскую функцию над выбранными записями:
|
||||
```php
|
||||
// пример
|
||||
$matcher->invoke(function($entryName) use($zipFile) {
|
||||
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
|
||||
$zipFile->rename($entryName, $newName);
|
||||
});
|
||||
```
|
||||
Функции для работы над выбранными записями:
|
||||
```php
|
||||
$matcher->delete(); // удалет выбранные записи из ZIP-архива
|
||||
$matcher->setPassword($password); // устанавливает новый пароль на выбранные записи
|
||||
$matcher->setPassword($password, $encryptionMethod); // устанавливает новый пароль и метод шифрования на выбранные записи
|
||||
$matcher->setEncryptionMethod($encryptionMethod); // устанавливает метод шифрования на выбранные записи
|
||||
$matcher->disableEncryption(); // отключает шифрование для выбранных записей
|
||||
```
|
||||
#### <a name="Documentation-Password"></a> Работа с паролями
|
||||
|
||||
Реализована поддержка методов шифрования:
|
||||
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_TRADITIONAL` - Traditional PKWARE encryption
|
||||
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256` - WinZip AES encryption 256 bit (рекомендуемое)
|
||||
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192` - WinZip AES encryption 192 bit
|
||||
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128` - WinZip AES encryption 128 bit
|
||||
|
||||
<a name="Documentation-ZipFile-setReadPassword"></a> **ZipFile::setReadPassword** - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей.
|
||||
|
||||
> _Установка пароля не является обязательной для добавления новых записей или удаления существующих, но если вы захотите извлечь контент или изменить метод/уровень сжатия, метод шифрования или изменить пароль, то в этом случае пароль необходимо указать._
|
||||
```php
|
||||
$zipFile->setReadPassword($password);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setReadPasswordEntry"></a> **ZipFile::setReadPasswordEntry** - устанавливает пароль на чтение конкретной зашифрованной записи открытого запароленного архива.
|
||||
```php
|
||||
$zipFile->setReadPasswordEntry($entryName, $password);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setPassword"></a> **ZipFile::setPassword** - устанавливает новый пароль для всех файлов, находящихся в архиве.
|
||||
|
||||
> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
|
||||
```php
|
||||
$zipFile->setPassword($password);
|
||||
```
|
||||
Можно установить метод шифрования:
|
||||
```php
|
||||
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
$zipFile->setPassword($password, $encryptionMethod);
|
||||
```
|
||||
<a name="Documentation-ZipFile-setPasswordEntry"></a> **ZipFile::setPasswordEntry** - устанавливает новый пароль для конкретного файла.
|
||||
```php
|
||||
$zipFile->setPasswordEntry($entryName, $password);
|
||||
```
|
||||
Можно установить метод шифрования:
|
||||
```php
|
||||
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod);
|
||||
```
|
||||
<a name="Documentation-ZipFile-disableEncryption"></a> **ZipFile::disableEncryption** - отключает шифрования всех записей, находящихся в архиве.
|
||||
|
||||
> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
|
||||
```php
|
||||
$zipFile->disableEncryption();
|
||||
```
|
||||
<a name="Documentation-ZipFile-disableEncryptionEntry"></a> **ZipFile::disableEncryptionEntry** - отключает шифрование записи по её имени.
|
||||
```php
|
||||
$zipFile->disableEncryptionEntry($entryName);
|
||||
```
|
||||
#### <a name="Documentation-ZipAlign-Usage"></a> zipalign
|
||||
<a name="Documentation-ZipFile-setZipAlign"></a> **ZipFile::setZipAlign** - устанавливает выравнивание архива для оптимизации APK файлов (Android packages).
|
||||
|
||||
Метод добавляет паддинги незашифрованным и не сжатым записям, для оптимизации расхода памяти в системе Android. Рекомендуется использовать для `APK` файлов. Файл может незначительно увеличиться.
|
||||
|
||||
Этот метод является альтернативой вызова команды `zipalign -f -v 4 filename.zip`.
|
||||
|
||||
Подробнее можно ознакомиться по [ссылке](https://developer.android.com/studio/command-line/zipalign.html).
|
||||
```php
|
||||
// вызовите до сохранения или вывода архива
|
||||
$zipFile->setZipAlign(4);
|
||||
```
|
||||
#### <a name="Documentation-Unchanged"></a> Отмена изменений
|
||||
<a name="Documentation-ZipFile-unchangeAll"></a> **ZipFile::unchangeAll** - отменяет все изменения, сделанные в архиве.
|
||||
```php
|
||||
$zipFile->unchangeAll();
|
||||
```
|
||||
<a name="Documentation-ZipFile-unchangeArchiveComment"></a> **ZipFile::unchangeArchiveComment** - отменяет изменения в комментарии к архиву.
|
||||
```php
|
||||
$zipFile->unchangeArchiveComment();
|
||||
```
|
||||
<a name="Documentation-ZipFile-unchangeEntry"></a> **ZipFile::unchangeEntry** - отменяет изменения для конкретной записи архива.
|
||||
```php
|
||||
$zipFile->unchangeEntry($entryName);
|
||||
```
|
||||
#### <a name="Documentation-Save-Or-Output-Entries"></a> Сохранение файла или вывод в браузер
|
||||
<a name="Documentation-ZipFile-saveAsFile"></a> **ZipFile::saveAsFile** - сохраняет архив в файл.
|
||||
```php
|
||||
$zipFile->saveAsFile($filename);
|
||||
```
|
||||
<a name="Documentation-ZipFile-saveAsStream"></a> **ZipFile::saveAsStream** - записывает архив в поток.
|
||||
```php
|
||||
// $fp = fopen($filename, 'w+b');
|
||||
|
||||
$zipFile->saveAsStream($fp);
|
||||
```
|
||||
<a name="Documentation-ZipFile-outputAsString"></a> **ZipFile::outputAsString** - выводит ZIP-архив в виде строки.
|
||||
```php
|
||||
$rawZipArchiveBytes = $zipFile->outputAsString();
|
||||
```
|
||||
<a name="Documentation-ZipFile-outputAsAttachment"></a> **ZipFile::outputAsAttachment** - выводит ZIP-архив в браузер.
|
||||
|
||||
При выводе устанавливаются необходимые заголовки, а после вывода завершается работа скрипта.
|
||||
```php
|
||||
$zipFile->outputAsAttachment($outputFilename);
|
||||
```
|
||||
Можно установить MIME-тип:
|
||||
```php
|
||||
$mimeType = 'application/zip'
|
||||
$zipFile->outputAsAttachment($outputFilename, $mimeType);
|
||||
```
|
||||
<a name="Documentation-ZipFile-outputAsResponse"></a> **ZipFile::outputAsResponse** - выводит ZIP-архив, как Response [PSR-7](http://www.php-fig.org/psr/psr-7/).
|
||||
|
||||
Метод вывода может использоваться в любом PSR-7 совместимом фреймворке.
|
||||
```php
|
||||
// $response = ....; // instance Psr\Http\Message\ResponseInterface
|
||||
$zipFile->outputAsResponse($response, $outputFilename);
|
||||
```
|
||||
Можно установить MIME-тип:
|
||||
```php
|
||||
$mimeType = 'application/zip'
|
||||
$zipFile->outputAsResponse($response, $outputFilename, $mimeType);
|
||||
```
|
||||
Пример для Slim Framework:
|
||||
```php
|
||||
$app = new \Slim\App;
|
||||
$app->get('/download', function ($req, $res, $args) {
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile['file.txt'] = 'content';
|
||||
return $zipFile->outputAsResponse($res, 'file.zip');
|
||||
});
|
||||
$app->run();
|
||||
```
|
||||
<a name="Documentation-ZipFile-rewrite"></a> **ZipFile::rewrite** - сохраняет изменения и заново открывает изменившийся архив.
|
||||
```php
|
||||
$zipFile->rewrite();
|
||||
```
|
||||
#### <a name="Documentation-Close-Zip-Archive"></a> Закрытие архива
|
||||
<a name="Documentation-ZipFile-close"></a> **ZipFile::close** - закрывает ZIP-архив.
|
||||
```php
|
||||
$zipFile->close();
|
||||
```
|
||||
### <a name="Running-Tests"></a> Запуск тестов
|
||||
Установите зависимости для разработки.
|
||||
```bash
|
||||
composer install --dev
|
||||
```
|
||||
Запустите тесты:
|
||||
```bash
|
||||
vendor/bin/phpunit -v -c phpunit.xml
|
||||
```
|
||||
### <a name="Changelog"></a> История изменений
|
||||
[Ссылка на Changelog](CHANGELOG.md)
|
||||
### <a name="Upgrade"></a> Обновление версий
|
||||
#### <a name="Upgrade-v2-to-v3"></a> Обновление с версии 2 до версии 3.0
|
||||
Обновите мажорную версию в файле `composer.json` до `^3.0`.
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"nelexa/zip": "^3.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
Затем установите обновления с помощью `Composer`:
|
||||
```bash
|
||||
composer update nelexa/zip
|
||||
```
|
||||
Обновите ваш код для работы с новой версией:
|
||||
- Класс `ZipOutputFile` объединён с `ZipFile` и удалён.
|
||||
+ Замените `new \PhpZip\ZipOutputFile()` на `new \PhpZip\ZipFile()`
|
||||
- Статичиская инициализация методов стала не статической.
|
||||
+ Замените `\PhpZip\ZipFile::openFromFile($filename);` на `(new \PhpZip\ZipFile())->openFile($filename);`
|
||||
+ Замените `\PhpZip\ZipOutputFile::openFromFile($filename);` на `(new \PhpZip\ZipFile())->openFile($filename);`
|
||||
+ Замените `\PhpZip\ZipFile::openFromString($contents);` на `(new \PhpZip\ZipFile())->openFromString($contents);`
|
||||
+ Замените `\PhpZip\ZipFile::openFromStream($stream);` на `(new \PhpZip\ZipFile())->openFromStream($stream);`
|
||||
+ Замените `\PhpZip\ZipOutputFile::create()` на `new \PhpZip\ZipFile()`
|
||||
+ Замените `\PhpZip\ZipOutputFile::openFromZipFile($zipFile)` на `(new \PhpZip\ZipFile())->openFile($filename);`
|
||||
- Переименуйте методы:
|
||||
+ `addFromFile` в `addFile`
|
||||
+ `setLevel` в `setCompressionLevel`
|
||||
+ `ZipFile::setPassword` в `ZipFile::withReadPassword`
|
||||
+ `ZipOutputFile::setPassword` в `ZipFile::withNewPassword`
|
||||
+ `ZipOutputFile::disableEncryptionAllEntries` в `ZipFile::withoutPassword`
|
||||
+ `ZipOutputFile::setComment` в `ZipFile::setArchiveComment`
|
||||
+ `ZipFile::getComment` в `ZipFile::getArchiveComment`
|
||||
- Изменились сигнатуры для методов `addDir`, `addFilesFromGlob`, `addFilesFromRegex`.
|
||||
- Удалены методы:
|
||||
+ `getLevel`
|
||||
+ `setCompressionMethod`
|
||||
+ `setEntryPassword`
|
@@ -1,25 +1,49 @@
|
||||
{
|
||||
"name": "nelexa/zip",
|
||||
"description": "Zip create, modify and extract tool. Alternative ZipArchive.",
|
||||
"type": "library",
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.5"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ne-Lexa",
|
||||
"email": "alexey@nelexa.ru"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"nelexa/buffer": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nelexa\\Zip\\": "src"
|
||||
}
|
||||
}
|
||||
"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": {
|
||||
"ext-zlib": "*",
|
||||
"php": "^5.5 || ^7.0",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
23
phpunit.xml
Normal file
23
phpunit.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php">
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="PhpZip test suite">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
class FilterFileIterator extends \FilterIterator
|
||||
{
|
||||
private $ignoreFiles;
|
||||
private static $ignoreAlways = array('..');
|
||||
|
||||
/**
|
||||
* @param \Iterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\Iterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge(self::$ignoreAlways, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.1.0)<br/>
|
||||
* 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.
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $value
|
||||
*/
|
||||
$value = $this->current();
|
||||
$pathName = $value->getRealPath();
|
||||
foreach ($this->ignoreFiles AS $ignoreFile) {
|
||||
if ($this->endsWith($pathName, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function endsWith($haystack, $needle)
|
||||
{
|
||||
// search forward starting from end minus needle length characters
|
||||
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== FALSE);
|
||||
}
|
||||
}
|
229
src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php
Normal file
229
src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
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
|
||||
*/
|
||||
const STD_DEC_HDR_SIZE = 12;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
];
|
||||
|
||||
/**
|
||||
* Encryption keys
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $keys = [];
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* ZipCryptoEngine constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial keys
|
||||
*
|
||||
* @param string $password
|
||||
*/
|
||||
private function initKeys($password)
|
||||
{
|
||||
$this->keys[0] = 305419896;
|
||||
$this->keys[1] = 591751049;
|
||||
$this->keys[2] = 878082192;
|
||||
foreach (unpack('C*', $password) as $b) {
|
||||
$this->updateKeys($b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update keys.
|
||||
*
|
||||
* @param string $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[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1);
|
||||
$this->keys[2] = PackUtil::toSignedInt32(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update crc.
|
||||
*
|
||||
* @param int $oldCrc
|
||||
* @param string $charAt
|
||||
* @return int
|
||||
*/
|
||||
private function crc32($oldCrc, $charAt)
|
||||
{
|
||||
return (($oldCrc >> 8) & 0xffffff) ^ self::$CRC_TABLE[($oldCrc ^ $charAt) & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
$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;
|
||||
$this->updateKeys($byte);
|
||||
}
|
||||
|
||||
if ($this->entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// compare against the file type from extended local headers
|
||||
$checkByte = ($this->entry->getDosTime() >> 8) & 0xff;
|
||||
} else {
|
||||
// compare against the CRC otherwise
|
||||
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
|
||||
}
|
||||
if ($byte !== $checkByte) {
|
||||
throw new ZipAuthenticationException(sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$this->entry->getName()
|
||||
));
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt byte.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function decryptByte()
|
||||
{
|
||||
$temp = $this->keys[2] | 2;
|
||||
return (($temp * ($temp ^ 1)) >> 8) & 0xffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption data
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws ZipCryptoException
|
||||
*/
|
||||
public function encrypt($data)
|
||||
{
|
||||
$crc = $this->entry->isDataDescriptorRequired() ?
|
||||
($this->entry->getDosTime() & 0x0000ffff) << 16 :
|
||||
$this->entry->getCrc();
|
||||
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
|
||||
|
||||
// Initialize again since the generated bytes were encrypted.
|
||||
$password = $this->entry->getPassword();
|
||||
$this->initKeys($password);
|
||||
|
||||
$headerBytes[self::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff);
|
||||
$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
|
||||
*/
|
||||
private function encryptData($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;
|
||||
}
|
||||
}
|
259
src/PhpZip/Crypto/WinZipAesEngine.php
Normal file
259
src/PhpZip/Crypto/WinZipAesEngine.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
*/
|
||||
class WinZipAesEngine implements ZipEncryptionEngine
|
||||
{
|
||||
/**
|
||||
* The block size of the Advanced Encryption Specification (AES) Algorithm
|
||||
* 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.
|
||||
*/
|
||||
const ITERATION_COUNT = 1000;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* WinZipAesEngine constructor.
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt from stream resource.
|
||||
*
|
||||
* @param string $content Input stream buffer
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
* @throws ZipCryptoException
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
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)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()];
|
||||
|
||||
// Get key strength.
|
||||
$keyStrengthBits = $field->getKeyStrength();
|
||||
$keyStrengthBytes = $keyStrengthBits / 8;
|
||||
|
||||
$pos = $keyStrengthBytes / 2;
|
||||
$salt = substr($content, 0, $pos);
|
||||
$passwordVerifier = substr($content, $pos, self::PWD_VERIFIER_BITS / 8);
|
||||
$pos += self::PWD_VERIFIER_BITS / 8;
|
||||
|
||||
$sha1Size = 20;
|
||||
|
||||
// Init start, end and size of encrypted data.
|
||||
$start = $pos;
|
||||
$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)");
|
||||
}
|
||||
|
||||
// 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!");
|
||||
}
|
||||
|
||||
$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/
|
||||
$password = substr($password, 0, 99);
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$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
|
||||
// Function 2 (PBKDF2) of PKCS #5 V2.0 alias RFC 2898.
|
||||
// Yes, the password verifier is only a 16 bit value.
|
||||
// So we must use the MAC for password verification, too.
|
||||
$keyParam = hash_pbkdf2(
|
||||
"sha1",
|
||||
$password,
|
||||
$salt,
|
||||
self::ITERATION_COUNT,
|
||||
(2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8,
|
||||
true
|
||||
);
|
||||
$key = substr($keyParam, 0, $keyStrengthBytes);
|
||||
$sha1MacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
|
||||
// Verify password.
|
||||
} while (!$passwordVerifier === substr($keyParam, 2 * $keyStrengthBytes));
|
||||
|
||||
$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)");
|
||||
}
|
||||
|
||||
return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decryption or encryption AES-CTR with Segment Integer Count (SIC).
|
||||
*
|
||||
* @param string $str Data
|
||||
* @param string $key Key
|
||||
* @param string $iv IV
|
||||
* @param bool $encrypted If true encryption else decryption
|
||||
* @return string
|
||||
*/
|
||||
private static function aesCtrSegmentIntegerCounter($str, $key, $iv, $encrypted = true)
|
||||
{
|
||||
$numOfBlocks = ceil(strlen($str) / 16);
|
||||
$ctrStr = '';
|
||||
for ($i = 0; $i < $numOfBlocks; ++$i) {
|
||||
for ($j = 0; $j < 16; ++$j) {
|
||||
$n = ord($iv[$j]);
|
||||
if (++$n === 0x100) {
|
||||
// overflow, set this one to 0, increment next
|
||||
$iv[$j] = chr(0);
|
||||
} else {
|
||||
// no overflow, just write incremented number back and abort
|
||||
$iv[$j] = chr($n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$data = substr($str, $i * 16, 16);
|
||||
$ctrStr .= $encrypted ?
|
||||
self::encryptCtr($data, $key, $iv) :
|
||||
self::decryptCtr($data, $key, $iv);
|
||||
}
|
||||
return $ctrStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Raw data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @return string Encrypted data
|
||||
*/
|
||||
private static function encryptCtr($data, $key, $iv)
|
||||
{
|
||||
if (extension_loaded("openssl")) {
|
||||
$numBits = strlen($key) * 8;
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Encrypted data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @return string Raw data
|
||||
*/
|
||||
private static function decryptCtr($data, $key, $iv)
|
||||
{
|
||||
if (extension_loaded("openssl")) {
|
||||
$numBits = strlen($key) * 8;
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption string.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
public function encrypt($content)
|
||||
{
|
||||
// Init key strength.
|
||||
$password = $this->entry->getPassword();
|
||||
if ($password === null) {
|
||||
throw new ZipException('No password was set for the entry "'.$this->entry->getName().'"');
|
||||
}
|
||||
|
||||
// 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());
|
||||
$keyStrengthBytes = $keyStrengthBits / 8;
|
||||
|
||||
$salt = CryptoUtil::randomBytes($keyStrengthBytes / 2);
|
||||
|
||||
$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);
|
||||
|
||||
$key = substr($keyParam, 0, $keyStrengthBytes);
|
||||
|
||||
$content = self::aesCtrSegmentIntegerCounter($content, $key, $iv, true);
|
||||
|
||||
$mac = hash_hmac('sha1', $content, $sha1HMacParam, true);
|
||||
|
||||
return ($salt .
|
||||
substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) .
|
||||
$content .
|
||||
substr($mac, 0, 10)
|
||||
);
|
||||
}
|
||||
}
|
32
src/PhpZip/Crypto/ZipEncryptionEngine.php
Normal file
32
src/PhpZip/Crypto/ZipEncryptionEngine.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
|
||||
/**
|
||||
* Encryption Engine
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipEncryptionEngine
|
||||
{
|
||||
/**
|
||||
* Decryption string.
|
||||
*
|
||||
* @param string $encryptionContent
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function decrypt($encryptionContent);
|
||||
|
||||
/**
|
||||
* Encryption string.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($content);
|
||||
}
|
72
src/PhpZip/Exception/Crc32Exception.php
Normal file
72
src/PhpZip/Exception/Crc32Exception.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate a CRC32 mismatch between the declared value in the
|
||||
* Central File Header and the Data Descriptor or between the declared value
|
||||
* and the computed value from the decompressed data.
|
||||
*
|
||||
* The exception detail message is the name of the ZIP entry.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class Crc32Exception extends ZipException
|
||||
{
|
||||
/**
|
||||
* Expected crc.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $expectedCrc;
|
||||
|
||||
/**
|
||||
* Actual crc.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $actualCrc;
|
||||
|
||||
/**
|
||||
* Crc32Exception constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $expected
|
||||
* @param int $actual
|
||||
*/
|
||||
public function __construct($name, $expected, $actual)
|
||||
{
|
||||
parent::__construct(
|
||||
sprintf(
|
||||
"%s (expected CRC32 value 0x%x, but is actually 0x%x)",
|
||||
$name,
|
||||
$expected,
|
||||
$actual
|
||||
)
|
||||
);
|
||||
assert($expected != $actual);
|
||||
$this->expectedCrc = $expected;
|
||||
$this->actualCrc = $actual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expected crc.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getExpectedCrc()
|
||||
{
|
||||
return $this->expectedCrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns actual crc.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getActualCrc()
|
||||
{
|
||||
return $this->actualCrc;
|
||||
}
|
||||
}
|
14
src/PhpZip/Exception/InvalidArgumentException.php
Normal file
14
src/PhpZip/Exception/InvalidArgumentException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a method has been passed an illegal or
|
||||
* inappropriate argument.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class InvalidArgumentException extends RuntimeException
|
||||
{
|
||||
}
|
14
src/PhpZip/Exception/RuntimeException.php
Normal file
14
src/PhpZip/Exception/RuntimeException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
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 \RuntimeException
|
||||
{
|
||||
}
|
13
src/PhpZip/Exception/ZipAuthenticationException.php
Normal file
13
src/PhpZip/Exception/ZipAuthenticationException.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an authenticated ZIP entry has been tampered with.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipAuthenticationException extends ZipCryptoException
|
||||
{
|
||||
}
|
14
src/PhpZip/Exception/ZipCryptoException.php
Normal file
14
src/PhpZip/Exception/ZipCryptoException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if there is an issue when reading or writing an encrypted ZIP file
|
||||
* or entry.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipCryptoException extends ZipException
|
||||
{
|
||||
}
|
38
src/PhpZip/Exception/ZipEntryNotFoundException.php
Normal file
38
src/PhpZip/Exception/ZipEntryNotFoundException.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
14
src/PhpZip/Exception/ZipException.php
Normal file
14
src/PhpZip/Exception/ZipException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Signals that a Zip exception of some sort has occurred.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @see \Exception
|
||||
*/
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
}
|
14
src/PhpZip/Exception/ZipNotFoundEntry.php
Normal file
14
src/PhpZip/Exception/ZipNotFoundEntry.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if entry not found.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @deprecated Rename class exception, using ZipEntryNotFoundException
|
||||
*/
|
||||
class ZipNotFoundEntry extends ZipEntryNotFoundException
|
||||
{
|
||||
}
|
14
src/PhpZip/Exception/ZipUnsupportMethod.php
Normal file
14
src/PhpZip/Exception/ZipUnsupportMethod.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if entry unsupport compression method.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @deprecated Rename exception class, using ZipUnsupportMethodException
|
||||
*/
|
||||
class ZipUnsupportMethod extends ZipUnsupportMethodException
|
||||
{
|
||||
}
|
7
src/PhpZip/Exception/ZipUnsupportMethodException.php
Normal file
7
src/PhpZip/Exception/ZipUnsupportMethodException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
class ZipUnsupportMethodException extends RuntimeException
|
||||
{
|
||||
}
|
35
src/PhpZip/Extra/ExtraField.php
Normal file
35
src/PhpZip/Extra/ExtraField.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
/**
|
||||
* Extra Field in a Local or Central Header of a ZIP archive.
|
||||
* It defines the common properties of all Extra Fields and how to
|
||||
* serialize/deserialize them to/from byte arrays.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ExtraField
|
||||
{
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
244
src/PhpZip/Extra/ExtraFieldsCollection.php
Normal file
244
src/PhpZip/Extra/ExtraFieldsCollection.php
Normal file
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Represents a collection of Extra Fields as they may
|
||||
* be present at several locations in ZIP files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
|
||||
{
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID to Extra Field.
|
||||
* Must not be null, but may be empty if no Extra Fields are used.
|
||||
* The map is sorted by Header IDs in ascending order.
|
||||
*
|
||||
* @var ExtraField[]
|
||||
*/
|
||||
protected $collection = [];
|
||||
|
||||
/**
|
||||
* Returns the number of Extra Fields in this collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return sizeof($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.
|
||||
*/
|
||||
public function get($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $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.
|
||||
*/
|
||||
public function add(ExtraField $extraField)
|
||||
{
|
||||
$headerId = $extraField::getHeaderId();
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
$this->collection[$headerId] = $extraField;
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Extra Field exists
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function has($headerId)
|
||||
{
|
||||
return isset($this->collection[$headerId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function remove($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $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
|
||||
* @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.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->collection[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to retrieve.
|
||||
* </p>
|
||||
* @return mixed Can return all value types.
|
||||
* @since 5.0.0
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->get($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
* @link 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 ZipException
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($value instanceof ExtraField) {
|
||||
if ($offset !== $value::getHeaderId()) {
|
||||
throw new InvalidArgumentException("Value header id !== array access key");
|
||||
}
|
||||
$this->add($value);
|
||||
} else {
|
||||
throw new InvalidArgumentException('value is not instanceof ' . ExtraField::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
* @return void
|
||||
* @since 5.0.0
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return current($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return void Any returned value is ignored.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
next($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return key($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->offsetExists($this->key());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
reset($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* If clone extra fields.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->collection as $k => $v) {
|
||||
$this->collection[$k] = clone $v;
|
||||
}
|
||||
}
|
||||
}
|
178
src/PhpZip/Extra/ExtraFieldsFactory.php
Normal file
178
src/PhpZip/Extra/ExtraFieldsFactory.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Extra\Fields\DefaultExtraField;
|
||||
use PhpZip\Extra\Fields\JarMarkerExtraField;
|
||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Extra Fields Factory
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFieldsFactory
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected static $registry;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @param ZipEntry|null $entry
|
||||
* @return ExtraFieldsCollection
|
||||
* @throws ZipException
|
||||
*/
|
||||
public static function createExtraFieldCollections($extra, ZipEntry $entry = null)
|
||||
{
|
||||
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||
if ($extra !== null) {
|
||||
$extraLength = strlen($extra);
|
||||
if ($extraLength > 0xffff) {
|
||||
throw new ZipException("Extra Fields too large: " . $extraLength);
|
||||
}
|
||||
$pos = 0;
|
||||
$endPos = $extraLength;
|
||||
|
||||
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);
|
||||
if ($extraField instanceof Zip64ExtraField && $entry !== null) {
|
||||
$extraField->setEntry($entry);
|
||||
}
|
||||
$extraField->deserialize(substr($extra, $pos, $dataSize));
|
||||
$pos += $dataSize;
|
||||
$extraFieldsCollection[$headerId] = $extraField;
|
||||
}
|
||||
}
|
||||
return $extraFieldsCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtraFieldsCollection $extraFieldsCollection
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public static function createSerializedData(ExtraFieldsCollection $extraFieldsCollection)
|
||||
{
|
||||
$extraData = '';
|
||||
foreach ($extraFieldsCollection as $extraField) {
|
||||
$data = $extraField->serialize();
|
||||
$extraData .= pack('vv', $extraField::getHeaderId(), strlen($data));
|
||||
$extraData .= $data;
|
||||
}
|
||||
|
||||
$size = strlen($extraData);
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData);
|
||||
}
|
||||
return $extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* A static factory method which creates a new Extra Field based on the
|
||||
* given Header ID.
|
||||
* 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.
|
||||
*/
|
||||
public static function create($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $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 ($headerId !== $extraField::getHeaderId()) {
|
||||
throw new ZipException('Runtime error support headerId ' . $headerId);
|
||||
}
|
||||
} else {
|
||||
$extraField = new DefaultExtraField($headerId);
|
||||
}
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered extra field classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function getRegistry()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WinZipAesEntryExtraField
|
||||
*/
|
||||
public static function createWinZipAesEntryExtra()
|
||||
{
|
||||
return new WinZipAesEntryExtraField();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NtfsExtraField
|
||||
*/
|
||||
public static function createNtfsExtra()
|
||||
{
|
||||
return new NtfsExtraField();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return Zip64ExtraField
|
||||
*/
|
||||
public static function createZip64Extra(ZipEntry $entry)
|
||||
{
|
||||
return new Zip64ExtraField($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param int $padding
|
||||
* @return ApkAlignmentExtraField
|
||||
*/
|
||||
public static function createApkAlignExtra(ZipEntry $entry, $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;
|
||||
}
|
||||
}
|
111
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
Normal file
111
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
|
||||
/**
|
||||
* Apk Alignment Extra Field
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ApkAlignmentExtraField implements ExtraField
|
||||
{
|
||||
/**
|
||||
* Minimum size (in bytes) of the extensible data block/field used
|
||||
* for alignment of uncompressed entries.
|
||||
*/
|
||||
const ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES = 6;
|
||||
|
||||
const ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $multiple;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $padding;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0xD935;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
if ($this->padding > 0) {
|
||||
$args = array_merge(
|
||||
['vc*', $this->multiple],
|
||||
array_fill(2, $this->padding, 0)
|
||||
);
|
||||
return call_user_func_array('pack', $args);
|
||||
}
|
||||
return pack('v', $this->multiple);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$length = strlen($data);
|
||||
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.");
|
||||
}
|
||||
$this->multiple = unpack('v', $data)[1];
|
||||
$this->padding = $length - 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMultiple()
|
||||
{
|
||||
return $this->multiple;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPadding()
|
||||
{
|
||||
return $this->padding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $multiple
|
||||
*/
|
||||
public function setMultiple($multiple)
|
||||
{
|
||||
$this->multiple = $multiple;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $padding
|
||||
*/
|
||||
public function setPadding($padding)
|
||||
{
|
||||
$this->padding = $padding;
|
||||
}
|
||||
}
|
71
src/PhpZip/Extra/Fields/DefaultExtraField.php
Normal file
71
src/PhpZip/Extra/Fields/DefaultExtraField.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
|
||||
/**
|
||||
* Default implementation for an Extra Field in a Local or Central Header of a
|
||||
* ZIP file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class DefaultExtraField implements ExtraField
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $headerId;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
self::$headerId = $headerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return self::$headerId & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
51
src/PhpZip/Extra/Fields/JarMarkerExtraField.php
Normal file
51
src/PhpZip/Extra/Fields/JarMarkerExtraField.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
|
||||
/**
|
||||
* Jar Marker Extra Field
|
||||
* An executable Java program can be packaged in a JAR file with all the libraries it uses.
|
||||
* Executable JAR files can easily be distinguished from the files packed in the JAR file
|
||||
* by the extra field in the first file, which is hexadecimal in the 0xCAFE bytes series.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class JarMarkerExtraField implements ExtraField
|
||||
{
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0xCAFE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
if (strlen($data) !== 0) {
|
||||
throw new ZipException("JarMarker doesn't expect any data");
|
||||
}
|
||||
}
|
||||
}
|
133
src/PhpZip/Extra/Fields/NtfsExtraField.php
Normal file
133
src/PhpZip/Extra/Fields/NtfsExtraField.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* Access Time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* Create Time
|
||||
*
|
||||
* @var int Unix Time
|
||||
*/
|
||||
private $ctime;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x000a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
$unpack = unpack('vtag/vsizeAttr', substr($data, 0, 4));
|
||||
if ($unpack['sizeAttr'] === 24) {
|
||||
$tagData = substr($data, 4, $unpack['sizeAttr']);
|
||||
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
|
||||
$this->atime = PackUtil::unpackLongLE(substr($tagData, 8, 8)) / 10000000 - 11644473600;
|
||||
$this->ctime = PackUtil::unpackLongLE(substr($tagData, 16, 8)) / 10000000 - 11644473600;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
$serialize = '';
|
||||
if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) {
|
||||
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
|
||||
$atimeLong = ($this->atime + 11644473600) * 10000000;
|
||||
$ctimeLong = ($this->ctime + 11644473600) * 10000000;
|
||||
|
||||
$serialize .= pack('Vvv', 0, 1, 8 * 3)
|
||||
. PackUtil::packLongLE($mtimeLong)
|
||||
. PackUtil::packLongLE($atimeLong)
|
||||
. PackUtil::packLongLE($ctimeLong);
|
||||
}
|
||||
return $serialize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mtime
|
||||
*/
|
||||
public function setMtime($mtime)
|
||||
{
|
||||
$this->mtime = (int)$mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAtime()
|
||||
{
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $atime
|
||||
*/
|
||||
public function setAtime($atime)
|
||||
{
|
||||
$this->atime = (int)$atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCtime()
|
||||
{
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ctime
|
||||
*/
|
||||
public function setCtime($ctime)
|
||||
{
|
||||
$this->ctime = (int)$ctime;
|
||||
}
|
||||
}
|
258
src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php
Normal file
258
src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* 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_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().
|
||||
*/
|
||||
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().
|
||||
*/
|
||||
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
|
||||
];
|
||||
|
||||
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
|
||||
];
|
||||
|
||||
/**
|
||||
* Vendor version.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $vendorVersion = self::VV_AE_1;
|
||||
|
||||
/**
|
||||
* Encryption strength.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $encryptionStrength = self::KEY_STRENGTH_256BIT;
|
||||
|
||||
/**
|
||||
* Zip compression method.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $method;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x9901;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vendor version.
|
||||
*
|
||||
* @see WinZipAesEntryExtraField::VV_AE_1
|
||||
* @see WinZipAesEntryExtraField::VV_AE_2
|
||||
*/
|
||||
public function getVendorVersion()
|
||||
{
|
||||
return $this->vendorVersion & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 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) {
|
||||
throw new ZipException($vendorVersion);
|
||||
}
|
||||
$this->vendorVersion = $vendorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns vendor id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVendorId()
|
||||
{
|
||||
return self::VENDOR_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|int
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getKeyStrength()
|
||||
{
|
||||
return self::keyStrength($this->encryptionStrength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionStrength Encryption strength as bits.
|
||||
* @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);
|
||||
}
|
||||
return $flipKeyStrength[$encryptionStrength];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns compression method.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal encryption method.
|
||||
*
|
||||
* @return int
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
return isset(self::$encryptionMethods[$this->getKeyStrength()]) ?
|
||||
self::$encryptionMethods[$this->getKeyStrength()] :
|
||||
self::$encryptionMethods[self::KEY_STRENGTH_256BIT];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
* @return int
|
||||
* @throws ZipException
|
||||
*/
|
||||
public static function getKeyStrangeFromEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
$flipKey = array_flip(self::$encryptionMethods);
|
||||
if (!isset($flipKey[$encryptionMethod])) {
|
||||
throw new ZipException("Unsupport encryption method " . $encryptionMethod);
|
||||
}
|
||||
return $flipKey[$encryptionMethod];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets compression method.
|
||||
*
|
||||
* @param int $compressionMethod Compression method
|
||||
* @throws ZipException Compression method out of range.
|
||||
*/
|
||||
public function setMethod($compressionMethod)
|
||||
{
|
||||
if (0x0000 > $compressionMethod || $compressionMethod > 0xffff) {
|
||||
throw new ZipException('Compression method out of range');
|
||||
}
|
||||
$this->method = $compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key strength.
|
||||
*
|
||||
* @param int $keyStrength
|
||||
*/
|
||||
public function setKeyStrength($keyStrength)
|
||||
{
|
||||
$this->encryptionStrength = self::encryptionStrength($keyStrength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encryption strength.
|
||||
*
|
||||
* @param int $keyStrength Key strength in bits.
|
||||
* @return int
|
||||
*/
|
||||
public static function encryptionStrength($keyStrength)
|
||||
{
|
||||
return isset(self::$keyStrengths[$keyStrength]) ?
|
||||
self::$keyStrengths[$keyStrength] :
|
||||
self::$keyStrengths[self::KEY_STRENGTH_128BIT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return pack(
|
||||
'vvcv',
|
||||
$this->vendorVersion,
|
||||
self::VENDOR_ID,
|
||||
$this->encryptionStrength,
|
||||
$this->method
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
throw new ZipException('WinZip AES Extra data invalid size: ' . $size . '. Must be ' . self::DATA_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int $vendorVersion
|
||||
* @var int $vendorId
|
||||
* @var int $keyStrength
|
||||
* @var int $method
|
||||
*/
|
||||
$unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', $data);
|
||||
$this->setVendorVersion($unpack['vendorVersion']);
|
||||
if (self::VENDOR_ID !== $unpack['vendorId']) {
|
||||
throw new ZipException('Vendor id invalid: ' . $unpack['vendorId'] . '. Must be ' . self::VENDOR_ID);
|
||||
}
|
||||
$this->setKeyStrength(self::keyStrength($unpack['keyStrength'])); // checked
|
||||
$this->setMethod($unpack['method']);
|
||||
}
|
||||
}
|
117
src/PhpZip/Extra/Fields/Zip64ExtraField.php
Normal file
117
src/PhpZip/Extra/Fields/Zip64ExtraField.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Extra\Fields;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* ZIP64 Extra Field
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class Zip64ExtraField implements ExtraField
|
||||
{
|
||||
/** The Header ID for a ZIP64 Extended Information Extra Field. */
|
||||
const ZIP64_HEADER_ID = 0x0001;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
protected $entry;
|
||||
|
||||
/**
|
||||
* Zip64ExtraField constructor.
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry = null)
|
||||
{
|
||||
if ($entry !== null) {
|
||||
$this->setEntry($entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function setEntry(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x0001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block.
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
if ($this->entry === null) {
|
||||
throw new RuntimeException("entry is null");
|
||||
}
|
||||
$data = '';
|
||||
// Write out Uncompressed Size.
|
||||
$size = $this->entry->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
$data .= PackUtil::packLongLE($size);
|
||||
}
|
||||
// Write out Compressed Size.
|
||||
$compressedSize = $this->entry->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
$data .= PackUtil::packLongLE($compressedSize);
|
||||
}
|
||||
// Write out Relative Header Offset.
|
||||
$offset = $this->entry->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
$data .= PackUtil::packLongLE($offset);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block.
|
||||
* @param string $data
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
public function deserialize($data)
|
||||
{
|
||||
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);
|
||||
$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);
|
||||
$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);
|
||||
$this->entry->setOffset(PackUtil::unpackLongLE(substr($data, $off, 8)));
|
||||
}
|
||||
}
|
||||
}
|
43
src/PhpZip/Mapper/OffsetPositionMapper.php
Normal file
43
src/PhpZip/Mapper/OffsetPositionMapper.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
30
src/PhpZip/Mapper/PositionMapper.php
Normal file
30
src/PhpZip/Mapper/PositionMapper.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
120
src/PhpZip/Model/EndOfCentralDirectory.php
Normal file
120
src/PhpZip/Model/EndOfCentralDirectory.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
/**
|
||||
* Read End of Central Directory
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class EndOfCentralDirectory
|
||||
{
|
||||
/** Zip64 End Of Central Directory Record. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
|
||||
/** Zip64 End Of Central Directory Locator. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
|
||||
/** End Of Central Directory Record signature. */
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
|
||||
/**
|
||||
* The minimum length of the End Of Central Directory Record.
|
||||
*
|
||||
* end of central dir signature 4
|
||||
* number of this disk 2
|
||||
* number of the disk with the
|
||||
* start of the central directory 2
|
||||
* total number of entries in the
|
||||
* central directory on this disk 2
|
||||
* total number of entries in
|
||||
* the central directory 2
|
||||
* size of the central directory 4
|
||||
* offset of start of central *
|
||||
* directory with respect to *
|
||||
* the starting disk number 4
|
||||
* 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
|
||||
* signature 4
|
||||
* number of the disk with the
|
||||
* start of the zip64 end of
|
||||
* central directory 4
|
||||
* relative offset of the zip64
|
||||
* end of central directory record 8
|
||||
* total number of disks 4
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
|
||||
/**
|
||||
* The minimum length of the Zip64 End Of Central Directory Record.
|
||||
*
|
||||
* zip64 end of central dir
|
||||
* signature 4
|
||||
* size of zip64 end of central
|
||||
* directory record 8
|
||||
* version made by 2
|
||||
* version needed to extract 2
|
||||
* number of this disk 4
|
||||
* number of the disk with the
|
||||
* start of the central directory 4
|
||||
* total number of entries in the
|
||||
* central directory on this disk 8
|
||||
* total number of entries in
|
||||
* the central directory 8
|
||||
* size of the central directory 8
|
||||
* offset of start of central
|
||||
* directory with respect to
|
||||
* 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
|
||||
*/
|
||||
private $entryCount;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $zip64 = false;
|
||||
|
||||
/**
|
||||
* EndOfCentralDirectory constructor.
|
||||
* @param int $entryCount
|
||||
* @param null|string $comment
|
||||
* @param bool $zip64
|
||||
*/
|
||||
public function __construct($entryCount, $comment, $zip64 = false)
|
||||
{
|
||||
$this->entryCount = $entryCount;
|
||||
$this->comment = $comment;
|
||||
$this->zip64 = $zip64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getEntryCount()
|
||||
{
|
||||
return $this->entryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZip64()
|
||||
{
|
||||
return $this->zip64;
|
||||
}
|
||||
}
|
49
src/PhpZip/Model/Entry/OutputOffsetEntry.php
Normal file
49
src/PhpZip/Model/Entry/OutputOffsetEntry.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Entry to write to the central directory.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class OutputOffsetEntry
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offset;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* @param int $pos
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct($pos, ZipEntry $entry)
|
||||
{
|
||||
$this->offset = $pos;
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function getEntry()
|
||||
{
|
||||
return $this->entry;
|
||||
}
|
||||
}
|
760
src/PhpZip/Model/Entry/ZipAbstractEntry.php
Normal file
760
src/PhpZip/Model/Entry/ZipAbstractEntry.php
Normal file
@@ -0,0 +1,760 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\DateTimeConverter;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
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
|
||||
*/
|
||||
private $compressedSize = self::UNKNOWN;
|
||||
/**
|
||||
* @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;
|
||||
/**
|
||||
* Collections of Extra Fields.
|
||||
* Keys from Header ID [int] and value Extra Field [ExtraField].
|
||||
* Should be null or may be empty if no Extra Fields are used.
|
||||
*
|
||||
* @var ExtraFieldsCollection
|
||||
*/
|
||||
private $extraFieldsCollection;
|
||||
/**
|
||||
* @var string Comment field.
|
||||
*/
|
||||
private $comment;
|
||||
/**
|
||||
* @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
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
|
||||
|
||||
/**
|
||||
* ZipAbstractEntry constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->extraFieldsCollection = new ExtraFieldsCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEntry(ZipEntry $entry)
|
||||
{
|
||||
$this->setName($entry->getName());
|
||||
$this->setPlatform($entry->getPlatform());
|
||||
$this->setVersionNeededToExtract($entry->getVersionNeededToExtract());
|
||||
$this->setMethod($entry->getMethod());
|
||||
$this->setGeneralPurposeBitFlags($entry->getGeneralPurposeBitFlags());
|
||||
$this->setDosTime($entry->getDosTime());
|
||||
$this->setCrc($entry->getCrc());
|
||||
$this->setCompressedSize($entry->getCompressedSize());
|
||||
$this->setSize($entry->getSize());
|
||||
$this->setExternalAttributes($entry->getExternalAttributes());
|
||||
$this->setOffset($entry->getOffset());
|
||||
$this->setExtra($entry->getExtra());
|
||||
$this->setComment($entry->getComment());
|
||||
$this->setPassword($entry->getPassword());
|
||||
$this->setEncryptionMethod($entry->getEncryptionMethod());
|
||||
$this->setCompressionLevel($entry->getCompressionLevel());
|
||||
$this->setEncrypted($entry->isEncrypted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ZIP entry name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry name.
|
||||
*
|
||||
* @param string $name New entry name
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$length = strlen($name);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new ZipException('Illegal zip entry name parameter');
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit)
|
||||
{
|
||||
if ($bit) {
|
||||
$this->general |= $mask;
|
||||
} else {
|
||||
$this->general &= ~$mask;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->isInit(self::BIT_PLATFORM) ? $this->platform & 0xffff : self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set platform
|
||||
*
|
||||
* @param int $platform
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
$this->setInit(self::BIT_PLATFORM, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
protected function isInit($mask)
|
||||
{
|
||||
return ($this->init & $mask) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @param bool $init
|
||||
*/
|
||||
protected function setInit($mask, $init)
|
||||
{
|
||||
if ($init) {
|
||||
$this->init |= $mask;
|
||||
} else {
|
||||
$this->init &= ~$mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionNeededToExtract()
|
||||
{
|
||||
return $this->versionNeededToExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set version needed to extract.
|
||||
*
|
||||
* @param int $version
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setVersionNeededToExtract($version)
|
||||
{
|
||||
$this->versionNeededToExtract = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZip64ExtensionsRequired()
|
||||
{
|
||||
return 0xffffffff <= $this->getCompressedSize()
|
||||
|| 0xffffffff <= $this->getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compressed size of this entry.
|
||||
*
|
||||
* @see int
|
||||
*/
|
||||
public function getCompressedSize()
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compressed size of this entry.
|
||||
*
|
||||
* @param int $compressedSize The Compressed Size.
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressedSize($compressedSize)
|
||||
{
|
||||
$this->compressedSize = $compressedSize;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uncompressed size of this entry.
|
||||
*
|
||||
* @see ZipEntry::setCompressedSize
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uncompressed size of this entry.
|
||||
*
|
||||
* @param int $size The (Uncompressed) Size.
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setSize($size)
|
||||
{
|
||||
$this->size = $size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return relative Offset Of Local File Header.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setOffset($offset)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
* @return int
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags()
|
||||
{
|
||||
return $this->general & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the General Purpose Bit Flags.
|
||||
*
|
||||
* @var int general
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setGeneralPurposeBitFlags($general)
|
||||
{
|
||||
if (0x0000 > $general || $general > 0xffff) {
|
||||
throw new ZipException('general out of range');
|
||||
}
|
||||
$this->general = $general;
|
||||
if ($this->method === ZipFileInterface::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;
|
||||
} elseif (!$bit1 && $bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_FAST;
|
||||
} elseif ($bit1 && $bit2) {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_SUPER_FAST;
|
||||
} else {
|
||||
$this->compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry is encrypted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->getGeneralPurposeBitFlag(self::GPBF_ENCRYPTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask)
|
||||
{
|
||||
return ($this->general & $mask) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function disableEncryption()
|
||||
{
|
||||
$this->setEncrypted(false);
|
||||
$headerId = WinZipAesEntryExtraField::getHeaderId();
|
||||
if (isset($this->extraFieldsCollection[$headerId])) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->extraFieldsCollection[$headerId];
|
||||
if (self::METHOD_WINZIP_AES === $this->getMethod()) {
|
||||
$this->setMethod($field === null ? self::UNKNOWN : $field->getMethod());
|
||||
}
|
||||
unset($this->extraFieldsCollection[$headerId]);
|
||||
}
|
||||
$this->password = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
* @param bool $encrypted
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted)
|
||||
{
|
||||
$encrypted = (bool)$encrypted;
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compression method for this entry.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
$isInit = $this->isInit(self::BIT_METHOD);
|
||||
return $isInit ?
|
||||
$this->method & 0xffff :
|
||||
self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression method for this entry.
|
||||
*
|
||||
* @param int $method
|
||||
* @return ZipEntry
|
||||
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
|
||||
*/
|
||||
public function setMethod($method)
|
||||
{
|
||||
if ($method === self::UNKNOWN) {
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, false);
|
||||
return $this;
|
||||
}
|
||||
if (0x0000 > $method || $method > 0xffff) {
|
||||
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:
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($this->name . " (unsupported compression method $method)");
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unix Timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTime()
|
||||
{
|
||||
if (!$this->isInit(self::BIT_DATE_TIME)) {
|
||||
return self::UNKNOWN;
|
||||
}
|
||||
return DateTimeConverter::toUnixTimestamp($this->getDosTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDosTime()
|
||||
{
|
||||
return $this->dosTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Dos Time
|
||||
* @param int $dosTime
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setDosTime($dosTime)
|
||||
{
|
||||
$dosTime = sprintf('%u', $dosTime);
|
||||
if (0x00000000 > $dosTime || $dosTime > 0xffffffff) {
|
||||
throw new ZipException('DosTime out of range');
|
||||
}
|
||||
$this->dosTime = $dosTime;
|
||||
$this->setInit(self::BIT_DATE_TIME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setTime($unixTimestamp)
|
||||
{
|
||||
$known = self::UNKNOWN != $unixTimestamp;
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes)
|
||||
{
|
||||
$known = self::UNKNOWN != $externalAttributes;
|
||||
if ($known) {
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
} else {
|
||||
$this->externalAttributes = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_EXTERNAL_ATTR, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory()
|
||||
{
|
||||
return StringUtil::endsWith($this->name, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExtraFieldsCollection
|
||||
*/
|
||||
public function &getExtraFieldsCollection()
|
||||
{
|
||||
return $this->extraFieldsCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the serialized Extra Fields.
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
return ExtraFieldsFactory::createSerializedData($this->extraFieldsCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serialized Extra Fields by making a protective copy.
|
||||
* Note that this method parses the serialized Extra Fields according to
|
||||
* the ZIP File Format Specification and limits its size to 64 KB.
|
||||
* Therefore, this property cannot not be used to hold arbitrary
|
||||
* (application) data.
|
||||
* Consider storing such data in a separate entry instead.
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
*/
|
||||
public function setExtra($data)
|
||||
{
|
||||
$this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comment entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return null !== $this->comment ? $this->comment : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param $comment
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
if ($comment !== null) {
|
||||
$commentLength = strlen($comment);
|
||||
if (0x0000 > $commentLength || $commentLength > 0xffff) {
|
||||
throw new ZipException("Comment too long");
|
||||
}
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
$this->comment = $comment;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDataDescriptorRequired()
|
||||
{
|
||||
return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) == self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return crc32 content or 0 for WinZip AES v2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc()
|
||||
{
|
||||
return $this->crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set crc32 content.
|
||||
*
|
||||
* @param int $crc
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCrc($crc)
|
||||
{
|
||||
$this->crc = $crc;
|
||||
$this->setInit(self::BIT_CRC, true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword()
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password and encryption method from entry
|
||||
*
|
||||
* @param string $password
|
||||
* @param null|int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
$this->password = $password;
|
||||
if ($encryptionMethod !== null) {
|
||||
$this->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
if (!empty($this->password)) {
|
||||
$this->setEncrypted(true);
|
||||
} else {
|
||||
$this->disableEncryption();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
return $this->encryptionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
* @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
if (null !== $encryptionMethod) {
|
||||
if (
|
||||
$encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL
|
||||
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128
|
||||
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192
|
||||
&& $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
||||
) {
|
||||
throw new ZipException('Invalid encryption method');
|
||||
}
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionLevel()
|
||||
{
|
||||
return $this->compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressionLevel
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION)
|
||||
{
|
||||
if ($compressionLevel < ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ||
|
||||
$compressionLevel > ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
) {
|
||||
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
|
||||
ZipFileInterface::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFileInterface::LEVEL_BEST_COMPRESSION);
|
||||
}
|
||||
$this->compressionLevel = $compressionLevel;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->extraFieldsCollection = clone $this->extraFieldsCollection;
|
||||
}
|
||||
}
|
65
src/PhpZip/Model/Entry/ZipChangesEntry.php
Normal file
65
src/PhpZip/Model/Entry/ZipChangesEntry.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Source Entry Changes
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipChangesEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* @var ZipSourceEntry
|
||||
*/
|
||||
protected $entry;
|
||||
|
||||
/**
|
||||
* ZipChangesEntry constructor.
|
||||
* @param ZipSourceEntry $entry
|
||||
* @throws ZipException
|
||||
* @throws \PhpZip\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(ZipSourceEntry $entry)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entry = $entry;
|
||||
$this->setEntry($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isChangedContent()
|
||||
{
|
||||
return !(
|
||||
$this->getCompressionLevel() === $this->entry->getCompressionLevel() &&
|
||||
$this->getMethod() === $this->entry->getMethod() &&
|
||||
$this->isEncrypted() === $this->entry->isEncrypted() &&
|
||||
$this->getEncryptionMethod() === $this->entry->getEncryptionMethod() &&
|
||||
$this->getPassword() === $this->entry->getPassword()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return $this->entry->getEntryContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipSourceEntry
|
||||
*/
|
||||
public function getSourceEntry()
|
||||
{
|
||||
return $this->entry;
|
||||
}
|
||||
}
|
86
src/PhpZip/Model/Entry/ZipNewEntry.php
Normal file
86
src/PhpZip/Model/Entry/ZipNewEntry.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* @var resource|string|null
|
||||
*/
|
||||
protected $content;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $clone = false;
|
||||
|
||||
/**
|
||||
* ZipNewEntry constructor.
|
||||
* @param string|resource|null $content
|
||||
*/
|
||||
public function __construct($content = null)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($content !== null && !is_string($content) && !is_resource($content)) {
|
||||
throw new InvalidArgumentException('invalid content');
|
||||
}
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
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
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->clone = true;
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->clone && $this->content !== null && is_resource($this->content)) {
|
||||
fclose($this->content);
|
||||
$this->content = null;
|
||||
}
|
||||
}
|
||||
}
|
53
src/PhpZip/Model/Entry/ZipNewFileEntry.php
Normal file
53
src/PhpZip/Model/Entry/ZipNewFileEntry.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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 null|string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (!is_file($this->file)) {
|
||||
throw new RuntimeException("File {$this->file} does not exist.");
|
||||
}
|
||||
return file_get_contents($this->file);
|
||||
}
|
||||
}
|
96
src/PhpZip/Model/Entry/ZipSourceEntry.php
Normal file
96
src/PhpZip/Model/Entry/ZipSourceEntry.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
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.
|
||||
*/
|
||||
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 524288; // 512 kb
|
||||
/**
|
||||
* @var ZipInputStreamInterface
|
||||
*/
|
||||
protected $inputStream;
|
||||
/**
|
||||
* @var string|resource Cached entry content.
|
||||
*/
|
||||
protected $entryContent;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $readPassword;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $clone = false;
|
||||
|
||||
/**
|
||||
* ZipSourceEntry constructor.
|
||||
* @param ZipInputStreamInterface $inputStream
|
||||
*/
|
||||
public function __construct(ZipInputStreamInterface $inputStream)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->inputStream = $inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipInputStreamInterface
|
||||
*/
|
||||
public function getInputStream()
|
||||
{
|
||||
return $this->inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if ($this->entryContent === null) {
|
||||
// In order not to unpack again, we cache the content in memory or on disk
|
||||
$content = $this->inputStream->readEntryContent($this);
|
||||
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
|
||||
$this->entryContent = $content;
|
||||
} else {
|
||||
$this->entryContent = fopen('php://temp', 'r+b');
|
||||
fwrite($this->entryContent, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
if (is_resource($this->entryContent)) {
|
||||
rewind($this->entryContent);
|
||||
return stream_get_contents($this->entryContent);
|
||||
}
|
||||
return $this->entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->clone = true;
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->clone && $this->entryContent !== null && is_resource($this->entryContent)) {
|
||||
fclose($this->entryContent);
|
||||
}
|
||||
}
|
||||
}
|
430
src/PhpZip/Model/ZipEntry.php
Normal file
430
src/PhpZip/Model/ZipEntry.php
Normal file
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Pseudo compression method for WinZip AES encrypted entries.
|
||||
* Require php extension openssl or mcrypt.
|
||||
*/
|
||||
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_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.
|
||||
*
|
||||
* local file header signature 4
|
||||
* version needed to extract 2
|
||||
* general purpose bit flag 2
|
||||
* compression method 2
|
||||
* last mod file time 2
|
||||
* last mod file date 2
|
||||
* crc-32 4
|
||||
* compressed size 4
|
||||
* uncompressed size 4
|
||||
* file name length 2
|
||||
* extra field length 2
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_MIN_LEN = 30;
|
||||
/**
|
||||
* Local File Header signature 4
|
||||
* Version Needed To Extract 2
|
||||
* General Purpose Bit Flags 2
|
||||
* Compression Method 2
|
||||
* Last Mod File Time 2
|
||||
* Last Mod File Date 2
|
||||
* CRC-32 4
|
||||
* Compressed Size 4
|
||||
* Uncompressed Size 4
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26;
|
||||
/**
|
||||
* Default compression level for bzip2
|
||||
*/
|
||||
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
|
||||
|
||||
/**
|
||||
* Returns the ZIP entry name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Set entry name.
|
||||
*
|
||||
* @param string $name New entry name
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*/
|
||||
public function getPlatform();
|
||||
|
||||
/**
|
||||
* Set platform
|
||||
*
|
||||
* @param int $platform
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setPlatform($platform);
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionNeededToExtract();
|
||||
|
||||
/**
|
||||
* Set version needed to extract.
|
||||
*
|
||||
* @param int $version
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setVersionNeededToExtract($version);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZip64ExtensionsRequired();
|
||||
|
||||
/**
|
||||
* Returns the compressed size of this entry.
|
||||
*
|
||||
* @see int
|
||||
*/
|
||||
public function getCompressedSize();
|
||||
|
||||
/**
|
||||
* Sets the compressed size of this entry.
|
||||
*
|
||||
* @param int $compressedSize The Compressed Size.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCompressedSize($compressedSize);
|
||||
|
||||
/**
|
||||
* Returns the uncompressed size of this entry.
|
||||
*
|
||||
* @see ZipEntry::setCompressedSize
|
||||
*/
|
||||
public function getSize();
|
||||
|
||||
/**
|
||||
* Sets the uncompressed size of this entry.
|
||||
*
|
||||
* @param int $size The (Uncompressed) Size.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setSize($size);
|
||||
|
||||
/**
|
||||
* Return relative Offset Of Local File Header.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset();
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setOffset($offset);
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory();
|
||||
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags();
|
||||
|
||||
/**
|
||||
* Sets the General Purpose Bit Flags.
|
||||
*
|
||||
* @var int general
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setGeneralPurposeBitFlags($general);
|
||||
|
||||
/**
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask);
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit);
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry is encrypted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted();
|
||||
|
||||
/**
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
* @param bool $encrypted
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted);
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function disableEncryption();
|
||||
|
||||
/**
|
||||
* Returns the compression method for this entry.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMethod();
|
||||
|
||||
/**
|
||||
* Sets the compression method for this entry.
|
||||
*
|
||||
* @param int $method
|
||||
* @return ZipEntry
|
||||
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
|
||||
*/
|
||||
public function setMethod($method);
|
||||
|
||||
/**
|
||||
* Get Unix Timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTime();
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp);
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDosTime();
|
||||
|
||||
/**
|
||||
* Set Dos Time
|
||||
* @param int $dosTime
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setDosTime($dosTime);
|
||||
|
||||
/**
|
||||
* Returns the external file attributes.
|
||||
*
|
||||
* @return int The external file attributes.
|
||||
*/
|
||||
public function getExternalAttributes();
|
||||
|
||||
/**
|
||||
* Sets the external file attributes.
|
||||
*
|
||||
* @param int $externalAttributes the external file attributes.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes);
|
||||
|
||||
/**
|
||||
* @return ExtraFieldsCollection
|
||||
*/
|
||||
public function getExtraFieldsCollection();
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the serialized Extra Fields.
|
||||
*
|
||||
* @return string A new byte array holding the serialized Extra Fields.
|
||||
* null is never returned.
|
||||
*/
|
||||
public function getExtra();
|
||||
|
||||
/**
|
||||
* Sets the serialized Extra Fields by making a protective copy.
|
||||
* Note that this method parses the serialized Extra Fields according to
|
||||
* the ZIP File Format Specification and limits its size to 64 KB.
|
||||
* Therefore, this property cannot not be used to hold arbitrary
|
||||
* (application) data.
|
||||
* Consider storing such data in a separate entry instead.
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
*/
|
||||
public function setExtra($data);
|
||||
|
||||
/**
|
||||
* Returns comment entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment();
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param $comment
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setComment($comment);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDataDescriptorRequired();
|
||||
|
||||
/**
|
||||
* Return crc32 content or 0 for WinZip AES v2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc();
|
||||
|
||||
/**
|
||||
* Set crc32 content.
|
||||
*
|
||||
* @param int $crc
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCrc($crc);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword();
|
||||
|
||||
/**
|
||||
* Set password and encryption method from entry
|
||||
*
|
||||
* @param string $password
|
||||
* @param null|int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod);
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent();
|
||||
|
||||
/**
|
||||
* @param int $compressionLevel
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionLevel();
|
||||
}
|
166
src/PhpZip/Model/ZipEntryMatcher.php
Normal file
166
src/PhpZip/Model/ZipEntryMatcher.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
/**
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipEntryMatcher implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $matches = [];
|
||||
|
||||
/**
|
||||
* ZipEntryMatcher constructor.
|
||||
* @param ZipModel $zipModel
|
||||
*/
|
||||
public function __construct(ZipModel $zipModel)
|
||||
{
|
||||
$this->zipModel = $zipModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
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;
|
||||
}
|
||||
});
|
||||
$this->matches = array_unique($this->matches);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
$this->matches = array_keys($this->zipModel->getEntries());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callable function for all select entries.
|
||||
*
|
||||
* Callable function signature:
|
||||
* function(string $entryName){}
|
||||
*
|
||||
* @param callable $callable
|
||||
*/
|
||||
public function invoke(callable $callable)
|
||||
{
|
||||
if (!empty($this->matches)) {
|
||||
array_walk($this->matches, function ($entryName) use ($callable) {
|
||||
call_user_func($callable, $entryName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMatches()
|
||||
{
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
array_walk($this->matches, function ($entry) {
|
||||
$this->zipModel->deleteEntry($entry);
|
||||
});
|
||||
$this->matches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $password
|
||||
* @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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
*/
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function disableEncryption()
|
||||
{
|
||||
array_walk($this->matches, function ($entry) {
|
||||
$entry = $this->zipModel->getEntry($entry);
|
||||
if (!$entry->isDirectory()) {
|
||||
$entry = $this->zipModel->getEntryForChanges($entry);
|
||||
$entry->disableEncryption();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->matches);
|
||||
}
|
||||
}
|
595
src/PhpZip/Model/ZipInfo.php
Normal file
595
src/PhpZip/Model/ZipInfo.php
Normal file
@@ -0,0 +1,595 @@
|
||||
<?php /** @noinspection PhpMissingBreakStatementInspection */
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Zip info
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
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 */
|
||||
|
||||
private static $valuesMadeBy = [
|
||||
self::MADE_BY_MS_DOS => 'FAT',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
self::MADE_BY_OPEN_VMS => 'OpenVMS',
|
||||
self::MADE_BY_UNIX => 'UNIX',
|
||||
self::MADE_BY_VM_CMS => 'VM/CMS',
|
||||
self::MADE_BY_ATARI => 'Atari ST',
|
||||
self::MADE_BY_OS_2 => 'OS/2 H.P.F.S.',
|
||||
self::MADE_BY_MACINTOSH => 'Macintosh',
|
||||
self::MADE_BY_Z_SYSTEM => 'Z-System',
|
||||
self::MADE_BY_CP_M => 'CP/M',
|
||||
self::MADE_BY_WINDOWS_NTFS => 'Windows NTFS',
|
||||
self::MADE_BY_MVS => 'MVS (OS/390 - Z/OS)',
|
||||
self::MADE_BY_VSE => 'VSE',
|
||||
self::MADE_BY_ACORN_RISC => 'Acorn Risc',
|
||||
self::MADE_BY_VFAT => 'VFAT',
|
||||
self::MADE_BY_ALTERNATE_MVS => 'Alternate MVS',
|
||||
self::MADE_BY_BEOS => 'BeOS',
|
||||
self::MADE_BY_TANDEM => 'Tandem',
|
||||
self::MADE_BY_OS_400 => 'OS/400',
|
||||
self::MADE_BY_OS_X => 'Mac OS X',
|
||||
];
|
||||
|
||||
private static $valuesCompressionMethod = [
|
||||
ZipEntry::UNKNOWN => 'unknown',
|
||||
ZipFileInterface::METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
4 => 'reduce level 3',
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
ZipFileInterface::METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
12 => 'bzip2',
|
||||
13 => 'reserved by PKWARE',
|
||||
14 => 'LZMA (EFS)',
|
||||
15 => 'reserved by PKWARE',
|
||||
16 => 'reserved by PKWARE',
|
||||
17 => 'reserved by PKWARE',
|
||||
18 => 'IBM TERSE',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
97 => 'WavPack',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
ZipEntry::METHOD_WINZIP_AES => 'WinZip AES',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $folder;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressedSize;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $mtime;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $ctime;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $atime;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $encrypted;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $comment;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $crc;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressionMethod;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $platform;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $version;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $attributes;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $encryptionMethod;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $compressionLevel;
|
||||
|
||||
/**
|
||||
* ZipInfo constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$mtime = $entry->getTime();
|
||||
$atime = null;
|
||||
$ctime = null;
|
||||
|
||||
$field = $entry->getExtraFieldsCollection()->get(NtfsExtraField::getHeaderId());
|
||||
if (null !== $field && $field instanceof NtfsExtraField) {
|
||||
/**
|
||||
* @var NtfsExtraField $field
|
||||
*/
|
||||
$atime = $field->getAtime();
|
||||
$ctime = $field->getCtime();
|
||||
$mtime = $field->getMtime();
|
||||
}
|
||||
|
||||
$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->mtime = $mtime;
|
||||
$this->ctime = $ctime;
|
||||
$this->atime = $atime;
|
||||
$this->encrypted = $entry->isEncrypted();
|
||||
$this->encryptionMethod = $entry->getEncryptionMethod();
|
||||
$this->comment = $entry->getComment();
|
||||
$this->crc = $entry->getCrc();
|
||||
$this->compressionMethod = self::getMethodId($entry);
|
||||
$this->methodName = self::getEntryMethodName($entry);
|
||||
$this->platform = self::getPlatformName($entry);
|
||||
$this->version = $entry->getVersionNeededToExtract();
|
||||
$this->compressionLevel = $entry->getCompressionLevel();
|
||||
|
||||
$attributes = str_repeat(" ", 12);
|
||||
$externalAttributes = $entry->getExternalAttributes();
|
||||
$externalAttributes = PHP_INT_SIZE === 4 ?
|
||||
sprintf('%u', $externalAttributes) :
|
||||
$externalAttributes;
|
||||
$xattr = (($externalAttributes >> 16) & 0xFFFF);
|
||||
switch ($entry->getPlatform()) {
|
||||
case self::MADE_BY_MS_DOS:
|
||||
case self::MADE_BY_WINDOWS_NTFS:
|
||||
if ($entry->getPlatform() != self::MADE_BY_MS_DOS ||
|
||||
($xattr & 0700) !=
|
||||
(0400 |
|
||||
(!($externalAttributes & 1) << 7) |
|
||||
(($externalAttributes & 0x10) << 2))
|
||||
) {
|
||||
$xattr = $externalAttributes & 0xFF;
|
||||
$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"])) {
|
||||
$attributes[3] = 'x';
|
||||
}
|
||||
}
|
||||
break;
|
||||
} /* else: fall through! */
|
||||
|
||||
// no break
|
||||
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;
|
||||
default:
|
||||
$attributes[0] = '?';
|
||||
break;
|
||||
}
|
||||
$attributes[1] = ($xattr & self::UNX_IRUSR) ? 'r' : '-';
|
||||
$attributes[4] = ($xattr & self::UNX_IRGRP) ? 'r' : '-';
|
||||
$attributes[7] = ($xattr & self::UNX_IROTH) ? 'r' : '-';
|
||||
$attributes[2] = ($xattr & self::UNX_IWUSR) ? 'w' : '-';
|
||||
$attributes[5] = ($xattr & self::UNX_IWGRP) ? 'w' : '-';
|
||||
$attributes[8] = ($xattr & self::UNX_IWOTH) ? 'w' : '-';
|
||||
|
||||
if ($xattr & self::UNX_IXUSR) {
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
|
||||
} else {
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-';
|
||||
} /* S==undefined */
|
||||
if ($xattr & self::UNX_IXGRP) {
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x';
|
||||
} /* == UNX_ENFMT */
|
||||
else {
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-';
|
||||
} /* SunOS 4.1.x */
|
||||
if ($xattr & self::UNX_IXOTH) {
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x';
|
||||
} /* "sticky bit" */
|
||||
else {
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-';
|
||||
} /* T==undefined */
|
||||
}
|
||||
$this->attributes = trim($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return int
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
private static function getMethodId(ZipEntry $entry)
|
||||
{
|
||||
$method = $entry->getMethod();
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$method = $field->getMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
* @throws \PhpZip\Exception\ZipException
|
||||
*/
|
||||
private static function getEntryMethodName(ZipEntry $entry)
|
||||
{
|
||||
$return = '';
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$return .= '-' . $field->getKeyStrength();
|
||||
if (isset(self::$valuesCompressionMethod[$field->getMethod()])) {
|
||||
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$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 $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';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated use \PhpZip\Model\ZipInfo::getName()
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFolder()
|
||||
{
|
||||
return $this->folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressedSize()
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCtime()
|
||||
{
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getAtime()
|
||||
{
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc()
|
||||
{
|
||||
return $this->crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->getMethodName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethodName()
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
return $this->encryptionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCompressionLevel()
|
||||
{
|
||||
return $this->compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionMethod()
|
||||
{
|
||||
return $this->compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getName(),
|
||||
'path' => $this->getName(), // deprecated
|
||||
'folder' => $this->isFolder(),
|
||||
'size' => $this->getSize(),
|
||||
'compressed_size' => $this->getCompressedSize(),
|
||||
'modified' => $this->getMtime(),
|
||||
'created' => $this->getCtime(),
|
||||
'accessed' => $this->getAtime(),
|
||||
'attributes' => $this->getAttributes(),
|
||||
'encrypted' => $this->isEncrypted(),
|
||||
'encryption_method' => $this->getEncryptionMethod(),
|
||||
'comment' => $this->getComment(),
|
||||
'crc' => $this->getCrc(),
|
||||
'method' => $this->getMethodName(), // deprecated
|
||||
'method_name' => $this->getMethodName(),
|
||||
'compression_method' => $this->getCompressionMethod(),
|
||||
'platform' => $this->getPlatform(),
|
||||
'version' => $this->getVersion()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return __CLASS__ . ' {'
|
||||
. 'Name="' . $this->getName() . '", '
|
||||
. ($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()) . '", ' : '')
|
||||
. ($this->isEncrypted() ? 'Encrypted, ' : '')
|
||||
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
|
||||
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')
|
||||
. 'Method name="' . $this->getMethodName() . '", '
|
||||
. 'Attributes="' . $this->getAttributes() . '", '
|
||||
. 'Platform="' . $this->getPlatform() . '", '
|
||||
. 'Version=' . $this->getVersion()
|
||||
. '}';
|
||||
}
|
||||
}
|
345
src/PhpZip/Model/ZipModel.php
Normal file
345
src/PhpZip/Model/ZipModel.php
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Entry\ZipChangesEntry;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Zip Model
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipModel implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var ZipSourceEntry[]
|
||||
*/
|
||||
protected $inputEntries = [];
|
||||
/**
|
||||
* @var ZipEntry[]
|
||||
*/
|
||||
protected $outEntries = [];
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $archiveComment;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $archiveCommentChanges;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $archiveCommentChanged = false;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
protected $zipAlign;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $zip64;
|
||||
|
||||
/**
|
||||
* @param ZipSourceEntry[] $entries
|
||||
* @param EndOfCentralDirectory $endOfCentralDirectory
|
||||
* @return ZipModel
|
||||
*/
|
||||
public static function newSourceModel(array $entries, EndOfCentralDirectory $endOfCentralDirectory)
|
||||
{
|
||||
$model = new self;
|
||||
$model->inputEntries = $entries;
|
||||
$model->outEntries = $entries;
|
||||
$model->archiveComment = $endOfCentralDirectory->getComment();
|
||||
$model->zip64 = $endOfCentralDirectory->isZip64();
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getArchiveComment()
|
||||
{
|
||||
if ($this->archiveCommentChanged) {
|
||||
return $this->archiveCommentChanges;
|
||||
}
|
||||
return $this->archiveComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setArchiveComment($comment)
|
||||
{
|
||||
if ($comment !== null && strlen($comment) !== 0) {
|
||||
$comment = (string)$comment;
|
||||
$length = strlen($comment);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new InvalidArgumentException('Length comment out of range');
|
||||
}
|
||||
}
|
||||
if ($comment !== $this->archiveComment) {
|
||||
$this->archiveCommentChanges = $comment;
|
||||
$this->archiveCommentChanged = true;
|
||||
} else {
|
||||
$this->archiveCommentChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a password for extracting files.
|
||||
*
|
||||
* @param null|string $password
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setReadPassword($password)
|
||||
{
|
||||
foreach ($this->inputEntries as $entry) {
|
||||
if ($entry->isEncrypted()) {
|
||||
$entry->setPassword($password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setReadPasswordEntry($entryName, $password)
|
||||
{
|
||||
if (!isset($this->inputEntries[$entryName])) {
|
||||
throw new ZipEntryNotFoundException($entryName);
|
||||
}
|
||||
if ($this->inputEntries[$entryName]->isEncrypted()) {
|
||||
$this->inputEntries[$entryName]->setPassword($password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getZipAlign()
|
||||
{
|
||||
return $this->zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $zipAlign
|
||||
*/
|
||||
public function setZipAlign($zipAlign)
|
||||
{
|
||||
$this->zipAlign = $zipAlign === null ? null : (int)$zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZipAlign()
|
||||
{
|
||||
return $this->zipAlign != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $writePassword
|
||||
*/
|
||||
public function setWritePassword($writePassword)
|
||||
{
|
||||
$this->matcher()->all()->setPassword($writePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove password
|
||||
*/
|
||||
public function removePassword()
|
||||
{
|
||||
$this->matcher()->all()->setPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
*/
|
||||
public function removePasswordEntry($entryName)
|
||||
{
|
||||
$this->matcher()->add($entryName)->setPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isArchiveCommentChanged()
|
||||
{
|
||||
return $this->archiveCommentChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $old
|
||||
* @param string|ZipEntry $new
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function renameEntry($old, $new)
|
||||
{
|
||||
$old = $old instanceof ZipEntry ? $old->getName() : (string)$old;
|
||||
$new = $new instanceof ZipEntry ? $new->getName() : (string)$new;
|
||||
|
||||
if (isset($this->outEntries[$new])) {
|
||||
throw new InvalidArgumentException("New entry name " . $new . ' is exists.');
|
||||
}
|
||||
|
||||
$entry = $this->getEntryForChanges($old);
|
||||
$entry->setName($new);
|
||||
$this->deleteEntry($old);
|
||||
$this->addEntry($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
* @return ZipChangesEntry|ZipEntry
|
||||
* @throws ZipException
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function getEntryForChanges($entry)
|
||||
{
|
||||
$entry = $this->getEntry($entry);
|
||||
if ($entry instanceof ZipSourceEntry) {
|
||||
$entry = new ZipChangesEntry($entry);
|
||||
$this->addEntry($entry);
|
||||
}
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return ZipEntry
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function getEntry($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
|
||||
if (isset($this->outEntries[$entryName])) {
|
||||
return $this->outEntries[$entryName];
|
||||
}
|
||||
throw new ZipEntryNotFoundException($entryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entry
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteEntry($entry)
|
||||
{
|
||||
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry;
|
||||
if (isset($this->outEntries[$entry])) {
|
||||
unset($this->outEntries[$entry]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function addEntry(ZipEntry $entry)
|
||||
{
|
||||
$this->outEntries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries with changes.
|
||||
*
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
public function &getEntries()
|
||||
{
|
||||
return $this->outEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName)
|
||||
{
|
||||
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName;
|
||||
return isset($this->outEntries[$entryName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all entries.
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
$this->outEntries = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link 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.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return sizeof($this->outEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive
|
||||
*/
|
||||
public function unchangeAll()
|
||||
{
|
||||
$this->outEntries = $this->inputEntries;
|
||||
$this->unchangeArchiveComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo change archive comment
|
||||
*/
|
||||
public function unchangeArchiveComment()
|
||||
{
|
||||
$this->archiveCommentChanges = null;
|
||||
$this->archiveCommentChanged = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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])) {
|
||||
$this->outEntries[$entry] = $this->inputEntries[$entry];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionMethod
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256)
|
||||
{
|
||||
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function matcher()
|
||||
{
|
||||
return new ZipEntryMatcher($this);
|
||||
}
|
||||
}
|
298
src/PhpZip/Stream/ResponseStream.php
Normal file
298
src/PhpZip/Stream/ResponseStream.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Implement PSR Message Stream
|
||||
*/
|
||||
class ResponseStream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* @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,
|
||||
],
|
||||
'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,
|
||||
],
|
||||
];
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $seekable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $readable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $writable;
|
||||
/**
|
||||
* @var array|mixed|null
|
||||
*/
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @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)) {
|
||||
throw new \InvalidArgumentException('Stream must be a resource');
|
||||
}
|
||||
$this->stream = $stream;
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
$this->seekable = $meta['seekable'];
|
||||
$this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
|
||||
$this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
|
||||
$this->uri = $this->getMetadata('uri');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream metadata as an associative array or retrieve a specific key.
|
||||
*
|
||||
* 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.
|
||||
* @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.
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
if (!$this->stream) {
|
||||
return $key ? null : [];
|
||||
}
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
return isset($meta[$key]) ? $meta[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all data from the stream into a string, from the beginning to end.
|
||||
*
|
||||
* This method MUST attempt to seek to the beginning of the stream before
|
||||
* reading data and read the stream until the end is reached.
|
||||
*
|
||||
* Warning: This could attempt to load a large amount of data into memory.
|
||||
*
|
||||
* This method MUST NOT raise an exception in order to conform with PHP's
|
||||
* string casting operations.
|
||||
*
|
||||
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if (!$this->stream) {
|
||||
return '';
|
||||
}
|
||||
$this->rewind();
|
||||
return (string)stream_get_contents($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the beginning of the stream.
|
||||
*
|
||||
* If the stream is not seekable, this method will raise an exception;
|
||||
* otherwise, it will perform a seek(0).
|
||||
*
|
||||
* @see seek()
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @throws \RuntimeException on failure.
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->seekable && rewind($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the stream if known.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
// Clear the stat cache if the stream has a URI
|
||||
if ($this->uri) {
|
||||
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
|
||||
*
|
||||
* @return int Position of the file pointer
|
||||
* @throws \RuntimeException on error.
|
||||
*/
|
||||
public function tell()
|
||||
{
|
||||
return $this->stream ? ftell($this->stream) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the stream is at the end of the stream.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
return !$this->stream || feof($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is seekable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSeekable()
|
||||
{
|
||||
return $this->seekable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a position in the stream.
|
||||
*
|
||||
* @link 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.
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
$this->seekable && fseek($this->stream, $offset, $whence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is writable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->writable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
$this->size = null;
|
||||
return $this->writable ? fwrite($this->stream, $string) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is readable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function read($length)
|
||||
{
|
||||
return $this->readable ? fread($this->stream, $length) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining contents in a string
|
||||
*
|
||||
* @return string
|
||||
* @throws \RuntimeException if unable to read or an error occurs while
|
||||
* reading.
|
||||
*/
|
||||
public function getContents()
|
||||
{
|
||||
return $this->stream ? stream_get_contents($this->stream) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream when the destructed
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and any underlying resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
$this->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates any underlying resources from the stream.
|
||||
*
|
||||
* After the stream has been detached, the stream is in an unusable state.
|
||||
*
|
||||
* @return resource|null Underlying PHP stream, if any
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
$result = $this->stream;
|
||||
$this->stream = $this->size = $this->uri = null;
|
||||
$this->readable = $this->writable = $this->seekable = false;
|
||||
return $result;
|
||||
}
|
||||
}
|
660
src/PhpZip/Stream/ZipInputStream.php
Normal file
660
src/PhpZip/Stream/ZipInputStream.php
Normal file
@@ -0,0 +1,660 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Read zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipInputStream implements ZipInputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* ZipInputStream constructor.
|
||||
* @param resource $in
|
||||
*/
|
||||
public function __construct($in)
|
||||
{
|
||||
if (!is_resource($in)) {
|
||||
throw new RuntimeException('$in must be resource');
|
||||
}
|
||||
$this->in = $in;
|
||||
$this->mapper = new PositionMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipModel
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readZip()
|
||||
{
|
||||
$this->checkZipFileSignature();
|
||||
$endOfCentralDirectory = $this->readEndOfCentralDirectory();
|
||||
$entries = $this->mountCentralDirectory($endOfCentralDirectory);
|
||||
$this->zipModel = ZipModel::newSourceModel($entries, $endOfCentralDirectory);
|
||||
return $this->zipModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check zip file signature
|
||||
*
|
||||
* @throws ZipException if this not .ZIP file.
|
||||
*/
|
||||
protected function checkZipFileSignature()
|
||||
{
|
||||
rewind($this->in);
|
||||
// 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.");
|
||||
}
|
||||
$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
|
||||
) {
|
||||
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EndOfCentralDirectory
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function readEndOfCentralDirectory()
|
||||
{
|
||||
$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;
|
||||
$min = $max >= 0xffff ? $max - 0xffff : 0;
|
||||
for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) {
|
||||
fseek($this->in, $endOfCentralDirRecordPos, SEEK_SET);
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
if (EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($this->in, 4))[1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// number of this disk - 2 bytes
|
||||
// number of the disk with the start of the
|
||||
// central directory - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory on this disk - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory - 2 bytes
|
||||
// size of the central directory - 4 bytes
|
||||
// offset of start of central directory with
|
||||
// respect to the starting disk number - 4 bytes
|
||||
// ZIP file comment length - 2 bytes
|
||||
$data = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
fread($this->in, 18)
|
||||
);
|
||||
|
||||
if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) {
|
||||
throw new ZipException(
|
||||
"ZIP file spanning/splitting is not supported!"
|
||||
);
|
||||
}
|
||||
// .ZIP file comment (variable size)
|
||||
if ($data['commentLength'] > 0) {
|
||||
$comment = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['commentLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['commentLength'] - $offset);
|
||||
$comment .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
}
|
||||
$this->preamble = $endOfCentralDirRecordPos;
|
||||
$this->postamble = $size - ftell($this->in);
|
||||
|
||||
// Check for ZIP64 End Of Central Directory Locator.
|
||||
$endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN;
|
||||
|
||||
fseek($this->in, $endOfCentralDirLocatorPos, SEEK_SET);
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
if (
|
||||
0 > $endOfCentralDirLocatorPos ||
|
||||
ftell($this->in) === $size ||
|
||||
EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($this->in, 4))[1]
|
||||
) {
|
||||
// Seek and check first CFH, probably requiring an offset mapper.
|
||||
$offset = $endOfCentralDirRecordPos - $data['cdSize'];
|
||||
fseek($this->in, $offset, SEEK_SET);
|
||||
$offset -= $data['cdPos'];
|
||||
if ($offset !== 0) {
|
||||
$this->mapper = new OffsetPositionMapper($offset);
|
||||
}
|
||||
$entryCount = $data['cdEntries'];
|
||||
return new EndOfCentralDirectory($entryCount, $comment);
|
||||
}
|
||||
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
$zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($this->in, 4))[1];
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
$zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// total number of disks 4 bytes
|
||||
$totalDisks = unpack('V', fread($this->in, 4))[1];
|
||||
if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
fseek($this->in, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET);
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
$zip64EndOfCentralDirSig = unpack('V', fread($this->in, 4))[1];
|
||||
if (EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) {
|
||||
throw new ZipException("Expected ZIP64 End Of Central Directory Record!");
|
||||
}
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
fseek($this->in, 12, SEEK_CUR);
|
||||
// number of this disk 4 bytes
|
||||
$diskNo = unpack('V', fread($this->in, 4))[1];
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
$cdDiskNo = unpack('V', fread($this->in, 4))[1];
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
$cdEntriesDisk = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
$cdEntries = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
if ($cdEntries < 0 || 0x7fffffff < $cdEntries) {
|
||||
throw new ZipException("Total Number Of Entries In The Central Directory out of range!");
|
||||
}
|
||||
// size of the central directory 8 bytes
|
||||
fseek($this->in, 8, SEEK_CUR);
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
$cdPos = PackUtil::unpackLongLE(fread($this->in, 8));
|
||||
// zip64 extensible data sector (variable size)
|
||||
fseek($this->in, $cdPos, SEEK_SET);
|
||||
$this->preamble = $zip64EndOfCentralDirectoryRecordPos;
|
||||
$entryCount = $cdEntries;
|
||||
$zip64 = true;
|
||||
return new EndOfCentralDirectory($entryCount, $comment, $zip64);
|
||||
}
|
||||
// Start recovering file entries from min.
|
||||
$this->preamble = $min;
|
||||
$this->postamble = $size - $min;
|
||||
return new EndOfCentralDirectory(0, $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the central directory from the given seekable byte channel
|
||||
* and populates the internal tables with ZipEntry instances.
|
||||
*
|
||||
* The ZipEntry's will know all data that can be obtained from the
|
||||
* central directory alone, but not the data that requires the local
|
||||
* file header or additional data to be read.
|
||||
*
|
||||
* @param EndOfCentralDirectory $endOfCentralDirectory
|
||||
* @return ZipEntry[]
|
||||
* @throws ZipException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
$entries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
if (($numEntries % 0x10000) !== 0) {
|
||||
throw new ZipException("Expected " . abs($numEntries) .
|
||||
($numEntries > 0 ? " more" : " less") .
|
||||
" entries in the Central Directory!");
|
||||
}
|
||||
|
||||
if ($this->preamble + $this->postamble >= fstat($this->in)['size']) {
|
||||
$this->checkZipFileSignature();
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readEntry()
|
||||
{
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
$fileHeaderSig = unpack('V', fread($this->in, 4))[1];
|
||||
if ($fileHeaderSig !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
|
||||
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
|
||||
}
|
||||
|
||||
// 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)
|
||||
);
|
||||
|
||||
// $utf8 = ($data['gpbf'] & ZipEntry::GPBF_UTF8) !== 0;
|
||||
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$name = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['fileLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['fileLength'] - $offset);
|
||||
$name .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
|
||||
$entry = new ZipSourceEntry($this);
|
||||
$entry->setName($name);
|
||||
$entry->setVersionNeededToExtract($data['versionNeededToExtract']);
|
||||
$entry->setPlatform($data['versionMadeBy'] >> 8);
|
||||
$entry->setMethod($data['rawMethod']);
|
||||
$entry->setGeneralPurposeBitFlags($data['gpbf']);
|
||||
$entry->setDosTime($data['rawTime']);
|
||||
$entry->setCrc($data['rawCrc']);
|
||||
$entry->setCompressedSize($data['rawCompressedSize']);
|
||||
$entry->setSize($data['rawSize']);
|
||||
$entry->setExternalAttributes($data['rawExternalAttributes']);
|
||||
$entry->setOffset($data['lfhOff']); // must be unmapped!
|
||||
if ($data['extraLength'] > 0) {
|
||||
$extra = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['extraLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['extraLength'] - $offset);
|
||||
$extra .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
$entry->setExtra($extra);
|
||||
}
|
||||
if ($data['commentLength'] > 0) {
|
||||
$comment = '';
|
||||
$offset = 0;
|
||||
while ($offset < $data['commentLength']) {
|
||||
$read = min(8192 /* chunk size */, $data['commentLength'] - $offset);
|
||||
$comment .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
$entry->setComment($comment);
|
||||
}
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readEntryContent(ZipEntry $entry)
|
||||
{
|
||||
if ($entry->isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
if (!($entry instanceof ZipSourceEntry)) {
|
||||
throw new InvalidArgumentException('entry must be ' . ZipSourceEntry::class);
|
||||
}
|
||||
$isEncrypted = $entry->isEncrypted();
|
||||
if ($isEncrypted && $entry->getPassword() === null) {
|
||||
throw new ZipException("Can not password from entry " . $entry->getName());
|
||||
}
|
||||
|
||||
$pos = $entry->getOffset();
|
||||
$pos = PHP_INT_SIZE === 4
|
||||
? sprintf('%u', $pos) // PHP 32-Bit
|
||||
: $pos; // PHP 64-Bit
|
||||
|
||||
$startPos = $pos = $this->mapper->map($pos);
|
||||
fseek($this->in, $startPos);
|
||||
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
if (unpack('V', fread($this->in, 4))[1] !== ZipEntry::LOCAL_FILE_HEADER_SIG) {
|
||||
throw new ZipException($entry->getName() . " (expected Local File Header)");
|
||||
}
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS);
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
$data = unpack('vfileLength/vextraLength', fread($this->in, 4));
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
|
||||
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
|
||||
|
||||
$method = $entry->getMethod();
|
||||
|
||||
fseek($this->in, $pos);
|
||||
|
||||
// Get raw entry content
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$compressedSize = PHP_INT_SIZE === 4 ? sprintf('%u', $compressedSize) : $compressedSize;
|
||||
$content = '';
|
||||
if ($compressedSize > 0) {
|
||||
$offset = 0;
|
||||
while ($offset < $compressedSize) {
|
||||
$read = min(8192 /* chunk size */, $compressedSize - $offset);
|
||||
$content .= fread($this->in, $read);
|
||||
$offset += $read;
|
||||
}
|
||||
}
|
||||
|
||||
$skipCheckCrc = false;
|
||||
if ($isEncrypted) {
|
||||
if ($method === ZipEntry::METHOD_WINZIP_AES) {
|
||||
// Strong Encryption Specification - WinZip AES
|
||||
$winZipAesEngine = new WinZipAesEngine($entry);
|
||||
$content = $winZipAesEngine->decrypt($content);
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
$method = $field->getMethod();
|
||||
$entry->setEncryptionMethod($field->getEncryptionMethod());
|
||||
$skipCheckCrc = true;
|
||||
} else {
|
||||
// Traditional PKWARE Decryption
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
|
||||
$content = $zipCryptoEngine->decrypt($content);
|
||||
$entry->setEncryptionMethod(ZipFileInterface::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:
|
||||
// All newer apps should write it (and so does TrueVFS),
|
||||
// but older apps might not.
|
||||
fseek($this->in, $pos + $compressedSize);
|
||||
$localCrc = unpack('V', fread($this->in, 4))[1];
|
||||
if ($localCrc === ZipEntry::DATA_DESCRIPTOR_SIG) {
|
||||
$localCrc = unpack('V', fread($this->in, 4))[1];
|
||||
}
|
||||
} else {
|
||||
fseek($this->in, $startPos + 14);
|
||||
// The CRC32 in the Local File Header.
|
||||
$localCrc = sprintf('%u', fread($this->in, 4)[1]);
|
||||
$localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc;
|
||||
}
|
||||
|
||||
$crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc();
|
||||
|
||||
if ($crc != $localCrc) {
|
||||
throw new Crc32Exception($entry->getName(), $crc, $localCrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
break;
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
$content = @gzinflate($content);
|
||||
break;
|
||||
case ZipFileInterface::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 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 ($isEncrypted) {
|
||||
throw new ZipAuthenticationException(sprintf(
|
||||
'Invalid password for zip entry "%s"',
|
||||
$entry->getName()
|
||||
));
|
||||
}
|
||||
throw new Crc32Exception($entry->getName(), $crc, $localCrc);
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->in;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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());
|
||||
|
||||
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 = '';
|
||||
$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));
|
||||
}
|
||||
} else {
|
||||
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||
}
|
||||
|
||||
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||
$copyInToOutLength = $entry->getCompressedSize();
|
||||
|
||||
fseek($this->in, $pos, SEEK_SET);
|
||||
|
||||
if (
|
||||
$this->zipModel->isZipAlign() &&
|
||||
!$entry->isEncrypted() &&
|
||||
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||
) {
|
||||
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
}
|
||||
|
||||
$dataMinStartOffset =
|
||||
ftell($out->getStream()) +
|
||||
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||
$destExtraLength +
|
||||
$nameLength +
|
||||
ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES;
|
||||
$padding =
|
||||
($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple))
|
||||
% $dataAlignmentMultiple;
|
||||
|
||||
$alignExtra = new ApkAlignmentExtraField();
|
||||
$alignExtra->setMultiple($dataAlignmentMultiple);
|
||||
$alignExtra->setPadding($padding);
|
||||
$extraFieldsCollection->add($alignExtra);
|
||||
|
||||
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
|
||||
|
||||
// copy Local File Header without extra field length
|
||||
// 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)));
|
||||
// skip 2 bytes to input stream
|
||||
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);
|
||||
} 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
|
||||
$copyInToOutLength += 8;
|
||||
}
|
||||
}
|
||||
// copy loc, data, data descriptor from input to output stream
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $copyInToOutLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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());
|
||||
|
||||
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);
|
||||
// copy raw data from input stream to output stream
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->in !== null) {
|
||||
fclose($this->in);
|
||||
$this->in = null;
|
||||
}
|
||||
}
|
||||
}
|
55
src/PhpZip/Stream/ZipInputStreamInterface.php
Normal file
55
src/PhpZip/Stream/ZipInputStreamInterface.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
|
||||
/**
|
||||
* Read zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipInputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @return ZipModel
|
||||
*/
|
||||
public function readZip();
|
||||
|
||||
/**
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function readEntry();
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readEntryContent(ZipEntry $entry);
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream();
|
||||
|
||||
/**
|
||||
* 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 ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out);
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param ZipOutputStreamInterface $out
|
||||
*/
|
||||
public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out);
|
||||
|
||||
public function close();
|
||||
}
|
528
src/PhpZip/Stream/ZipOutputStream.php
Normal file
528
src/PhpZip/Stream/ZipOutputStream.php
Normal file
@@ -0,0 +1,528 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Model\Entry\OutputOffsetEntry;
|
||||
use PhpZip\Model\Entry\ZipChangesEntry;
|
||||
use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
* Write zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputStream implements ZipOutputStreamInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $out;
|
||||
/**
|
||||
* @var ZipModel
|
||||
*/
|
||||
protected $zipModel;
|
||||
|
||||
/**
|
||||
* ZipOutputStream constructor.
|
||||
* @param resource $out
|
||||
* @param ZipModel $zipModel
|
||||
*/
|
||||
public function __construct($out, ZipModel $zipModel)
|
||||
{
|
||||
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);
|
||||
}
|
||||
$this->writeEndOfCentralDirectoryRecord($centralDirectoryOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeEntry(ZipEntry $entry)
|
||||
{
|
||||
if ($entry instanceof ZipSourceEntry) {
|
||||
$entry->getInputStream()->copyEntry($entry, $this);
|
||||
return;
|
||||
}
|
||||
|
||||
$entryContent = $this->entryCommitChangesAndReturnContent($entry);
|
||||
|
||||
$offset = ftell($this->out);
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
|
||||
$extra = $entry->getExtra();
|
||||
|
||||
$nameLength = strlen($entry->getName());
|
||||
$extraLength = strlen($extra);
|
||||
|
||||
// zip align
|
||||
if (
|
||||
$this->zipModel->isZipAlign() &&
|
||||
!$entry->isEncrypted() &&
|
||||
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||
) {
|
||||
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
}
|
||||
$dataMinStartOffset =
|
||||
$offset +
|
||||
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||
$extraLength +
|
||||
$nameLength +
|
||||
ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES;
|
||||
|
||||
$padding =
|
||||
($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple))
|
||||
% $dataAlignmentMultiple;
|
||||
|
||||
$alignExtra = new ApkAlignmentExtraField();
|
||||
$alignExtra->setMultiple($dataAlignmentMultiple);
|
||||
$alignExtra->setPadding($padding);
|
||||
|
||||
$extraFieldsCollection = clone $entry->getExtraFieldsCollection();
|
||||
$extraFieldsCollection->add($alignExtra);
|
||||
|
||||
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
|
||||
$extraLength = strlen($extra);
|
||||
}
|
||||
|
||||
$size = $nameLength + $extraLength;
|
||||
if ($size > 0xffff) {
|
||||
throw new ZipException(
|
||||
$entry->getName() . " (the total size of " . $size .
|
||||
" bytes for the name, extra fields and comment " .
|
||||
"exceeds the maximum size of " . 0xffff . " bytes)"
|
||||
);
|
||||
}
|
||||
|
||||
$dd = $entry->isDataDescriptorRequired();
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$entry->getMethod(),
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$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 ($entryContent !== null) {
|
||||
fwrite($this->out, $entryContent);
|
||||
}
|
||||
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getCrc());
|
||||
assert(ZipEntry::UNKNOWN !== $entry->getSize());
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
// crc-32 4 bytes
|
||||
fwrite($this->out, pack('VV', ZipEntry::DATA_DESCRIPTOR_SIG, $entry->getCrc()));
|
||||
// compressed size 4 or 8 bytes
|
||||
// uncompressed size 4 or 8 bytes
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
fwrite($this->out, PackUtil::packLongLE($compressedSize));
|
||||
fwrite($this->out, PackUtil::packLongLE($entry->getSize()));
|
||||
} else {
|
||||
fwrite($this->out, pack('VV', $entry->getCompressedSize(), $entry->getSize()));
|
||||
}
|
||||
} elseif ($compressedSize != $entry->getCompressedSize()) {
|
||||
throw new ZipException(
|
||||
$entry->getName() . " (expected compressed entry size of "
|
||||
. $entry->getCompressedSize() . " bytes, " .
|
||||
"but is actually " . $compressedSize . " bytes)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function entryCommitChangesAndReturnContent(ZipEntry $entry)
|
||||
{
|
||||
if ($entry->getPlatform() === ZipEntry::UNKNOWN) {
|
||||
$entry->setPlatform(ZipEntry::PLATFORM_UNIX);
|
||||
}
|
||||
if ($entry->getTime() === ZipEntry::UNKNOWN) {
|
||||
$entry->setTime(time());
|
||||
}
|
||||
$method = $entry->getMethod();
|
||||
|
||||
$encrypted = $entry->isEncrypted();
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$utf8 = true;
|
||||
|
||||
if ($encrypted && $entry->getPassword() === null) {
|
||||
throw new ZipException("Can not password from entry " . $entry->getName());
|
||||
}
|
||||
|
||||
// Compose General Purpose Bit Flag.
|
||||
$general = ($encrypted ? ZipEntry::GPBF_ENCRYPTED : 0)
|
||||
| ($entry->isDataDescriptorRequired() ? ZipEntry::GPBF_DATA_DESCRIPTOR : 0)
|
||||
| ($utf8 ? ZipEntry::GPBF_UTF8 : 0);
|
||||
|
||||
$entryContent = null;
|
||||
$extraFieldsCollection = $entry->getExtraFieldsCollection();
|
||||
if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) {
|
||||
$entryContent = $entry->getEntryContent();
|
||||
|
||||
if ($entryContent !== null) {
|
||||
$entry->setSize(strlen($entryContent));
|
||||
$entry->setCrc(crc32($entryContent));
|
||||
|
||||
if ($encrypted && $method === ZipEntry::METHOD_WINZIP_AES) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if ($field !== null) {
|
||||
$method = $field->getMethod();
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFileInterface::METHOD_STORED:
|
||||
break;
|
||||
|
||||
case ZipFileInterface::METHOD_DEFLATED:
|
||||
$entryContent = gzdeflate($entryContent, $entry->getCompressionLevel());
|
||||
break;
|
||||
|
||||
case ZipFileInterface::METHOD_BZIP2:
|
||||
$compressionLevel = $entry->getCompressionLevel() === ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ?
|
||||
ZipEntry::LEVEL_DEFAULT_BZIP2_COMPRESSION :
|
||||
$entry->getCompressionLevel();
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
$entryContent = bzcompress($entryContent, $compressionLevel);
|
||||
if (is_int($entryContent)) {
|
||||
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
|
||||
}
|
||||
break;
|
||||
|
||||
case ZipEntry::UNKNOWN:
|
||||
$entryContent = $this->determineBestCompressionMethod($entry, $entryContent);
|
||||
$method = $entry->getMethod();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($entry->getName() . " (unsupported compression method " . $method . ")");
|
||||
}
|
||||
|
||||
if ($method === ZipFileInterface::METHOD_DEFLATED) {
|
||||
$bit1 = false;
|
||||
$bit2 = false;
|
||||
switch ($entry->getCompressionLevel()) {
|
||||
case ZipFileInterface::LEVEL_BEST_COMPRESSION:
|
||||
$bit1 = true;
|
||||
break;
|
||||
|
||||
case ZipFileInterface::LEVEL_FAST:
|
||||
$bit2 = true;
|
||||
break;
|
||||
|
||||
case ZipFileInterface::LEVEL_SUPER_FAST:
|
||||
$bit1 = true;
|
||||
$bit2 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$general |= ($bit1 ? ZipEntry::GPBF_COMPRESSION_FLAG1 : 0);
|
||||
$general |= ($bit2 ? ZipEntry::GPBF_COMPRESSION_FLAG2 : 0);
|
||||
}
|
||||
|
||||
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
|
||||
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
|
||||
$field->setKeyStrength($keyStrength);
|
||||
$field->setMethod($method);
|
||||
$size = $entry->getSize();
|
||||
if ($size >= 20 && $method !== ZipFileInterface::METHOD_BZIP2) {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
||||
} else {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
||||
$entry->setCrc(0);
|
||||
}
|
||||
$extraFieldsCollection->add($field);
|
||||
$entry->setMethod(ZipEntry::METHOD_WINZIP_AES);
|
||||
|
||||
$winZipAesEngine = new WinZipAesEngine($entry);
|
||||
$entryContent = $winZipAesEngine->encrypt($entryContent);
|
||||
} elseif ($entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL) {
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry);
|
||||
$entryContent = $zipCryptoEngine->encrypt($entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
$compressedSize = strlen($entryContent);
|
||||
$entry->setCompressedSize($compressedSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Commit changes.
|
||||
$entry->setGeneralPurposeBitFlags($general);
|
||||
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
$extraFieldsCollection->add(ExtraFieldsFactory::createZip64Extra($entry));
|
||||
} elseif ($extraFieldsCollection->has(Zip64ExtraField::getHeaderId())) {
|
||||
$extraFieldsCollection->remove(Zip64ExtraField::getHeaderId());
|
||||
}
|
||||
return $entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
|
||||
{
|
||||
if ($content !== null) {
|
||||
$entryContent = gzdeflate($content, $entry->getCompressionLevel());
|
||||
if (strlen($entryContent) < strlen($content)) {
|
||||
$entry->setMethod(ZipFileInterface::METHOD_DEFLATED);
|
||||
return $entryContent;
|
||||
}
|
||||
$entry->setMethod(ZipFileInterface::METHOD_STORED);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Central File Header record.
|
||||
*
|
||||
* @param OutputOffsetEntry $outEntry
|
||||
*/
|
||||
protected function writeCentralDirectoryHeader(OutputOffsetEntry $outEntry)
|
||||
{
|
||||
$entry = $outEntry->getEntry();
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$size = $entry->getSize();
|
||||
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
|
||||
// UNKNOWN!
|
||||
if (($compressedSize | $size) === ZipEntry::UNKNOWN) {
|
||||
throw new RuntimeException("invalid entry");
|
||||
}
|
||||
$extra = $entry->getExtra();
|
||||
$extraSize = strlen($extra);
|
||||
|
||||
$commentLength = strlen($entry->getComment());
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvvVVVVvvvvvVV',
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
self::CENTRAL_FILE_HEADER_SIG,
|
||||
// version made by 2 bytes
|
||||
($entry->getPlatform() << 8) | 63,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$entry->getMethod(),
|
||||
// last mod file datetime 4 bytes
|
||||
$entry->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
strlen($entry->getName()),
|
||||
// extra field length 2 bytes
|
||||
$extraSize,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
0,
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$outEntry->getOffset()
|
||||
)
|
||||
);
|
||||
// file name (variable size)
|
||||
fwrite($this->out, $entry->getName());
|
||||
if ($extraSize > 0) {
|
||||
// extra field (variable size)
|
||||
fwrite($this->out, $extra);
|
||||
}
|
||||
if ($commentLength > 0) {
|
||||
// file comment (variable size)
|
||||
fwrite($this->out, $entry->getComment());
|
||||
}
|
||||
}
|
||||
|
||||
protected function writeEndOfCentralDirectoryRecord($centralDirectoryOffset)
|
||||
{
|
||||
$centralDirectoryEntriesCount = 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
|
||||
// signature 4 bytes (0x06064b50)
|
||||
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_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));
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount));
|
||||
// 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));
|
||||
// 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);
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
'VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG,
|
||||
// number of this disk 2 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 2 bytes
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// size of the central directory 4 bytes
|
||||
$centralDirectorySize32,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$centralDirectoryOffset32,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
if ($commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($this->out, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->out;
|
||||
}
|
||||
}
|
29
src/PhpZip/Stream/ZipOutputStreamInterface.php
Normal file
29
src/PhpZip/Stream/ZipOutputStreamInterface.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Stream;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Write zip file
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipOutputStreamInterface
|
||||
{
|
||||
/** Central File Header signature. */
|
||||
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
|
||||
|
||||
public function writeZip();
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function writeEntry(ZipEntry $entry);
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream();
|
||||
}
|
39
src/PhpZip/Util/CryptoUtil.php
Normal file
39
src/PhpZip/Util/CryptoUtil.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Crypto Utils
|
||||
*/
|
||||
class CryptoUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns random bytes.
|
||||
*
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
final public static function randomBytes($length)
|
||||
{
|
||||
$length = (int)$length;
|
||||
if (function_exists('random_bytes')) {
|
||||
try {
|
||||
return random_bytes($length);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException("Could not generate a random string.");
|
||||
}
|
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return openssl_random_pseudo_bytes($length);
|
||||
} elseif (function_exists('mcrypt_create_iv')) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mcrypt_create_iv($length);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
}
|
78
src/PhpZip/Util/DateTimeConverter.php
Normal file
78
src/PhpZip/Util/DateTimeConverter.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Convert unix timestamp values to DOS date/time values and vice versa.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class DateTimeConverter
|
||||
{
|
||||
/**
|
||||
* Smallest supported DOS date/time value in a ZIP file,
|
||||
* which is January 1st, 1980 AD 00:00:00 local time.
|
||||
*/
|
||||
const MIN_DOS_TIME = 0x210000; // (1 << 21) | (1 << 16)
|
||||
|
||||
/**
|
||||
* Largest supported DOS date/time value in a ZIP file,
|
||||
* which is December 31st, 2107 AD 23:59:58 local time.
|
||||
*/
|
||||
const MAX_DOS_TIME = 0xff9fbf7d; // ((2107 - 1980) << 25) | (12 << 21) | (31 << 16) | (23 << 11) | (59 << 5) | (58 >> 1);
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$dosTime = self::MIN_DOS_TIME;
|
||||
} elseif (self::MAX_DOS_TIME < $dosTime) {
|
||||
$dosTime = self::MAX_DOS_TIME;
|
||||
}
|
||||
|
||||
return mktime(
|
||||
($dosTime >> 11) & 0x1f, // hour
|
||||
($dosTime >> 5) & 0x3f, // minute
|
||||
2 * ($dosTime & 0x1f), // second
|
||||
($dosTime >> 21) & 0x0f, // month
|
||||
($dosTime >> 16) & 0x1f, // day
|
||||
1980 + (($dosTime >> 25) & 0x7f) // year
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static function toDosTime($unixTimestamp)
|
||||
{
|
||||
if (0 > $unixTimestamp) {
|
||||
throw new ZipException("Negative unix timestamp: " . $unixTimestamp);
|
||||
}
|
||||
|
||||
$date = getdate($unixTimestamp);
|
||||
|
||||
if ($date['year'] < 1980) {
|
||||
return self::MIN_DOS_TIME;
|
||||
}
|
||||
|
||||
$date['year'] -= 1980;
|
||||
return ($date['year'] << 25 | $date['mon'] << 21 |
|
||||
$date['mday'] << 16 | $date['hours'] << 11 |
|
||||
$date['minutes'] << 5 | $date['seconds'] >> 1);
|
||||
}
|
||||
}
|
247
src/PhpZip/Util/FilesUtil.php
Normal file
247
src/PhpZip/Util/FilesUtil.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Files util.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class FilesUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Is empty directory
|
||||
*
|
||||
* @param string $dir Directory
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEmptyDir($dir)
|
||||
{
|
||||
if (!is_readable($dir)) {
|
||||
return false;
|
||||
}
|
||||
return count(scandir($dir)) === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove recursive directory.
|
||||
*
|
||||
* @param string $dir Directory path.
|
||||
*/
|
||||
public static function removeDir($dir)
|
||||
{
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($files as $fileInfo) {
|
||||
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$function($fileInfo->getRealPath());
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert glob pattern to regex pattern.
|
||||
*
|
||||
* @param string $globPattern
|
||||
* @return string
|
||||
*/
|
||||
public static function convertGlobToRegEx($globPattern)
|
||||
{
|
||||
// Remove beginning and ending * globs because they're useless
|
||||
$globPattern = trim($globPattern, '*');
|
||||
$escaping = false;
|
||||
$inCurrent = 0;
|
||||
$chars = str_split($globPattern);
|
||||
$regexPattern = '';
|
||||
foreach ($chars as $currentChar) {
|
||||
switch ($currentChar) {
|
||||
case '*':
|
||||
$regexPattern .= ($escaping ? "\\*" : '.*');
|
||||
$escaping = false;
|
||||
break;
|
||||
case '?':
|
||||
$regexPattern .= ($escaping ? "\\?" : '.');
|
||||
$escaping = false;
|
||||
break;
|
||||
case '.':
|
||||
case '(':
|
||||
case ')':
|
||||
case '+':
|
||||
case '|':
|
||||
case '^':
|
||||
case '$':
|
||||
case '@':
|
||||
case '%':
|
||||
$regexPattern .= '\\' . $currentChar;
|
||||
$escaping = false;
|
||||
break;
|
||||
case '\\':
|
||||
if ($escaping) {
|
||||
$regexPattern .= "\\\\";
|
||||
$escaping = false;
|
||||
} else {
|
||||
$escaping = true;
|
||||
}
|
||||
break;
|
||||
case '{':
|
||||
if ($escaping) {
|
||||
$regexPattern .= "\\{";
|
||||
} else {
|
||||
$regexPattern = '(';
|
||||
$inCurrent++;
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
case '}':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= ')';
|
||||
$inCurrent--;
|
||||
} elseif ($escaping) {
|
||||
$regexPattern = "\\}";
|
||||
} else {
|
||||
$regexPattern = "}";
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
case ',':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= '|';
|
||||
} elseif ($escaping) {
|
||||
$regexPattern .= "\\,";
|
||||
} else {
|
||||
$regexPattern = ",";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$escaping = false;
|
||||
$regexPattern .= $currentChar;
|
||||
}
|
||||
}
|
||||
return $regexPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files.
|
||||
*
|
||||
* @param string $inputDir
|
||||
* @param bool $recursive
|
||||
* @param array $ignoreFiles
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
|
||||
{
|
||||
$directoryIterator = $recursive ?
|
||||
new \RecursiveDirectoryIterator($inputDir) :
|
||||
new \DirectoryIterator($inputDir);
|
||||
|
||||
if (!empty($ignoreFiles)) {
|
||||
$directoryIterator = $recursive ?
|
||||
new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles) :
|
||||
new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
|
||||
}
|
||||
|
||||
$iterator = $recursive ?
|
||||
new \RecursiveIteratorIterator($directoryIterator) :
|
||||
new \IteratorIterator($directoryIterator);
|
||||
|
||||
$fileList = [];
|
||||
foreach ($iterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files from glob pattern.
|
||||
*
|
||||
* @param string $globPattern
|
||||
* @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;
|
||||
$files = glob($globPattern, $flags);
|
||||
if (!$recursive) {
|
||||
return $files;
|
||||
}
|
||||
foreach (glob(dirname($globPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
|
||||
$files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive));
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files from regex pattern.
|
||||
*
|
||||
* @param string $folder
|
||||
* @param string $pattern
|
||||
* @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);
|
||||
$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 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 << 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";
|
||||
}
|
||||
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 !== '..';
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
61
src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php
Normal file
61
src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Iterator for ignore files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
{
|
||||
/**
|
||||
* Ignore list files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignoreFiles = ['..'];
|
||||
|
||||
/**
|
||||
* @param \Iterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\Iterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $fileInfo
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& StringUtil::endsWith($ignoreFile, '/')
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handler filename
|
||||
if (StringUtil::endsWith($pathname, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Recursive iterator for ignore files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
/**
|
||||
* Ignore list files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignoreFiles = ['..'];
|
||||
|
||||
/**
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $fileInfo
|
||||
*/
|
||||
$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] === '/'
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handler filename
|
||||
if (StringUtil::endsWith($pathname, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IgnoreFilesRecursiveFilterIterator
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
|
||||
}
|
||||
}
|
62
src/PhpZip/Util/PackUtil.php
Normal file
62
src/PhpZip/Util/PackUtil.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* Pack util
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
$left = 0xffffffff00000000;
|
||||
$right = 0x00000000ffffffff;
|
||||
|
||||
$r = ($longValue & $left) >> 32;
|
||||
$l = $longValue & $right;
|
||||
|
||||
return pack('VV', $l, $r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $value
|
||||
* @return int
|
||||
*/
|
||||
public static function unpackLongLE($value)
|
||||
{
|
||||
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
|
||||
*
|
||||
* @param int $int
|
||||
* @return int
|
||||
*/
|
||||
public static function toSignedInt32($int)
|
||||
{
|
||||
if (PHP_INT_SIZE === 8) {
|
||||
$int = $int & 0xffffffff;
|
||||
if ($int & 0x80000000) {
|
||||
return $int - 0x100000000;
|
||||
}
|
||||
}
|
||||
return $int;
|
||||
}
|
||||
}
|
56
src/PhpZip/Util/StringUtil.php
Normal file
56
src/PhpZip/Util/StringUtil.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* String Util
|
||||
*/
|
||||
class StringUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
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
|
||||
&& strpos($haystack, $needle, $temp) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public static function cp866toUtf8($str)
|
||||
{
|
||||
if (function_exists('iconv')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return iconv('CP866', 'UTF-8//IGNORE', $str);
|
||||
} elseif (function_exists('mb_convert_encoding')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
return mb_convert_encoding($str, 'UTF-8', 'CP866');
|
||||
} elseif (class_exists('UConverter')) {
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
$converter = new \UConverter('UTF-8', 'CP866');
|
||||
return $converter->convert($str, false);
|
||||
} else {
|
||||
static $cp866Utf8Pairs;
|
||||
if (empty($cp866Utf8Pairs)) {
|
||||
$cp866Utf8Pairs = require __DIR__ . '/encodings/cp866-utf8.php';
|
||||
}
|
||||
return strtr($str, $cp866Utf8Pairs);
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/PhpZip/Util/encodings/cp866-utf8.php
Normal file
BIN
src/PhpZip/Util/encodings/cp866-utf8.php
Normal file
Binary file not shown.
1542
src/PhpZip/ZipFile.php
Normal file
1542
src/PhpZip/ZipFile.php
Normal file
File diff suppressed because it is too large
Load Diff
606
src/PhpZip/ZipFileInterface.php
Normal file
606
src/PhpZip/ZipFileInterface.php
Normal file
@@ -0,0 +1,606 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipEntryMatcher;
|
||||
use PhpZip\Model\ZipInfo;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Create, open .ZIP files, modify, get info and extract files.
|
||||
*
|
||||
* Implemented support traditional PKWARE encryption and WinZip AES encryption.
|
||||
* Implemented support ZIP64.
|
||||
* Implemented support skip a preamble like the one found in self extracting archives.
|
||||
* Support ZipAlign functional.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
const LEVEL_DEFAULT_COMPRESSION = -1;
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*/
|
||||
const LEVEL_FAST = 2;
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*/
|
||||
const LEVEL_BEST_SPEED = 1;
|
||||
const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED;
|
||||
/**
|
||||
* Compression level for best compression.
|
||||
*/
|
||||
const LEVEL_BEST_COMPRESSION = 9;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES = self::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_192 = 3;
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param string $filename
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException if can't open file.
|
||||
*/
|
||||
public function openFile($filename);
|
||||
|
||||
/**
|
||||
* Open zip archive from raw string data.
|
||||
*
|
||||
* @param string $data
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException if can't open temp stream.
|
||||
*/
|
||||
public function openFromString($data);
|
||||
|
||||
/**
|
||||
* Open zip archive from stream resource
|
||||
*
|
||||
* @param resource $handle
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function openFromStream($handle);
|
||||
|
||||
/**
|
||||
* @return string[] Returns the list files.
|
||||
*/
|
||||
public function getListFiles();
|
||||
|
||||
/**
|
||||
* Returns the file comment.
|
||||
*
|
||||
* @return string The file comment.
|
||||
*/
|
||||
public function getArchiveComment();
|
||||
|
||||
/**
|
||||
* Set archive comment.
|
||||
*
|
||||
* @param null|string $comment
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setArchiveComment($comment = null);
|
||||
|
||||
/**
|
||||
* Checks that the entry in the archive is a directory.
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return bool
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function isDirectory($entryName);
|
||||
|
||||
/**
|
||||
* Returns entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return string
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function getEntryComment($entryName);
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string|null $comment
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function setEntryComment($entryName, $comment = null);
|
||||
|
||||
/**
|
||||
* Returns the entry contents.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContents($entryName);
|
||||
|
||||
/**
|
||||
* Checks if there is an entry in the archive.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName);
|
||||
|
||||
/**
|
||||
* Get info by entry.
|
||||
*
|
||||
* @param string|ZipEntry $entryName
|
||||
* @return ZipInfo
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function getEntryInfo($entryName);
|
||||
|
||||
/**
|
||||
* Get info by all entries.
|
||||
*
|
||||
* @return ZipInfo[]
|
||||
*/
|
||||
public function getAllInfo();
|
||||
|
||||
/**
|
||||
* @return ZipEntryMatcher
|
||||
*/
|
||||
public function matcher();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function extractTo($destination, $entries = null);
|
||||
|
||||
/**
|
||||
* Add entry from the string.
|
||||
*
|
||||
* @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.
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::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.
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::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 int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function addFromStream($stream, $localName, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add an empty directory in the zip archive.
|
||||
*
|
||||
* @param string $dirName
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
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 int|null $compressionMethod Compression method.
|
||||
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2.
|
||||
* If null, then auto choosing method.
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function addDir($inputDir, $localPath = "/", $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add recursive directory to the zip archive.
|
||||
*
|
||||
* @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.
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
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.
|
||||
* @return ZipFileInterface
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::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.
|
||||
* @return ZipFileInterface
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return ZipFileInterface
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return ZipFileInterface
|
||||
* @internal param bool $recursive Recursive search.
|
||||
*/
|
||||
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.
|
||||
* @return ZipFileInterface
|
||||
* @internal param bool $recursive Recursive search.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function addAll(array $mapData);
|
||||
|
||||
/**
|
||||
* Rename the entry.
|
||||
*
|
||||
* @param string $oldName Old entry name.
|
||||
* @param string $newName New entry name.
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function rename($oldName, $newName);
|
||||
|
||||
/**
|
||||
* Delete entry by name.
|
||||
*
|
||||
* @param string $entryName Zip Entry name.
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipEntryNotFoundException If entry not found.
|
||||
*/
|
||||
public function deleteFromName($entryName);
|
||||
|
||||
/**
|
||||
* Delete entries by glob pattern.
|
||||
*
|
||||
* @param string $globPattern Glob pattern
|
||||
* @return ZipFileInterface
|
||||
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
|
||||
*/
|
||||
public function deleteFromGlob($globPattern);
|
||||
|
||||
/**
|
||||
* Delete entries by regex pattern.
|
||||
*
|
||||
* @param string $regexPattern Regex pattern
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function deleteFromRegex($regexPattern);
|
||||
|
||||
/**
|
||||
* Delete all entries
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function deleteAll();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionLevel
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
* @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFileInterface::LEVEL_SUPER_FAST
|
||||
* @see ZipFileInterface::LEVEL_FAST
|
||||
* @see ZipFileInterface::LEVEL_BEST_COMPRESSION
|
||||
*/
|
||||
public function setCompressionLevelEntry($entryName, $compressionLevel);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionMethod
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
* @see ZipFileInterface::METHOD_STORED
|
||||
* @see ZipFileInterface::METHOD_DEFLATED
|
||||
* @see ZipFileInterface::METHOD_BZIP2
|
||||
*/
|
||||
public function setCompressionMethodEntry($entryName, $compressionMethod);
|
||||
|
||||
/**
|
||||
* zipalign is optimization to Android application (APK) files.
|
||||
*
|
||||
* @param int|null $align
|
||||
* @return ZipFileInterface
|
||||
* @link https://developer.android.com/studio/command-line/zipalign.html
|
||||
*/
|
||||
public function setZipAlign($align = null);
|
||||
|
||||
/**
|
||||
* Set password to all input encrypted entries.
|
||||
*
|
||||
* @param string $password Password
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::setReadPassword()
|
||||
*/
|
||||
public function withReadPassword($password);
|
||||
|
||||
/**
|
||||
* Set password to all input encrypted entries.
|
||||
*
|
||||
* @param string $password Password
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setReadPassword($password);
|
||||
|
||||
/**
|
||||
* Set password to concrete input entry.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $password Password
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setReadPasswordEntry($entryName, $password);
|
||||
|
||||
/**
|
||||
* Set password for all entries for update.
|
||||
*
|
||||
* @param string $password If password null then encryption clear
|
||||
* @param int|null $encryptionMethod Encryption method
|
||||
* @return ZipFileInterface
|
||||
* @deprecated using ZipFileInterface::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 int|null $encryptionMethod Encryption method
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
|
||||
|
||||
/**
|
||||
* Sets a new password of an entry defined by its name.
|
||||
*
|
||||
* @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()
|
||||
*/
|
||||
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
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeAll();
|
||||
|
||||
/**
|
||||
* Undo change archive comment
|
||||
* @return ZipFileInterface
|
||||
*/
|
||||
public function unchangeArchiveComment();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Save as file.
|
||||
*
|
||||
* @param string $filename Output filename
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function saveAsFile($filename);
|
||||
|
||||
/**
|
||||
* Save as stream.
|
||||
*
|
||||
* @param resource $handle Output stream resource
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function saveAsStream($handle);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true);
|
||||
|
||||
/**
|
||||
* Returns the zip archive as a string.
|
||||
* @return string
|
||||
*/
|
||||
public function outputAsString();
|
||||
|
||||
/**
|
||||
* Save and reopen zip archive.
|
||||
* @return ZipFileInterface
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function rewrite();
|
||||
|
||||
/**
|
||||
* Close zip archive and release input stream.
|
||||
*/
|
||||
public function close();
|
||||
}
|
905
src/ZipEntry.php
905
src/ZipEntry.php
@@ -1,905 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
use Nelexa\Buffer\Buffer;
|
||||
use Nelexa\Buffer\StringBuffer;
|
||||
|
||||
class ZipEntry
|
||||
{
|
||||
// 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;
|
||||
|
||||
private static $valuesMadeBy = array(
|
||||
self::MADE_BY_MS_DOS => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
self::MADE_BY_OPEN_VMS => 'OpenVMS',
|
||||
self::MADE_BY_UNIX => 'UNIX',
|
||||
self::MADE_BY_VM_CMS => 'VM/CMS',
|
||||
self::MADE_BY_ATARI => 'Atari ST',
|
||||
self::MADE_BY_OS_2 => 'OS/2 H.P.F.S.',
|
||||
self::MADE_BY_MACINTOSH => 'Macintosh',
|
||||
self::MADE_BY_Z_SYSTEM => 'Z-System',
|
||||
self::MADE_BY_CP_M => 'CP/M',
|
||||
self::MADE_BY_WINDOWS_NTFS => 'Windows NTFS',
|
||||
self::MADE_BY_MVS => 'MVS (OS/390 - Z/OS)',
|
||||
self::MADE_BY_VSE => 'VSE',
|
||||
self::MADE_BY_ACORN_RISC => 'Acorn Risc',
|
||||
self::MADE_BY_VFAT => 'VFAT',
|
||||
self::MADE_BY_ALTERNATE_MVS => 'alternate MVS',
|
||||
self::MADE_BY_BEOS => 'BeOS',
|
||||
self::MADE_BY_TANDEM => 'Tandem',
|
||||
self::MADE_BY_OS_400 => 'OS/400',
|
||||
self::MADE_BY_OS_X => 'OS X (Darwin)',
|
||||
);
|
||||
|
||||
// constants version by extract
|
||||
const EXTRACT_VERSION_10 = 10;
|
||||
const EXTRACT_VERSION_11 = 11;
|
||||
const EXTRACT_VERSION_20 = 20;
|
||||
//1.0 - Default value
|
||||
//1.1 - File is a volume label
|
||||
//2.0 - File is a folder (directory)
|
||||
//2.0 - File is compressed using Deflate compression
|
||||
//2.0 - File is encrypted using traditional PKWARE encryption
|
||||
//2.1 - File is compressed using Deflate64(tm)
|
||||
//2.5 - File is compressed using PKWARE DCL Implode
|
||||
//2.7 - File is a patch data set
|
||||
//4.5 - File uses ZIP64 format extensions
|
||||
//4.6 - File is compressed using BZIP2 compression*
|
||||
//5.0 - File is encrypted using DES
|
||||
//5.0 - File is encrypted using 3DES
|
||||
//5.0 - File is encrypted using original RC2 encryption
|
||||
//5.0 - File is encrypted using RC4 encryption
|
||||
//5.1 - File is encrypted using AES encryption
|
||||
//5.1 - File is encrypted using corrected RC2 encryption**
|
||||
//5.2 - File is encrypted using corrected RC2-64 encryption**
|
||||
//6.1 - File is encrypted using non-OAEP key wrapping***
|
||||
//6.2 - Central directory encryption
|
||||
//6.3 - File is compressed using LZMA
|
||||
//6.3 - File is compressed using PPMd+
|
||||
//6.3 - File is encrypted using Blowfish
|
||||
//6.3 - File is encrypted using Twofish
|
||||
|
||||
const FLAG_ENCRYPTION = 0;
|
||||
const FLAG_DATA_DESCRIPTION = 3;
|
||||
const FLAG_UTF8 = 11;
|
||||
private static $valuesFlag = array(
|
||||
self::FLAG_ENCRYPTION => 'encrypted file', // 1 << 0
|
||||
1 => 'compression option', // 1 << 1
|
||||
2 => 'compression option', // 1 << 2
|
||||
self::FLAG_DATA_DESCRIPTION => 'data descriptor', // 1 << 3
|
||||
4 => 'enhanced deflation', // 1 << 4
|
||||
5 => 'compressed patched data', // 1 << 5
|
||||
6 => 'strong encryption', // 1 << 6
|
||||
7 => 'unused', // 1 << 7
|
||||
8 => 'unused', // 1 << 8
|
||||
9 => 'unused', // 1 << 9
|
||||
10 => 'unused', // 1 << 10
|
||||
self::FLAG_UTF8 => 'language encoding', // 1 << 11
|
||||
12 => 'reserved', // 1 << 12
|
||||
13 => 'mask header values', // 1 << 13
|
||||
14 => 'reserved', // 1 << 14
|
||||
15 => 'reserved', // 1 << 15
|
||||
);
|
||||
|
||||
// compression method constants
|
||||
const COMPRESS_METHOD_STORED = 0;
|
||||
const COMPRESS_METHOD_DEFLATED = 8;
|
||||
const COMPRESS_METHOD_AES = 99;
|
||||
|
||||
private static $valuesCompressionMethod = array(
|
||||
self::COMPRESS_METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
4 => 'reduce level 3',
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
self::COMPRESS_METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
12 => 'bzip2',
|
||||
13 => 'reserved by PKWARE',
|
||||
14 => 'LZMA (EFS)',
|
||||
15 => 'reserved by PKWARE',
|
||||
16 => 'reserved by PKWARE',
|
||||
17 => 'reserved by PKWARE',
|
||||
18 => 'IBM TERSE',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
97 => 'WavPack',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
self::COMPRESS_METHOD_AES => 'AES Encryption',
|
||||
);
|
||||
|
||||
const INTERNAL_ATTR_DEFAULT = 0;
|
||||
const EXTERNAL_ATTR_DEFAULT = 0;
|
||||
|
||||
/*
|
||||
* Extra field header ID
|
||||
*/
|
||||
const EXTID_ZIP64 = 0x0001; // Zip64
|
||||
const EXTID_NTFS = 0x000a; // NTFS (for storing full file times information)
|
||||
const EXTID_UNIX = 0x000d; // UNIX
|
||||
const EXTID_EXTT = 0x5455; // Info-ZIP Extended Timestamp
|
||||
const EXTID_UNICODE_FILENAME = 0x7075; // for Unicode filenames
|
||||
const EXTID_UNICODE_ = 0x6375; // for Unicode file comments
|
||||
const EXTID_STORING_STRINGS = 0x5A4C; // for storing strings code pages and Unicode filenames using custom Unicode implementation (see Unicode Support: Using Non-English Characters in Filenames, Comments and Passwords).
|
||||
const EXTID_OFFSETS_COMPRESS_DATA = 0x5A4D; // for saving offsets array from seekable compressed data
|
||||
const EXTID_AES_ENCRYPTION = 0x9901; // WinZip AES encryption (http://www.winzip.com/aes_info.htm)
|
||||
|
||||
/**
|
||||
* entry name
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* version made by
|
||||
* @var int
|
||||
*/
|
||||
private $versionMadeBy = self::MADE_BY_WINDOWS_NTFS;
|
||||
/**
|
||||
* version needed to extract
|
||||
* @var int
|
||||
*/
|
||||
private $versionExtract = self::EXTRACT_VERSION_20;
|
||||
/**
|
||||
* general purpose bit flag
|
||||
* @var int
|
||||
*/
|
||||
private $flag = 0;
|
||||
/**
|
||||
* compression method
|
||||
* @var int
|
||||
*/
|
||||
private $compressionMethod = self::COMPRESS_METHOD_DEFLATED;
|
||||
/**
|
||||
* last mod file datetime
|
||||
* @var int Unix timestamp
|
||||
*/
|
||||
private $lastModDateTime;
|
||||
/**
|
||||
* crc-32
|
||||
* @var int
|
||||
*/
|
||||
private $crc32;
|
||||
/**
|
||||
* compressed size
|
||||
* @var int
|
||||
*/
|
||||
private $compressedSize;
|
||||
/**
|
||||
* uncompressed size
|
||||
* @var int
|
||||
*/
|
||||
private $unCompressedSize;
|
||||
/**
|
||||
* disk number start
|
||||
* @var int
|
||||
*/
|
||||
private $diskNumber = 0;
|
||||
/**
|
||||
* internal file attributes
|
||||
* @var int
|
||||
*/
|
||||
private $internalAttributes = self::INTERNAL_ATTR_DEFAULT;
|
||||
/**
|
||||
* external file attributes
|
||||
* @var int
|
||||
*/
|
||||
private $externalAttributes = self::EXTERNAL_ATTR_DEFAULT;
|
||||
/**
|
||||
* relative offset of local header
|
||||
* @var int
|
||||
*/
|
||||
private $offsetOfLocal;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offsetOfCentral;
|
||||
|
||||
/**
|
||||
* optional extra field data for entry
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $extraCentral = "";
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $extraLocal = "";
|
||||
/**
|
||||
* optional comment string for entry
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $comment = "";
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getLengthOfLocal()
|
||||
{
|
||||
return $this->getLengthLocalHeader() + $this->compressedSize + ($this->hasDataDescriptor() ? 12 : 0);
|
||||
}
|
||||
|
||||
public function getLengthLocalHeader()
|
||||
{
|
||||
return 30 + strlen($this->name) + strlen($this->extraLocal);
|
||||
}
|
||||
|
||||
public function getLengthOfCentral()
|
||||
{
|
||||
return 46 + strlen($this->name) + strlen($this->extraCentral) + strlen($this->comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Buffer $buffer
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readCentralHeader(Buffer $buffer)
|
||||
{
|
||||
$signature = $buffer->getUnsignedInt(); // after offset 4
|
||||
if ($signature !== ZipFile::SIGNATURE_CENTRAL_DIR) {
|
||||
throw new ZipException("Can not read central directory. Bad signature: " . $signature);
|
||||
}
|
||||
$this->versionMadeBy = $buffer->getUnsignedShort(); // after offset 6
|
||||
$this->versionExtract = $buffer->getUnsignedShort(); // after offset 8
|
||||
$this->flag = $buffer->getUnsignedShort(); // after offset 10
|
||||
$this->compressionMethod = $buffer->getUnsignedShort(); // after offset 12
|
||||
$lastModTime = $buffer->getUnsignedShort(); // after offset 14
|
||||
$lastModDate = $buffer->getUnsignedShort(); // after offset 16
|
||||
$this->setLastModifyDosDatetime($lastModTime, $lastModDate);
|
||||
$this->crc32 = $buffer->getUnsignedInt(); // after offset 20
|
||||
$this->compressedSize = $buffer->getUnsignedInt(); // after offset 24
|
||||
$this->unCompressedSize = $buffer->getUnsignedInt(); // after offset 28
|
||||
$fileNameLength = $buffer->getUnsignedShort(); // after offset 30
|
||||
$extraCentralLength = $buffer->getUnsignedShort(); // after offset 32
|
||||
$fileCommentLength = $buffer->getUnsignedShort(); // after offset 34
|
||||
$this->diskNumber = $buffer->getUnsignedShort(); // after offset 36
|
||||
$this->internalAttributes = $buffer->getUnsignedShort(); // after offset 38
|
||||
$this->externalAttributes = $buffer->getUnsignedInt(); // after offset 42
|
||||
$this->offsetOfLocal = $buffer->getUnsignedInt(); // after offset 46
|
||||
$this->name = $buffer->getString($fileNameLength);
|
||||
$this->setExtra($buffer->getString($extraCentralLength));
|
||||
$this->comment = $buffer->getString($fileCommentLength);
|
||||
|
||||
$currentPos = $buffer->position();
|
||||
$buffer->setPosition($this->offsetOfLocal + 28);
|
||||
$extraLocalLength = $buffer->getUnsignedShort();
|
||||
$buffer->skip($fileNameLength);
|
||||
$this->extraLocal = $buffer->getString($extraLocalLength);
|
||||
$buffer->setPosition($currentPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional extra field data for the entry.
|
||||
*
|
||||
* @param string $extra the extra field data bytes
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function setExtra($extra)
|
||||
{
|
||||
if (!empty($extra)) {
|
||||
$len = strlen($extra);
|
||||
if ($len > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$buffer = new StringBuffer($extra);
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
// extra fields are in "HeaderID(2)DataSize(2)Data... format
|
||||
while ($buffer->position() + 4 < $len) {
|
||||
$tag = $buffer->getUnsignedShort();
|
||||
$sz = $buffer->getUnsignedShort();
|
||||
if ($buffer->position() + $sz > $len) // invalid data
|
||||
break;
|
||||
switch ($tag) {
|
||||
case self::EXTID_ZIP64:
|
||||
// not support zip64
|
||||
break;
|
||||
case self::EXTID_NTFS:
|
||||
$buffer->skip(4); // reserved 4 bytes
|
||||
if ($buffer->getUnsignedShort() != 0x0001 || $buffer->getUnsignedShort() != 24)
|
||||
break;
|
||||
// $mtime = winTimeToFileTime($buffer->getLong());
|
||||
// $atime = winTimeToFileTime($buffer->getLong());
|
||||
// $ctime = winTimeToFileTime($buffer->getLong());
|
||||
break;
|
||||
case self::EXTID_EXTT:
|
||||
$flag = $buffer->getUnsignedByte();
|
||||
$sz0 = 1;
|
||||
// The CEN-header extra field contains the modification
|
||||
// time only, or no timestamp at all. 'sz' is used to
|
||||
// flag its presence or absence. But if mtime is present
|
||||
// in LOC it must be present in CEN as well.
|
||||
if (($flag & 0x1) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$mtime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
if (($flag & 0x2) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$atime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
if (($flag & 0x4) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$ctime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->extraCentral = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Buffer
|
||||
*/
|
||||
public function writeLocalHeader()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
$buffer->insertInt(ZipFile::SIGNATURE_LOCAL_HEADER);
|
||||
$buffer->insertShort($this->versionExtract);
|
||||
$buffer->insertShort($this->flag);
|
||||
$buffer->insertShort($this->compressionMethod);
|
||||
$buffer->insertShort($this->getLastModifyDosTime());
|
||||
$buffer->insertShort($this->getLastModifyDosDate());
|
||||
if ($this->hasDataDescriptor()) {
|
||||
$buffer->insertInt(0);
|
||||
$buffer->insertInt(0);
|
||||
$buffer->insertInt(0);
|
||||
} else {
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
}
|
||||
$buffer->insertShort(strlen($this->name));
|
||||
$buffer->insertShort(strlen($this->extraLocal)); // offset 30
|
||||
$buffer->insertString($this->name);
|
||||
$buffer->insertString($this->extraLocal);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bit
|
||||
* @return bool
|
||||
*/
|
||||
public function setFlagBit($bit)
|
||||
{
|
||||
if ($bit < 0 || $bit > 15) {
|
||||
return false;
|
||||
}
|
||||
$this->flag |= 1 << $bit;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bit
|
||||
* @return bool
|
||||
*/
|
||||
public function testFlagBit($bit)
|
||||
{
|
||||
return (($this->flag & (1 << $bit)) !== 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDataDescriptor()
|
||||
{
|
||||
return $this->testFlagBit(self::FLAG_DATA_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->testFlagBit(self::FLAG_ENCRYPTION);
|
||||
}
|
||||
|
||||
public function writeDataDescriptor()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Buffer
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeCentralHeader()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
|
||||
$buffer->insertInt(ZipFile::SIGNATURE_CENTRAL_DIR);
|
||||
$buffer->insertShort($this->versionMadeBy);
|
||||
$buffer->insertShort($this->versionExtract);
|
||||
$buffer->insertShort($this->flag);
|
||||
$buffer->insertShort($this->compressionMethod);
|
||||
$buffer->insertShort($this->getLastModifyDosTime());
|
||||
$buffer->insertShort($this->getLastModifyDosDate());
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
$buffer->insertShort(strlen($this->name));
|
||||
$buffer->insertShort(strlen($this->extraCentral));
|
||||
$buffer->insertShort(strlen($this->comment));
|
||||
$buffer->insertShort($this->diskNumber);
|
||||
$buffer->insertShort($this->internalAttributes);
|
||||
$buffer->insertInt($this->externalAttributes);
|
||||
$buffer->insertInt($this->offsetOfLocal);
|
||||
$buffer->insertString($this->name);
|
||||
$buffer->insertString($this->extraCentral);
|
||||
$buffer->insertString($this->comment);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory()
|
||||
{
|
||||
return $this->name[strlen($this->name) - 1] === "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesMadeBy()
|
||||
{
|
||||
return self::$valuesMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesMadeBy
|
||||
*/
|
||||
public static function setValuesMadeBy($valuesMadeBy)
|
||||
{
|
||||
self::$valuesMadeBy = $valuesMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesFlag()
|
||||
{
|
||||
return self::$valuesFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesFlag
|
||||
*/
|
||||
public static function setValuesFlag($valuesFlag)
|
||||
{
|
||||
self::$valuesFlag = $valuesFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesCompressionMethod()
|
||||
{
|
||||
return self::$valuesCompressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesCompressionMethod
|
||||
*/
|
||||
public static function setValuesCompressionMethod($valuesCompressionMethod)
|
||||
{
|
||||
self::$valuesCompressionMethod = $valuesCompressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
if (strlen($name) > 0xFFFF) {
|
||||
throw new ZipException("entry name too long");
|
||||
}
|
||||
$this->name = $name;
|
||||
$encoding = mb_detect_encoding($this->name, "ASCII, UTF-8", true);
|
||||
if ($encoding === 'UTF-8') {
|
||||
$this->setFlagBit(self::FLAG_UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionMadeBy()
|
||||
{
|
||||
return $this->versionMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionMadeBy
|
||||
*/
|
||||
public function setVersionMadeBy($versionMadeBy)
|
||||
{
|
||||
$this->versionMadeBy = $versionMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionExtract()
|
||||
{
|
||||
return $this->versionExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionExtract
|
||||
*/
|
||||
public function setVersionExtract($versionExtract)
|
||||
{
|
||||
$this->versionExtract = $versionExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFlag()
|
||||
{
|
||||
return $this->flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $flag
|
||||
*/
|
||||
public function setFlag($flag)
|
||||
{
|
||||
$this->flag = $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionMethod()
|
||||
{
|
||||
return $this->compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressionMethod
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCompressionMethod($compressionMethod)
|
||||
{
|
||||
if (!isset(self::$valuesCompressionMethod[$compressionMethod])) {
|
||||
throw new ZipException("invalid compression method " . $compressionMethod);
|
||||
}
|
||||
$this->compressionMethod = $compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModDateTime()
|
||||
{
|
||||
return $this->lastModDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastModDateTime
|
||||
*/
|
||||
public function setLastModDateTime($lastModDateTime)
|
||||
{
|
||||
$this->lastModDateTime = $lastModDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc32()
|
||||
{
|
||||
return $this->crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $crc32
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCrc32($crc32)
|
||||
{
|
||||
if ($crc32 < 0 || $crc32 > 0xFFFFFFFF) {
|
||||
throw new ZipException("invalid entry crc-32");
|
||||
}
|
||||
$this->crc32 = $crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressedSize()
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressedSize
|
||||
*/
|
||||
public function setCompressedSize($compressedSize)
|
||||
{
|
||||
$this->compressedSize = $compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUnCompressedSize()
|
||||
{
|
||||
return $this->unCompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $unCompressedSize
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setUnCompressedSize($unCompressedSize)
|
||||
{
|
||||
if ($unCompressedSize < 0 || $unCompressedSize > 0xFFFFFFFF) {
|
||||
throw new ZipException("invalid entry size");
|
||||
}
|
||||
$this->unCompressedSize = $unCompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDiskNumber()
|
||||
{
|
||||
return $this->diskNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $diskNumber
|
||||
*/
|
||||
public function setDiskNumber($diskNumber)
|
||||
{
|
||||
$this->diskNumber = $diskNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getInternalAttributes()
|
||||
{
|
||||
return $this->internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $internalAttributes
|
||||
*/
|
||||
public function setInternalAttributes($internalAttributes)
|
||||
{
|
||||
$this->internalAttributes = $internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExternalAttributes()
|
||||
{
|
||||
return $this->externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $externalAttributes
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes)
|
||||
{
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetOfLocal()
|
||||
{
|
||||
return $this->offsetOfLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offsetOfLocal
|
||||
*/
|
||||
public function setOffsetOfLocal($offsetOfLocal)
|
||||
{
|
||||
$this->offsetOfLocal = $offsetOfLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetOfCentral()
|
||||
{
|
||||
return $this->offsetOfCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offsetOfCentral
|
||||
*/
|
||||
public function setOffsetOfCentral($offsetOfCentral)
|
||||
{
|
||||
$this->offsetOfCentral = $offsetOfCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraCentral()
|
||||
{
|
||||
return $this->extraCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExtraCentral($extra)
|
||||
{
|
||||
if ($extra !== null && strlen($extra) > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$this->extraCentral = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExtraLocal($extra)
|
||||
{
|
||||
if ($extra !== null && strlen($extra) > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$this->extraLocal = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraLocal()
|
||||
{
|
||||
return $this->extraLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastModTime
|
||||
* @param int $lastModDate
|
||||
*/
|
||||
private function setLastModifyDosDatetime($lastModTime, $lastModDate)
|
||||
{
|
||||
$hour = ($lastModTime & 0xF800) >> 11;
|
||||
$minute = ($lastModTime & 0x07E0) >> 5;
|
||||
$seconds = ($lastModTime & 0x001F) * 2;
|
||||
|
||||
$year = (($lastModDate & 0xFE00) >> 9) + 1980;
|
||||
$month = ($lastModDate & 0x01E0) >> 5;
|
||||
$day = $lastModDate & 0x001F;
|
||||
|
||||
// ----- Get UNIX date format
|
||||
$this->lastModDateTime = mktime($hour, $minute, $seconds, $month, $day, $year);
|
||||
}
|
||||
|
||||
public function getLastModifyDosTime()
|
||||
{
|
||||
$date = getdate($this->lastModDateTime);
|
||||
return ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2;
|
||||
}
|
||||
|
||||
public function getLastModifyDosDate()
|
||||
{
|
||||
$date = getdate($this->lastModDateTime);
|
||||
return (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'];
|
||||
}
|
||||
|
||||
public function versionMadeToString()
|
||||
{
|
||||
if (isset(self::$valuesMadeBy[$this->versionMadeBy])) {
|
||||
return self::$valuesMadeBy[$this->versionMadeBy];
|
||||
} else return "unknown";
|
||||
}
|
||||
|
||||
public function compressionMethodToString()
|
||||
{
|
||||
if (isset(self::$valuesCompressionMethod[$this->compressionMethod])) {
|
||||
return self::$valuesCompressionMethod[$this->compressionMethod];
|
||||
} else return "unknown";
|
||||
}
|
||||
|
||||
public function flagToString()
|
||||
{
|
||||
$return = array();
|
||||
foreach (self::$valuesFlag AS $bit => $value) {
|
||||
if ($this->testFlagBit($bit)) {
|
||||
$return[] = $value;
|
||||
}
|
||||
}
|
||||
if (!empty($return)) {
|
||||
return implode(', ', $return);
|
||||
} else if ($this->flag === 0) {
|
||||
return "default";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return __CLASS__ . '{' .
|
||||
'name="' . $this->name . '"' .
|
||||
', versionMadeBy={' . $this->versionMadeBy . ' => "' . $this->versionMadeToString() . '"}' .
|
||||
', versionExtract="' . $this->versionExtract . '"' .
|
||||
', flag={' . $this->flag . ' => ' . $this->flagToString() . '}' .
|
||||
', compressionMethod={' . $this->compressionMethod . ' => ' . $this->compressionMethodToString() . '}' .
|
||||
', lastModify=' . date("Y-m-d H:i:s", $this->lastModDateTime) .
|
||||
', crc32=0x' . dechex($this->crc32) .
|
||||
', compressedSize=' . ZipUtils::humanSize($this->compressedSize) .
|
||||
', unCompressedSize=' . ZipUtils::humanSize($this->unCompressedSize) .
|
||||
', diskNumber=' . $this->diskNumber .
|
||||
', internalAttributes=' . $this->internalAttributes .
|
||||
', externalAttributes=' . $this->externalAttributes .
|
||||
', offsetOfLocal=' . $this->offsetOfLocal .
|
||||
', offsetOfCentral=' . $this->offsetOfCentral .
|
||||
', extraCentral="' . $this->extraCentral . '"' .
|
||||
', extraLocal="' . $this->extraLocal . '"' .
|
||||
', comment="' . $this->comment . '"' .
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
|
||||
}
|
1374
src/ZipFile.php
1374
src/ZipFile.php
File diff suppressed because it is too large
Load Diff
104
src/ZipUtils.php
104
src/ZipUtils.php
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
use Nelexa\Buffer\MathHelper;
|
||||
|
||||
class ZipUtils
|
||||
{
|
||||
const DECRYPT_HEADER_SIZE = 12;
|
||||
public static $CFH_SIGNATURE = array(0x50, 0x4b, 0x01, 0x02);
|
||||
public static $LFH_SIGNATURE = array(0x50, 0x4b, 0x03, 0x04);
|
||||
public static $ECD_SIGNATURE = array(0x50, 0x4b, 0x05, 0x06);
|
||||
public static $DD_SIGNATURE = array(0x50, 0x4b, 0x07, 0x08);
|
||||
|
||||
private static $CRC_TABLE = array(
|
||||
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,
|
||||
);
|
||||
|
||||
/**
|
||||
* @param int $charAt
|
||||
* @param int[] $keys
|
||||
* @return array
|
||||
*/
|
||||
public static function updateKeys($charAt, array $keys)
|
||||
{
|
||||
$keys[0] = self::crc32($keys[0], $charAt);
|
||||
$keys[1] = MathHelper::add($keys[1], MathHelper::bitwiseAnd($keys[0], 0xff));
|
||||
$keys[1] = MathHelper::castToInt(MathHelper::add(MathHelper::mul($keys[1], 134775813), 1));
|
||||
$keys[2] = self::crc32($keys[2], MathHelper::rightShift32($keys[1], 24));
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alg: ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff])
|
||||
*
|
||||
* @param int $oldCrc
|
||||
* @param int $charAt
|
||||
* @return int|string
|
||||
*/
|
||||
public static function crc32($oldCrc, $charAt)
|
||||
{
|
||||
return MathHelper::castToInt(MathHelper::bitwiseXor(MathHelper::unsignedRightShift32($oldCrc, 8), self::$CRC_TABLE[MathHelper::bitwiseAnd(MathHelper::bitwiseXor($oldCrc, $charAt), 0xff)]));
|
||||
}
|
||||
|
||||
public static function decryptByte($byte)
|
||||
{
|
||||
$temp = $byte | 2;
|
||||
return MathHelper::unsignedRightShift32($temp * ($temp ^ 1), 8);
|
||||
}
|
||||
|
||||
public static function humanSize($size, $unit = "")
|
||||
{
|
||||
if ((!$unit && $size >= 1 << 30) || $unit == "GB")
|
||||
return number_format($size / (1 << 30), 2) . "GB";
|
||||
if ((!$unit && $size >= 1 << 20) || $unit == "MB")
|
||||
return number_format($size / (1 << 20), 2) . "MB";
|
||||
if ((!$unit && $size >= 1 << 10) || $unit == "KB")
|
||||
return number_format($size / (1 << 10), 2) . "KB";
|
||||
return number_format($size) . " bytes";
|
||||
}
|
||||
|
||||
|
||||
}
|
104
tests/PhpZip/Issue24Test.php
Normal file
104
tests/PhpZip/Issue24Test.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
class Issue24Test extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* This method is called before the first test of this test class is run.
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
stream_wrapper_register("dummyfs", DummyFileSystemStream::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testDummyFS()
|
||||
{
|
||||
$fileContents = str_repeat(base64_encode(CryptoUtil::randomBytes(12000)), 100);
|
||||
|
||||
// create zip file
|
||||
$zip = new ZipFile();
|
||||
$zip->addFromString(
|
||||
'file.txt',
|
||||
$fileContents,
|
||||
ZipFile::METHOD_DEFLATED
|
||||
);
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb');
|
||||
$this->assertNotFalse($stream);
|
||||
$zip->openFromStream($stream);
|
||||
$this->assertEquals($zip->getListFiles(), ['file.txt']);
|
||||
$this->assertEquals($zip['file.txt'], $fileContents);
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load using dummy stream
|
||||
*/
|
||||
class DummyFileSystemStream
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $fp;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_open($path, $mode, $options)" . PHP_EOL;
|
||||
|
||||
$parsedUrl = parse_url($path);
|
||||
$path = $parsedUrl['path'];
|
||||
$this->fp = fopen($path, $mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_read($count)" . PHP_EOL;
|
||||
$position = ftell($this->fp);
|
||||
|
||||
// echo "Loading chunk " . $position . " to " . ($position + $count - 1) . PHP_EOL;
|
||||
$ret = fread($this->fp, $count);
|
||||
|
||||
// echo "String length: " . strlen($ret) . PHP_EOL;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_tell()" . PHP_EOL;
|
||||
return ftell($this->fp);
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_eof()" . PHP_EOL;
|
||||
$isfeof = feof($this->fp);
|
||||
return $isfeof;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_seek($offset, $whence)" . PHP_EOL;
|
||||
fseek($this->fp, $offset, $whence);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
// echo "DummyFileSystemStream->stream_stat()" . PHP_EOL;
|
||||
return fstat($this->fp);
|
||||
}
|
||||
}
|
148
tests/PhpZip/PhpZipExtResourceTest.php
Normal file
148
tests/PhpZip/PhpZipExtResourceTest.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Some tests from the official extension of php-zip.
|
||||
*/
|
||||
class PhpZipExtResourceTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/binarynull.zip';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
foreach ($zipFile as $name => $contents) {
|
||||
$info = $zipFile->getEntryInfo($name);
|
||||
$this->assertEquals(strlen($contents), $info->getSize());
|
||||
}
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/bug8009.zip';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->addFromString('2.txt', '=)');
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertCount(2, $zipFile);
|
||||
$this->assertTrue(isset($zipFile['1.txt']));
|
||||
$this->assertTrue(isset($zipFile['2.txt']));
|
||||
$this->assertEquals($zipFile['2.txt'], $zipFile['1.txt']);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #40228 (extractTo does not create recursive empty path)
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228.phpt
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228-mb.phpt
|
||||
* @dataProvider provideBug40228
|
||||
* @param string $filename
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBug40228($filename)
|
||||
{
|
||||
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->extractTo($this->outputDirname);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertTrue(is_dir($this->outputDirname . '/test/empty'));
|
||||
}
|
||||
|
||||
public function provideBug40228()
|
||||
{
|
||||
return [
|
||||
[__DIR__ . '/php-zip-ext-test-resources/bug40228.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()
|
||||
{
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/bug49072.zip';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->getEntryContents('file1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug #70752 (Depacking with wrong password leaves 0 length files)
|
||||
* @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug70752.phpt
|
||||
* @expectedException \PhpZip\Exception\ZipAuthenticationException
|
||||
* @expectedExceptionMessage nvalid password for zip entry "bug70752.txt"
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBug70752()
|
||||
{
|
||||
$filename = __DIR__ . '/php-zip-ext-test-resources/bug70752.zip';
|
||||
|
||||
$this->assertTrue(mkdir($this->outputDirname, 0755, true));
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
try {
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->setReadPassword('bar');
|
||||
$zipFile->extractTo($this->outputDirname);
|
||||
$this->markTestIncomplete('failed test');
|
||||
} catch (ZipException $exception) {
|
||||
$this->assertFalse(file_exists($this->outputDirname . '/bug70752.txt'));
|
||||
$zipFile->close();
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
$entryName = 'MYLOGOV2.GFX';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
|
||||
$info = $zipFile->getEntryInfo($entryName);
|
||||
$this->assertTrue($info->getSize() > 0);
|
||||
|
||||
$contents = $zipFile[$entryName];
|
||||
$this->assertEquals(strlen($contents), $info->getSize());
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
158
tests/PhpZip/ZipAlignTest.php
Normal file
158
tests/PhpZip/ZipAlignTest.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* Test ZipAlign
|
||||
*/
|
||||
class ZipAlignTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testApkAlignedAndSetZipAlignAndReSave()
|
||||
{
|
||||
$filename = __DIR__ . '/resources/apk.zip';
|
||||
|
||||
$this->assertCorrectZipArchive($filename);
|
||||
$result = $this->assertVerifyZipAlign($filename);
|
||||
if (null !== $result) {
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
|
||||
if (null !== $result) {
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test zip alignment.
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testZipAlignSourceZip()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile->addFromString(
|
||||
'entry' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
ZipFileInterface::METHOD_STORED
|
||||
);
|
||||
}
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if ($result === null) {
|
||||
return;
|
||||
} // zip align not installed
|
||||
|
||||
// check not zip align
|
||||
$this->assertFalse($result);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
|
||||
$this->assertNotNull($result);
|
||||
|
||||
// check zip align
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
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
|
||||
);
|
||||
}
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if ($result === null) {
|
||||
return;
|
||||
} // zip align not installed
|
||||
// check not zip align
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
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
|
||||
);
|
||||
}
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if ($result === null) {
|
||||
return;
|
||||
} // zip align not installed
|
||||
|
||||
// check not zip align
|
||||
$this->assertFalse($result);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->deleteFromRegex("~entry2[\d]+\.txt$~s");
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$isStored = (bool)mt_rand(0, 1);
|
||||
|
||||
$zipFile->addFromString(
|
||||
'entry_new_' . ($isStored ? 'stored' : 'deflated') . '_' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
$isStored ?
|
||||
ZipFileInterface::METHOD_STORED :
|
||||
ZipFileInterface::METHOD_DEFLATED
|
||||
);
|
||||
}
|
||||
$zipFile->setZipAlign(4);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename, true);
|
||||
$this->assertNotNull($result);
|
||||
|
||||
// check zip align
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
}
|
47
tests/PhpZip/ZipEventTest.php
Normal file
47
tests/PhpZip/ZipEventTest.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
class ZipFileExtended extends ZipFile
|
||||
{
|
||||
protected function onBeforeSave()
|
||||
{
|
||||
parent::onBeforeSave();
|
||||
$this->setZipAlign(4);
|
||||
$this->deleteFromRegex('~^META\-INF/~i');
|
||||
}
|
||||
}
|
||||
|
||||
class ZipEventTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBeforeSave()
|
||||
{
|
||||
$zipFile = new ZipFileExtended();
|
||||
$zipFile->openFile(__DIR__ . '/resources/apk.zip');
|
||||
$this->assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
$this->assertTrue(isset($zipFile['META-INF/CERT.SF']));
|
||||
$this->assertTrue(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
$result = $this->assertVerifyZipAlign($this->outputFilename);
|
||||
if (null !== $result) {
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
$this->assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
418
tests/PhpZip/ZipFileAddDirTest.php
Normal file
418
tests/PhpZip/ZipFileAddDirTest.php
Normal file
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Test add directory to zip archive.
|
||||
*/
|
||||
class ZipFileAddDirTest extends ZipTestCase
|
||||
{
|
||||
protected static $files = [
|
||||
'.hidden' => 'Hidden file',
|
||||
'text file.txt' => 'Text file',
|
||||
'Текстовый документ.txt' => 'Текстовый документ',
|
||||
'empty dir/' => null,
|
||||
'empty dir2/ещё пустой каталог/' => null,
|
||||
'catalog/New File' => 'New Catalog File',
|
||||
'catalog/New File 2' => 'New Catalog File 2',
|
||||
'catalog/Empty Dir/' => null,
|
||||
'category/list.txt' => 'Category list',
|
||||
'category/Pictures/128x160/Car/01.jpg' => 'File 01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg' => 'File 02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg' => 'File 01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg' => 'File 02.jpg',
|
||||
];
|
||||
|
||||
/**
|
||||
* Before test
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->fillDirectory();
|
||||
}
|
||||
|
||||
protected function fillDirectory()
|
||||
{
|
||||
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);
|
||||
if (!is_dir($dirname)) {
|
||||
mkdir($dirname, 0755, true);
|
||||
}
|
||||
file_put_contents($fullName, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static function assertFilesResult(ZipFileInterface $zipFile, array $actualResultFiles = [], $localPath = '/')
|
||||
{
|
||||
$localPath = rtrim($localPath, '/');
|
||||
$localPath = empty($localPath) ? "" : $localPath . '/';
|
||||
self::assertEquals(sizeof($zipFile), sizeof($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);
|
||||
unset($actualResultFiles[$file]);
|
||||
} else {
|
||||
self::assertFalse(isset($zipFile[$zipEntryName]));
|
||||
}
|
||||
}
|
||||
self::assertEmpty($actualResultFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddDirWithLocalPath()
|
||||
{
|
||||
$localPath = 'to/path';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDir($this->outputDirname, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddDirWithoutLocalPath()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDir($this->outputDirname);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromIterator()
|
||||
{
|
||||
$localPath = 'to/project';
|
||||
|
||||
$directoryIterator = new \DirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromIteratorEmptyLocalPath()
|
||||
{
|
||||
$localPath = '';
|
||||
|
||||
$directoryIterator = new \DirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromRecursiveIterator()
|
||||
{
|
||||
$localPath = 'to/project';
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddRecursiveDirWithLocalPath()
|
||||
{
|
||||
$localPath = 'to/path';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDirRecursive($this->outputDirname, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddRecursiveDirWithoutLocalPath()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDirRecursive($this->outputDirname);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files));
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromIteratorWithIgnoreFiles()
|
||||
{
|
||||
$localPath = 'to/project';
|
||||
$ignoreFiles = [
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/'
|
||||
];
|
||||
|
||||
$directoryIterator = new \DirectoryIterator($this->outputDirname);
|
||||
$ignoreIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($ignoreIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromRecursiveIteratorWithIgnoreFiles()
|
||||
{
|
||||
$localPath = 'to/project';
|
||||
$ignoreFiles = [
|
||||
'.hidden',
|
||||
'empty dir2/ещё пустой каталог/',
|
||||
'list.txt',
|
||||
'category/Pictures/240x320',
|
||||
];
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($this->outputDirname);
|
||||
$ignoreIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($ignoreIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
'catalog/New File',
|
||||
'catalog/New File 2',
|
||||
'catalog/Empty Dir/',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files from glob pattern
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromGlob()
|
||||
{
|
||||
$localPath = '/';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromGlob($this->outputDirname, '**.{txt,jpg}', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add recursively files from glob pattern
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromGlobRecursive()
|
||||
{
|
||||
$localPath = '/';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromGlobRecursive($this->outputDirname, '**.{txt,jpg}', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files from regex pattern
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromRegex()
|
||||
{
|
||||
$localPath = 'path';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromRegex($this->outputDirname, '~\.(txt|jpe?g)$~i', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files recursively from regex pattern
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddFilesFromRegexRecursive()
|
||||
{
|
||||
$localPath = '/';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromRegexRecursive($this->outputDirname, '~\.(txt|jpe?g)$~i', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testArrayAccessAddDir()
|
||||
{
|
||||
$localPath = 'path/to';
|
||||
$iterator = new \RecursiveDirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile[$localPath] = $iterator;
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
2138
tests/PhpZip/ZipFileTest.php
Normal file
2138
tests/PhpZip/ZipFileTest.php
Normal file
File diff suppressed because it is too large
Load Diff
111
tests/PhpZip/ZipMatcherTest.php
Normal file
111
tests/PhpZip/ZipMatcherTest.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Model\ZipEntryMatcher;
|
||||
use PhpZip\Model\ZipInfo;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
class ZipMatcherTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testMatcher()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile[$i] = $i;
|
||||
}
|
||||
|
||||
$matcher = $zipFile->matcher();
|
||||
$this->assertInstanceOf(ZipEntryMatcher::class, $matcher);
|
||||
|
||||
$this->assertTrue(is_array($matcher->getMatches()));
|
||||
$this->assertCount(0, $matcher);
|
||||
|
||||
$matcher->add(1)->add(10)->add(20);
|
||||
$this->assertCount(3, $matcher);
|
||||
$this->assertEquals($matcher->getMatches(), ['1', '10', '20']);
|
||||
|
||||
$matcher->delete();
|
||||
$this->assertCount(97, $zipFile);
|
||||
$this->assertCount(0, $matcher);
|
||||
|
||||
$matcher->match('~^[2][1-5]|[3][6-9]|40$~s');
|
||||
$this->assertCount(10, $matcher);
|
||||
$actualMatches = [
|
||||
'21', '22', '23', '24', '25',
|
||||
'36', '37', '38', '39',
|
||||
'40'
|
||||
];
|
||||
$this->assertEquals($matcher->getMatches(), $actualMatches);
|
||||
$matcher->setPassword('qwerty');
|
||||
$info = $zipFile->getAllInfo();
|
||||
array_walk($info, function (ZipInfo $zipInfo) use ($actualMatches) {
|
||||
$this->assertEquals($zipInfo->isEncrypted(), in_array($zipInfo->getName(), $actualMatches));
|
||||
});
|
||||
|
||||
$matcher->all();
|
||||
$this->assertCount(count($zipFile), $matcher);
|
||||
|
||||
$expectedNames = [];
|
||||
$matcher->invoke(function ($entryName) use (&$expectedNames) {
|
||||
$expectedNames[] = $entryName;
|
||||
});
|
||||
$this->assertEquals($expectedNames, $matcher->getMatches());
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testDocsExample()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipFile['file_'.$i.'.jpg'] = CryptoUtil::randomBytes(100);
|
||||
}
|
||||
|
||||
$renameEntriesArray = [
|
||||
'file_10.jpg',
|
||||
'file_11.jpg',
|
||||
'file_12.jpg',
|
||||
'file_13.jpg',
|
||||
'file_14.jpg',
|
||||
'file_15.jpg',
|
||||
'file_16.jpg',
|
||||
'file_17.jpg',
|
||||
'file_18.jpg',
|
||||
'file_19.jpg',
|
||||
'file_50.jpg',
|
||||
'file_51.jpg',
|
||||
'file_52.jpg',
|
||||
'file_53.jpg',
|
||||
'file_54.jpg',
|
||||
'file_55.jpg',
|
||||
'file_56.jpg',
|
||||
'file_57.jpg',
|
||||
'file_58.jpg',
|
||||
'file_59.jpg',
|
||||
];
|
||||
|
||||
foreach ($renameEntriesArray as $name) {
|
||||
$this->assertTrue(isset($zipFile[$name]));
|
||||
}
|
||||
|
||||
$matcher = $zipFile->matcher();
|
||||
$matcher->match('~^file_(1|5)\d+~');
|
||||
$this->assertEquals($matcher->getMatches(), $renameEntriesArray);
|
||||
|
||||
$matcher->invoke(function ($entryName) use ($zipFile) {
|
||||
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
|
||||
$zipFile->rename($entryName, $newName);
|
||||
});
|
||||
|
||||
foreach ($renameEntriesArray as $name) {
|
||||
$this->assertFalse(isset($zipFile[$name]));
|
||||
|
||||
$pathInfo = pathinfo($name);
|
||||
$newName = $pathInfo['filename'].'.no_optimize.'.$pathInfo['extension'];
|
||||
$this->assertTrue(isset($zipFile[$newName]));
|
||||
}
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
427
tests/PhpZip/ZipPasswordTest.php
Normal file
427
tests/PhpZip/ZipPasswordTest.php
Normal file
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipInfo;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* Tests with zip password.
|
||||
*/
|
||||
class ZipPasswordTest extends ZipFileAddDirTest
|
||||
{
|
||||
/**
|
||||
* Test archive password.
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testSetPassword()
|
||||
{
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
|
||||
}
|
||||
|
||||
$password = base64_encode(CryptoUtil::randomBytes(100));
|
||||
$badPassword = "bad password";
|
||||
|
||||
// create encryption password with ZipCrypto
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDir(__DIR__);
|
||||
$zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
// check bad password for ZipCrypto
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->setReadPassword($badPassword);
|
||||
foreach ($zipFile->getListFiles() as $entryName) {
|
||||
try {
|
||||
$zipFile[$entryName];
|
||||
$this->fail("Expected Exception has not been raised.");
|
||||
} catch (ZipAuthenticationException $ae) {
|
||||
$this->assertContains('Invalid password for zip entry', $ae->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// check correct password for ZipCrypto
|
||||
$zipFile->setReadPassword($password);
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
$decryptContent = $zipFile[$info->getName()];
|
||||
$this->assertNotEmpty($decryptContent);
|
||||
$this->assertContains('<?php', $decryptContent);
|
||||
}
|
||||
|
||||
// change encryption method to WinZip Aes and update file
|
||||
$zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
// check from WinZip AES encryption
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
// set bad password WinZip AES
|
||||
$zipFile->setReadPassword($badPassword);
|
||||
foreach ($zipFile->getListFiles() as $entryName) {
|
||||
try {
|
||||
$zipFile[$entryName];
|
||||
$this->fail("Expected Exception has not been raised.");
|
||||
} catch (ZipAuthenticationException $ae) {
|
||||
$this->assertNotNull($ae);
|
||||
}
|
||||
}
|
||||
|
||||
// set correct password WinZip AES
|
||||
$zipFile->setReadPassword($password);
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip', $info->getMethodName());
|
||||
$decryptContent = $zipFile[$info->getName()];
|
||||
$this->assertNotEmpty($decryptContent);
|
||||
$this->assertContains('<?php', $decryptContent);
|
||||
}
|
||||
|
||||
// clear password
|
||||
$zipFile->addFromString('file1', '');
|
||||
$zipFile->disableEncryption();
|
||||
$zipFile->addFromString('file2', '');
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
// check remove password
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertFalse($info->isEncrypted());
|
||||
}
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testTraditionalEncryption()
|
||||
{
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
$this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.');
|
||||
}
|
||||
|
||||
$password = base64_encode(CryptoUtil::randomBytes(50));
|
||||
|
||||
$zip = new ZipFile();
|
||||
$zip->addDirRecursive($this->outputDirname);
|
||||
$zip->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPassword($password);
|
||||
$this->assertFilesResult($zip, array_keys(self::$files));
|
||||
foreach ($zip->getAllInfo() as $info) {
|
||||
if (!$info->isFolder()) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider winZipKeyStrengthProvider
|
||||
* @param int $encryptionMethod
|
||||
* @param int $bitSize
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testWinZipAesEncryption($encryptionMethod, $bitSize)
|
||||
{
|
||||
$password = base64_encode(CryptoUtil::randomBytes(50));
|
||||
|
||||
$zip = new ZipFile();
|
||||
$zip->addDirRecursive($this->outputDirname);
|
||||
$zip->setPassword($password, $encryptionMethod);
|
||||
$zip->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPassword($password);
|
||||
$this->assertFilesResult($zip, array_keys(self::$files));
|
||||
foreach ($zip->getAllInfo() as $info) {
|
||||
if (!$info->isFolder()) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertEquals($info->getEncryptionMethod(), $encryptionMethod);
|
||||
$this->assertContains('WinZip AES-' . $bitSize, $info->getMethodName());
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
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],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.');
|
||||
}
|
||||
|
||||
$password1 = '353442434235424234';
|
||||
$password2 = 'adgerhvrwjhqqehtqhkbqrgewg';
|
||||
|
||||
$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->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPasswordEntry('.hidden', $password1);
|
||||
$zip->setReadPasswordEntry('text file.txt', $password2);
|
||||
$this->assertFilesResult($zip, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
|
||||
$info = $zip->getEntryInfo('.hidden');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
|
||||
$info = $zip->getEntryInfo('text file.txt');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip AES', $info->getMethodName());
|
||||
|
||||
$this->assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted());
|
||||
$this->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.');
|
||||
}
|
||||
|
||||
$password1 = '353442434235424234';
|
||||
$password2 = 'adgerhvrwjhqqehtqhkbqrgewg';
|
||||
$defaultPassword = ' f f f f f ffff f5 ';
|
||||
|
||||
$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->saveAsFile($this->outputFilename);
|
||||
$zip->close();
|
||||
|
||||
$zip->openFile($this->outputFilename);
|
||||
$zip->setReadPassword($defaultPassword);
|
||||
$zip->setReadPasswordEntry('.hidden', $password1);
|
||||
$zip->setReadPasswordEntry('text file.txt', $password2);
|
||||
$this->assertFilesResult($zip, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
|
||||
$info = $zip->getEntryInfo('.hidden');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('ZipCrypto', $info->getMethodName());
|
||||
|
||||
$info = $zip->getEntryInfo('text file.txt');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip AES', $info->getMethodName());
|
||||
|
||||
$info = $zip->getEntryInfo('Текстовый документ.txt');
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
$this->assertContains('WinZip AES', $info->getMethodName());
|
||||
|
||||
$this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted());
|
||||
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \PhpZip\Exception\ZipException
|
||||
* @expectedExceptionMessage Invalid encryption method
|
||||
*/
|
||||
public function testSetEncryptionMethodInvalid()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$encryptionMethod = 9999;
|
||||
$zipFile->setPassword('pass', $encryptionMethod);
|
||||
$zipFile['entry'] = 'content';
|
||||
$zipFile->outputAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception\ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testEntryPassword()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->setPassword('pass');
|
||||
$zipFile['file'] = 'content';
|
||||
$this->assertFalse($zipFile->getEntryInfo('file')->isEncrypted());
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$zipFile['file' . $i] = 'content';
|
||||
if ($i < 6) {
|
||||
$zipFile->setPasswordEntry('file' . $i, 'pass');
|
||||
$this->assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted());
|
||||
} else {
|
||||
$this->assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted());
|
||||
}
|
||||
}
|
||||
$zipFile->disableEncryptionEntry('file3');
|
||||
$this->assertFalse($zipFile->getEntryInfo('file3')->isEncrypted());
|
||||
$this->asserttrue($zipFile->getEntryInfo('file2')->isEncrypted());
|
||||
$zipFile->disableEncryption();
|
||||
$infoList = $zipFile->getAllInfo();
|
||||
array_walk($infoList, function (ZipInfo $zipInfo) {
|
||||
$this->assertFalse($zipInfo->isEncrypted());
|
||||
});
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \PhpZip\Exception\ZipException
|
||||
* @expectedExceptionMessage Invalid encryption method
|
||||
*/
|
||||
public function testInvalidEncryptionMethodEntry()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFromString('file', 'content', ZipFileInterface::METHOD_STORED);
|
||||
$zipFile->setPasswordEntry('file', 'pass', 99);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testArchivePasswordUpdateWithoutSetReadPassword()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile['file1'] = 'content';
|
||||
$zipFile['file2'] = 'content';
|
||||
$zipFile['file3'] = 'content';
|
||||
$zipFile->setPassword('password');
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, 'password');
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->assertCount(3, $zipFile);
|
||||
foreach ($zipFile->getAllInfo() as $info) {
|
||||
$this->assertTrue($info->isEncrypted());
|
||||
}
|
||||
unset($zipFile['file3']);
|
||||
$zipFile['file4'] = 'content';
|
||||
$zipFile->rewrite();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, 'password');
|
||||
|
||||
$this->assertCount(3, $zipFile);
|
||||
$this->assertFalse(isset($zipFile['file3']));
|
||||
$this->assertTrue(isset($zipFile['file4']));
|
||||
$this->assertTrue($zipFile->getEntryInfo('file1')->isEncrypted());
|
||||
$this->assertTrue($zipFile->getEntryInfo('file2')->isEncrypted());
|
||||
$this->assertFalse($zipFile->getEntryInfo('file4')->isEncrypted());
|
||||
$this->assertEquals($zipFile['file4'], 'content');
|
||||
|
||||
$zipFile->extractTo($this->outputDirname, ['file4']);
|
||||
|
||||
$this->assertTrue(file_exists($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'));
|
||||
$this->assertEquals(file_get_contents($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'), $zipFile['file4']);
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/Ne-Lexa/php-zip/issues/9
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testIssues9()
|
||||
{
|
||||
$contents = str_pad('', 1000, 'test;test2;test3' . PHP_EOL, STR_PAD_RIGHT);
|
||||
$password = base64_encode(CryptoUtil::randomBytes(20));
|
||||
|
||||
$encryptMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile
|
||||
->addFromString('codes.csv', $contents)
|
||||
->setPassword($password, $encryptMethod)
|
||||
->saveAsFile($this->outputFilename)
|
||||
->close();
|
||||
|
||||
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$zipFile->setReadPassword($password);
|
||||
$this->assertEquals($zipFile['codes.csv'], $contents);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testReadAesEncryptedAndRewriteArchive()
|
||||
{
|
||||
$file = __DIR__ . '/resources/aes_password_archive.zip';
|
||||
$password = '1234567890';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($file);
|
||||
$zipFile->setReadPassword($password);
|
||||
$zipFile->setEntryComment('contents.txt', 'comment'); // change entry, but not changed contents
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
|
||||
$zipFile2 = new ZipFile();
|
||||
$zipFile2->openFile($this->outputFilename);
|
||||
$zipFile2->setReadPassword($password);
|
||||
$this->assertEquals($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);
|
||||
}
|
||||
$zipFile2->close();
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
55
tests/PhpZip/ZipRemoteFileTest.php
Normal file
55
tests/PhpZip/ZipRemoteFileTest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Test add remote files to zip archive
|
||||
*/
|
||||
class ZipRemoteFileTest extends ZipTestCase
|
||||
{
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testAddRemoteFileFromStream()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$outputZip = $this->outputFilename;
|
||||
$fileUrl = 'https://raw.githubusercontent.com/Ne-Lexa/php-zip/master/README.md';
|
||||
$fp = @fopen($fileUrl, 'rb', false, stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 3,
|
||||
]
|
||||
]));
|
||||
if ($fp === false) {
|
||||
self::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();
|
||||
self::assertCount(1, $files);
|
||||
self::assertSame($fileName, $files[0]);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
}
|
41
tests/PhpZip/ZipSlipVulnerabilityTest.php
Normal file
41
tests/PhpZip/ZipSlipVulnerabilityTest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
/**
|
||||
* Class ZipSlipVulnerabilityTest
|
||||
*
|
||||
* @package PhpZip
|
||||
* @see https://github.com/Ne-Lexa/php-zip/issues/39 Issue#31
|
||||
* @see https://snyk.io/research/zip-slip-vulnerability Zip Slip Vulnerability
|
||||
*/
|
||||
class ZipSlipVulnerabilityTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* @throws Exception\ZipException
|
||||
*/
|
||||
public function testCreateSlipVulnerabilityFile()
|
||||
{
|
||||
$localFile = '../dir/./../../file.txt';
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFromString($localFile, 'contents');
|
||||
self::assertContains($localFile, $zipFile->getListFiles());
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception\ZipException
|
||||
*/
|
||||
public function testUnpack()
|
||||
{
|
||||
$this->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';
|
||||
self::assertTrue(is_file($expectedExtractedFile));
|
||||
}
|
||||
}
|
149
tests/PhpZip/ZipTestCase.php
Normal file
149
tests/PhpZip/ZipTestCase.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
|
||||
/**
|
||||
* PHPUnit test case and helper methods.
|
||||
*/
|
||||
class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $outputFilename;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $outputDirname;
|
||||
|
||||
/**
|
||||
* Before test
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$id = uniqid('phpzip', true);
|
||||
$tempDir = sys_get_temp_dir() . '/phpunit-phpzip';
|
||||
if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) {
|
||||
throw new \RuntimeException('Dir ' . $tempDir . " can't created");
|
||||
}
|
||||
$this->outputFilename = $tempDir . '/' . $id . '.zip';
|
||||
$this->outputDirname = $tempDir . '/' . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* After test
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
if ($this->outputFilename !== null && file_exists($this->outputFilename)) {
|
||||
unlink($this->outputFilename);
|
||||
}
|
||||
if ($this->outputDirname !== null && is_dir($this->outputDirname)) {
|
||||
FilesUtil::removeDir($this->outputDirname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert correct zip archive.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string|null $password
|
||||
*/
|
||||
public static function assertCorrectZipArchive($filename, $password = null)
|
||||
{
|
||||
if (self::existsProgram('unzip')) {
|
||||
$command = 'unzip';
|
||||
if ($password !== null) {
|
||||
$command .= ' -P ' . escapeshellarg($password);
|
||||
}
|
||||
$command .= ' -t ' . escapeshellarg($filename);
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
if ($password !== null && $returnCode === 81) {
|
||||
if (self::existsProgram('7z')) {
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename);
|
||||
exec($command, $output, $returnCode);
|
||||
/**
|
||||
* @var array $output
|
||||
*/
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
self::assertEquals($returnCode, 0);
|
||||
self::assertNotContains(' Errors', $output);
|
||||
self::assertContains(' Ok', $output);
|
||||
} else {
|
||||
fwrite(STDERR, 'Program unzip cannot support this function.' . PHP_EOL);
|
||||
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
self::assertEquals($returnCode, 0, $output);
|
||||
self::assertNotContains('incorrect password', $output);
|
||||
self::assertContains(' OK', $output);
|
||||
self::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.
|
||||
*
|
||||
* @param $filename
|
||||
*/
|
||||
public static function assertCorrectEmptyZip($filename)
|
||||
{
|
||||
if (self::existsProgram('zipinfo')) {
|
||||
exec('zipinfo ' . escapeshellarg($filename), $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
self::assertContains('Empty zipfile', $output);
|
||||
}
|
||||
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
|
||||
self::assertStringEqualsFile($filename, $actualEmptyZipData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param bool $showErrors
|
||||
* @return bool|null If null - can not install zipalign
|
||||
*/
|
||||
public static function assertVerifyZipAlign($filename, $showErrors = false)
|
||||
{
|
||||
if (self::existsProgram('zipalign')) {
|
||||
exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode);
|
||||
if ($showErrors && $returnCode !== 0) {
|
||||
fwrite(STDERR, implode(PHP_EOL, $output));
|
||||
}
|
||||
return $returnCode === 0;
|
||||
}
|
||||
|
||||
fwrite(STDERR, 'Can not find program "zipalign" for test' . PHP_EOL);
|
||||
return null;
|
||||
}
|
||||
}
|
BIN
tests/PhpZip/php-zip-ext-test-resources/binarynull.zip
Normal file
BIN
tests/PhpZip/php-zip-ext-test-resources/binarynull.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/php-zip-ext-test-resources/bug40228.zip
Normal file
BIN
tests/PhpZip/php-zip-ext-test-resources/bug40228.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/php-zip-ext-test-resources/bug49072.zip
Normal file
BIN
tests/PhpZip/php-zip-ext-test-resources/bug49072.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/php-zip-ext-test-resources/bug70752.zip
Normal file
BIN
tests/PhpZip/php-zip-ext-test-resources/bug70752.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/php-zip-ext-test-resources/bug8009.zip
Normal file
BIN
tests/PhpZip/php-zip-ext-test-resources/bug8009.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/php-zip-ext-test-resources/pecl12414.zip
Normal file
BIN
tests/PhpZip/php-zip-ext-test-resources/pecl12414.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/resources/aes_password_archive.zip
Normal file
BIN
tests/PhpZip/resources/aes_password_archive.zip
Normal file
Binary file not shown.
BIN
tests/PhpZip/resources/apk.zip
Normal file
BIN
tests/PhpZip/resources/apk.zip
Normal file
Binary file not shown.
@@ -1,244 +0,0 @@
|
||||
<?php
|
||||
|
||||
class TestZipFile extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$output = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-create.zip';
|
||||
$extractOutputDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-create';
|
||||
|
||||
$listFilename = 'files.txt';
|
||||
$listFileContent = implode(PHP_EOL, glob('*'));
|
||||
$dirName = 'src/';
|
||||
$archiveComment = 'Archive comment - 😀';
|
||||
$commentIndex0 = basename(__FILE__);
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->create();
|
||||
$zip->addFile(__FILE__);
|
||||
$zip->addFromString($listFilename, $listFileContent);
|
||||
$zip->addEmptyDir($dirName);
|
||||
$zip->setArchiveComment($archiveComment);
|
||||
$zip->setCommentIndex(0, $commentIndex0);
|
||||
$zip->saveAs($output);
|
||||
$zip->close();
|
||||
|
||||
$this->assertTrue(file_exists($output));
|
||||
$this->assertCorrectZipArchive($output);
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->open($output);
|
||||
$listFiles = $zip->getListFiles();
|
||||
|
||||
$this->assertEquals(sizeof($listFiles), 3);
|
||||
$filenameIndex0 = basename(__FILE__);
|
||||
$this->assertEquals($listFiles[0], $filenameIndex0);
|
||||
$this->assertEquals($listFiles[1], $listFilename);
|
||||
$this->assertEquals($listFiles[2], $dirName);
|
||||
|
||||
$this->assertEquals($zip->getFromIndex(0), $zip->getFromName(basename(__FILE__)));
|
||||
$this->assertEquals($zip->getFromIndex(0), file_get_contents(__FILE__));
|
||||
$this->assertEquals($zip->getFromIndex(1), $zip->getFromName($listFilename));
|
||||
$this->assertEquals($zip->getFromIndex(1), $listFileContent);
|
||||
|
||||
$this->assertEquals($zip->getArchiveComment(), $archiveComment);
|
||||
$this->assertEquals($zip->getCommentIndex(0), $commentIndex0);
|
||||
|
||||
if (!file_exists($extractOutputDir)) {
|
||||
$this->assertTrue(mkdir($extractOutputDir, 0755, true));
|
||||
}
|
||||
|
||||
$zip->extractTo($extractOutputDir);
|
||||
|
||||
$this->assertTrue(file_exists($extractOutputDir . DIRECTORY_SEPARATOR . $filenameIndex0));
|
||||
$this->assertEquals(md5_file($extractOutputDir . DIRECTORY_SEPARATOR . $filenameIndex0), md5_file(__FILE__));
|
||||
|
||||
$this->assertTrue(file_exists($extractOutputDir . DIRECTORY_SEPARATOR . $listFilename));
|
||||
$this->assertEquals(file_get_contents($extractOutputDir . DIRECTORY_SEPARATOR . $listFilename), $listFileContent);
|
||||
|
||||
$zip->close();
|
||||
|
||||
unlink($output);
|
||||
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($extractOutputDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($files as $fileInfo) {
|
||||
$todo = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$todo($fileInfo->getRealPath());
|
||||
}
|
||||
|
||||
rmdir($extractOutputDir);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function testUpdate()
|
||||
{
|
||||
$file = __DIR__ . '/res/file.apk';
|
||||
$privateKey = __DIR__ . '/res/private.pem';
|
||||
$publicKey = __DIR__ . '/res/public.pem';
|
||||
$outputFile = sys_get_temp_dir() . '/test-update.apk';
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile($file);
|
||||
$zip->open($file);
|
||||
|
||||
// signed apk file
|
||||
$certList = array();
|
||||
$manifestMf = new Manifest();
|
||||
$manifestMf->appendLine("Manifest-Version: 1.0");
|
||||
$manifestMf->appendLine("Created-By: 1.0 (Android)");
|
||||
$manifestMf->appendLine('');
|
||||
for ($i = 0, $length = $zip->getCountFiles(); $i < $length; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if ($name[strlen($name) - 1] === '/') continue; // is path
|
||||
$content = $zip->getFromIndex($i);
|
||||
|
||||
$certManifest = $this->createSha1EncodeEntryManifest($name, $content);
|
||||
$manifestMf->appendManifest($certManifest);
|
||||
$certList[$name] = $certManifest;
|
||||
}
|
||||
$manifestMf = $manifestMf->getContent();
|
||||
|
||||
$certSf = new Manifest();
|
||||
$certSf->appendLine('Signature-Version: 1.0');
|
||||
$certSf->appendLine('Created-By: 1.0 (Android)');
|
||||
$certSf->appendLine('SHA1-Digest-Manifest: ' . base64_encode(sha1($manifestMf, 1)));
|
||||
$certSf->appendLine('');
|
||||
foreach ($certList AS $filename => $content) {
|
||||
$certManifest = $this->createSha1EncodeEntryManifest($filename, $content->getContent());
|
||||
$certSf->appendManifest($certManifest);
|
||||
}
|
||||
$certSf = $certSf->getContent();
|
||||
unset($certList);
|
||||
|
||||
$zip->addFromString('META-INF/MANIFEST.MF', $manifestMf);
|
||||
$zip->addFromString('META-INF/CERT.SF', $certSf);
|
||||
|
||||
if (`which openssl`) {
|
||||
$openssl_cmd = 'printf ' . escapeshellarg($certSf) . ' | openssl smime -md sha1 -sign -inkey ' . escapeshellarg($privateKey) . ' -signer ' . $publicKey . ' -binary -outform DER -noattr';
|
||||
|
||||
ob_start();
|
||||
passthru($openssl_cmd, $error);
|
||||
$rsaContent = ob_get_clean();
|
||||
$this->assertEquals($error, 0);
|
||||
|
||||
$zip->addFromString('META-INF/CERT.RSA', $rsaContent);
|
||||
}
|
||||
|
||||
$zip->saveAs($outputFile);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($outputFile);
|
||||
|
||||
if (`which jarsigner`) {
|
||||
ob_start();
|
||||
passthru('jarsigner -verify -verbose -certs ' . escapeshellarg($outputFile), $error);
|
||||
$verifedResult = ob_get_clean();
|
||||
|
||||
$this->assertEquals($error, 0);
|
||||
$this->assertContains('jar verified', $verifedResult);
|
||||
}
|
||||
|
||||
unlink($outputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $filename
|
||||
*/
|
||||
private function assertCorrectZipArchive($filename)
|
||||
{
|
||||
exec("zip -T " . escapeshellarg($filename), $output, $returnCode);
|
||||
$this->assertEquals($returnCode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $content
|
||||
* @return Manifest
|
||||
*/
|
||||
private function createSha1EncodeEntryManifest($filename, $content)
|
||||
{
|
||||
$manifest = new Manifest();
|
||||
$manifest->appendLine('Name: ' . $filename);
|
||||
$manifest->appendLine('SHA1-Digest: ' . base64_encode(sha1($content, 1)));
|
||||
return $manifest;
|
||||
}
|
||||
}
|
||||
|
||||
class Manifest
|
||||
{
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return trim($this->content) . "\r\n\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a long manifest line and add continuation if required
|
||||
* @param $line string
|
||||
* @return Manifest
|
||||
*/
|
||||
public function appendLine($line)
|
||||
{
|
||||
$begin = 0;
|
||||
$sb = '';
|
||||
$lineLength = mb_strlen($line, "UTF-8");
|
||||
for ($end = 70; $lineLength - $begin > 70; $end += 69) {
|
||||
$sb .= mb_substr($line, $begin, $end - $begin, "UTF-8") . "\r\n ";
|
||||
$begin = $end;
|
||||
}
|
||||
$this->content .= $sb . mb_substr($line, $begin, $lineLength, "UTF-8") . "\r\n";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function appendManifest(Manifest $manifest)
|
||||
{
|
||||
$this->content .= $manifest->getContent();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->content = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $manifestContent
|
||||
* @return Manifest
|
||||
*/
|
||||
public static function createFromManifest($manifestContent)
|
||||
{
|
||||
$manifestContent = trim($manifestContent);
|
||||
$lines = explode("\n", $manifestContent);
|
||||
|
||||
// normalize manifest
|
||||
$content = '';
|
||||
$trim = array("\r", "\n");
|
||||
foreach ($lines AS $line) {
|
||||
|
||||
$line = str_replace($trim, '', $line);
|
||||
if ($line[0] === ' ') {
|
||||
$content = rtrim($content, "\n\r");
|
||||
$line = ltrim($line);
|
||||
}
|
||||
$content .= $line . "\r\n";
|
||||
}
|
||||
|
||||
$manifset = new self;
|
||||
$lines = explode("\n", $content);
|
||||
foreach ($lines AS $line) {
|
||||
$line = trim($line, "\n\r");
|
||||
$manifset->appendLine($line);
|
||||
}
|
||||
return $manifset;
|
||||
}
|
||||
}
|
Binary file not shown.
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwiURw5w8TAS0G
|
||||
iWaBzJqbQyacFsIzKc+orHDzBdBdKlrx/aUlw3fBA310aP7343hc7vMm6koar/1n
|
||||
8iEh2W4retDHzNf9j3IXETa5/D+3WJY4aVJkYcFr8v6OEnuSXIwKZduMeL4BpQwM
|
||||
Xu/z6gkoTa9o+Dzhl46l71UX8umdPIx/4YWwX2oSm6EGklcGJyYdqMvwOXVXDE/J
|
||||
qqPzC7ZQiu422cDDvqBYt32CVyjLKCo5YjWBb24jxjtl5M4y8xPOcHlVEG2WwPBU
|
||||
sv2aw4Z9/Q0erZ35gMyzu+Y+2623B3tuAbfsswgBINq0bl2v+M17Kpv2tjhMKj1H
|
||||
ds6CK/BVAgMBAAECggEAdTUt86f1IjENq+Fd5Z/qplsXL1sM5NtFvD+BXljl1nVg
|
||||
nHpDQ6dbwxKGINv1LLAiIdGkLpovSTi/jlv8E3VA6C1KoN0oKnkqzpXnN+R6iUiP
|
||||
tDR5N5yPxxQ2Xi13Td2UPPMTqVghDwZ90VjXB6LDIbcyVwc5pK3zT8hvPs9Qu8t1
|
||||
S2pCEKcowvTRSB1DMTZ3lrNjEEIMdV0H8Qik3lf7ognRGoDywu5pA1bc/Yg+XlmP
|
||||
/ZmQinFeg3izNQzDdP6Ppo1i/QFeVXVuMs2ergMMHJRNUhBXKz8iNyVupqfroE8a
|
||||
xRpD3eO+KvSNb0TJR5TXf64t62zEEpHaRsmgACEMAQKBgQDXo0jVUa67oqfJBFBU
|
||||
3zfHIlgcrydRE4Av+LJ0hdEnFpMwVpUihJXUaJijawTzTKOgpVImhxfr/T1HMalm
|
||||
MTXH5Tc7inJTiB9A1IffLPqgoOr2JRwQ2q8lgWkQPkq1ySd+q0vhkj1tuAe3qI1i
|
||||
jiMo1Vb9zdVjcxmvPnZRKJgiIQKBgQDRlFm6PKc2Zx46BXeNPtXnHhSduUBJf2iO
|
||||
n9/pKTANQuDlPwC3Q4edSKe44fZ/oj4KRAnzX254wXBMX+ktKX/kqXbwEanxcd/v
|
||||
Lnvgv8QhsEKO3Ye09yasAfC2lYsSVSwHv+dYurb0nZ2JEPL1IP+V76RgTbdeMdic
|
||||
Mt53jN/vtQKBgQC+D+mOO+Sq9X61qtuzMtvS5O6MucUJrQp7PdTs51WmAjvRiz7/
|
||||
oaT+BwMiZp2CZLaETbLOypvHIPn12kvZCt7ARcQc8rY58ey6E5l+mAJ/udXfBm5q
|
||||
XJWrlRipfH4VJCtvdkP3mhIStvX2ZtXXXDiZMRDvu5CtizHESGW4uvL8gQKBgCI7
|
||||
ukBagfG3/E7776BJwETlO/bbeK3Iuvp5EOkUCj5QS04G8YX96Nv/Ly5a8pm8lae1
|
||||
n256ix/8cOx4yizPV42xRLVIHVtL/4khLajzigT6tpSBiRY9PLriAkDAwpu2/98w
|
||||
MIjkzte8Gyx1cUorHrSOFWqJp0cim0BAauhaQYX1AoGAPvb5TG/A6LhYKgCWUMOH
|
||||
lucrnV3Ns1BzaaMmOaokuYGtyTv2loVlrv+7QGdC9UBRXDz46DTE7CPHBwReNnWB
|
||||
R7YW50VwB9YfD7dqRM24Y08F3B7RCNhqsAnpAtVgXf+/01o2nfJbzxTty/STiBNB
|
||||
OjjxKHnAgIfhe7xAIiY2eow=
|
||||
-----END PRIVATE KEY-----
|
@@ -1,21 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDeTCCAmGgAwIBAgIEDeVWNjANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJT
|
||||
QTEPMA0GA1UECBMGUml5YWRoMQ8wDQYDVQQHEwZSaXlhZGgxEDAOBgNVBAoTB1lv
|
||||
dXR5cGUxEDAOBgNVBAsTB1lvdXR5cGUxGDAWBgNVBAMTD0x1Y2llbm5lIEFuc2Vs
|
||||
YTAeFw0xNjA5MDgxNDM2MjJaFw00NDAxMjUxNDM2MjJaMG0xCzAJBgNVBAYTAlNB
|
||||
MQ8wDQYDVQQIEwZSaXlhZGgxDzANBgNVBAcTBlJpeWFkaDEQMA4GA1UEChMHWW91
|
||||
dHlwZTEQMA4GA1UECxMHWW91dHlwZTEYMBYGA1UEAxMPTHVjaWVubmUgQW5zZWxh
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsIlEcOcPEwEtBolmgcya
|
||||
m0MmnBbCMynPqKxw8wXQXSpa8f2lJcN3wQN9dGj+9+N4XO7zJupKGq/9Z/IhIdlu
|
||||
K3rQx8zX/Y9yFxE2ufw/t1iWOGlSZGHBa/L+jhJ7klyMCmXbjHi+AaUMDF7v8+oJ
|
||||
KE2vaPg84ZeOpe9VF/LpnTyMf+GFsF9qEpuhBpJXBicmHajL8Dl1VwxPyaqj8wu2
|
||||
UIruNtnAw76gWLd9glcoyygqOWI1gW9uI8Y7ZeTOMvMTznB5VRBtlsDwVLL9msOG
|
||||
ff0NHq2d+YDMs7vmPtuttwd7bgG37LMIASDatG5dr/jNeyqb9rY4TCo9R3bOgivw
|
||||
VQIDAQABoyEwHzAdBgNVHQ4EFgQUEPoIQyYzpjseEK7hqm6UALvjJj8wDQYJKoZI
|
||||
hvcNAQEFBQADggEBAD/C/48B4MvF2WzhMtLIAWuhtp73xBy6GCQBKT1dn9dgtXfD
|
||||
LuHAvkx28CoOTso4Ia+JhWuu7jGfYdtL00ezV8d8Ma1k/SJfWyHpgDDk1MEhvn+h
|
||||
tOoUQpt0S+QhKFxDm+INv2zw/P/TDIIodHQqkX+YVSLQMhUGRTq3vhDnfJqedAUr
|
||||
QIhZjCZx9VjjiM4yhcabKEHpxqLQOcoeHB8zchnP1j/N+QSIW6hICqjcPLPLzpPu
|
||||
M0RmEuRYz3EJ2P3jINhaCLFRLHTnoN2lVDS32v5Cr+IC7A1hPUcHG+07junRMEiG
|
||||
uTYj9+UYI6phGJBABfFp7/oxs080RXCrKUhR+Go=
|
||||
-----END CERTIFICATE-----
|
Reference in New Issue
Block a user