1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-01-17 07:18:14 +01:00

Merge branch 'release/3.2.0'

This commit is contained in:
Ne-Lexa 2020-01-09 17:39:56 +03:00
commit 25d5ddcbff
131 changed files with 14727 additions and 7963 deletions

4
.gitattributes vendored
View File

@ -1,6 +1,8 @@
.gitattributes export-ignore
.github export-ignore
.gitignore export-ignore
.php_cs export-ignore
.travis.yml export-ignore
bootstrap.php export-ignore
phpunit.xml export-ignore
tests export-ignore
tests export-ignore

View File

@ -232,9 +232,10 @@ $rules = [
'mute_deprecation_error' => true,
'noise_remaining_usages' => true,
'noise_remaining_usages_exclude' => [
'gzinflate',
'fclose',
'fopen',
'gzinflate',
'iconv',
'mime_content_type',
'rename',
'unlink',

110
.phpstorm.meta.php Normal file
View File

@ -0,0 +1,110 @@
<?php
namespace PHPSTORM_META {
registerArgumentsSet(
"bool",
true,
false
);
registerArgumentsSet(
"compression_methods",
\PhpZip\Constants\ZipCompressionMethod::STORED,
\PhpZip\Constants\ZipCompressionMethod::DEFLATED,
\PhpZip\Constants\ZipCompressionMethod::BZIP2
);
expectedArguments(\PhpZip\ZipFile::addFile(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFromStream(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFromString(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addDir(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addDirRecursive(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFilesFromIterator(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFilesFromIterator(), 2, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFilesFromGlob(), 3, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFilesFromGlobRecursive(), 3, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFilesFromRegex(), 3, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::addFilesFromRegexRecursive(), 3, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\ZipFile::setCompressionMethodEntry(), 1, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\Model\ZipEntry::setCompressionMethod(), 0, argumentsSet("compression_methods"));
expectedArguments(\PhpZip\Model\ZipEntry::setMethod(), 0, argumentsSet("compression_methods"));
registerArgumentsSet(
'compression_levels',
\PhpZip\Constants\ZipCompressionLevel::MAXIMUM,
\PhpZip\Constants\ZipCompressionLevel::NORMAL,
\PhpZip\Constants\ZipCompressionLevel::FAST,
\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST
);
expectedArguments(\PhpZip\ZipFile::setCompressionLevel(), 0, argumentsSet("compression_levels"));
expectedArguments(\PhpZip\ZipFile::setCompressionLevelEntry(), 1, argumentsSet("compression_levels"));
expectedArguments(\PhpZip\Model\ZipEntry::setCompressionLevel(), 0, argumentsSet("compression_levels"));
registerArgumentsSet(
'encryption_methods',
\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256,
\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192,
\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128,
\PhpZip\Constants\ZipEncryptionMethod::PKWARE
);
expectedArguments(\PhpZip\ZipFile::setPassword(), 1, argumentsSet("encryption_methods"));
expectedArguments(\PhpZip\ZipFile::setPasswordEntry(), 2, argumentsSet("encryption_methods"));
expectedArguments(\PhpZip\Model\ZipEntry::setEncryptionMethod(), 0, argumentsSet("encryption_methods"));
expectedArguments(\PhpZip\Model\ZipEntry::setPassword(), 1, argumentsSet("encryption_methods"));
registerArgumentsSet(
'zip_mime_types',
null,
'application/zip',
'application/vnd.android.package-archive',
'application/java-archive'
);
expectedArguments(\PhpZip\ZipFile::outputAsAttachment(), 1, argumentsSet("zip_mime_types"));
expectedArguments(\PhpZip\ZipFile::outputAsAttachment(), 2, argumentsSet("bool"));
expectedArguments(\PhpZip\ZipFile::outputAsResponse(), 2, argumentsSet("zip_mime_types"));
expectedArguments(\PhpZip\ZipFile::outputAsResponse(), 3, argumentsSet("bool"));
registerArgumentsSet(
'dos_charset',
\PhpZip\Constants\DosCodePage::CP_LATIN_US,
\PhpZip\Constants\DosCodePage::CP_GREEK,
\PhpZip\Constants\DosCodePage::CP_BALT_RIM,
\PhpZip\Constants\DosCodePage::CP_LATIN1,
\PhpZip\Constants\DosCodePage::CP_LATIN2,
\PhpZip\Constants\DosCodePage::CP_CYRILLIC,
\PhpZip\Constants\DosCodePage::CP_TURKISH,
\PhpZip\Constants\DosCodePage::CP_PORTUGUESE,
\PhpZip\Constants\DosCodePage::CP_ICELANDIC,
\PhpZip\Constants\DosCodePage::CP_HEBREW,
\PhpZip\Constants\DosCodePage::CP_CANADA,
\PhpZip\Constants\DosCodePage::CP_ARABIC,
\PhpZip\Constants\DosCodePage::CP_NORDIC,
\PhpZip\Constants\DosCodePage::CP_CYRILLIC_RUSSIAN,
\PhpZip\Constants\DosCodePage::CP_GREEK2,
\PhpZip\Constants\DosCodePage::CP_THAI,
);
expectedArguments(\PhpZip\Model\ZipEntry::setCharset(), 0, argumentsSet('dos_charset'));
expectedArguments(\PhpZip\Constants\DosCodePage::toUTF8(), 1, argumentsSet('dos_charset'));
expectedArguments(\PhpZip\Constants\DosCodePage::fromUTF8(), 1, argumentsSet('dos_charset'));
registerArgumentsSet(
"zip_os",
\PhpZip\Constants\ZipPlatform::OS_UNIX,
\PhpZip\Constants\ZipPlatform::OS_DOS,
\PhpZip\Constants\ZipPlatform::OS_MAC_OSX,
);
expectedArguments(\PhpZip\Model\ZipEntry::setCreatedOS(), 0, argumentsSet('zip_os'));
expectedArguments(\PhpZip\Model\ZipEntry::setExtractedOS(), 0, argumentsSet('zip_os'));
expectedArguments(\PhpZip\Model\ZipEntry::setPlatform(), 0, argumentsSet('zip_os'));
registerArgumentsSet(
"zip_gpbf",
\PhpZip\Constants\GeneralPurposeBitFlag::ENCRYPTION |
\PhpZip\Constants\GeneralPurposeBitFlag::DATA_DESCRIPTOR |
\PhpZip\Constants\GeneralPurposeBitFlag::COMPRESSION_FLAG1 |
\PhpZip\Constants\GeneralPurposeBitFlag::COMPRESSION_FLAG2 |
\PhpZip\Constants\GeneralPurposeBitFlag::UTF8
);
expectedArguments(\PhpZip\Model\ZipEntry::setGeneralPurposeBitFlags(), 0, argumentsSet('zip_gpbf'));
}

View File

@ -1,22 +1,67 @@
dist: trusty
language: php
php:
- '5.5'
- '5.6'
- '7.0'
- '7.1'
- '7.2'
- '7.3'
- '7.4'
env:
global:
- ZIPALIGN_INSTALL=false
- COVERAGE=false
- PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests"
matrix:
include:
- php: 5.5
os: linux
dist: trusty
- php: 5.6
os: linux
dist: xenial
env: ZIPALIGN_INSTALL=true
- php: 7.0
os: linux
dist: xenial
env: ZIPALIGN_INSTALL=true
- php: 7.1
os: linux
dist: xenial
env: ZIPALIGN_INSTALL=true
- php: 7.2
os: linux
dist: xenial
env: ZIPALIGN_INSTALL=true
- php: 7.3
os: linux
dist: xenial
env: ZIPALIGN_INSTALL=true
- php: 7.4
os: linux
dist: bionic
env: COVERAGE=true ZIPALIGN_INSTALL=true PHPUNIT_FLAGS="-v -c phpunit.xml --testsuite only_fast_tests --coverage-clover=coverage.clover"
before_install:
- if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
install:
- travis_retry composer self-update && composer --version
- travis_retry composer install --no-interaction
addons:
apt:
packages:
- unzip
- p7zip-full
before_script:
- sudo apt-get install p7zip-full
- if [[ $ZIPALIGN_INSTALL = true ]]; then sudo apt-get install -y zipalign; fi
script:
- composer validate --no-check-lock
- vendor/bin/phpunit -v -c phpunit.xml
- vendor/bin/phpunit ${PHPUNIT_FLAGS}
after_success:
- if [[ $COVERAGE = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
- if [[ $COVERAGE = true ]]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi

View File

@ -1,42 +0,0 @@
# 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)`.

View File

@ -80,9 +80,9 @@
$zipFile = new \PhpZip\ZipFile();
try{
$zipFile
->addFromString("zip/entry/filename", "Is file content") // добавить запись из строки
->addFile("/path/to/file", "data/tofile") // добавить запись из файла
->addDir(__DIR__, "to/path/") // добавить файлы из директории
->addFromString('zip/entry/filename', "Is file content") // добавить запись из строки
->addFile('/path/to/file', 'data/tofile') // добавить запись из файла
->addDir(__DIR__, 'to/path/') // добавить файлы из директории
->saveAsFile($outputFilename) // сохранить архив в файл
->close(); // закрыть архив
@ -115,6 +115,8 @@ finally{
- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - добавляет файлы из директории по указанному пути c вложенными директориями.
- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - добавляет в ZIP-архив новую директорию.
- [ZipFile::addFile](#Documentation-ZipFile-addFile) - добавляет в ZIP-архив файл по указанному пути.
- [ZipFile::addSplFile](#Documentation-ZipFile-addSplFile) - добавляет объект `\SplFileInfo` в zip-архив.
- [ZipFile::addFromFinder](#Documentation-ZipFile-addFromFinder) - добавляет файлы из `Symfony\Component\Finder\Finder` в zip архив.
- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - добавляет файлы из итератора директорий.
- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - добавляет файлы из директории в соответствии с glob шаблоном без вложенных директорий.
- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - добавляет файлы из директории в соответствии с glob шаблоном c вложенными директориями.
@ -192,12 +194,15 @@ $zipFile->openFromStream($stream);
#### <a name="Documentation-Open-Zip-Entries"></a> Чтение записей из архива
<a name="Documentation-ZipFile-count"></a> **ZipFile::count** - возвращает количество записей в архиве.
```php
$zipFile = new \PhpZip\ZipFile();
$count = count($zipFile);
// или
$count = $zipFile->count();
```
<a name="Documentation-ZipFile-getListFiles"></a> **ZipFile::getListFiles** - возвращает список файлов архива.
```php
$zipFile = new \PhpZip\ZipFile();
$listFiles = $zipFile->getListFiles();
// Пример содержимого массива:
@ -210,6 +215,7 @@ $listFiles = $zipFile->getListFiles();
<a name="Documentation-ZipFile-getEntryContent"></a> **ZipFile::getEntryContent** - возвращает содержимое записи.
```php
// $entryName = 'path/to/example-entry-name.txt';
$zipFile = new \PhpZip\ZipFile();
$contents = $zipFile[$entryName];
// или
@ -218,6 +224,7 @@ $contents = $zipFile->getEntryContents($entryName);
<a name="Documentation-ZipFile-hasEntry"></a> **ZipFile::hasEntry** - проверяет, присутствует ли запись в архиве.
```php
// $entryName = 'path/to/example-entry-name.txt';
$zipFile = new \PhpZip\ZipFile();
$hasEntry = isset($zipFile[$entryName]);
// или
@ -226,23 +233,26 @@ $hasEntry = $zipFile->hasEntry($entryName);
<a name="Documentation-ZipFile-isDirectory"></a> **ZipFile::isDirectory** - проверяет, является ли запись в архиве директорией.
```php
// $entryName = 'path/to/';
$zipFile = new \PhpZip\ZipFile();
$isDirectory = $zipFile->isDirectory($entryName);
```
<a name="Documentation-ZipFile-extractTo"></a> **ZipFile::extractTo** - извлекает содержимое архива в заданную директорию.
Директория должна существовать.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->extractTo($directory);
```
Можно извлечь только некоторые записи в заданную директорию.
Директория должна существовать.
```php
$extractOnlyFiles = [
"filename1",
"filename2",
"dir/dir/dir/"
'filename1',
'filename2',
'dir/dir/dir/'
];
$zipFile->extractTo($directory, $extractOnlyFiles);
$zipFile = new \PhpZip\ZipFile();
$zipFile->extractTo($toDirectory, $extractOnlyFiles);
```
#### <a name="Documentation-Zip-Iterate"></a> Перебор записей/Итератор
`ZipFile` является итератором.
@ -251,7 +261,7 @@ $zipFile->extractTo($directory, $extractOnlyFiles);
foreach($zipFile as $entryName => $contents){
echo "Файл: $entryName" . PHP_EOL;
echo "Содержимое: $contents" . PHP_EOL;
echo "-----------------------------" . PHP_EOL;
echo '-----------------------------' . PHP_EOL;
}
```
Можно использовать паттерн `Iterator`.
@ -264,7 +274,7 @@ while ($iterator->valid())
echo "Файл: $entryName" . PHP_EOL;
echo "Содержимое: $contents" . PHP_EOL;
echo "-----------------------------" . PHP_EOL;
echo '-----------------------------' . PHP_EOL;
$iterator->next();
}
@ -280,110 +290,85 @@ $commentEntry = $zipFile->getEntryComment($entryName);
```
<a name="Documentation-ZipFile-getEntryInfo"></a> **ZipFile::getEntryInfo** - возвращает подробную информацию о записи в архиве.
```php
$zipFile = new \PhpZip\ZipFile();
$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`
- `\PhpZip\Constants\ZipCompressionMethod::STORED` - без сжатия
- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate сжатие
- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
<a name="Documentation-ZipFile-addFile"></a> **ZipFile::addFile** - добавляет в ZIP-архив файл по указанному пути из файловой системы.
```php
$zipFile = new \PhpZip\ZipFile();
// $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 сжатие
$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addSplFile"></a>
**ZipFile::addSplFile"** - добавляет объект `\SplFileInfo` в zip-архив.
```php
// $file = '...../file.ext';
// $entryName = 'file2.ext'
$zipFile = new \PhpZip\ZipFile();
$splFile = new \SplFileInfo('README.md');
$zipFile->addSplFile($splFile);
$zipFile->addSplFile($splFile, $entryName);
// or
$zipFile[$entryName] = new \SplFileInfo($file);
// установить метод сжатия
$zipFile->addSplFile($splFile, $entryName, $options = [
\PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
]);
```
<a name="Documentation-ZipFile-addFromFinder"></a>
**ZipFile::addFromFinder"** - добавляет файлы из `Symfony\Component\Finder\Finder` в zip архив.
https://symfony.com/doc/current/components/finder.html
```php
$finder = new \Symfony\Component\Finder\Finder();
$finder
->files()
->name('*.{jpg,jpeg,gif,png}')
->name('/^[0-9a-f]\./')
->contains('/lorem\s+ipsum$/i')
->in('path');
$zipFile = new \PhpZip\ZipFile();
$zipFile->addFromFinder($finder, $options = [
\PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
\PhpZip\Constants\ZipOptions::MODIFIED_TIME => new \DateTimeImmutable('-1 day 5 min')
]);
```
<a name="Documentation-ZipFile-addFromString"></a> **ZipFile::addFromString** - добавляет файл в ZIP-архив, используя его содержимое в виде строки.
```php
$zipFile = new \PhpZip\ZipFile();
$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 сжатие
$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addFromStream"></a> **ZipFile::addFromStream** - добавляет в ZIP-архив запись из потока.
```php
@ -392,9 +377,9 @@ $zipFile->addFromString($entryName, $contents, ZipFile::METHOD_BZIP2); // BZIP2
$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 сжатие
$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addEmptyDir"></a> **ZipFile::addEmptyDir** - добавляет в ZIP-архив новую (пустую) директорию.
```php
@ -424,9 +409,9 @@ $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 сжатие
$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addDirRecursive"></a> **ZipFile::addDirRecursive** - добавляет файлы из директории по указанному пути c вложенными директориями.
```php
@ -437,9 +422,9 @@ $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 сжатие
$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addFilesFromIterator"></a> **ZipFile::addFilesFromIterator** - добавляет файлы из итератора директорий.
```php
@ -455,9 +440,9 @@ $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 сжатие
$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
Пример добавления файлов из директории в архив с игнорированием некоторых файлов при помощи итератора директорий.
```php
@ -488,9 +473,9 @@ $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 сжатие
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addFilesFromGlobRecursive"></a> **ZipFile::addFilesFromGlobRecursive** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) c вложенными директориями.
```php
@ -503,9 +488,9 @@ $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 сжатие
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addFilesFromRegex"></a> **ZipFile::addFilesFromRegex** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) без вложенных директорий.
```php
@ -518,9 +503,9 @@ $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 сжатие
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
<a name="Documentation-ZipFile-addFilesFromRegexRecursive"></a> **ZipFile::addFilesFromRegexRecursive** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) с вложенными директориями.
```php
@ -533,9 +518,9 @@ $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 сжатие
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие
```
#### <a name="Documentation-Remove-Zip-Entries"></a> Удаление записей из архива
<a name="Documentation-ZipFile-deleteFromName"></a> **ZipFile::deleteFromName** - удаляет запись по имени.
@ -567,26 +552,26 @@ $zipFile->rename($oldName, $newName);
> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._
По умолчанию используется уровень сжатия -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) или уровень сжатия, определённый в архиве для Deflate сжатия.
По умолчанию используется уровень сжатия 5 (`\PhpZip\Constants\ZipCompressionLevel::NORMAL`) или уровень сжатия, определённый в архиве для Deflate сжатия.
Поддерживаются значения -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) и диапазон от 1 (`\PhpZip\ZipFile::LEVEL_BEST_SPEED`) до 9 (`\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION`). Чем выше число, тем лучше и дольше сжатие.
Поддерживаются диапазон значений от 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) до 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`). Чем выше число, тем лучше и дольше сжатие.
```php
$zipFile->setCompressionLevel(\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION);
$zipFile->setCompressionLevel(\PhpZip\Constants\ZipCompressionLevel::MAXIMUM);
```
<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`). Чем выше число, тем лучше и дольше сжатие.
Поддерживаются диапазон значений от 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) до 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`). Чем выше число, тем лучше и дольше сжатие.
```php
$zipFile->setCompressionLevelEntry($entryName, \PhpZip\ZipFile::LEVEL_BEST_COMPRESSION);
$zipFile->setCompressionLevelEntry($entryName, \PhpZip\Constants\ZipCompressionLevel::MAXIMUM);
```
<a name="Documentation-ZipFile-setCompressionMethodEntry"></a> **ZipFile::setCompressionMethodEntry** - устанавливает метод сжатия для определённой записи в архиве.
Доступны следующие методы сжатия:
- `\PhpZip\ZipFile::METHOD_STORED` - без сжатия
- `\PhpZip\ZipFile::METHOD_DEFLATED` - Deflate сжатие
- `\PhpZip\ZipFile::METHOD_BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
- `\PhpZip\Constants\ZipCompressionMethod::STORED` - без сжатия
- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate сжатие
- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2`
```php
$zipFile->setCompressionMethodEntry($entryName, ZipFile::METHOD_DEFLATED);
$zipFile->setCompressionMethodEntry($entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED);
```
<a name="Documentation-ZipFile-setArchiveComment"></a> **ZipFile::setArchiveComment** - устанавливает комментарий к ZIP-архиву.
```php
@ -652,10 +637,10 @@ $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
- `\PhpZip\Constants\ZipEncryptionMethod::PKWARE` - Traditional PKWARE encryption
- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256` - WinZip AES encryption 256 bit (рекомендуемое)
- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192` - WinZip AES encryption 192 bit
- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128` - WinZip AES encryption 128 bit
<a name="Documentation-ZipFile-setReadPassword"></a> **ZipFile::setReadPassword** - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей.
@ -675,7 +660,7 @@ $zipFile->setPassword($password);
```
Можно установить метод шифрования:
```php
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
$zipFile->setPassword($password, $encryptionMethod);
```
<a name="Documentation-ZipFile-setPasswordEntry"></a> **ZipFile::setPasswordEntry** - устанавливает новый пароль для конкретного файла.
@ -684,7 +669,7 @@ $zipFile->setPasswordEntry($entryName, $password);
```
Можно установить метод шифрования:
```php
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod);
```
<a name="Documentation-ZipFile-disableEncryption"></a> **ZipFile::disableEncryption** - отключает шифрования всех записей, находящихся в архиве.
@ -789,7 +774,8 @@ composer install --dev
vendor/bin/phpunit -v -c phpunit.xml
```
### <a name="Changelog"></a> История изменений
[Ссылка на Changelog](CHANGELOG.md)
История изменений на [странице релизов](https://github.com/Ne-Lexa/php-zip/releases).
### <a name="Upgrade"></a> Обновление версий
#### <a name="Upgrade-v2-to-v3"></a> Обновление с версии 2 до версии 3.0
Обновите мажорную версию в файле `composer.json` до `^3.0`.

300
README.md
View File

@ -80,9 +80,9 @@ Latest stable version: [![Latest Stable Version](https://poser.pugx.org/nelexa/z
$zipFile = new \PhpZip\ZipFile();
try{
$zipFile
->addFromString("zip/entry/filename", "Is file content") // add an entry from the string
->addFile("/path/to/file", "data/tofile") // add an entry from the file
->addDir(__DIR__, "to/path/") // add files from the directory
->addFromString('zip/entry/filename', 'Is file content') // add an entry from the string
->addFile('/path/to/file', 'data/tofile') // add an entry from the file
->addDir(__DIR__, 'to/path/') // add files from the directory
->saveAsFile($outputFilename) // save the archive to a file
->close(); // close archive
@ -115,6 +115,8 @@ Other examples can be found in the `tests/` folder
- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - adds files to the archive from the directory on the specified path with subdirectories.
- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - add a new directory.
- [ZipFile::addFile](#Documentation-ZipFile-addFile) - adds a file to a ZIP archive from the given path.
- [ZipFile::addSplFile](#Documentation-ZipFile-addSplFile) - adds a `\SplFileInfo` to a ZIP archive.
- [ZipFile::addFromFinder](#Documentation-ZipFile-addFromFinder) - adds files from the `Symfony\Component\Finder\Finder` to a ZIP archive.
- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - adds files from the iterator of directories.
- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - adds files from a directory by glob pattern without subdirectories.
- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - adds files from a directory by glob pattern with subdirectories.
@ -192,12 +194,15 @@ $zipFile->openFromStream($stream);
#### <a name="Documentation-Open-Zip-Entries"></a> Reading entries from the archive
<a name="Documentation-ZipFile-count"></a> **ZipFile::count** - returns the number of entries in the archive.
```php
$zipFile = new \PhpZip\ZipFile();
$count = count($zipFile);
// or
$count = $zipFile->count();
```
<a name="Documentation-ZipFile-getListFiles"></a> **ZipFile::getListFiles** - returns list of archive files.
```php
$zipFile = new \PhpZip\ZipFile();
$listFiles = $zipFile->getListFiles();
// example array contents:
@ -205,11 +210,13 @@ $listFiles = $zipFile->getListFiles();
// 0 => 'info.txt',
// 1 => 'path/to/file.jpg',
// 2 => 'another path/',
// 3 => '0',
// )
```
<a name="Documentation-ZipFile-getEntryContent"></a> **ZipFile::getEntryContent** - returns the entry contents using its name.
```php
// $entryName = 'path/to/example-entry-name.txt';
$zipFile = new \PhpZip\ZipFile();
$contents = $zipFile[$entryName];
// or
@ -218,6 +225,7 @@ $contents = $zipFile->getEntryContents($entryName);
<a name="Documentation-ZipFile-hasEntry"></a> **ZipFile::hasEntry** - checks if there is an entry in the archive.
```php
// $entryName = 'path/to/example-entry-name.txt';
$zipFile = new \PhpZip\ZipFile();
$hasEntry = isset($zipFile[$entryName]);
// or
@ -226,23 +234,27 @@ $hasEntry = $zipFile->hasEntry($entryName);
<a name="Documentation-ZipFile-isDirectory"></a> **ZipFile::isDirectory** - checks that the entry in the archive is a directory.
```php
// $entryName = 'path/to/';
$zipFile = new \PhpZip\ZipFile();
$isDirectory = $zipFile->isDirectory($entryName);
```
<a name="Documentation-ZipFile-extractTo"></a> **ZipFile::extractTo** - extract the archive contents.
The directory must exist.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->extractTo($directory);
```
Extract some files to the directory.
The directory must exist.
```php
// $toDirectory = '/tmp';
$extractOnlyFiles = [
"filename1",
"filename2",
"dir/dir/dir/"
'filename1',
'filename2',
'dir/dir/dir/'
];
$zipFile->extractTo($directory, $extractOnlyFiles);
$zipFile = new \PhpZip\ZipFile();
$zipFile->extractTo($toDirectory, $extractOnlyFiles);
```
#### <a name="Documentation-Zip-Iterate"></a> Iterating entries
`ZipFile` is an iterator.
@ -251,7 +263,7 @@ Can iterate all the entries in the `foreach` loop.
foreach($zipFile as $entryName => $contents){
echo "Filename: $entryName" . PHP_EOL;
echo "Contents: $contents" . PHP_EOL;
echo "-----------------------------" . PHP_EOL;
echo '-----------------------------' . PHP_EOL;
}
```
Can iterate through the `Iterator`.
@ -264,7 +276,7 @@ while ($iterator->valid())
echo "Filename: $entryName" . PHP_EOL;
echo "Contents: $contents" . PHP_EOL;
echo "-----------------------------" . PHP_EOL;
echo '-----------------------------' . PHP_EOL;
$iterator->next();
}
@ -272,121 +284,100 @@ while ($iterator->valid())
#### <a name="Documentation-Zip-Info"></a> Getting information about entries
<a name="Documentation-ZipFile-getArchiveComment"></a> **ZipFile::getArchiveComment** - returns the Zip archive comment.
```php
$zipFile = new \PhpZip\ZipFile();
$commentArchive = $zipFile->getArchiveComment();
```
<a name="Documentation-ZipFile-getEntryComment"></a> **ZipFile::getEntryComment** - returns the comment of an entry using the entry name.
```php
$zipFile = new \PhpZip\ZipFile();
$commentEntry = $zipFile->getEntryComment($entryName);
```
<a name="Documentation-ZipFile-getEntryInfo"></a> **ZipFile::getEntryInfo** - returns detailed information about the entry in the archive
```php
$zipFile = new \PhpZip\ZipFile();
$zipInfo = $zipFile->getEntryInfo('file.txt');
$arrayInfo = $zipInfo->toArray();
// example array contents:
// 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);
// output:
//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;
// Output:
// 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** - returns detailed information about all entries in the archive.
```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> Adding entries to the archive
All methods of adding entries to a ZIP archive allow you to specify a method for compressing content.
The following methods of compression are available:
- `\PhpZip\ZipFile::METHOD_STORED` - no compression
- `\PhpZip\ZipFile::METHOD_DEFLATED` - Deflate compression
- `\PhpZip\ZipFile::METHOD_BZIP2` - Bzip2 compression with the extension `ext-bz2`
- `\PhpZip\Constants\ZipCompressionMethod::STORED` - no compression
- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate compression
- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 compression with the extension `ext-bz2`
<a name="Documentation-ZipFile-addFile"></a> **ZipFile::addFile** - adds a file to a ZIP archive from the given path.
```php
$zipFile = new \PhpZip\ZipFile();
// $file = '...../file.ext';
// $entryName = 'file2.ext'
$zipFile->addFile($file);
// you can specify the name of the entry in the archive (if null, then the last component from the file name is used)
$zipFile->addFile($file, $entryName);
// you can specify a compression method
$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addSplFile"></a>
**ZipFile::addSplFile"** - adds a `\SplFileInfo` to a ZIP archive.
```php
// $file = '...../file.ext';
// $entryName = 'file2.ext'
$zipFile = new \PhpZip\ZipFile();
$splFile = new \SplFileInfo('README.md');
$zipFile->addSplFile($splFile);
$zipFile->addSplFile($splFile, $entryName);
// or
$zipFile[$entryName] = new \SplFileInfo($file);
// you can specify a compression method
$zipFile->addFile($file, $entryName, ZipFile::METHOD_STORED); // No compression
$zipFile->addFile($file, $entryName, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFile($file, $entryName, ZipFile::METHOD_BZIP2); // BZIP2 compression
// set compression method
$zipFile->addSplFile($splFile, $entryName, $options = [
\PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
]);
```
<a name="Documentation-ZipFile-addFromFinder"></a>
**ZipFile::addFromFinder"** - adds files from the `Symfony\Component\Finder\Finder` to a ZIP archive.
https://symfony.com/doc/current/components/finder.html
```php
$finder = new \Symfony\Component\Finder\Finder();
$finder
->files()
->name('*.{jpg,jpeg,gif,png}')
->name('/^[0-9a-f]\./')
->contains('/lorem\s+ipsum$/i')
->in('path');
$zipFile = new \PhpZip\ZipFile();
$zipFile->addFromFinder($finder, $options = [
\PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED,
\PhpZip\Constants\ZipOptions::MODIFIED_TIME => new \DateTimeImmutable('-1 day 5 min')
]);
```
<a name="Documentation-ZipFile-addFromString"></a> **ZipFile::addFromString** - adds a file to a ZIP archive using its contents.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile[$entryName] = $contents;
// or
$zipFile->addFromString($entryName, $contents);
// you can specify a compression method
$zipFile->addFromString($entryName, $contents, ZipFile::METHOD_STORED); // No compression
$zipFile->addFromString($entryName, $contents, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFromString($entryName, $contents, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addFromStream"></a> **ZipFile::addFromStream** - adds a entry from the stream to the ZIP archive.
```php
$zipFile = new \PhpZip\ZipFile();
// $stream = fopen(..., 'rb');
$zipFile->addFromStream($stream, $entryName);
@ -394,14 +385,14 @@ $zipFile->addFromStream($stream, $entryName);
$zipFile[$entryName] = $stream;
// you can specify a compression method
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_STORED); // No compression
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addEmptyDir"></a> **ZipFile::addEmptyDir** - add a new directory.
```php
$zipFile = new \PhpZip\ZipFile();
// $path = "path/to/";
$zipFile->addEmptyDir($path);
// or
$zipFile[$path] = null;
@ -411,67 +402,71 @@ $zipFile[$path] = null;
$entries = [
'file.txt' => 'file contents', // add an entry from the string contents
'empty dir/' => null, // add empty directory
'path/to/file.jpg' => fopen('..../filename', 'r'), // add an entry from the stream
'path/to/file.jpg' => fopen('..../filename', 'rb'), // add an entry from the stream
'path/to/file.dat' => new \SplFileInfo('..../filename'), // add an entry from the file
];
$zipFile = new \PhpZip\ZipFile();
$zipFile->addAll($entries);
```
<a name="Documentation-ZipFile-addDir"></a> **ZipFile::addDir** - adds files to the archive from the directory on the specified path without subdirectories.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->addDir($dirName);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addDir($dirName, $localPath);
// you can specify a compression method
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addDirRecursive"></a> **ZipFile::addDirRecursive** - adds files to the archive from the directory on the specified path with subdirectories.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->addDirRecursive($dirName);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addDirRecursive($dirName, $localPath);
// you can specify a compression method
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addFilesFromIterator"></a> **ZipFile::addFilesFromIterator** - adds files from the iterator of directories.
```php
// $directoryIterator = new \DirectoryIterator($dir); // without subdirectories
// $directoryIterator = new \RecursiveDirectoryIterator($dir); // with subdirectories
$zipFile = new \PhpZip\ZipFile();
$zipFile->addFilesFromIterator($directoryIterator);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
// or
$zipFile[$localPath] = $directoryIterator;
// you can specify a compression method
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
Example with some files ignoring:
```php
$ignoreFiles = [
"file_ignore.txt",
"dir_ignore/sub dir ignore/"
'file_ignore.txt',
'dir_ignore/sub dir ignore/'
];
// $directoryIterator = new \DirectoryIterator($dir); // without subdirectories
// $directoryIterator = new \RecursiveDirectoryIterator($dir); // with subdirectories
// use \PhpZip\Util\Iterator\IgnoreFilesFilterIterator for non-recursive search
$zipFile = new \PhpZip\ZipFile();
$ignoreIterator = new \PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator(
$directoryIterator,
$ignoreFiles
@ -483,123 +478,138 @@ $zipFile->addFilesFromIterator($ignoreIterator);
```php
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
$zipFile = new \PhpZip\ZipFile();
$zipFile->addFilesFromGlob($dir, $globPattern);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath);
// you can specify a compression method
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addFilesFromGlobRecursive"></a> **ZipFile::addFilesFromGlobRecursive** - adds files from a directory by [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) with subdirectories.
```php
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
$zipFile = new \PhpZip\ZipFile();
$zipFile->addFilesFromGlobRecursive($dir, $globPattern);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath);
// you can specify a compression method
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addFilesFromRegex"></a> **ZipFile::addFilesFromRegex** - adds files from a directory by [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression) without subdirectories.
```php
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
$zipFile = new \PhpZip\ZipFile();
$zipFile->addFilesFromRegex($dir, $regexPattern);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath);
// you can specify a compression method
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
<a name="Documentation-ZipFile-addFilesFromRegexRecursive"></a> **ZipFile::addFilesFromRegexRecursive** - adds files from a directory by [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression) with subdirectories.
```php
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern);
// you can specify the path in the archive to which you want to put entries
$localPath = "to/path/";
$localPath = 'to/path/';
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath);
// you can specify a compression method
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression
```
#### <a name="Documentation-Remove-Zip-Entries"></a> Deleting entries from the archive
<a name="Documentation-ZipFile-deleteFromName"></a> **ZipFile::deleteFromName** - deletes an entry in the archive using its name.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->deleteFromName($entryName);
```
<a name="Documentation-ZipFile-deleteFromGlob"></a> **ZipFile::deleteFromGlob** - deletes a entries in the archive using [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)).
```php
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> delete all .jpg, .jpeg, .png and .gif files
$zipFile = new \PhpZip\ZipFile();
$zipFile->deleteFromGlob($globPattern);
```
<a name="Documentation-ZipFile-deleteFromRegex"></a> **ZipFile::deleteFromRegex** - deletes a entries in the archive using [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression).
```php
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> delete all .jpg, .jpeg, .png and .gif files
$zipFile = new \PhpZip\ZipFile();
$zipFile->deleteFromRegex($regexPattern);
```
<a name="Documentation-ZipFile-deleteAll"></a> **ZipFile::deleteAll** - deletes all entries in the ZIP archive.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->deleteAll();
```
#### <a name="Documentation-Entries"></a> Working with entries and archive
<a name="Documentation-ZipFile-rename"></a> **ZipFile::rename** - renames an entry defined by its name.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->rename($oldName, $newName);
```
<a name="Documentation-ZipFile-setCompressionLevel"></a> **ZipFile::setCompressionLevel** - set the compression level for all files in the archive.
> _Note that this method does not apply to entries that are added after this method is run._
By default, the compression level is -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) or the compression level specified in the archive for Deflate compression.
By default, the compression level is 5 (`\PhpZip\Constants\ZipCompressionLevel::NORMAL`) or the compression level specified in the archive for Deflate compression.
The values -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) and the range from 1 (`\PhpZip\ZipFile::LEVEL_BEST_SPEED`) to 9 (`\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION`) are supported. The higher the number, the better and longer the compression.
The values range from 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) to 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`) are supported. The higher the number, the better and longer the compression.
```php
$zipFile->setCompressionLevel(\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION);
$zipFile = new \PhpZip\ZipFile();
$zipFile->setCompressionLevel(\PhpZip\Constants\ZipCompressionLevel::MAXIMUM);
```
<a name="Documentation-ZipFile-setCompressionLevelEntry"></a> **ZipFile::setCompressionLevelEntry** - sets the compression level for the entry by its name.
The values -1 (`\PhpZip\ZipFile::LEVEL_DEFAULT_COMPRESSION`) and the range from 1 (`\PhpZip\ZipFile::LEVEL_BEST_SPEED`) to 9 (`\PhpZip\ZipFile::LEVEL_BEST_COMPRESSION`) are supported. The higher the number, the better and longer the compression.
The values range from 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) to 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`) are supported. The higher the number, the better and longer the compression.
```php
$zipFile->setCompressionLevelEntry($entryName, \PhpZip\ZipFile::LEVEL_BEST_COMPRESSION);
$zipFile = new \PhpZip\ZipFile();
$zipFile->setCompressionLevelEntry($entryName, \PhpZip\Constants\ZipCompressionLevel::FAST);
```
<a name="Documentation-ZipFile-setCompressionMethodEntry"></a> **ZipFile::setCompressionMethodEntry** - sets the compression method for the entry by its name.
The following compression methods are available:
- `\PhpZip\ZipFile::METHOD_STORED` - No compression
- `\PhpZip\ZipFile::METHOD_DEFLATED` - Deflate compression
- `\PhpZip\ZipFile::METHOD_BZIP2` - Bzip2 compression with the extension `ext-bz2`
- `\PhpZip\Constants\ZipCompressionMethod::STORED` - No compression
- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate compression
- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 compression with the extension `ext-bz2`
```php
$zipFile->setCompressionMethodEntry($entryName, ZipFile::METHOD_DEFLATED);
$zipFile = new \PhpZip\ZipFile();
$zipFile->setCompressionMethodEntry($entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED);
```
<a name="Documentation-ZipFile-setArchiveComment"></a> **ZipFile::setArchiveComment** - set the comment of a ZIP archive.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->setArchiveComment($commentArchive);
```
<a name="Documentation-ZipFile-setEntryComment"></a> **ZipFile::setEntryComment** - set the comment of an entry defined by its name.
```php
$zipFile = new \PhpZip\ZipFile();
$zipFile->setEntryComment($entryName, $comment);
```
<a name="Documentation-ZipFile-matcher"></a> **ZipFile::matcher** - selecting entries in the archive to perform operations on them.
```php
$zipFile = new \PhpZip\ZipFile();
$matcher = $zipFile->matcher();
```
Selecting files from the archive one at a time:
@ -638,7 +648,7 @@ $entries = $matcher->getMatches();
invoke() - invoke a callable function on selected entries:
```php
// example
$matcher->invoke(function($entryName) use($zipFile) {
$matcher->invoke(static function($entryName) use($zipFile) {
$newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName);
$zipFile->rename($entryName, $newName);
});
@ -654,10 +664,10 @@ $matcher->disableEncryption(); // disables encryption for selected entries
#### <a name="Documentation-Password"></a> Working with passwords
Implemented support for encryption methods:
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_TRADITIONAL` - Traditional PKWARE encryption
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256` - WinZip AES encryption 256 bit (recommended)
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192` - WinZip AES encryption 192 bit
- `\PhpZip\ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128` - WinZip AES encryption 128 bit
- `\PhpZip\Constants\ZipEncryptionMethod::PKWARE` - Traditional PKWARE encryption (legacy)
- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256` - WinZip AES encryption 256 bit (recommended)
- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192` - WinZip AES encryption 192 bit
- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128` - WinZip AES encryption 128 bit
<a name="Documentation-ZipFile-setReadPassword"></a> **ZipFile::setReadPassword** - set the password for the open archive.
@ -677,7 +687,7 @@ $zipFile->setPassword($password);
```
You can set the encryption method:
```php
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
$zipFile->setPassword($password, $encryptionMethod);
```
<a name="Documentation-ZipFile-setPasswordEntry"></a> **ZipFile::setPasswordEntry** - sets a new password of an entry defined by its name.
@ -686,7 +696,7 @@ $zipFile->setPasswordEntry($entryName, $password);
```
You can set the encryption method:
```php
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256;
$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod);
```
<a name="Documentation-ZipFile-disableEncryption"></a> **ZipFile::disableEncryption** - disable encryption for all entries that are already in the archive.
@ -744,7 +754,7 @@ $zipFile->outputAsAttachment($outputFilename);
```
You can set the Mime-Type:
```php
$mimeType = 'application/zip'
$mimeType = 'application/zip';
$zipFile->outputAsAttachment($outputFilename, $mimeType);
```
<a name="Documentation-ZipFile-outputAsResponse"></a> **ZipFile::outputAsResponse** - outputs a ZIP-archive as [PSR-7 Response](http://www.php-fig.org/psr/psr-7/).
@ -756,19 +766,9 @@ $zipFile->outputAsResponse($response, $outputFilename);
```
You can set the Mime-Type:
```php
$mimeType = 'application/zip'
$mimeType = 'application/zip';
$zipFile->outputAsResponse($response, $outputFilename, $mimeType);
```
An example for the 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** - save changes and re-open the changed archive.
```php
$zipFile->rewrite();
@ -785,10 +785,10 @@ composer install --dev
```
Run the tests:
```bash
vendor/bin/phpunit -v -c phpunit.xml
vendor/bin/phpunit
```
### <a name="Changelog"></a> Changelog
[Link to Changelog](CHANGELOG.md)
Changes are documented in the [releases page](https://github.com/Ne-Lexa/php-zip/releases).
### <a name="Upgrade"></a> Upgrade
#### <a name="Upgrade-v2-to-v3"></a> Upgrade version 2 to version 3.0

6
bootstrap.php Normal file
View File

@ -0,0 +1,6 @@
<?php
// see https://stackoverflow.com/questions/33299149/phpstorm-8-and-phpunit-problems-with-runinseparateprocess/37174348#37174348
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/vendor/autoload.php');
}

View File

@ -21,23 +21,28 @@
}
],
"require": {
"php": "^5.5 || ^7.0",
"php": "^5.5.9 || ^7.0",
"ext-zlib": "*",
"psr/http-message": "^1.0",
"paragonie/random_compat": ">=1 <9.99"
"paragonie/random_compat": ">=1 <9.99",
"symfony/finder": "^3.0|^4.0|^5.0"
},
"require-dev": {
"ext-bz2": "*",
"ext-openssl": "*",
"ext-fileinfo": "*",
"guzzlehttp/psr7": "^1.6",
"phpunit/phpunit": "^4.8|^5.7",
"zendframework/zend-diactoros": "^1.4"
"symfony/var-dumper": "^3.0|^4.0|^5.0"
},
"autoload": {
"psr-4": {
"PhpZip\\": "src/PhpZip"
"PhpZip\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PhpZip\\": "tests/PhpZip"
"PhpZip\\Tests\\": "tests/"
}
},
"suggest": {
@ -50,11 +55,5 @@
"scripts": {
"php:fix": "php .php_cs --force",
"php:fix:debug": "php .php_cs"
},
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev",
"dev-develop": "3.2.x-dev"
}
}
}

View File

@ -4,15 +4,22 @@
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php">
bootstrap="bootstrap.php">
<php>
<ini name="error_reporting" value="-1"/>
</php>
<testsuites>
<testsuite name="PhpZip test suite">
<testsuite name="all_tests">
<directory>tests</directory>
</testsuite>
<testsuite name="only_fast_tests">
<directory>tests</directory>
<exclude>tests/SlowTests</exclude>
</testsuite>
<testsuite name="only_slow_tests">
<directory>tests/SlowTests</directory>
</testsuite>
</testsuites>
<filter>
@ -20,4 +27,4 @@
<directory>src</directory>
</whitelist>
</filter>
</phpunit>
</phpunit>

View File

@ -0,0 +1,33 @@
<?php
namespace PhpZip\Constants;
/**
* Interface DosAttrs.
*/
interface DosAttrs
{
/** @var int DOS File Attribute Read Only */
const DOS_READ_ONLY = 0x01;
/** @var int DOS File Attribute Hidden */
const DOS_HIDDEN = 0x02;
/** @var int DOS File Attribute System */
const DOS_SYSTEM = 0x04;
/** @var int DOS File Attribute Label */
const DOS_LABEL = 0x08;
/** @var int DOS File Attribute Directory */
const DOS_DIRECTORY = 0x10;
/** @var int DOS File Attribute Archive */
const DOS_ARCHIVE = 0x20;
/** @var int DOS File Attribute Link */
const DOS_LINK = 0x40;
/** @var int DOS File Attribute Execute */
const DOS_EXE = 0x80;
}

View File

@ -0,0 +1,105 @@
<?php
/** @noinspection PhpComposerExtensionStubsInspection */
namespace PhpZip\Constants;
/**
* Class DosCodePage.
*/
final class DosCodePage
{
const CP_LATIN_US = 'cp437';
const CP_GREEK = 'cp737';
const CP_BALT_RIM = 'cp775';
const CP_LATIN1 = 'cp850';
const CP_LATIN2 = 'cp852';
const CP_CYRILLIC = 'cp855';
const CP_TURKISH = 'cp857';
const CP_PORTUGUESE = 'cp860';
const CP_ICELANDIC = 'cp861';
const CP_HEBREW = 'cp862';
const CP_CANADA = 'cp863';
const CP_ARABIC = 'cp864';
const CP_NORDIC = 'cp865';
const CP_CYRILLIC_RUSSIAN = 'cp866';
const CP_GREEK2 = 'cp869';
const CP_THAI = 'cp874';
/** @var string[] */
private static $CP_CHARSETS = [
self::CP_LATIN_US,
self::CP_GREEK,
self::CP_BALT_RIM,
self::CP_LATIN1,
self::CP_LATIN2,
self::CP_CYRILLIC,
self::CP_TURKISH,
self::CP_PORTUGUESE,
self::CP_ICELANDIC,
self::CP_HEBREW,
self::CP_CANADA,
self::CP_ARABIC,
self::CP_NORDIC,
self::CP_CYRILLIC_RUSSIAN,
self::CP_GREEK2,
self::CP_THAI,
];
/**
* @param string $str
* @param string $sourceEncoding
*
* @return string
*/
public static function toUTF8($str, $sourceEncoding)
{
$s = iconv($sourceEncoding, 'UTF-8', $str);
if ($s === false) {
return $str;
}
return $s;
}
/**
* @param string $str
* @param string $destEncoding
*
* @return string
*/
public static function fromUTF8($str, $destEncoding)
{
$s = iconv('UTF-8', $destEncoding, $str);
if ($s === false) {
return $str;
}
return $s;
}
/**
* @return string[]
*/
public static function getCodePages()
{
return self::$CP_CHARSETS;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace PhpZip\Constants;
/**
* General purpose bit flag constants.
*/
interface GeneralPurposeBitFlag
{
/**
* General Purpose Bit Flag mask for encrypted data.
* Bit 0: If set, indicates that the file is encrypted.
*/
const ENCRYPTION = 1; // 1 << 0
/**
* Compression Flag Bit 1 for method Deflating.
*
* Bit 2 Bit 1
* 0 0 Normal compression
* 0 1 Maximum compression
* 1 0 Fast compression
* 1 1 Super Fast compression
*
* @see GeneralPurposeBitFlag::COMPRESSION_FLAG2
*/
const COMPRESSION_FLAG1 = 2; // 1 << 1
/**
* Compression Flag Bit 2 for method Deflating.
*
* Bit 2 Bit 1
* 0 0 Normal compression
* 0 1 Maximum compression
* 1 0 Fast compression
* 1 1 Super Fast compression
*
* @see GeneralPurposeBitFlag::COMPRESSION_FLAG1
*/
const COMPRESSION_FLAG2 = 4; // 1 << 2
/**
* General Purpose Bit Flag mask for data descriptor.
*
* Bit 3: If this bit is set, the fields crc-32, compressed
* size and uncompressed size are set to zero in the
* local header. The correct values are put in the data
* descriptor immediately following the compressed data.
*/
const DATA_DESCRIPTOR = 8; // 1 << 3
/**
* General Purpose Bit Flag mask for strong encryption.
*
* Bit 6: Strong encryption.
* If this bit is set, you MUST set the version needed to extract
* value to at least 50 and you MUST also set bit 0.
* If AES encryption is used, the version needed to extract value
* MUST be at least 51.
*/
const STRONG_ENCRYPTION = 64; // 1 << 6
/**
* General Purpose Bit Flag mask for UTF-8.
*
* Bit 11: Language encoding flag (EFS).
* If this bit is set, the filename and comment fields
* for this file MUST be encoded using UTF-8. (see APPENDIX D)
*/
const UTF8 = 2048; // 1 << 11
}

View File

@ -0,0 +1,84 @@
<?php
namespace PhpZip\Constants;
/**
* Unix stat constants.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
interface UnixStat
{
/** @var int unix file type mask */
const UNX_IFMT = 0170000;
/** @var int unix regular file */
const UNX_IFREG = 0100000;
/** @var int unix socket (BSD, not SysV or Amiga) */
const UNX_IFSOCK = 0140000;
/** @var int unix symbolic link (not SysV, Amiga) */
const UNX_IFLNK = 0120000;
/** @var int unix block special (not Amiga) */
const UNX_IFBLK = 0060000;
/** @var int unix directory */
const UNX_IFDIR = 0040000;
/** @var int unix character special (not Amiga) */
const UNX_IFCHR = 0020000;
/** @var int unix fifo (BCC, not MSC or Amiga) */
const UNX_IFIFO = 0010000;
/** @var int unix set user id on execution */
const UNX_ISUID = 04000;
/** @var int unix set group id on execution */
const UNX_ISGID = 02000;
/** @var int unix directory permissions control */
const UNX_ISVTX = 01000;
/** @var int unix record locking enforcement flag */
const UNX_ENFMT = 02000;
/** @var int unix read, write, execute: owner */
const UNX_IRWXU = 00700;
/** @var int unix read permission: owner */
const UNX_IRUSR = 00400;
/** @var int unix write permission: owner */
const UNX_IWUSR = 00200;
/** @var int unix execute permission: owner */
const UNX_IXUSR = 00100;
/** @var int unix read, write, execute: group */
const UNX_IRWXG = 00070;
/** @var int unix read permission: group */
const UNX_IRGRP = 00040;
/** @var int unix write permission: group */
const UNX_IWGRP = 00020;
/** @var int unix execute permission: group */
const UNX_IXGRP = 00010;
/** @var int unix read, write, execute: other */
const UNX_IRWXO = 00007;
/** @var int unix read permission: other */
const UNX_IROTH = 00004;
/** @var int unix write permission: other */
const UNX_IWOTH = 00002;
/** @var int unix execute permission: other */
const UNX_IXOTH = 00001;
}

View File

@ -0,0 +1,54 @@
<?php
namespace PhpZip\Constants;
/**
* Compression levels for Deflate and BZIP2.
*
* {@see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT} Section 4.4.4:
*
* For Methods 8 and 9 - Deflating
* -------------------------------
* Bit 2 Bit 1
* 0 0 Normal (-en) compression option was used.
* 0 1 Maximum (-exx/-ex) compression option was used.
* 1 0 Fast (-ef) compression option was used.
* 1 1 Super Fast (-es) compression option was used.
*
* Different programs encode compression level information in different ways:
*
* Deflate Compress Level pkzip zip 7z, WinRAR WinZip
* ---------------------- ---------------- ------- ---------- ------
* Super Fast compression 1 1
* Fast compression 2 1, 2
* Normal Compression 3 - 8 (5 default) 3 - 7 1 - 9
* Maximum compression 9 8, 9 9
*/
interface ZipCompressionLevel
{
/** @var int Compression level for super fast compression. */
const SUPER_FAST = 1;
/** @var int compression level for fast compression */
const FAST = 2;
/** @var int compression level for normal compression */
const NORMAL = 5;
/** @var int compression level for maximum compression */
const MAXIMUM = 9;
/**
* @var int int Minimum compression level
*
* @internal
*/
const LEVEL_MIN = self::SUPER_FAST;
/**
* @var int int Maximum compression level
*
* @internal
*/
const LEVEL_MAX = self::MAXIMUM;
}

View File

@ -0,0 +1,102 @@
<?php
namespace PhpZip\Constants;
use PhpZip\Exception\ZipUnsupportMethodException;
/**
* Class ZipCompressionMethod.
*/
final class ZipCompressionMethod
{
/** @var int Compression method Store */
const STORED = 0;
/** @var int Compression method Deflate */
const DEFLATED = 8;
/** @var int Compression method Bzip2 */
const BZIP2 = 12;
/** @var int Compression method AES-Encryption */
const WINZIP_AES = 99;
/** @var array Compression Methods */
private static $ZIP_COMPRESSION_METHODS = [
self::STORED => 'Stored',
1 => 'Shrunk',
2 => 'Reduced compression factor 1',
3 => 'Reduced compression factor 2',
4 => 'Reduced compression factor 3',
5 => 'Reduced compression factor 4',
6 => 'Imploded',
7 => 'Reserved for Tokenizing compression algorithm',
self::DEFLATED => 'Deflated',
9 => 'Enhanced Deflating using Deflate64(tm)',
10 => 'PKWARE Data Compression Library Imploding',
11 => 'Reserved by PKWARE',
self::BZIP2 => 'BZIP2',
13 => 'Reserved by PKWARE',
14 => 'LZMA',
15 => 'Reserved by PKWARE',
16 => 'Reserved by PKWARE',
17 => 'Reserved by PKWARE',
18 => 'File is compressed using IBM TERSE (new)',
19 => 'IBM LZ77 z Architecture (PFS)',
96 => 'WinZip JPEG Compression',
97 => 'WavPack compressed data',
98 => 'PPMd version I, Rev 1',
self::WINZIP_AES => 'AES Encryption',
];
/**
* @param int $value
*
* @return string
*/
public static function getCompressionMethodName($value)
{
return isset(self::$ZIP_COMPRESSION_METHODS[$value]) ?
self::$ZIP_COMPRESSION_METHODS[$value] :
'Unknown Method';
}
/**
* @return int[]
*/
public static function getSupportMethods()
{
static $methods;
if ($methods === null) {
$methods = [
self::STORED,
self::DEFLATED,
];
if (\extension_loaded('bz2')) {
$methods[] = self::BZIP2;
}
}
return $methods;
}
/**
* @param int $compressionMethod
*
* @throws ZipUnsupportMethodException
*/
public static function checkSupport($compressionMethod)
{
$compressionMethod = (int) $compressionMethod;
if (!\in_array($compressionMethod, self::getSupportMethods(), true)) {
throw new ZipUnsupportMethodException(sprintf(
'Compression method %d (%s) is not supported.',
$compressionMethod,
self::getCompressionMethodName($compressionMethod)
));
}
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace PhpZip\Constants;
/**
* Zip Constants.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
interface ZipConstants
{
/** @var int End Of Central Directory Record signature. */
const END_CD = 0x06054B50; // "PK\005\006"
/** @var int Zip64 End Of Central Directory Record. */
const ZIP64_END_CD = 0x06064B50; // "PK\006\006"
/** @var int Zip64 End Of Central Directory Locator. */
const ZIP64_END_CD_LOC = 0x07064B50; // "PK\006\007"
/** @var int Central File Header signature. */
const CENTRAL_FILE_HEADER = 0x02014B50; // "PK\001\002"
/** @var int Local File Header signature. */
const LOCAL_FILE_HEADER = 0x04034B50; // "PK\003\004"
/** @var int Data Descriptor signature. */
const DATA_DESCRIPTOR = 0x08074B50; // "PK\007\008"
/**
* @var int value stored in four-byte size and similar fields
* if ZIP64 extensions are used
*/
const ZIP64_MAGIC = 0xFFFFFFFF;
/**
* 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.
*
* @var int Local File Header filename position
*/
const LFH_FILENAME_LENGTH_POS = 26;
/**
* 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 LFH_FILENAME_POS = 30;
/** @var int the length of the Zip64 End Of Central Directory Locator */
const ZIP64_END_CD_LOC_LEN = 20;
/** @var int the minimum length of the End Of Central Directory Record */
const END_CD_MIN_LEN = 22;
/**
* 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
*
* @var int ZIP64 End Of Central Directory length
*/
const ZIP64_END_OF_CD_LEN = 56;
}

View File

@ -0,0 +1,93 @@
<?php
namespace PhpZip\Constants;
use PhpZip\Exception\InvalidArgumentException;
/**
* Class ZipEncryptionMethod.
*/
final class ZipEncryptionMethod
{
const NONE = -1;
/** @var int Traditional PKWARE encryption. */
const PKWARE = 0;
/** @var int WinZip AES-256 */
const WINZIP_AES_256 = 1;
/** @var int WinZip AES-128 */
const WINZIP_AES_128 = 2;
/** @var int WinZip AES-192 */
const WINZIP_AES_192 = 3;
/** @var array<int, string> */
private static $ENCRYPTION_METHODS = [
self::NONE => 'no encryption',
self::PKWARE => 'Traditional PKWARE encryption',
self::WINZIP_AES_128 => 'WinZip AES-128',
self::WINZIP_AES_192 => 'WinZip AES-192',
self::WINZIP_AES_256 => 'WinZip AES-256',
];
/**
* @param int $value
*
* @return string
*/
public static function getEncryptionMethodName($value)
{
$value = (int) $value;
return isset(self::$ENCRYPTION_METHODS[$value]) ?
self::$ENCRYPTION_METHODS[$value] :
'Unknown Encryption Method';
}
/**
* @param int $encryptionMethod
*
* @return bool
*/
public static function hasEncryptionMethod($encryptionMethod)
{
return isset(self::$ENCRYPTION_METHODS[$encryptionMethod]);
}
/**
* @param int $encryptionMethod
*
* @return bool
*/
public static function isWinZipAesMethod($encryptionMethod)
{
return \in_array(
(int) $encryptionMethod,
[
self::WINZIP_AES_256,
self::WINZIP_AES_192,
self::WINZIP_AES_128,
],
true
);
}
/**
* @param int $encryptionMethod
*
* @throws InvalidArgumentException
*/
public static function checkSupport($encryptionMethod)
{
$encryptionMethod = (int) $encryptionMethod;
if (!self::hasEncryptionMethod($encryptionMethod)) {
throw new InvalidArgumentException(sprintf(
'Encryption method %d is not supported.',
$encryptionMethod
));
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace PhpZip\Constants;
/**
* Interface ZipOptions.
*/
interface ZipOptions
{
/**
* Boolean option for store just file names (skip directory names).
*
* @var string
*/
const STORE_ONLY_FILES = 'only_files';
/** @var string */
const COMPRESSION_METHOD = 'compression_method';
/** @var string */
const MODIFIED_TIME = 'mtime';
/**
* @var string
*
* @see DosCodePage::getCodePages()
*/
const CHARSET = 'charset';
}

View File

@ -0,0 +1,53 @@
<?php
namespace PhpZip\Constants;
/**
* Class ZipPlatform.
*/
final class ZipPlatform
{
/** @var int MS-DOS OS */
const OS_DOS = 0;
/** @var int Unix OS */
const OS_UNIX = 3;
/** MacOS platform */
const OS_MAC_OSX = 19;
/** @var array Zip Platforms */
private static $platforms = [
self::OS_DOS => 'MS-DOS',
1 => 'Amiga',
2 => 'OpenVMS',
self::OS_UNIX => 'Unix',
4 => 'VM/CMS',
5 => 'Atari ST',
6 => 'HPFS (OS/2, NT 3.x)',
7 => 'Macintosh',
8 => 'Z-System',
9 => 'CP/M',
10 => 'Windows NTFS or TOPS-20',
11 => 'MVS or NTFS',
12 => 'VSE or SMS/QDOS',
13 => 'Acorn RISC OS',
14 => 'VFAT',
15 => 'alternate MVS',
16 => 'BeOS',
17 => 'Tandem',
18 => 'OS/400',
self::OS_MAC_OSX => 'OS/X (Darwin)',
30 => 'AtheOS/Syllable',
];
/**
* @param int $platform
*
* @return string
*/
public static function getPlatformName($platform)
{
return isset(self::$platforms[$platform]) ? self::$platforms[$platform] : 'Unknown';
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace PhpZip\Constants;
/**
* Version needed to extract or software version.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT Section 4.4.3
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
interface ZipVersion
{
/** @var int 1.0 - Default value */
const v10_DEFAULT_MIN = 10;
/** @var int 1.1 - File is a volume label */
const v11_FILE_VOLUME_LABEL = 11;
/**
* 2.0 - File is a folder (directory)
* 2.0 - File is compressed using Deflate compression
* 2.0 - File is encrypted using traditional PKWARE encryption.
*
* @var int
*/
const v20_DEFLATED_FOLDER_ZIPCRYPTO = 20;
/** @var int 2.1 - File is compressed using Deflate64(tm) */
const v21_DEFLATED64 = 21;
/** @var int 2.5 - File is compressed using PKWARE DCL Implode */
const v25_IMPLODED = 25;
/** @var int 2.7 - File is a patch data set */
const v27_PATCH_DATA = 27;
/** @var int 4.5 - File uses ZIP64 format extensions */
const v45_ZIP64_EXT = 45;
/** @var int 4.6 - File is compressed using BZIP2 compression */
const v46_BZIP2 = 46;
/**
* 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.
*
* @var int
*/
const v50_ENCR_DES_3DES_RC2_ORIG_RC4 = 50;
/**
* 5.1 - File is encrypted using AES encryption
* 5.1 - File is encrypted using corrected RC2 encryption**.
*
* @var int
*/
const v51_ENCR_AES_RC2_CORRECT = 51;
/** @var int 5.2 - File is encrypted using corrected RC2-64 encryption** */
const v52_ENCR_RC2_64_CORRECT = 52;
/** @var int 6.1 - File is encrypted using non-OAEP key wrapping*** */
const v61_ENCR_NON_OAE_KEY_WRAP = 61;
/** @var int 6.2 - Central directory encryption */
const v62_ENCR_CENTRAL_DIR = 62;
/**
* 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.
*
* @var int
*/
const v63_LZMA_PPMD_BLOWFISH_TWOFISH = 63;
}

View File

@ -2,6 +2,8 @@
namespace PhpZip\Exception;
use PhpZip\Model\ZipEntry;
/**
* Thrown if entry not found.
*
@ -16,16 +18,15 @@ class ZipEntryNotFoundException extends ZipException
/**
* ZipEntryNotFoundException constructor.
*
* @param string $entryName
* @param ZipEntry|string $entryName
*/
public function __construct($entryName)
{
parent::__construct(
sprintf(
'Zip Entry "%s" was not found in the archive.',
$entryName
)
);
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : $entryName;
parent::__construct(sprintf(
'Zip Entry "%s" was not found in the archive.',
$entryName
));
$this->entryName = $entryName;
}

View File

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

View File

@ -1,11 +1,9 @@
<?php
namespace PhpZip\Crypto;
namespace PhpZip\IO\Filter\Cipher\Pkware;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipCryptoException;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
/**
@ -16,7 +14,7 @@ use PhpZip\Util\PackUtil;
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
class PKCryptContext
{
/** Encryption header size */
const STD_DEC_HDR_SIZE = 12;
@ -24,7 +22,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
/**
* Crc table.
*
* @var array
* @var int[]|array
*/
private static $CRC_TABLE = [
0x00000000,
@ -290,37 +288,80 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
*
* @var array
*/
private $keys = [];
/** @var ZipEntry */
private $entry;
private $keys;
/**
* ZipCryptoEngine constructor.
*
* @param ZipEntry $entry
*/
public function __construct(ZipEntry $entry)
{
$this->entry = $entry;
}
/**
* Initial keys.
* PKCryptContext constructor.
*
* @param string $password
*/
private function initKeys($password)
public function __construct($password)
{
$this->keys[0] = 305419896;
$this->keys[1] = 591751049;
$this->keys[2] = 878082192;
if (\PHP_INT_SIZE === 4) {
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
}
$this->keys = [
305419896,
591751049,
878082192,
];
foreach (unpack('C*', $password) as $b) {
$this->updateKeys($b);
}
}
/**
* @param string $header
* @param int $checkByte
*
* @throws ZipAuthenticationException
*/
public function checkHeader($header, $checkByte)
{
$byte = 0;
foreach (unpack('C*', $header) as $byte) {
$byte = ($byte ^ $this->decryptByte()) & 0xff;
$this->updateKeys($byte);
}
if ($byte !== $checkByte) {
throw new ZipAuthenticationException(sprintf('Invalid password'));
}
}
/**
* @param string $content
*
* @return string
*/
public function decryptString($content)
{
$decryptContent = '';
foreach (unpack('C*', $content) as $byte) {
$byte = ($byte ^ $this->decryptByte()) & 0xff;
$this->updateKeys($byte);
$decryptContent .= \chr($byte);
}
return $decryptContent;
}
/**
* Decrypt byte.
*
* @return int
*/
private function decryptByte()
{
$temp = $this->keys[2] | 2;
return (($temp * ($temp ^ 1)) >> 8) & 0xffffff;
}
/**
* Update keys.
*
@ -350,123 +391,17 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine
/**
* @param string $content
*
* @throws ZipAuthenticationException
*
* @return string
*/
public function decrypt($content)
public function encryptString($content)
{
if (\PHP_INT_SIZE === 4) {
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
}
$password = $this->entry->getPassword();
$this->initKeys($password);
$headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE)));
$byte = 0;
for ($i = 0; $i < self::STD_DEC_HDR_SIZE; $i++) {
$byte = ($headerBytes[$i] ^ $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
*
* @throws ZipCryptoException
*
* @return string
*/
public function encrypt($data)
{
if (\PHP_INT_SIZE === 4) {
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
}
$crc = $this->entry->isDataDescriptorRequired() ?
($this->entry->getDosTime() & 0x0000ffff) << 16 :
$this->entry->getCrc();
try {
$headerBytes = random_bytes(self::STD_DEC_HDR_SIZE);
} catch (\Exception $e) {
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
}
// Initialize again since the generated bytes were encrypted.
$password = $this->entry->getPassword();
$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
*
* @throws ZipCryptoException
*
* @return string
*/
private function encryptData($content)
{
if ($content === null) {
throw new ZipCryptoException('content is null');
}
$buff = '';
$encryptContent = '';
foreach (unpack('C*', $content) as $val) {
$buff .= pack('c', $this->encryptByte($val));
$encryptContent .= pack('c', $this->encryptByte($val));
}
return $buff;
return $encryptContent;
}
/**

View File

@ -0,0 +1,118 @@
<?php
namespace PhpZip\IO\Filter\Cipher\Pkware;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipEntry;
/**
* Decryption PKWARE Traditional Encryption.
*/
class PKDecryptionStreamFilter extends \php_user_filter
{
const FILTER_NAME = 'phpzip.decryption.pkware';
/** @var int */
private $checkByte = 0;
/** @var int */
private $readLength = 0;
/** @var int */
private $size = 0;
/** @var bool */
private $readHeader = false;
/** @var PKCryptContext */
private $context;
/**
* @return bool
*/
public static function register()
{
return stream_filter_register(self::FILTER_NAME, __CLASS__);
}
/**
* @see https://php.net/manual/en/php-user-filter.oncreate.php
*
* @return bool
*/
public function onCreate()
{
if (!isset($this->params['entry'])) {
return false;
}
if (!($this->params['entry'] instanceof ZipEntry)) {
throw new \RuntimeException('ZipEntry expected');
}
/** @var ZipEntry $entry */
$entry = $this->params['entry'];
$password = $entry->getPassword();
if ($password === null) {
return false;
}
$this->size = $entry->getCompressedSize();
// init context
$this->context = new PKCryptContext($password);
// init check byte
if ($entry->isDataDescriptorEnabled()) {
$this->checkByte = ($entry->getDosTime() >> 8) & 0xff;
} else {
$this->checkByte = ($entry->getCrc() >> 24) & 0xff;
}
$this->readLength = 0;
$this->readHeader = false;
return true;
}
/**
* Decryption filter.
*
* @param resource $in
* @param resource $out
* @param int $consumed
* @param bool $closing
*
* @throws ZipException
*
* @return int
*
* @todo USE FFI in php 7.4
*/
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$buffer = $bucket->data;
$this->readLength += $bucket->datalen;
if ($this->readLength > $this->size) {
$buffer = substr($buffer, 0, $this->size - $this->readLength);
}
if (!$this->readHeader) {
$header = substr($buffer, 0, PKCryptContext::STD_DEC_HDR_SIZE);
$this->context->checkHeader($header, $this->checkByte);
$buffer = substr($buffer, PKCryptContext::STD_DEC_HDR_SIZE);
$this->readHeader = true;
}
$bucket->data = $this->context->decryptString($buffer);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return \PSFS_PASS_ON;
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace PhpZip\IO\Filter\Cipher\Pkware;
use PhpZip\Exception\RuntimeException;
use PhpZip\Model\ZipEntry;
/**
* Encryption PKWARE Traditional Encryption.
*/
class PKEncryptionStreamFilter extends \php_user_filter
{
const FILTER_NAME = 'phpzip.encryption.pkware';
/** @var int */
private $size;
/** @var string */
private $headerBytes;
/** @var int */
private $writeLength;
/** @var bool */
private $writeHeader;
/** @var PKCryptContext */
private $context;
/**
* @return bool
*/
public static function register()
{
return stream_filter_register(self::FILTER_NAME, __CLASS__);
}
/**
* @see https://php.net/manual/en/php-user-filter.oncreate.php
*
* @return bool
*/
public function onCreate()
{
if (\PHP_INT_SIZE === 4) {
throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
}
if (!isset($this->params['entry'], $this->params['size'])) {
return false;
}
if (!($this->params['entry'] instanceof ZipEntry)) {
throw new \RuntimeException('ZipEntry expected');
}
/** @var ZipEntry $entry */
$entry = $this->params['entry'];
$password = $entry->getPassword();
if ($password === null) {
return false;
}
$this->size = (int) $this->params['size'];
// init keys
$this->context = new PKCryptContext($password);
$crc = $entry->isDataDescriptorRequired() || $entry->getCrc() === ZipEntry::UNKNOWN ?
($entry->getDosTime() & 0x0000ffff) << 16 :
$entry->getCrc();
try {
$headerBytes = random_bytes(PKCryptContext::STD_DEC_HDR_SIZE);
} catch (\Exception $e) {
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
}
$headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff);
$headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
$this->headerBytes = $headerBytes;
$this->writeLength = 0;
$this->writeHeader = false;
return true;
}
/**
* Encryption filter.
*
* @param resource $in
* @param resource $out
* @param int $consumed
* @param bool $closing
*
* @return int
*
* @todo USE FFI in php 7.4
*/
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$buffer = $bucket->data;
$this->writeLength += $bucket->datalen;
if ($this->writeLength > $this->size) {
$buffer = substr($buffer, 0, $this->size - $this->writeLength);
}
$data = '';
if (!$this->writeHeader) {
$data .= $this->context->encryptString($this->headerBytes);
$this->writeHeader = true;
}
$data .= $this->context->encryptString($buffer);
$bucket->data = $data;
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return \PSFS_PASS_ON;
}
}

View File

@ -0,0 +1,166 @@
<?php
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Util\CryptoUtil;
/**
* WinZip Aes Encryption.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT APPENDIX E
* @see https://www.winzip.com/win/en/aes_info.html
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
class WinZipAesContext
{
/** @var int AES Block size */
const BLOCK_SIZE = self::IV_SIZE;
/** @var int Footer size */
const FOOTER_SIZE = 10;
/** @var int The iteration count for the derived keys of the cipher, KLAC and MAC. */
const ITERATION_COUNT = 1000;
/** @var int Password verifier size */
const PASSWORD_VERIFIER_SIZE = 2;
/** @var int IV size */
const IV_SIZE = 16;
/** @var string */
private $iv;
/** @var string */
private $key;
/** @var \HashContext|resource */
private $hmacContext;
/** @var string */
private $passwordVerifier;
/**
* WinZipAesContext constructor.
*
* @param int $encryptionStrengthBits
* @param string $password
* @param string $salt
*/
public function __construct($encryptionStrengthBits, $password, $salt)
{
$encryptionStrengthBits = (int) $encryptionStrengthBits;
if ($password === '') {
throw new RuntimeException('$password is empty');
}
if (empty($salt)) {
throw new RuntimeException('$salt is empty');
}
// WinZip 99-character limit https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
$password = substr($password, 0, 99);
$this->iv = str_repeat("\0", self::IV_SIZE);
$keyStrengthBytes = (int) ($encryptionStrengthBits / 8);
$hashLength = $keyStrengthBytes * 2 + self::PASSWORD_VERIFIER_SIZE * 8;
$hash = hash_pbkdf2(
'sha1',
$password,
$salt,
self::ITERATION_COUNT,
$hashLength,
true
);
$this->key = substr($hash, 0, $keyStrengthBytes);
$sha1Mac = substr($hash, $keyStrengthBytes, $keyStrengthBytes);
$this->hmacContext = hash_init('sha1', \HASH_HMAC, $sha1Mac);
$this->passwordVerifier = substr($hash, 2 * $keyStrengthBytes, self::PASSWORD_VERIFIER_SIZE);
}
/**
* @return string
*/
public function getPasswordVerifier()
{
return $this->passwordVerifier;
}
public function updateIv()
{
for ($ivCharIndex = 0; $ivCharIndex < self::IV_SIZE; $ivCharIndex++) {
$ivByte = \ord($this->iv[$ivCharIndex]);
if (++$ivByte === 256) {
// overflow, set this one to 0, increment next
$this->iv[$ivCharIndex] = "\0";
} else {
// no overflow, just write incremented number back and abort
$this->iv[$ivCharIndex] = \chr($ivByte);
break;
}
}
}
/**
* @param string $data
*
* @return string
*/
public function decryption($data)
{
hash_update($this->hmacContext, $data);
return CryptoUtil::decryptAesCtr($data, $this->key, $this->iv);
}
/**
* @param string $data
*
* @return string
*/
public function encrypt($data)
{
$encryptionData = CryptoUtil::encryptAesCtr($data, $this->key, $this->iv);
hash_update($this->hmacContext, $encryptionData);
return $encryptionData;
}
/**
* @param string $authCode
*
* @throws ZipAuthenticationException
*/
public function checkAuthCode($authCode)
{
$hmac = $this->getHmac();
// check authenticationCode
if (strcmp($hmac, $authCode) !== 0) {
throw new ZipAuthenticationException('Authenticated WinZip AES entry content has been tampered with.');
}
}
/**
* @return string
*/
public function getHmac()
{
return substr(
hash_final($this->hmacContext, true),
0,
self::FOOTER_SIZE
);
}
}

View File

@ -0,0 +1,187 @@
<?php
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
use PhpZip\Model\ZipEntry;
/**
* Decrypt WinZip AES stream.
*/
class WinZipAesDecryptionStreamFilter extends \php_user_filter
{
const FILTER_NAME = 'phpzip.decryption.winzipaes';
/** @var string */
private $buffer;
/** @var string */
private $authenticationCode;
/** @var int */
private $encBlockPosition = 0;
/** @var int */
private $encBlockLength = 0;
/** @var int */
private $readLength = 0;
/** @var ZipEntry */
private $entry;
/** @var WinZipAesContext|null */
private $context;
/**
* @return bool
*/
public static function register()
{
return stream_filter_register(self::FILTER_NAME, __CLASS__);
}
/**
* @return bool
*
* @noinspection DuplicatedCode
*/
public function onCreate()
{
if (!isset($this->params['entry'])) {
return false;
}
if (!($this->params['entry'] instanceof ZipEntry)) {
throw new \RuntimeException('ZipEntry expected');
}
$this->entry = $this->params['entry'];
if (
$this->entry->getPassword() === null ||
!$this->entry->isEncrypted() ||
!$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
) {
return false;
}
$this->buffer = '';
return true;
}
/**
* @param resource $in
* @param resource $out
* @param int $consumed
* @param bool $closing
*
* @throws ZipAuthenticationException
*
* @return int
*/
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$this->buffer .= $bucket->data;
$this->readLength += $bucket->datalen;
if ($this->readLength > $this->entry->getCompressedSize()) {
$this->buffer = substr($this->buffer, 0, $this->entry->getCompressedSize() - $this->readLength);
}
// read header
if ($this->context === null) {
/**
* @var WinZipAesExtraField|null $winZipExtra
*/
$winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipExtra === null) {
throw new RuntimeException('$winZipExtra is null');
}
$saltSize = $winZipExtra->getSaltSize();
$headerSize = $saltSize + WinZipAesContext::PASSWORD_VERIFIER_SIZE;
if (\strlen($this->buffer) < $headerSize) {
return \PSFS_FEED_ME;
}
$salt = substr($this->buffer, 0, $saltSize);
$passwordVerifier = substr($this->buffer, $saltSize, WinZipAesContext::PASSWORD_VERIFIER_SIZE);
$password = $this->entry->getPassword();
if ($password === null) {
throw new RuntimeException('$password is null');
}
$this->context = new WinZipAesContext($winZipExtra->getEncryptionStrength(), $password, $salt);
unset($password);
// Verify password.
if ($passwordVerifier !== $this->context->getPasswordVerifier()) {
throw new ZipAuthenticationException('Invalid password');
}
$this->encBlockPosition = 0;
$this->encBlockLength = $this->entry->getCompressedSize() - $headerSize - WinZipAesContext::FOOTER_SIZE;
$this->buffer = substr($this->buffer, $headerSize);
}
// encrypt data
$plainText = '';
$offset = 0;
$len = \strlen($this->buffer);
$remaining = $this->encBlockLength - $this->encBlockPosition;
if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
return \PSFS_FEED_ME;
}
$limit = min($len, $remaining);
if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
$limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
}
while ($offset < $limit) {
$this->context->updateIv();
$length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
$data = substr($this->buffer, 0, $length);
$plainText .= $this->context->decryption($data);
$offset += $length;
$this->buffer = substr($this->buffer, $length);
}
$this->encBlockPosition += $offset;
if (
$this->encBlockPosition === $this->encBlockLength &&
\strlen($this->buffer) === WinZipAesContext::FOOTER_SIZE
) {
$this->authenticationCode = $this->buffer;
$this->buffer = '';
}
$bucket->data = $plainText;
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return \PSFS_PASS_ON;
}
/**
* @see http://php.net/manual/en/php-user-filter.onclose.php
*
* @throws ZipAuthenticationException
*/
public function onClose()
{
$this->buffer = '';
if ($this->context !== null) {
$this->context->checkAuthCode($this->authenticationCode);
}
}
}

View File

@ -0,0 +1,158 @@
<?php
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
use PhpZip\Exception\RuntimeException;
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
use PhpZip\Model\ZipEntry;
/**
* Encrypt WinZip AES stream.
*/
class WinZipAesEncryptionStreamFilter extends \php_user_filter
{
const FILTER_NAME = 'phpzip.encryption.winzipaes';
/** @var string */
private $buffer;
/** @var int */
private $remaining = 0;
/** @var ZipEntry */
private $entry;
/** @var int */
private $size;
/** @var WinZipAesContext|null */
private $context;
/**
* @return bool
*/
public static function register()
{
return stream_filter_register(self::FILTER_NAME, __CLASS__);
}
/**
* @return bool
*
* @noinspection DuplicatedCode
*/
public function onCreate()
{
if (!isset($this->params['entry'])) {
return false;
}
if (!($this->params['entry'] instanceof ZipEntry)) {
throw new \RuntimeException('ZipEntry expected');
}
$this->entry = $this->params['entry'];
if (
$this->entry->getPassword() === null ||
!$this->entry->isEncrypted() ||
!$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID)
) {
return false;
}
$this->size = (int) $this->params['size'];
$this->context = null;
$this->buffer = '';
return true;
}
/**
* @param resource $in
* @param resource $out
* @param int $consumed
* @param bool $closing
*
* @return int
*/
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$this->buffer .= $bucket->data;
$this->remaining += $bucket->datalen;
if ($this->remaining > $this->size) {
$this->buffer = substr($this->buffer, 0, $this->size - $this->remaining);
$this->remaining = $this->size;
}
$encryptionText = '';
// write header
if ($this->context === null) {
/**
* @var WinZipAesExtraField|null $winZipExtra
*/
$winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipExtra === null) {
throw new RuntimeException('$winZipExtra is null');
}
$saltSize = $winZipExtra->getSaltSize();
try {
$salt = random_bytes($saltSize);
} catch (\Exception $e) {
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
}
$password = $this->entry->getPassword();
if ($password === null) {
throw new RuntimeException('$password is null');
}
$this->context = new WinZipAesContext(
$winZipExtra->getEncryptionStrength(),
$password,
$salt
);
$encryptionText .= $salt . $this->context->getPasswordVerifier();
}
// encrypt data
$offset = 0;
$len = \strlen($this->buffer);
$remaining = $this->remaining - $this->size;
if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) {
return \PSFS_FEED_ME;
}
$limit = max($len, $remaining);
if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) {
$limit -= ($limit % WinZipAesContext::BLOCK_SIZE);
}
while ($offset < $limit) {
$this->context->updateIv();
$length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset);
$encryptionText .= $this->context->encrypt(
substr($this->buffer, 0, $length)
);
$offset += $length;
$this->buffer = substr($this->buffer, $length);
}
if ($remaining === 0) {
$encryptionText .= $this->context->getHmac();
}
$bucket->data = $encryptionText;
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return \PSFS_PASS_ON;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace PhpZip\Stream;
namespace PhpZip\IO\Stream;
use Psr\Http\Message\StreamInterface;
@ -53,7 +53,7 @@ class ResponseStream implements StreamInterface
/** @var resource */
private $stream;
/** @var int */
/** @var int|null */
private $size;
/** @var bool */
@ -65,7 +65,7 @@ class ResponseStream implements StreamInterface
/** @var bool */
private $writable;
/** @var array|mixed|null */
/** @var string|null */
private $uri;
/**
@ -166,7 +166,7 @@ class ResponseStream implements StreamInterface
return null;
}
// Clear the stat cache if the stream has a URI
if ($this->uri) {
if ($this->uri !== null) {
clearstatcache(true, $this->uri);
}
$stats = fstat($this->stream);
@ -326,8 +326,12 @@ class ResponseStream implements StreamInterface
public function detach()
{
$result = $this->stream;
$this->stream = $this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = false;
$this->stream = null;
$this->size = null;
$this->uri = null;
$this->readable = false;
$this->writable = false;
$this->seekable = false;
return $result;
}

View File

@ -0,0 +1,309 @@
<?php
namespace PhpZip\IO\Stream;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipEntry;
/**
* The class provides stream reuse functionality.
*
* Stream will not be closed at {@see fclose}.
*
* @see https://www.php.net/streamwrapper
*/
final class ZipEntryStreamWrapper
{
/** @var string the registered protocol */
const PROTOCOL = 'zipentry';
/** @var resource */
public $context;
/** @var resource */
private $fp;
/**
* @return bool
*/
public static function register()
{
$protocol = self::PROTOCOL;
if (!\in_array($protocol, stream_get_wrappers(), true)) {
if (!stream_wrapper_register($protocol, self::class)) {
throw new \RuntimeException("Failed to register '{$protocol}://' protocol");
}
return true;
}
return false;
}
public static function unregister()
{
stream_wrapper_unregister(self::PROTOCOL);
}
/**
* @param ZipEntry $entry
*
* @return resource
*/
public static function wrap(ZipEntry $entry)
{
self::register();
$context = stream_context_create(
[
self::PROTOCOL => [
'entry' => $entry,
],
]
);
$uri = self::PROTOCOL . '://' . $entry->getName();
$fp = fopen($uri, 'r+b', false, $context);
if ($fp === false) {
throw new \RuntimeException('Error open ' . $uri);
}
return $fp;
}
/**
* Opens file or URL.
*
* This method is called immediately after the wrapper is
* initialized (f.e. by {@see fopen()} and {@see file_get_contents()}).
*
* @param string $path specifies the URL that was passed to
* the original function
* @param string $mode the mode used to open the file, as detailed
* for {@see fopen()}
* @param int $options Holds additional flags set by the streams
* API. It can hold one or more of the
* following values OR'd together.
* @param string $opened_path if the path is opened successfully, and
* STREAM_USE_PATH is set in options,
* opened_path should be set to the
* full path of the file/resource that
* was actually opened
*
* @throws ZipException
*
* @return bool
*
* @see https://www.php.net/streamwrapper.stream-open
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
if ($this->context === null) {
throw new \RuntimeException('stream context is null');
}
$streamOptions = stream_context_get_options($this->context);
if (!isset($streamOptions[self::PROTOCOL]['entry'])) {
throw new \RuntimeException('no stream option ["' . self::PROTOCOL . '"]["entry"]');
}
$zipEntry = $streamOptions[self::PROTOCOL]['entry'];
if (!$zipEntry instanceof ZipEntry) {
throw new \RuntimeException('invalid stream context');
}
$zipData = $zipEntry->getData();
if ($zipData === null) {
throw new ZipException(sprintf('No data for zip entry "%s"', $zipEntry->getName()));
}
$this->fp = $zipData->getDataAsStream();
return $this->fp !== false;
}
/**
* Read from stream.
*
* This method is called in response to {@see fread()} and {@see fgets()}.
*
* Note: Remember to update the read/write position of the stream
* (by the number of bytes that were successfully read).
*
* @param int $count how many bytes of data from the current
* position should be returned
*
* @return false|string If there are less than count bytes available,
* return as many as are available. If no more data
* is available, return either FALSE or
* an empty string.
*
* @see https://www.php.net/streamwrapper.stream-read
*/
public function stream_read($count)
{
return fread($this->fp, $count);
}
/**
* Seeks to specific location in a stream.
*
* This method is called in response to {@see fseek()}.
* The read/write position of the stream should be updated according
* to the offset and whence.
*
* @param int $offset the stream offset to seek to
* @param int $whence Possible values:
* {@see \SEEK_SET} - Set position equal to offset bytes.
* {@see \SEEK_CUR} - Set position to current location plus offset.
* {@see \SEEK_END} - Set position to end-of-file plus offset.
*
* @return bool return TRUE if the position was updated, FALSE otherwise
*
* @see https://www.php.net/streamwrapper.stream-seek
*/
public function stream_seek($offset, $whence = \SEEK_SET)
{
return fseek($this->fp, $offset, $whence) === 0;
}
/**
* Retrieve the current position of a stream.
*
* This method is called in response to {@see fseek()} to determine
* the current position.
*
* @return int should return the current position of the stream
*
* @see https://www.php.net/streamwrapper.stream-tell
*/
public function stream_tell()
{
$pos = ftell($this->fp);
if ($pos === false) {
throw new \RuntimeException('Cannot get stream position.');
}
return $pos;
}
/**
* Tests for end-of-file on a file pointer.
*
* This method is called in response to {@see feof()}.
*
* @return bool should return TRUE if the read/write position is at
* the end of the stream and if no more data is available
* to be read, or FALSE otherwise
*
* @see https://www.php.net/streamwrapper.stream-eof
*/
public function stream_eof()
{
return feof($this->fp);
}
/**
* Retrieve information about a file resource.
*
* This method is called in response to {@see fstat()}.
*
* @return array
*
* @see https://www.php.net/streamwrapper.stream-stat
* @see https://www.php.net/stat
* @see https://www.php.net/fstat
*/
public function stream_stat()
{
return fstat($this->fp);
}
/**
* Flushes the output.
*
* This method is called in response to {@see fflush()} and when the
* stream is being closed while any unflushed data has been written to
* it before.
* If you have cached data in your stream but not yet stored it into
* the underlying storage, you should do so now.
*
* @return bool should return TRUE if the cached data was successfully
* stored (or if there was no data to store), or FALSE
* if the data could not be stored
*
* @see https://www.php.net/streamwrapper.stream-flush
*/
public function stream_flush()
{
return fflush($this->fp);
}
/**
* Truncate stream.
*
* Will respond to truncation, e.g., through {@see ftruncate()}.
*
* @param int $new_size the new size
*
* @return bool returns TRUE on success or FALSE on failure
*
* @see https://www.php.net/streamwrapper.stream-truncate
*/
public function stream_truncate($new_size)
{
return ftruncate($this->fp, (int) $new_size);
}
/**
* Write to stream.
*
* This method is called in response to {@see fwrite().}
*
* Note: Remember to update the current position of the stream by
* number of bytes that were successfully written.
*
* @param string $data should be stored into the underlying stream
*
* @return int should return the number of bytes that were successfully stored, or 0 if none could be stored
*
* @see https://www.php.net/streamwrapper.stream-write
*/
public function stream_write($data)
{
$bytes = fwrite($this->fp, $data);
return $bytes === false ? 0 : $bytes;
}
/**
* Retrieve the underlaying resource.
*
* This method is called in response to {@see stream_select()}.
*
* @param int $cast_as can be {@see STREAM_CAST_FOR_SELECT} when {@see stream_select()}
* is callingstream_cast() or {@see STREAM_CAST_AS_STREAM} when
* stream_cast() is called for other uses
*
* @return resource
*/
public function stream_cast($cast_as)
{
return $this->fp;
}
/**
* Close a resource.
*
* This method is called in response to {@see fclose()}.
* All resources that were locked, or allocated, by the wrapper should be released.
*
* @see https://www.php.net/streamwrapper.stream-close
*/
public function stream_close()
{
}
}

898
src/IO/ZipReader.php Normal file
View File

@ -0,0 +1,898 @@
<?php
namespace PhpZip\IO;
use PhpZip\Constants\DosCodePage;
use PhpZip\Constants\GeneralPurposeBitFlag;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipConstants;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Constants\ZipOptions;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\IO\Filter\Cipher\Pkware\PKDecryptionStreamFilter;
use PhpZip\IO\Filter\Cipher\WinZipAes\WinZipAesDecryptionStreamFilter;
use PhpZip\Model\Data\ZipSourceFileData;
use PhpZip\Model\EndOfCentralDirectory;
use PhpZip\Model\Extra\ExtraFieldsCollection;
use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
use PhpZip\Model\Extra\Fields\UnrecognizedExtraField;
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
use PhpZip\Model\Extra\ZipExtraDriver;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ImmutableZipContainer;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
/**
* Zip reader.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipReader
{
/** @var int file size */
protected $size;
/** @var resource */
protected $inStream;
/** @var array */
protected $options;
/**
* @param resource $inStream
* @param array $options
*/
public function __construct($inStream, array $options = [])
{
if (!\is_resource($inStream)) {
throw new InvalidArgumentException('Stream must be a resource');
}
$type = get_resource_type($inStream);
if ($type !== 'stream') {
throw new InvalidArgumentException("Invalid resource type {$type}.");
}
$meta = stream_get_meta_data($inStream);
$wrapperType = isset($meta['wrapper_type']) ? $meta['wrapper_type'] : 'Unknown';
$supportStreamWrapperTypes = ['plainfile', 'PHP', 'user-space'];
if (!\in_array($wrapperType, $supportStreamWrapperTypes, true)) {
throw new InvalidArgumentException(
'The stream wrapper type "' . $wrapperType . '" is not supported. Support: ' . implode(
', ',
$supportStreamWrapperTypes
)
);
}
if (
$wrapperType === 'plainfile' &&
(
$meta['stream_type'] === 'dir' ||
(isset($meta['uri']) && is_dir($meta['uri']))
)
) {
throw new InvalidArgumentException('Directory stream not supported');
}
$seekable = $meta['seekable'];
if (!$seekable) {
throw new InvalidArgumentException('Resource does not support seekable.');
}
$this->size = fstat($inStream)['size'];
$this->inStream = $inStream;
/** @noinspection AdditionOperationOnArraysInspection */
$options += $this->getDefaultOptions();
$this->options = $options;
}
/**
* @return array
*/
protected function getDefaultOptions()
{
return [
ZipOptions::CHARSET => null,
];
}
/**
* @throws ZipException
*
* @return ImmutableZipContainer
*/
public function read()
{
if ($this->size < ZipConstants::END_CD_MIN_LEN) {
throw new ZipException('Corrupt zip file');
}
$endOfCentralDirectory = $this->readEndOfCentralDirectory();
$entries = $this->readCentralDirectory($endOfCentralDirectory);
return new ImmutableZipContainer($entries, $endOfCentralDirectory->getComment());
}
/**
* @return array
*/
public function getStreamMetaData()
{
return stream_get_meta_data($this->inStream);
}
/**
* Read End of central directory record.
*
* end of central dir signature 4 bytes (0x06054b50)
* 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
* .ZIP file comment (variable size)
*
* @throws ZipException
*
* @return EndOfCentralDirectory
*/
protected function readEndOfCentralDirectory()
{
if (!$this->findEndOfCentralDirectory()) {
throw new ZipException('Invalid zip file. The end of the central directory could not be found.');
}
$positionECD = ftell($this->inStream) - 4;
$sizeECD = $this->size - ftell($this->inStream);
$buffer = fread($this->inStream, $sizeECD);
$unpack = unpack(
'vdiskNo/vcdDiskNo/vcdEntriesDisk/' .
'vcdEntries/VcdSize/VcdPos/vcommentLength',
substr($buffer, 0, 18)
);
if (
$unpack['diskNo'] !== 0 ||
$unpack['cdDiskNo'] !== 0 ||
$unpack['cdEntriesDisk'] !== $unpack['cdEntries']
) {
throw new ZipException(
'ZIP file spanning/splitting is not supported!'
);
}
// .ZIP file comment (variable sizeECD)
$comment = null;
if ($unpack['commentLength'] > 0) {
$comment = substr($buffer, 18, $unpack['commentLength']);
}
// Check for ZIP64 End Of Central Directory Locator exists.
$zip64ECDLocatorPosition = $positionECD - ZipConstants::ZIP64_END_CD_LOC_LEN;
fseek($this->inStream, $zip64ECDLocatorPosition);
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
if ($zip64ECDLocatorPosition > 0 && unpack(
'V',
fread($this->inStream, 4)
)[1] === ZipConstants::ZIP64_END_CD_LOC) {
if (!$this->isZip64Support()) {
throw new ZipException('ZIP64 not supported this archive.');
}
$positionECD = $this->findZip64ECDPosition();
$endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD);
$endCentralDirectory->setComment($comment);
} else {
$endCentralDirectory = new EndOfCentralDirectory(
$unpack['cdEntries'],
$unpack['cdPos'],
$unpack['cdSize'],
false,
$comment
);
}
return $endCentralDirectory;
}
/**
* @return bool
*/
protected function findEndOfCentralDirectory()
{
$max = $this->size - ZipConstants::END_CD_MIN_LEN;
$min = $max >= 0xffff ? $max - 0xffff : 0;
// Search for End of central directory record.
for ($position = $max; $position >= $min; $position--) {
fseek($this->inStream, $position);
// end of central dir signature 4 bytes (0x06054b50)
if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::END_CD) {
continue;
}
return true;
}
return false;
}
/**
* Read Zip64 end of central directory locator and returns
* Zip64 end of central directory position.
*
* number of the disk with the
* start of the zip64 end of
* central directory 4 bytes
* relative offset of the zip64
* end of central directory record 8 bytes
* total number of disks 4 bytes
*
* @throws ZipException
*
* @return int Zip64 End Of Central Directory position
*/
protected function findZip64ECDPosition()
{
$diskNo = unpack('V', fread($this->inStream, 4))[1];
$zip64ECDPos = PackUtil::unpackLongLE(fread($this->inStream, 8));
$totalDisks = unpack('V', fread($this->inStream, 4))[1];
if ($diskNo !== 0 || $totalDisks > 1) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
return $zip64ECDPos;
}
/**
* Read zip64 end of central directory locator and zip64 end
* of central directory record.
*
* zip64 end of central dir
* signature 4 bytes (0x06064b50)
* size of zip64 end of central
* directory record 8 bytes
* version made by 2 bytes
* version needed to extract 2 bytes
* number of this disk 4 bytes
* number of the disk with the
* start of the central directory 4 bytes
* total number of entries in the
* central directory on this disk 8 bytes
* total number of entries in the
* central directory 8 bytes
* size of the central directory 8 bytes
* offset of start of central
* directory with respect to
* the starting disk number 8 bytes
* zip64 extensible data sector (variable size)
*
* @param int $zip64ECDPosition
*
* @throws ZipException
*
* @return EndOfCentralDirectory
*/
protected function readZip64EndOfCentralDirectory($zip64ECDPosition)
{
fseek($this->inStream, $zip64ECDPosition);
$buffer = fread($this->inStream, ZipConstants::ZIP64_END_OF_CD_LEN);
if (unpack('V', $buffer)[1] !== ZipConstants::ZIP64_END_CD) {
throw new ZipException('Expected ZIP64 End Of Central Directory Record!');
}
$data = unpack(
// 'Psize/vversionMadeBy/vextractVersion/' .
'VdiskNo/VcdDiskNo',
substr($buffer, 16, 8)
);
$cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8));
$entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8));
$cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8));
$cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8));
// $platform = ZipPlatform::fromValue(($data['versionMadeBy'] & 0xFF00) >> 8);
// $softwareVersion = $data['versionMadeBy'] & 0x00FF;
if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
if ($entryCount < 0 || $entryCount > 0x7fffffff) {
throw new ZipException('Total Number Of Entries In The Central Directory out of range!');
}
// skip zip64 extensible data sector (variable sizeEndCD)
return new EndOfCentralDirectory(
$entryCount,
$cdPos,
$cdSize,
true
);
}
/**
* 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 $endCD
*
* @throws ZipException
*
* @return ZipEntry[]
*/
protected function readCentralDirectory(EndOfCentralDirectory $endCD)
{
$entries = [];
$cdOffset = $endCD->getCdOffset();
fseek($this->inStream, $cdOffset);
if (!($cdStream = fopen('php://temp', 'w+b'))) {
throw new ZipException('Temp resource can not open from write');
}
stream_copy_to_stream($this->inStream, $cdStream, $endCD->getCdSize());
rewind($cdStream);
for ($numEntries = $endCD->getEntryCount(); $numEntries > 0; $numEntries--) {
$zipEntry = $this->readZipEntry($cdStream);
$entryName = $zipEntry->getName();
/** @var UnicodePathExtraField|null $unicodePathExtraField */
$unicodePathExtraField = $zipEntry->getExtraField(UnicodePathExtraField::HEADER_ID);
if ($unicodePathExtraField !== null) {
$unicodePath = $unicodePathExtraField->getUnicodeValue();
if ($unicodePath !== null) {
$unicodePath = str_replace('\\', '/', $unicodePath);
if (
$unicodePath !== '' &&
substr_count($entryName, '/') === substr_count($unicodePath, '/')
) {
$entryName = $unicodePath;
}
}
}
$entries[$entryName] = $zipEntry;
}
return $entries;
}
/**
* Read central directory entry.
*
* central file header signature 4 bytes (0x02014b50)
* version made by 2 bytes
* version needed to extract 2 bytes
* general purpose bit flag 2 bytes
* compression method 2 bytes
* last mod file time 2 bytes
* last mod file date 2 bytes
* crc-32 4 bytes
* compressed size 4 bytes
* uncompressed size 4 bytes
* file name length 2 bytes
* extra field length 2 bytes
* file comment length 2 bytes
* disk number start 2 bytes
* internal file attributes 2 bytes
* external file attributes 4 bytes
* relative offset of local header 4 bytes
*
* file name (variable size)
* extra field (variable size)
* file comment (variable size)
*
* @param resource $stream
*
* @throws ZipException
*
* @return ZipEntry
*/
protected function readZipEntry($stream)
{
if (unpack('V', fread($stream, 4))[1] !== ZipConstants::CENTRAL_FILE_HEADER) {
throw new ZipException('Corrupt zip file. Cannot read zip entry.');
}
$unpack = unpack(
'vversionMadeBy/vversionNeededToExtract/' .
'vgeneralPurposeBitFlag/vcompressionMethod/' .
'VlastModFile/Vcrc/VcompressedSize/' .
'VuncompressedSize/vfileNameLength/vextraFieldLength/' .
'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' .
'VexternalFileAttributes/VoffsetLocalHeader',
fread($stream, 42)
);
if ($unpack['diskNumberStart'] !== 0) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
$generalPurposeBitFlags = $unpack['generalPurposeBitFlag'];
$isUtf8 = ($generalPurposeBitFlags & GeneralPurposeBitFlag::UTF8) !== 0;
$name = fread($stream, $unpack['fileNameLength']);
$createdOS = ($unpack['versionMadeBy'] & 0xFF00) >> 8;
$softwareVersion = $unpack['versionMadeBy'] & 0x00FF;
$extractedOS = ($unpack['versionNeededToExtract'] & 0xFF00) >> 8;
$extractVersion = $unpack['versionNeededToExtract'] & 0x00FF;
$dosTime = $unpack['lastModFile'];
$comment = null;
if ($unpack['fileCommentLength'] > 0) {
$comment = fread($stream, $unpack['fileCommentLength']);
}
// decode code page names
$fallbackCharset = null;
if (!$isUtf8 && isset($this->options[ZipOptions::CHARSET])) {
$charset = $this->options[ZipOptions::CHARSET];
$fallbackCharset = $charset;
$name = DosCodePage::toUTF8($name, $charset);
if ($comment !== null) {
$comment = DosCodePage::toUTF8($comment, $charset);
}
}
$zipEntry = ZipEntry::create(
$name,
$createdOS,
$extractedOS,
$softwareVersion,
$extractVersion,
$unpack['compressionMethod'],
$generalPurposeBitFlags,
$dosTime,
$unpack['crc'],
$unpack['compressedSize'],
$unpack['uncompressedSize'],
$unpack['internalFileAttributes'],
$unpack['externalFileAttributes'],
$unpack['offsetLocalHeader'],
$comment,
$fallbackCharset
);
if ($unpack['extraFieldLength'] > 0) {
$this->parseExtraFields(
fread($stream, $unpack['extraFieldLength']),
$zipEntry,
false
);
/** @var Zip64ExtraField|null $extraZip64 */
$extraZip64 = $zipEntry->getCdExtraField(Zip64ExtraField::HEADER_ID);
if ($extraZip64 !== null) {
$this->handleZip64Extra($extraZip64, $zipEntry);
}
}
$this->loadLocalExtraFields($zipEntry);
$this->handleExtraEncryptionFields($zipEntry);
$this->handleExtraFields($zipEntry);
return $zipEntry;
}
/**
* @param string $buffer
* @param ZipEntry $zipEntry
* @param bool $local
*
* @return ExtraFieldsCollection
*/
protected function parseExtraFields($buffer, ZipEntry $zipEntry, $local = false)
{
$collection = $local ?
$zipEntry->getLocalExtraFields() :
$zipEntry->getCdExtraFields();
if (!empty($buffer)) {
$pos = 0;
$endPos = \strlen($buffer);
while ($endPos - $pos >= 4) {
/** @var int[] $data */
$data = unpack('vheaderId/vdataSize', substr($buffer, $pos, 4));
$pos += 4;
if ($endPos - $pos - $data['dataSize'] < 0) {
break;
}
$bufferData = substr($buffer, $pos, $data['dataSize']);
$headerId = $data['headerId'];
/** @var string|ZipExtraField|null $className */
$className = ZipExtraDriver::getClassNameOrNull($headerId);
if ($className !== null) {
try {
$extraField = $local ?
\call_user_func([$className, 'unpackLocalFileData'], $bufferData, $zipEntry) :
\call_user_func([$className, 'unpackCentralDirData'], $bufferData, $zipEntry);
} catch (\Throwable $e) {
throw new \RuntimeException(
sprintf(
'Error parse %s extra field 0x%04X',
$local ? 'local' : 'central directory',
$headerId
)
);
}
} else {
$extraField = new UnrecognizedExtraField($headerId, $bufferData);
}
$collection->add($extraField);
$pos += $data['dataSize'];
}
}
return $collection;
}
/**
* @param Zip64ExtraField $extraZip64
* @param ZipEntry $zipEntry
*/
protected function handleZip64Extra(Zip64ExtraField $extraZip64, ZipEntry $zipEntry)
{
$uncompressedSize = $extraZip64->getUncompressedSize();
$compressedSize = $extraZip64->getCompressedSize();
$localHeaderOffset = $extraZip64->getLocalHeaderOffset();
if ($uncompressedSize !== null) {
$zipEntry->setUncompressedSize($uncompressedSize);
}
if ($compressedSize !== null) {
$zipEntry->setCompressedSize($compressedSize);
}
if ($localHeaderOffset !== null) {
$zipEntry->setLocalHeaderOffset($localHeaderOffset);
}
}
/**
* Read Local File Header.
*
* local file header signature 4 bytes (0x04034b50)
* 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 name (variable size)
* extra field (variable size)
*
* @param ZipEntry $entry
*
* @throws ZipException
*/
protected function loadLocalExtraFields(ZipEntry $entry)
{
$offsetLocalHeader = $entry->getLocalHeaderOffset();
fseek($this->inStream, $offsetLocalHeader);
if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::LOCAL_FILE_HEADER) {
throw new ZipException(sprintf('%s (expected Local File Header)', $entry->getName()));
}
fseek($this->inStream, $offsetLocalHeader + ZipConstants::LFH_FILENAME_LENGTH_POS);
$unpack = unpack('vfileNameLength/vextraFieldLength', fread($this->inStream, 4));
$offsetData = ftell($this->inStream)
+ $unpack['fileNameLength']
+ $unpack['extraFieldLength'];
fseek($this->inStream, $unpack['fileNameLength'], \SEEK_CUR);
if ($unpack['extraFieldLength'] > 0) {
$this->parseExtraFields(
fread($this->inStream, $unpack['extraFieldLength']),
$entry,
true
);
}
$zipData = new ZipSourceFileData($this, $entry, $offsetData);
$entry->setData($zipData);
}
/**
* @param ZipEntry $zipEntry
*
* @throws ZipException
*/
private function handleExtraEncryptionFields(ZipEntry $zipEntry)
{
if ($zipEntry->isEncrypted()) {
if ($zipEntry->getCompressionMethod() === ZipCompressionMethod::WINZIP_AES) {
/** @var WinZipAesExtraField|null $extraField */
$extraField = $zipEntry->getExtraField(WinZipAesExtraField::HEADER_ID);
if ($extraField === null) {
throw new ZipException(
sprintf(
'Extra field 0x%04x (WinZip-AES Encryption) expected for compression method %d',
WinZipAesExtraField::HEADER_ID,
$zipEntry->getCompressionMethod()
)
);
}
$zipEntry->setCompressionMethod($extraField->getCompressionMethod());
$zipEntry->setEncryptionMethod($extraField->getEncryptionMethod());
} else {
$zipEntry->setEncryptionMethod(ZipEncryptionMethod::PKWARE);
}
}
}
/**
* Handle extra data in zip records.
*
* This is a special method in which you can process ExtraField
* and make changes to ZipEntry.
*
* @param ZipEntry $zipEntry
*/
protected function handleExtraFields(ZipEntry $zipEntry)
{
}
/**
* @param ZipSourceFileData $zipFileData
*
* @throws ZipException
* @throws Crc32Exception
*
* @return resource
*/
public function getEntryStream(ZipSourceFileData $zipFileData)
{
$outStream = fopen('php://temp', 'w+b');
$this->copyUncompressedDataToStream($zipFileData, $outStream);
rewind($outStream);
return $outStream;
}
/**
* @param ZipSourceFileData $zipFileData
* @param resource $outStream
*
* @throws Crc32Exception
* @throws ZipException
*/
public function copyUncompressedDataToStream(ZipSourceFileData $zipFileData, $outStream)
{
if (!\is_resource($outStream)) {
throw new InvalidArgumentException('outStream is not resource');
}
$entry = $zipFileData->getSourceEntry();
// if ($entry->isDirectory()) {
// throw new InvalidArgumentException('Streams not supported for directories');
// }
if ($entry->isStrongEncryption()) {
throw new ZipException('Not support encryption zip.');
}
$compressionMethod = $entry->getCompressionMethod();
fseek($this->inStream, $zipFileData->getOffset());
$filters = [];
$skipCheckCrc = false;
$isEncrypted = $entry->isEncrypted();
if ($isEncrypted) {
if ($entry->getPassword() === null) {
throw new ZipException('Can not password from entry ' . $entry->getName());
}
if (ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
/** @var WinZipAesExtraField|null $winZipAesExtra */
$winZipAesExtra = $entry->getExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipAesExtra === null) {
throw new ZipException(
sprintf('WinZip AES must contain the extra field %s', WinZipAesExtraField::HEADER_ID)
);
}
$compressionMethod = $winZipAesExtra->getCompressionMethod();
WinZipAesDecryptionStreamFilter::register();
$cipherFilterName = WinZipAesDecryptionStreamFilter::FILTER_NAME;
if ($winZipAesExtra->isV2()) {
$skipCheckCrc = true;
}
} else {
PKDecryptionStreamFilter::register();
$cipherFilterName = PKDecryptionStreamFilter::FILTER_NAME;
}
$encContextFilter = stream_filter_append(
$this->inStream,
$cipherFilterName,
\STREAM_FILTER_READ,
[
'entry' => $entry,
]
);
if (!$encContextFilter) {
throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
}
$filters[] = $encContextFilter;
}
// hack, see https://groups.google.com/forum/#!topic/alt.comp.lang.php/37_JZeW63uc
$pos = ftell($this->inStream);
rewind($this->inStream);
fseek($this->inStream, $pos);
$contextDecompress = null;
switch ($compressionMethod) {
case ZipCompressionMethod::STORED:
// file without compression, do nothing
break;
case ZipCompressionMethod::DEFLATED:
if (!($contextDecompress = stream_filter_append(
$this->inStream,
'zlib.inflate',
\STREAM_FILTER_READ
))) {
throw new \RuntimeException('Could not append filter "zlib.inflate" to stream');
}
$filters[] = $contextDecompress;
break;
case ZipCompressionMethod::BZIP2:
if (!($contextDecompress = stream_filter_append(
$this->inStream,
'bzip2.decompress',
\STREAM_FILTER_READ
))) {
throw new \RuntimeException('Could not append filter "bzip2.decompress" to stream');
}
$filters[] = $contextDecompress;
break;
default:
throw new ZipException(
sprintf(
'%s (compression method %d (%s) is not supported)',
$entry->getName(),
$compressionMethod,
ZipCompressionMethod::getCompressionMethodName($compressionMethod)
)
);
}
$limit = $zipFileData->getUncompressedSize();
$offset = 0;
$chunkSize = 8192;
try {
if ($skipCheckCrc) {
while ($offset < $limit) {
$length = min($chunkSize, $limit - $offset);
$buffer = fread($this->inStream, $length);
if ($buffer === false) {
throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName()));
}
fwrite($outStream, $buffer);
$offset += $length;
}
} else {
$contextHash = hash_init('crc32b');
while ($offset < $limit) {
$length = min($chunkSize, $limit - $offset);
$buffer = fread($this->inStream, $length);
if ($buffer === false) {
throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName()));
}
fwrite($outStream, $buffer);
hash_update($contextHash, $buffer);
$offset += $length;
}
$expectedCrc = (int) hexdec(hash_final($contextHash));
if ($expectedCrc !== $entry->getCrc()) {
throw new Crc32Exception($entry->getName(), $expectedCrc, $entry->getCrc());
}
}
} finally {
for ($i = \count($filters); $i > 0; $i--) {
stream_filter_remove($filters[$i - 1]);
}
}
}
/**
* @param ZipSourceFileData $zipData
* @param resource $outStream
*/
public function copyCompressedDataToStream(ZipSourceFileData $zipData, $outStream)
{
if ($zipData->getCompressedSize() > 0) {
fseek($this->inStream, $zipData->getOffset());
stream_copy_to_stream($this->inStream, $outStream, $zipData->getCompressedSize());
}
}
/**
* @return bool
*/
protected function isZip64Support()
{
return \PHP_INT_SIZE === 8; // true for 64bit system
}
public function close()
{
if (\is_resource($this->inStream)) {
fclose($this->inStream);
}
}
public function __destruct()
{
$this->close();
}
}

884
src/IO/ZipWriter.php Normal file
View File

@ -0,0 +1,884 @@
<?php
namespace PhpZip\IO;
use PhpZip\Constants\DosCodePage;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipConstants;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Constants\ZipPlatform;
use PhpZip\Constants\ZipVersion;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipUnsupportMethodException;
use PhpZip\IO\Filter\Cipher\Pkware\PKEncryptionStreamFilter;
use PhpZip\IO\Filter\Cipher\WinZipAes\WinZipAesEncryptionStreamFilter;
use PhpZip\Model\Data\ZipSourceFileData;
use PhpZip\Model\Extra\Fields\ApkAlignmentExtraField;
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
use PhpZip\Model\ZipContainer;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
use PhpZip\Util\StringUtil;
/**
* Class ZipWriter.
*/
class ZipWriter
{
/** @var int Chunk read size */
const CHUNK_SIZE = 8192;
/** @var ZipContainer */
protected $zipContainer;
/**
* ZipWriter constructor.
*
* @param ZipContainer $container
*/
public function __construct(ZipContainer $container)
{
$this->zipContainer = $container;
}
/**
* @param resource $outStream
*
* @throws ZipException
*/
public function write($outStream)
{
if (!\is_resource($outStream)) {
throw new \InvalidArgumentException('$outStream must be resource');
}
$this->beforeWrite();
$this->writeLocalBlock($outStream);
$cdOffset = ftell($outStream);
$this->writeCentralDirectoryBlock($outStream);
$cdSize = ftell($outStream) - $cdOffset;
$this->writeEndOfCentralDirectoryBlock($outStream, $cdOffset, $cdSize);
}
protected function beforeWrite()
{
}
/**
* @param resource $outStream
*
* @throws ZipException
*/
protected function writeLocalBlock($outStream)
{
$zipEntries = $this->zipContainer->getEntries();
foreach ($zipEntries as $zipEntry) {
$this->writeLocalHeader($outStream, $zipEntry);
$this->writeData($outStream, $zipEntry);
if ($zipEntry->isDataDescriptorEnabled()) {
$this->writeDataDescriptor($outStream, $zipEntry);
}
}
}
/**
* @param resource $outStream
* @param ZipEntry $entry
*
* @throws ZipException
*/
protected function writeLocalHeader($outStream, ZipEntry $entry)
{
// todo in 4.0 version move zipalign functional to ApkWriter class
if ($this->zipContainer->isZipAlign()) {
$this->zipAlign($outStream, $entry);
}
$relativeOffset = ftell($outStream);
$entry->setLocalHeaderOffset($relativeOffset);
if ($entry->isEncrypted() && $entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
$entry->enableDataDescriptor(true);
}
$dd = $entry->isDataDescriptorRequired() ||
$entry->isDataDescriptorEnabled();
$compressedSize = $entry->getCompressedSize();
$uncompressedSize = $entry->getUncompressedSize();
$entry->getLocalExtraFields()->remove(Zip64ExtraField::HEADER_ID);
if ($compressedSize > ZipConstants::ZIP64_MAGIC || $uncompressedSize > ZipConstants::ZIP64_MAGIC) {
$entry->getLocalExtraFields()->add(
new Zip64ExtraField($uncompressedSize, $compressedSize)
);
$compressedSize = ZipConstants::ZIP64_MAGIC;
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
}
$compressionMethod = $entry->getCompressionMethod();
$crc = $entry->getCrc();
if ($entry->isEncrypted() && ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) {
/** @var WinZipAesExtraField|null $winZipAesExtra */
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipAesExtra === null) {
$winZipAesExtra = WinZipAesExtraField::create($entry);
}
if ($winZipAesExtra->isV2()) {
$crc = 0;
}
$compressionMethod = ZipCompressionMethod::WINZIP_AES;
}
$extra = $this->getExtraFieldsContents($entry, true);
$name = $entry->getName();
$dosCharset = $entry->getCharset();
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
$name = DosCodePage::fromUTF8($name, $dosCharset);
}
$nameLength = \strlen($name);
$extraLength = \strlen($extra);
$size = $nameLength + $extraLength;
if ($size > 0xffff) {
throw new ZipException(
sprintf(
'%s (the total size of %s bytes for the name, extra fields and comment exceeds the maximum size of %d bytes)',
$entry->getName(),
$size,
0xffff
)
);
}
$extractedBy = ($entry->getExtractedOS() << 8) | $entry->getExtractVersion();
fwrite(
$outStream,
pack(
'VvvvVVVVvv',
// local file header signature 4 bytes (0x04034b50)
ZipConstants::LOCAL_FILE_HEADER,
// version needed to extract 2 bytes
$extractedBy,
// general purpose bit flag 2 bytes
$entry->getGeneralPurposeBitFlags(),
// compression method 2 bytes
$compressionMethod,
// last mod file time 2 bytes
// last mod file date 2 bytes
$entry->getDosTime(),
// crc-32 4 bytes
$dd ? 0 : $crc,
// compressed size 4 bytes
$dd ? 0 : $compressedSize,
// uncompressed size 4 bytes
$dd ? 0 : $uncompressedSize,
// file name length 2 bytes
$nameLength,
// extra field length 2 bytes
$extraLength
)
);
if ($nameLength > 0) {
fwrite($outStream, $name);
}
if ($extraLength > 0) {
fwrite($outStream, $extra);
}
}
/**
* @param resource $outStream
* @param ZipEntry $entry
*
* @throws ZipException
*/
private function zipAlign($outStream, ZipEntry $entry)
{
if (!$entry->isDirectory() && $entry->getCompressionMethod() === ZipCompressionMethod::STORED) {
$entry->removeExtraField(ApkAlignmentExtraField::HEADER_ID);
$extra = $this->getExtraFieldsContents($entry, true);
$extraLength = \strlen($extra);
$name = $entry->getName();
$dosCharset = $entry->getCharset();
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
$name = DosCodePage::fromUTF8($name, $dosCharset);
}
$nameLength = \strlen($name);
$multiple = ApkAlignmentExtraField::ALIGNMENT_BYTES;
if (StringUtil::endsWith($name, '.so')) {
$multiple = ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES;
}
$offset = ftell($outStream);
$dataMinStartOffset =
$offset +
ZipConstants::LFH_FILENAME_POS +
$extraLength +
$nameLength;
$padding =
($multiple - ($dataMinStartOffset % $multiple))
% $multiple;
if ($padding > 0) {
$dataMinStartOffset += ApkAlignmentExtraField::MIN_SIZE;
$padding =
($multiple - ($dataMinStartOffset % $multiple))
% $multiple;
$entry->getLocalExtraFields()->add(
new ApkAlignmentExtraField($multiple, $padding)
);
}
}
}
/**
* Merges the local file data fields of the given ZipExtraFields.
*
* @param ZipEntry $entry
* @param bool $local
*
* @throws ZipException
*
* @return string
*/
protected function getExtraFieldsContents(ZipEntry $entry, $local)
{
$local = (bool) $local;
$collection = $local ?
$entry->getLocalExtraFields() :
$entry->getCdExtraFields();
$extraData = '';
foreach ($collection as $extraField) {
if ($local) {
$data = $extraField->packLocalFileData();
} else {
$data = $extraField->packCentralDirData();
}
$extraData .= pack(
'vv',
$extraField->getHeaderId(),
\strlen($data)
);
$extraData .= $data;
}
$size = \strlen($extraData);
if ($size > 0xffff) {
throw new ZipException(
sprintf(
'Size extra out of range: %d. Extra data: %s',
$size,
$extraData
)
);
}
return $extraData;
}
/**
* @param resource $outStream
* @param ZipEntry $entry
*
* @throws ZipException
*/
protected function writeData($outStream, ZipEntry $entry)
{
$zipData = $entry->getData();
if ($zipData === null) {
if ($entry->isDirectory()) {
return;
}
throw new ZipException(sprintf('No zip data for entry "%s"', $entry->getName()));
}
// data write variants:
// --------------------
// * data of source zip file -> copy compressed data
// * store - simple write
// * store and encryption - apply encryption filter and simple write
// * deflate or bzip2 - apply compression filter and simple write
// * (deflate or bzip2) and encryption - create temp stream and apply
// compression filter to it, then apply encryption filter to root
// stream and write temp stream data.
// (PHP cannot apply the filter for encryption after the compression
// filter, so a temporary stream is created for the compressed data)
if ($zipData instanceof ZipSourceFileData && !$zipData->hasRecompressData($entry)) {
// data of source zip file -> copy compressed data
$zipData->copyCompressedDataToStream($outStream);
return;
}
$entryStream = $zipData->getDataAsStream();
if (stream_get_meta_data($entryStream)['seekable']) {
rewind($entryStream);
}
$uncompressedSize = $entry->getUncompressedSize();
$posBeforeWrite = ftell($outStream);
$compressionMethod = $entry->getCompressionMethod();
if ($entry->isEncrypted()) {
if ($compressionMethod === ZipCompressionMethod::STORED) {
$contextFilter = $this->appendEncryptionFilter($outStream, $entry, $uncompressedSize);
$checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
} else {
$compressStream = fopen('php://temp', 'w+b');
$contextFilter = $this->appendCompressionFilter($compressStream, $entry);
$checksum = $this->writeAndCountChecksum($entryStream, $compressStream, $uncompressedSize);
if ($contextFilter !== null) {
stream_filter_remove($contextFilter);
$contextFilter = null;
}
rewind($compressStream);
$compressedSize = fstat($compressStream)['size'];
$contextFilter = $this->appendEncryptionFilter($outStream, $entry, $compressedSize);
stream_copy_to_stream($compressStream, $outStream);
}
} else {
$contextFilter = $this->appendCompressionFilter($outStream, $entry);
$checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize);
}
if ($contextFilter !== null) {
stream_filter_remove($contextFilter);
$contextFilter = null;
}
// my hack {@see https://bugs.php.net/bug.php?id=49874}
fseek($outStream, 0, \SEEK_END);
$compressedSize = ftell($outStream) - $posBeforeWrite;
$entry->setCompressedSize($compressedSize);
$entry->setCrc($checksum);
if (!$entry->isDataDescriptorEnabled()) {
if ($uncompressedSize > ZipConstants::ZIP64_MAGIC || $compressedSize > ZipConstants::ZIP64_MAGIC) {
/** @var Zip64ExtraField|null $zip64ExtraLocal */
$zip64ExtraLocal = $entry->getLocalExtraField(Zip64ExtraField::HEADER_ID);
// if there is a zip64 extra record, then update it;
// if not, write data to data descriptor
if ($zip64ExtraLocal !== null) {
$zip64ExtraLocal->setCompressedSize($compressedSize);
$zip64ExtraLocal->setUncompressedSize($uncompressedSize);
$posExtra = $entry->getLocalHeaderOffset() + ZipConstants::LFH_FILENAME_POS + \strlen($entry->getName());
fseek($outStream, $posExtra);
fwrite($outStream, $this->getExtraFieldsContents($entry, true));
} else {
$posGPBF = $entry->getLocalHeaderOffset() + 6;
$entry->enableDataDescriptor(true);
fseek($outStream, $posGPBF);
fwrite(
$outStream,
pack(
'v',
// general purpose bit flag 2 bytes
$entry->getGeneralPurposeBitFlags()
)
);
}
$compressedSize = ZipConstants::ZIP64_MAGIC;
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
}
$posChecksum = $entry->getLocalHeaderOffset() + 14;
/** @var WinZipAesExtraField|null $winZipAesExtra */
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
$checksum = 0;
}
fseek($outStream, $posChecksum);
fwrite(
$outStream,
pack(
'VVV',
// crc-32 4 bytes
$checksum,
// compressed size 4 bytes
$compressedSize,
// uncompressed size 4 bytes
$uncompressedSize
)
);
fseek($outStream, 0, \SEEK_END);
}
}
/**
* @param resource $inStream
* @param resource $outStream
* @param int $size
*
* @return int
*/
private function writeAndCountChecksum($inStream, $outStream, $size)
{
$contextHash = hash_init('crc32b');
$offset = 0;
while ($offset < $size) {
$read = min(self::CHUNK_SIZE, $size - $offset);
$buffer = fread($inStream, $read);
fwrite($outStream, $buffer);
hash_update($contextHash, $buffer);
$offset += $read;
}
return (int) hexdec(hash_final($contextHash));
}
/**
* @param resource $outStream
* @param ZipEntry $entry
*
* @throws ZipUnsupportMethodException
*
* @return resource|null
*/
protected function appendCompressionFilter($outStream, ZipEntry $entry)
{
$contextCompress = null;
switch ($entry->getCompressionMethod()) {
case ZipCompressionMethod::DEFLATED:
if (!($contextCompress = stream_filter_append(
$outStream,
'zlib.deflate',
\STREAM_FILTER_WRITE,
['level' => $entry->getCompressionLevel()]
))) {
throw new \RuntimeException('Could not append filter "zlib.deflate" to out stream');
}
break;
case ZipCompressionMethod::BZIP2:
if (!($contextCompress = stream_filter_append(
$outStream,
'bzip2.compress',
\STREAM_FILTER_WRITE,
['blocks' => $entry->getCompressionLevel(), 'work' => 0]
))) {
throw new \RuntimeException('Could not append filter "bzip2.compress" to out stream');
}
break;
case ZipCompressionMethod::STORED:
// file without compression, do nothing
break;
default:
throw new ZipUnsupportMethodException(
sprintf(
'%s (compression method %d (%s) is not supported)',
$entry->getName(),
$entry->getCompressionMethod(),
ZipCompressionMethod::getCompressionMethodName($entry->getCompressionMethod())
)
);
}
return $contextCompress;
}
/**
* @param resource $outStream
* @param ZipEntry $entry
* @param int $size
*
* @return resource|null
*/
protected function appendEncryptionFilter($outStream, ZipEntry $entry, $size)
{
$encContextFilter = null;
if ($entry->isEncrypted()) {
if ($entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) {
PKEncryptionStreamFilter::register();
$cipherFilterName = PKEncryptionStreamFilter::FILTER_NAME;
} else {
WinZipAesEncryptionStreamFilter::register();
$cipherFilterName = WinZipAesEncryptionStreamFilter::FILTER_NAME;
}
$encContextFilter = stream_filter_append(
$outStream,
$cipherFilterName,
\STREAM_FILTER_WRITE,
[
'entry' => $entry,
'size' => $size,
]
);
if (!$encContextFilter) {
throw new \RuntimeException('Not apply filter ' . $cipherFilterName);
}
}
return $encContextFilter;
}
/**
* @param resource $outStream
* @param ZipEntry $entry
*/
protected function writeDataDescriptor($outStream, ZipEntry $entry)
{
$crc = $entry->getCrc();
/** @var WinZipAesExtraField|null $winZipAesExtra */
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) {
$crc = 0;
}
fwrite(
$outStream,
pack(
'VV',
// data descriptor signature 4 bytes (0x08074b50)
ZipConstants::DATA_DESCRIPTOR,
// crc-32 4 bytes
$crc
)
);
if (
$entry->isZip64ExtensionsRequired() ||
$entry->getLocalExtraFields()->has(Zip64ExtraField::HEADER_ID)
) {
$dd =
// compressed size 8 bytes
PackUtil::packLongLE($entry->getCompressedSize()) .
// uncompressed size 8 bytes
PackUtil::packLongLE($entry->getUncompressedSize());
} else {
$dd = pack(
'VV',
// compressed size 4 bytes
$entry->getCompressedSize(),
// uncompressed size 4 bytes
$entry->getUncompressedSize()
);
}
fwrite($outStream, $dd);
}
/**
* @param resource $outStream
*
* @throws ZipException
*/
protected function writeCentralDirectoryBlock($outStream)
{
foreach ($this->zipContainer->getEntries() as $outputEntry) {
$this->writeCentralDirectoryHeader($outStream, $outputEntry);
}
}
/**
* Writes a Central File Header record.
*
* @param resource $outStream
* @param ZipEntry $entry
*
* @throws ZipException
*/
protected function writeCentralDirectoryHeader($outStream, ZipEntry $entry)
{
$compressedSize = $entry->getCompressedSize();
$uncompressedSize = $entry->getUncompressedSize();
$localHeaderOffset = $entry->getLocalHeaderOffset();
$entry->getCdExtraFields()->remove(Zip64ExtraField::HEADER_ID);
if (
$localHeaderOffset > ZipConstants::ZIP64_MAGIC ||
$compressedSize > ZipConstants::ZIP64_MAGIC ||
$uncompressedSize > ZipConstants::ZIP64_MAGIC
) {
$zip64ExtraField = new Zip64ExtraField();
if ($uncompressedSize >= ZipConstants::ZIP64_MAGIC) {
$zip64ExtraField->setUncompressedSize($uncompressedSize);
$uncompressedSize = ZipConstants::ZIP64_MAGIC;
}
if ($compressedSize >= ZipConstants::ZIP64_MAGIC) {
$zip64ExtraField->setCompressedSize($compressedSize);
$compressedSize = ZipConstants::ZIP64_MAGIC;
}
if ($localHeaderOffset >= ZipConstants::ZIP64_MAGIC) {
$zip64ExtraField->setLocalHeaderOffset($localHeaderOffset);
$localHeaderOffset = ZipConstants::ZIP64_MAGIC;
}
$entry->getCdExtraFields()->add($zip64ExtraField);
}
$extra = $this->getExtraFieldsContents($entry, false);
$extraLength = \strlen($extra);
$name = $entry->getName();
$comment = $entry->getComment();
$dosCharset = $entry->getCharset();
if ($dosCharset !== null && !$entry->isUtf8Flag()) {
$name = DosCodePage::fromUTF8($name, $dosCharset);
if ($comment) {
$comment = DosCodePage::fromUTF8($comment, $dosCharset);
}
}
$commentLength = \strlen($comment);
$compressionMethod = $entry->getCompressionMethod();
$crc = $entry->getCrc();
/** @var WinZipAesExtraField|null $winZipAesExtra */
$winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID);
if ($winZipAesExtra !== null) {
if ($winZipAesExtra->isV2()) {
$crc = 0;
}
$compressionMethod = ZipCompressionMethod::WINZIP_AES;
}
fwrite(
$outStream,
pack(
'VvvvvVVVVvvvvvVV',
// central file header signature 4 bytes (0x02014b50)
ZipConstants::CENTRAL_FILE_HEADER,
// version made by 2 bytes
($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
// version needed to extract 2 bytes
($entry->getExtractedOS() << 8) | $entry->getExtractVersion(),
// general purpose bit flag 2 bytes
$entry->getGeneralPurposeBitFlags(),
// compression method 2 bytes
$compressionMethod,
// last mod file datetime 4 bytes
$entry->getDosTime(),
// crc-32 4 bytes
$crc,
// compressed size 4 bytes
$compressedSize,
// uncompressed size 4 bytes
$uncompressedSize,
// file name length 2 bytes
\strlen($name),
// extra field length 2 bytes
$extraLength,
// file comment length 2 bytes
$commentLength,
// disk number start 2 bytes
0,
// internal file attributes 2 bytes
$entry->getInternalAttributes(),
// external file attributes 4 bytes
$entry->getExternalAttributes(),
// relative offset of local header 4 bytes
$localHeaderOffset
)
);
// file name (variable size)
fwrite($outStream, $name);
if ($extraLength > 0) {
// extra field (variable size)
fwrite($outStream, $extra);
}
if ($commentLength > 0) {
// file comment (variable size)
fwrite($outStream, $comment);
}
}
/**
* @param resource $outStream
* @param int $centralDirectoryOffset
* @param int $centralDirectorySize
*/
protected function writeEndOfCentralDirectoryBlock(
$outStream,
$centralDirectoryOffset,
$centralDirectorySize
) {
$cdEntriesCount = \count($this->zipContainer);
$cdEntriesZip64 = $cdEntriesCount > 0xffff;
$cdSizeZip64 = $centralDirectorySize > ZipConstants::ZIP64_MAGIC;
$cdOffsetZip64 = $centralDirectoryOffset > ZipConstants::ZIP64_MAGIC;
$zip64Required = $cdEntriesZip64
|| $cdSizeZip64
|| $cdOffsetZip64;
if ($zip64Required) {
$zip64EndOfCentralDirectoryOffset = ftell($outStream);
// find max software version, version needed to extract and most common platform
list($softwareVersion, $versionNeededToExtract) = array_reduce(
$this->zipContainer->getEntries(),
static function (array $carry, ZipEntry $entry) {
$carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
$carry[1] = max($carry[1], $entry->getExtractVersion() & 0xFF);
return $carry;
},
[ZipVersion::v10_DEFAULT_MIN, ZipVersion::v45_ZIP64_EXT]
);
$createdOS = $extractedOS = ZipPlatform::OS_DOS;
$versionMadeBy = ($createdOS << 8) | max($softwareVersion, ZipVersion::v45_ZIP64_EXT);
$versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, ZipVersion::v45_ZIP64_EXT);
// write zip64 end of central directory signature
fwrite(
$outStream,
pack(
'V',
// signature 4 bytes (0x06064b50)
ZipConstants::ZIP64_END_CD
)
);
// size of zip64 end of central
// directory record 8 bytes
fwrite($outStream, PackUtil::packLongLE(ZipConstants::ZIP64_END_OF_CD_LEN - 12));
fwrite(
$outStream,
pack(
'vvVV',
// version made by 2 bytes
$versionMadeBy & 0xFFFF,
// version needed to extract 2 bytes
$versionExtractedBy & 0xFFFF,
// number of this disk 4 bytes
0,
// number of the disk with the
// start of the central directory 4 bytes
0
)
);
fwrite(
$outStream,
// total number of entries in the
// central directory on this disk 8 bytes
PackUtil::packLongLE($cdEntriesCount) .
// total number of entries in the
// central directory 8 bytes
PackUtil::packLongLE($cdEntriesCount) .
// size of the central directory 8 bytes
PackUtil::packLongLE($centralDirectorySize) .
// offset of start of central
// directory with respect to
// the starting disk number 8 bytes
PackUtil::packLongLE($centralDirectoryOffset)
);
// write zip64 end of central directory locator
fwrite(
$outStream,
pack(
'VV',
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
ZipConstants::ZIP64_END_CD_LOC,
// number of the disk with the
// start of the zip64 end of
// central directory 4 bytes
0
) .
// relative offset of the zip64
// end of central directory record 8 bytes
PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset) .
// total number of disks 4 bytes
pack('V', 1)
);
}
$comment = $this->zipContainer->getArchiveComment();
$commentLength = $comment !== null ? \strlen($comment) : 0;
fwrite(
$outStream,
pack(
'VvvvvVVv',
// end of central dir signature 4 bytes (0x06054b50)
ZipConstants::END_CD,
// 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
$cdEntriesZip64 ? 0xffff : $cdEntriesCount,
// total number of entries in
// the central directory 2 bytes
$cdEntriesZip64 ? 0xffff : $cdEntriesCount,
// size of the central directory 4 bytes
$cdSizeZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectorySize,
// offset of start of central
// directory with respect to
// the starting disk number 4 bytes
$cdOffsetZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectoryOffset,
// .ZIP file comment length 2 bytes
$commentLength
)
);
if ($comment !== null && $commentLength > 0) {
// .ZIP file comment (variable size)
fwrite($outStream, $comment);
}
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace PhpZip\Model\Data;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipData;
/**
* Class ZipFileData.
*/
class ZipFileData implements ZipData
{
/** @var \SplFileInfo */
private $file;
/**
* ZipStringData constructor.
*
* @param \SplFileInfo $fileInfo
*
* @throws ZipException
*/
public function __construct(\SplFileInfo $fileInfo)
{
if (!$fileInfo->isFile()) {
throw new ZipException('$fileInfo is not a file.');
}
if (!$fileInfo->isReadable()) {
throw new ZipException('$fileInfo is not readable.');
}
$this->file = $fileInfo;
}
/**
* @throws ZipException
*
* @return resource returns stream data
*/
public function getDataAsStream()
{
if (!$this->file->isReadable()) {
throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname()));
}
return fopen($this->file->getPathname(), 'rb');
}
/**
* @throws ZipException
*
* @return string returns data as string
*/
public function getDataAsString()
{
if (!$this->file->isReadable()) {
throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname()));
}
return file_get_contents($this->file->getPathname());
}
/**
* @param resource $outStream
*
* @throws ZipException
*/
public function copyDataToStream($outStream)
{
try {
$stream = $this->getDataAsStream();
stream_copy_to_stream($stream, $outStream);
} finally {
fclose($stream);
}
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace PhpZip\Model\Data;
use PhpZip\Model\ZipData;
use PhpZip\Model\ZipEntry;
/**
* Class ZipNewData.
*/
class ZipNewData implements ZipData
{
/** @var resource */
private $stream;
/** @var ZipEntry */
private $zipEntry;
/**
* ZipStringData constructor.
*
* @param ZipEntry $zipEntry
* @param string|resource $data
*/
public function __construct(ZipEntry $zipEntry, $data)
{
$this->zipEntry = $zipEntry;
if (\is_string($data)) {
$zipEntry->setUncompressedSize(\strlen($data));
if (!($handle = fopen('php://temp', 'w+b'))) {
throw new \RuntimeException('Temp resource can not open from write.');
}
fwrite($handle, $data);
rewind($handle);
$this->stream = $handle;
} elseif (\is_resource($data)) {
$this->stream = $data;
}
}
/**
* @return resource returns stream data
*/
public function getDataAsStream()
{
if (!\is_resource($this->stream)) {
throw new \LogicException(sprintf('Resource was closed (entry=%s).', $this->zipEntry->getName()));
}
return $this->stream;
}
/**
* @return string returns data as string
*/
public function getDataAsString()
{
$stream = $this->getDataAsStream();
$pos = ftell($stream);
try {
rewind($stream);
return stream_get_contents($stream);
} finally {
fseek($stream, $pos);
}
}
/**
* @param resource $outStream
*/
public function copyDataToStream($outStream)
{
$stream = $this->getDataAsStream();
rewind($stream);
stream_copy_to_stream($stream, $outStream);
}
public function __destruct()
{
if (\is_resource($this->stream)) {
fclose($this->stream);
}
}
}

View File

@ -0,0 +1,172 @@
<?php
namespace PhpZip\Model\Data;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\ZipException;
use PhpZip\IO\ZipReader;
use PhpZip\Model\ZipData;
use PhpZip\Model\ZipEntry;
/**
* Class ZipFileData.
*/
class ZipSourceFileData implements ZipData
{
/** @var ZipReader */
private $zipReader;
/** @var resource|null */
private $stream;
/** @var ZipEntry */
private $sourceEntry;
/** @var int */
private $offset;
/** @var int */
private $uncompressedSize;
/** @var int */
private $compressedSize;
/**
* ZipFileData constructor.
*
* @param ZipReader $zipReader
* @param ZipEntry $zipEntry
* @param int $offsetData
*/
public function __construct(ZipReader $zipReader, ZipEntry $zipEntry, $offsetData)
{
$this->zipReader = $zipReader;
$this->offset = $offsetData;
$this->sourceEntry = $zipEntry;
$this->compressedSize = $zipEntry->getCompressedSize();
$this->uncompressedSize = $zipEntry->getUncompressedSize();
}
/**
* @param ZipEntry $entry
*
* @return bool
*/
public function hasRecompressData(ZipEntry $entry)
{
return $this->sourceEntry->getCompressionLevel() !== $entry->getCompressionLevel() ||
$this->sourceEntry->getCompressionMethod() !== $entry->getCompressionMethod() ||
$this->sourceEntry->isEncrypted() !== $entry->isEncrypted() ||
$this->sourceEntry->getEncryptionMethod() !== $entry->getEncryptionMethod() ||
$this->sourceEntry->getPassword() !== $entry->getPassword() ||
$this->sourceEntry->getCompressedSize() !== $entry->getCompressedSize() ||
$this->sourceEntry->getUncompressedSize() !== $entry->getUncompressedSize() ||
$this->sourceEntry->getCrc() !== $entry->getCrc();
}
/**
* @throws ZipException
*
* @return resource returns stream data
*/
public function getDataAsStream()
{
if (!\is_resource($this->stream)) {
$this->stream = $this->zipReader->getEntryStream($this);
}
return $this->stream;
}
/**
* @throws ZipException
*
* @return string returns data as string
*/
public function getDataAsString()
{
$autoClosable = $this->stream === null;
$stream = $this->getDataAsStream();
$pos = ftell($stream);
try {
rewind($stream);
return stream_get_contents($stream);
} finally {
if ($autoClosable) {
fclose($stream);
$this->stream = null;
} else {
fseek($stream, $pos);
}
}
}
/**
* @param resource $outputStream Output stream
*
* @throws ZipException
* @throws Crc32Exception
*/
public function copyDataToStream($outputStream)
{
if (\is_resource($this->stream)) {
rewind($this->stream);
stream_copy_to_stream($this->stream, $outputStream);
} else {
$this->zipReader->copyUncompressedDataToStream($this, $outputStream);
}
}
/**
* @param resource $outputStream Output stream
*/
public function copyCompressedDataToStream($outputStream)
{
$this->zipReader->copyCompressedDataToStream($this, $outputStream);
}
/**
* @return ZipEntry
*/
public function getSourceEntry()
{
return $this->sourceEntry;
}
/**
* @return int
*/
public function getCompressedSize()
{
return $this->compressedSize;
}
/**
* @return int
*/
public function getUncompressedSize()
{
return $this->uncompressedSize;
}
/**
* @return int
*/
public function getOffset()
{
return $this->offset;
}
/**
* {@inheritdoc}
*/
public function __destruct()
{
if (\is_resource($this->stream)) {
fclose($this->stream);
}
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace PhpZip\Model;
/**
* End of Central Directory.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class EndOfCentralDirectory
{
/** @var int Count files. */
private $entryCount;
/** @var int Central Directory Offset. */
private $cdOffset;
/** @var int */
private $cdSize;
/** @var string|null The archive comment. */
private $comment;
/** @var bool Zip64 extension */
private $zip64;
/**
* EndOfCentralDirectory constructor.
*
* @param int $entryCount
* @param int $cdOffset
* @param int $cdSize
* @param bool $zip64
* @param string|null $comment
*/
public function __construct($entryCount, $cdOffset, $cdSize, $zip64, $comment = null)
{
$this->entryCount = $entryCount;
$this->cdOffset = $cdOffset;
$this->cdSize = $cdSize;
$this->zip64 = $zip64;
$this->comment = $comment;
}
/**
* @param string|null $comment
*/
public function setComment($comment)
{
$this->comment = $comment;
}
/**
* @return int
*/
public function getEntryCount()
{
return $this->entryCount;
}
/**
* @return int
*/
public function getCdOffset()
{
return $this->cdOffset;
}
/**
* @return int
*/
public function getCdSize()
{
return $this->cdSize;
}
/**
* @return string|null
*/
public function getComment()
{
return $this->comment;
}
/**
* @return bool
*/
public function isZip64()
{
return $this->zip64;
}
}

View File

@ -1,18 +1,12 @@
<?php
namespace PhpZip\Extra;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
namespace PhpZip\Model\Extra;
/**
* 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
class ExtraFieldsCollection implements \ArrayAccess, \Countable, \Iterator
{
/**
* The map of Extra Fields.
@ -20,7 +14,7 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
* 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[]
* @var ZipExtraField[]
*/
protected $collection = [];
@ -40,46 +34,70 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @param int $headerId the requested Header ID
*
* @throws ZipException if headerId is out of range
*
* @return ExtraField|null the Extra Field with the given Header ID or
* if no such Extra Field exists
* @return ZipExtraField|null the Extra Field with the given Header ID or
* if no such Extra Field exists
*/
public function get($headerId)
{
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
$this->validateHeaderId($headerId);
if (isset($this->collection[$headerId])) {
return $this->collection[$headerId];
}
return isset($this->collection[$headerId]) ? $this->collection[$headerId] : null;
}
return null;
/**
* @param int $headerId
*/
private function validateHeaderId($headerId)
{
if ($headerId < 0 || $headerId > 0xffff) {
throw new \InvalidArgumentException('$headerId out of range');
}
}
/**
* Stores the given Extra Field in this collection.
*
* @param ExtraField $extraField the Extra Field to store in this collection
* @param ZipExtraField $extraField the Extra Field to store in this collection
*
* @throws ZipException if headerId is out of range
*
* @return ExtraField the Extra Field previously associated with the Header ID of
* of the given Extra Field or null if no such Extra Field existed
* @return ZipExtraField the Extra Field previously associated with the Header ID of
* of the given Extra Field or null if no such Extra Field existed
*/
public function add(ExtraField $extraField)
public function add(ZipExtraField $extraField)
{
$headerId = $extraField::getHeaderId();
$headerId = $extraField->getHeaderId();
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
$this->validateHeaderId($headerId);
$this->collection[$headerId] = $extraField;
return $extraField;
}
/**
* @param ZipExtraField[] $extraFields
*/
public function addAll(array $extraFields)
{
foreach ($extraFields as $extraField) {
$this->add($extraField);
}
}
/**
* @param ExtraFieldsCollection $collection
*/
public function addCollection(self $collection)
{
$this->addAll($collection->collection);
}
/**
* @return ZipExtraField[]
*/
public function getAll()
{
return $this->collection;
}
/**
* Returns Extra Field exists.
*
@ -97,16 +115,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @param int $headerId the requested Header ID
*
* @throws ZipException if headerId is out of range or extra field not found
*
* @return ExtraField the Extra Field with the given Header ID or null
* if no such Extra Field exists
* @return ZipExtraField|null the Extra Field with the given Header ID or null
* if no such Extra Field exists
*/
public function remove($headerId)
{
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
$this->validateHeaderId($headerId);
if (isset($this->collection[$headerId])) {
$ef = $this->collection[$headerId];
@ -115,7 +129,7 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
return $ef;
}
throw new ZipException('ExtraField not found');
return null;
}
/**
@ -123,20 +137,13 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
* @param int $offset an offset to check for
*
* @return bool true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
*
* @since 5.0.0
* @return bool true on success or false on failure
*/
public function offsetExists($offset)
{
return isset($this->collection[$offset]);
return isset($this->collection[(int) $offset]);
}
/**
@ -144,19 +151,13 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @see http://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @param int $offset the offset to retrieve
*
* @throws ZipException
*
* @return mixed can return all value types
*
* @since 5.0.0
* @return ZipExtraField|null
*/
public function offsetGet($offset)
{
return $this->get($offset);
return isset($this->collection[$offset]) ? $this->collection[$offset] : null;
}
/**
@ -164,27 +165,15 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @see http://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*
* @throws ZipException
*
* @since 5.0.0
* @param mixed $offset the offset to assign the value to
* @param ZipExtraField $value the value to set
*/
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);
if (!$value instanceof ZipExtraField) {
throw new \InvalidArgumentException('value is not instanceof ' . ZipExtraField::class);
}
$this->add($value);
}
/**
@ -192,13 +181,7 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*
* @since 5.0.0
*
* @throws ZipException
* @param mixed $offset the offset to unset
*/
public function offsetUnset($offset)
{
@ -210,9 +193,7 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @see http://php.net/manual/en/iterator.current.php
*
* @return mixed can return any type
*
* @since 5.0.0
* @return ZipExtraField
*/
public function current()
{
@ -223,7 +204,6 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
* Move forward to next element.
*
* @see http://php.net/manual/en/iterator.next.php
* @since 5.0.0
*/
public function next()
{
@ -235,9 +215,7 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @see http://php.net/manual/en/iterator.key.php
*
* @return mixed scalar on success, or null on failure
*
* @since 5.0.0
* @return int scalar on success, or null on failure
*/
public function key()
{
@ -251,25 +229,41 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
*
* @return bool The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*
* @since 5.0.0
*/
public function valid()
{
return $this->offsetExists($this->key());
return key($this->collection) !== null;
}
/**
* Rewind the Iterator to the first element.
*
* @see http://php.net/manual/en/iterator.rewind.php
* @since 5.0.0
*/
public function rewind()
{
reset($this->collection);
}
public function clear()
{
$this->collection = [];
}
/**
* @return string
*/
public function __toString()
{
$formats = [];
foreach ($this->collection as $key => $value) {
$formats[] = (string) $value;
}
return implode("\n", $formats);
}
/**
* If clone extra fields.
*/

View File

@ -0,0 +1,137 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* A common base class for Unicode extra information extra fields.
*/
abstract class AbstractUnicodeExtraField implements ZipExtraField
{
const DEFAULT_VERSION = 0x01;
/** @var int */
private $crc32;
/** @var string */
private $unicodeValue;
/**
* @param int $crc32
* @param string $unicodeValue
*/
public function __construct($crc32, $unicodeValue)
{
$this->crc32 = (int) $crc32;
$this->unicodeValue = (string) $unicodeValue;
}
/**
* @param string $unicodeValue
*
* @return static
*/
public static function create($unicodeValue)
{
return new static(crc32($unicodeValue), $unicodeValue);
}
/**
* @return int the CRC32 checksum of the filename or comment as
* encoded in the central directory of the zip file
*/
public function getCrc32()
{
return $this->crc32;
}
/**
* @return string
*/
public function getUnicodeValue()
{
return $this->unicodeValue;
}
/**
* @param string $unicodeValue the UTF-8 encoded name to set
*/
public function setUnicodeValue($unicodeValue)
{
$this->unicodeValue = $unicodeValue;
$this->crc32 = crc32($unicodeValue);
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return static
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
if (\strlen($buffer) < 5) {
throw new ZipException('UniCode path extra data must have at least 5 bytes.');
}
$version = unpack('C', $buffer)[1];
if ($version !== self::DEFAULT_VERSION) {
throw new ZipException(sprintf('Unsupported version [%d] for UniCode path extra data.', $version));
}
$crc32 = unpack('V', substr($buffer, 1))[1];
$unicodeValue = substr($buffer, 5);
return new static($crc32, $unicodeValue);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return static
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
return pack(
'CV',
self::DEFAULT_VERSION,
$this->crc32
) .
$this->unicodeValue;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return $this->packLocalFileData();
}
}

View File

@ -0,0 +1,176 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* Apk Alignment Extra Field.
*
* @see https://android.googlesource.com/platform/tools/apksig/+/master/src/main/java/com/android/apksig/ApkSigner.java
* @see https://developer.android.com/studio/command-line/zipalign
*/
class ApkAlignmentExtraField implements ZipExtraField
{
/**
* @var int Extensible data block/field header ID used for storing
* information about alignment of uncompressed entries as
* well as for aligning the entries's data. See ZIP
* appnote.txt section 4.5 Extensible data fields.
*/
const HEADER_ID = 0xd935;
/**
* @var int minimum size (in bytes) of the extensible data block/field used
* for alignment of uncompressed entries
*/
const MIN_SIZE = 6;
/** @var int */
const ALIGNMENT_BYTES = 4;
/** @var int */
const COMMON_PAGE_ALIGNMENT_BYTES = 4096;
/** @var int */
private $multiple;
/** @var int */
private $padding;
/**
* @param int $multiple
* @param int $padding
*/
public function __construct($multiple, $padding)
{
$this->multiple = $multiple;
$this->padding = $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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* @return int
*/
public function getMultiple()
{
return $this->multiple;
}
/**
* @return int
*/
public function getPadding()
{
return $this->padding;
}
/**
* @param int $multiple
*/
public function setMultiple($multiple)
{
$this->multiple = (int) $multiple;
}
/**
* @param int $padding
*/
public function setPadding($padding)
{
$this->padding = (int) $padding;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return ApkAlignmentExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$length = \strlen($buffer);
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 ZipException(
'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.'
);
}
$multiple = unpack('v', $buffer)[1];
$padding = $length - 2;
return new self($multiple, $padding);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return ApkAlignmentExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
return pack('vx' . $this->padding, $this->multiple);
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return $this->packLocalFileData();
}
/**
* @return string
*/
public function __toString()
{
return sprintf(
'0x%04x APK Alignment: Multiple=%d Padding=%d',
self::HEADER_ID,
$this->multiple,
$this->padding
);
}
}

View File

@ -0,0 +1,300 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Constants\UnixStat;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* ASi Unix Extra Field:
* ====================.
*
* The following is the layout of the ASi extra block for Unix. The
* local-header and central-header versions are identical.
* (Last Revision 19960916)
*
* Value Size Description
* ----- ---- -----------
* (Unix3) 0x756e Short tag for this extra block type ("nu")
* TSize Short total data size for this block
* CRC Long CRC-32 of the remaining data
* Mode Short file permissions
* SizDev Long symlink'd size OR major/minor dev num
* UID Short user ID
* GID Short group ID
* (var.) variable symbolic link filename
*
* Mode is the standard Unix st_mode field from struct stat, containing
* user/group/other permissions, setuid/setgid and symlink info, etc.
*
* If Mode indicates that this file is a symbolic link, SizDev is the
* size of the file to which the link points. Otherwise, if the file
* is a device, SizDev contains the standard Unix st_rdev field from
* struct stat (includes the major and minor numbers of the device).
* SizDev is undefined in other cases.
*
* If Mode indicates that the file is a symbolic link, the final field
* will be the name of the file to which the link points. The file-
* name length can be inferred from TSize.
*
* [Note that TSize may incorrectly refer to the data size not counting
* the CRC; i.e., it may be four bytes too small.]
*
* @see ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip Info-ZIP version Specification
*/
class AsiExtraField implements ZipExtraField
{
/** @var int Header id */
const HEADER_ID = 0x756e;
const USER_GID_PID = 1000;
/** Bits used for permissions (and sticky bit). */
const PERM_MASK = 07777;
/** @var int Standard Unix stat(2) file mode. */
private $mode;
/** @var int User ID. */
private $uid;
/** @var int Group ID. */
private $gid;
/**
* @var string File this entry points to, if it is a symbolic link.
* Empty string - if entry is not a symbolic link.
*/
private $link;
/**
* AsiExtraField constructor.
*
* @param int $mode
* @param int $uid
* @param int $gid
* @param string $link
*/
public function __construct($mode, $uid = self::USER_GID_PID, $gid = self::USER_GID_PID, $link = '')
{
$this->mode = (int) $mode;
$this->uid = (int) $uid;
$this->gid = (int) $gid;
$this->link = (string) $link;
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws Crc32Exception
*
* @return static
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$givenChecksum = unpack('V', $buffer)[1];
$buffer = substr($buffer, 4);
$realChecksum = crc32($buffer);
if ($givenChecksum !== $realChecksum) {
throw new Crc32Exception('Asi Unix Extra Filed Data', $givenChecksum, $realChecksum);
}
$data = unpack('vmode/VlinkSize/vuid/vgid', $buffer);
$link = '';
if ($data['linkSize'] > 0) {
$link = substr($buffer, 8);
}
return new self($data['mode'], $data['uid'], $data['gid'], $link);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws Crc32Exception
*
* @return AsiExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
$data = pack(
'vVvv',
$this->mode,
\strlen($this->link),
$this->uid,
$this->gid
) . $this->link;
return pack('V', crc32($data)) . $data;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return $this->packLocalFileData();
}
/**
* Name of linked file.
*
* @return string name of the file this entry links to if it is a
* symbolic link, the empty string otherwise
*/
public function getLink()
{
return $this->link;
}
/**
* Indicate that this entry is a symbolic link to the given filename.
*
* @param string $link name of the file this entry links to, empty
* string if it is not a symbolic link
*/
public function setLink($link)
{
$this->link = $link;
$this->mode = $this->getPermissionsMode($this->mode);
}
/**
* Is this entry a symbolic link?
*
* @return bool true if this is a symbolic link
*/
public function isLink()
{
return !empty($this->link);
}
/**
* Get the file mode for given permissions with the correct file type.
*
* @param int $mode the mode
*
* @return int the type with the mode
*/
protected function getPermissionsMode($mode)
{
$type = UnixStat::UNX_IFMT;
if ($this->isLink()) {
$type = UnixStat::UNX_IFLNK;
} elseif ($this->isDirectory()) {
$type = UnixStat::UNX_IFDIR;
}
return $type | ($mode & self::PERM_MASK);
}
/**
* Is this entry a directory?
*
* @return bool true if this entry is a directory
*/
public function isDirectory()
{
return ($this->mode & UnixStat::UNX_IFDIR) !== 0 && !$this->isLink();
}
/**
* @return int
*/
public function getMode()
{
return $this->mode;
}
/**
* @param int $mode
*/
public function setMode($mode)
{
$this->mode = $this->getPermissionsMode($mode);
}
/**
* @return int
*/
public function getUserId()
{
return $this->uid;
}
/**
* @param int $uid
*/
public function setUserId($uid)
{
$this->uid = $uid;
}
/**
* @return int
*/
public function getGroupId()
{
return $this->gid;
}
/**
* @param int $gid
*/
public function setGroupId($gid)
{
$this->gid = $gid;
}
/**
* @return string
*/
public function __toString()
{
return sprintf(
'0x%04x ASI: Mode=%o UID=%d GID=%d Link="%s',
self::HEADER_ID,
$this->mode,
$this->uid,
$this->gid,
$this->link
);
}
}

View File

@ -0,0 +1,425 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* Extended Timestamp Extra Field:
* ==============================.
*
* The following is the layout of the extended-timestamp extra block.
* (Last Revision 19970118)
*
* Local-header version:
*
* Value Size Description
* ----- ---- -----------
* (time) 0x5455 Short tag for this extra block type ("UT")
* TSize Short total data size for this block
* Flags Byte info bits
* (ModTime) Long time of last modification (UTC/GMT)
* (AcTime) Long time of last access (UTC/GMT)
* (CrTime) Long time of original creation (UTC/GMT)
*
* Central-header version:
*
* Value Size Description
* ----- ---- -----------
* (time) 0x5455 Short tag for this extra block type ("UT")
* TSize Short total data size for this block
* Flags Byte info bits (refers to local header!)
* (ModTime) Long time of last modification (UTC/GMT)
*
* The central-header extra field contains the modification time only,
* or no timestamp at all. TSize is used to flag its presence or
* absence. But note:
*
* If "Flags" indicates that Modtime is present in the local header
* field, it MUST be present in the central header field, too!
* This correspondence is required because the modification time
* value may be used to support trans-timezone freshening and
* updating operations with zip archives.
*
* The time values are in standard Unix signed-long format, indicating
* the number of seconds since 1 January 1970 00:00:00. The times
* are relative to Coordinated Universal Time (UTC), also sometimes
* referred to as Greenwich Mean Time (GMT). To convert to local time,
* the software must know the local timezone offset from UTC/GMT.
*
* The lower three bits of Flags in both headers indicate which time-
* stamps are present in the LOCAL extra field:
*
* bit 0 if set, modification time is present
* bit 1 if set, access time is present
* bit 2 if set, creation time is present
* bits 3-7 reserved for additional timestamps; not set
*
* Those times that are present will appear in the order indicated, but
* any combination of times may be omitted. (Creation time may be
* present without access time, for example.) TSize should equal
* (1 + 4*(number of set bits in Flags)), as the block is currently
* defined. Other timestamps may be added in the future.
*
* @see ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip Info-ZIP version Specification
*/
class ExtendedTimestampExtraField implements ZipExtraField
{
/** @var int Header id */
const HEADER_ID = 0x5455;
/**
* @var int the bit set inside the flags by when the last modification time
* is present in this extra field
*/
const MODIFY_TIME_BIT = 1;
/**
* @var int the bit set inside the flags by when the last access time is
* present in this extra field
*/
const ACCESS_TIME_BIT = 2;
/**
* @var int the bit set inside the flags by when the original creation time
* is present in this extra field
*/
const CREATE_TIME_BIT = 4;
/**
* @var int The 3 boolean fields (below) come from this flags byte. The remaining 5 bits
* are ignored according to the current version of the spec (December 2012).
*/
private $flags;
/** @var int|null Modify time */
private $modifyTime;
/** @var int|null Access time */
private $accessTime;
/** @var int|null Create time */
private $createTime;
/**
* @param int $flags
* @param int|null $modifyTime
* @param int|null $accessTime
* @param int|null $createTime
*/
public function __construct($flags, $modifyTime, $accessTime, $createTime)
{
$this->flags = (int) $flags;
$this->modifyTime = $modifyTime;
$this->accessTime = $accessTime;
$this->createTime = $createTime;
}
/**
* @param int|null $modifyTime
* @param int|null $accessTime
* @param int|null $createTime
*
* @return ExtendedTimestampExtraField
*/
public static function create($modifyTime, $accessTime, $createTime)
{
$flags = 0;
if ($modifyTime !== null) {
$modifyTime = (int) $modifyTime;
$flags |= self::MODIFY_TIME_BIT;
}
if ($accessTime !== null) {
$accessTime = (int) $accessTime;
$flags |= self::ACCESS_TIME_BIT;
}
if ($createTime !== null) {
$createTime = (int) $createTime;
$flags |= self::CREATE_TIME_BIT;
}
return new self($flags, $modifyTime, $accessTime, $createTime);
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return ExtendedTimestampExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$length = \strlen($buffer);
$flags = unpack('C', $buffer)[1];
$offset = 1;
$modifyTime = null;
$accessTime = null;
$createTime = null;
if (($flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT) {
$modifyTime = unpack('V', substr($buffer, $offset, 4))[1];
$offset += 4;
}
// Notice the extra length check in case we are parsing the shorter
// central data field (for both access and create timestamps).
if ((($flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT) && $offset + 4 <= $length) {
$accessTime = unpack('V', substr($buffer, $offset, 4))[1];
$offset += 4;
}
if ((($flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT) && $offset + 4 <= $length) {
$createTime = unpack('V', substr($buffer, $offset, 4))[1];
}
return new self($flags, $modifyTime, $accessTime, $createTime);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return ExtendedTimestampExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
$data = '';
if (($this->flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT && $this->modifyTime !== null) {
$data .= pack('V', $this->modifyTime);
}
if (($this->flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT && $this->accessTime !== null) {
$data .= pack('V', $this->accessTime);
}
if (($this->flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT && $this->createTime !== null) {
$data .= pack('V', $this->createTime);
}
return pack('C', $this->flags) . $data;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* Note: even if bit1 and bit2 are set, the Central data will still
* not contain access/create fields: only local data ever holds those!
*
* @return string the data
*/
public function packCentralDirData()
{
$cdLength = 1 + ($this->modifyTime !== null ? 4 : 0);
return substr($this->packLocalFileData(), 0, $cdLength);
}
/**
* Gets flags byte.
*
* The flags byte tells us which of the three datestamp fields are
* present in the data:
* bit0 - modify time
* bit1 - access time
* bit2 - create time
*
* Only first 3 bits of flags are used according to the
* latest version of the spec (December 2012).
*
* @return int flags byte indicating which of the
* three datestamp fields are present
*/
public function getFlags()
{
return $this->flags;
}
/**
* Returns the modify time (seconds since epoch) of this zip entry,
* or null if no such timestamp exists in the zip entry.
*
* @return int|null modify time (seconds since epoch) or null
*/
public function getModifyTime()
{
return $this->modifyTime;
}
/**
* Returns the access time (seconds since epoch) of this zip entry,
* or null if no such timestamp exists in the zip entry.
*
* @return int|null access time (seconds since epoch) or null
*/
public function getAccessTime()
{
return $this->accessTime;
}
/**
* Returns the create time (seconds since epoch) of this zip entry,
* or null if no such timestamp exists in the zip entry.
*
* Note: modern linux file systems (e.g., ext2)
* do not appear to store a "create time" value, and so
* it's usually omitted altogether in the zip extra
* field. Perhaps other unix systems track this.
*
* @return int|null create time (seconds since epoch) or null
*/
public function getCreateTime()
{
return $this->createTime;
}
/**
* Returns the modify time as a \DateTimeInterface
* of this zip entry, or null if no such timestamp exists in the zip entry.
* The milliseconds are always zeroed out, since the underlying data
* offers only per-second precision.
*
* @return \DateTimeInterface|null modify time as \DateTimeInterface or null
*/
public function getModifyDateTime()
{
return self::timestampToDateTime($this->modifyTime);
}
/**
* Returns the access time as a \DateTimeInterface
* of this zip entry, or null if no such timestamp exists in the zip entry.
* The milliseconds are always zeroed out, since the underlying data
* offers only per-second precision.
*
* @return \DateTimeInterface|null access time as \DateTimeInterface or null
*/
public function getAccessDateTime()
{
return self::timestampToDateTime($this->accessTime);
}
/**
* Returns the create time as a a \DateTimeInterface
* of this zip entry, or null if no such timestamp exists in the zip entry.
* The milliseconds are always zeroed out, since the underlying data
* offers only per-second precision.
*
* Note: modern linux file systems (e.g., ext2)
* do not appear to store a "create time" value, and so
* it's usually omitted altogether in the zip extra
* field. Perhaps other unix systems track $this->.
*
* @return \DateTimeInterface|null create time as \DateTimeInterface or null
*/
public function getCreateDateTime()
{
return self::timestampToDateTime($this->createTime);
}
/**
* Sets the modify time (seconds since epoch) of this zip entry
* using a integer.
*
* @param int|null $unixTime unix time of the modify time (seconds per epoch) or null
*/
public function setModifyTime($unixTime)
{
$this->modifyTime = $unixTime;
}
/**
* Sets the access time (seconds since epoch) of this zip entry
* using a integer.
*
* @param int|null $unixTime Unix time of the access time (seconds per epoch) or null
*/
public function setAccessTime($unixTime)
{
$this->accessTime = $unixTime;
}
/**
* Sets the create time (seconds since epoch) of this zip entry
* using a integer.
*
* @param int|null $unixTime Unix time of the create time (seconds per epoch) or null
*/
public function setCreateTime($unixTime)
{
$this->createTime = $unixTime;
}
/**
* @param int|null $timestamp
*
* @return \DateTimeInterface|null
*/
private static function timestampToDateTime($timestamp)
{
try {
return $timestamp !== null ? new \DateTimeImmutable('@' . $timestamp) : null;
} catch (\Exception $e) {
return null;
}
}
/**
* @return string
*/
public function __toString()
{
$args = [self::HEADER_ID];
$format = '0x%04x ExtendedTimestamp:';
if ($this->modifyTime !== null) {
$format .= ' Modify:[%s]';
$args[] = date(\DATE_W3C, $this->modifyTime);
}
if ($this->accessTime !== null) {
$format .= ' Access:[%s]';
$args[] = date(\DATE_W3C, $this->accessTime);
}
if ($this->createTime !== null) {
$format .= ' Create:[%s]';
$args[] = date(\DATE_W3C, $this->createTime);
}
return vsprintf($format, $args);
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipContainer;
use PhpZip\Model\ZipEntry;
/**
* 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.
* If this extra field is added as the very first extra field of
* the archive, Solaris will consider it an executable jar file.
*
* @license MIT
*/
class JarMarkerExtraField implements ZipExtraField
{
/** @var int Header id. */
const HEADER_ID = 0xCAFE;
/**
* @param ZipContainer $container
*/
public static function setJarMarker(ZipContainer $container)
{
$zipEntries = $container->getEntries();
if (!empty($zipEntries)) {
foreach ($zipEntries as $zipEntry) {
$zipEntry->removeExtraField(self::HEADER_ID);
}
// set jar execute bit
reset($zipEntries);
$zipEntry = current($zipEntries);
$zipEntry->getCdExtraFields()[] = new self();
}
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
return '';
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return '';
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return JarMarkerExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
if (!empty($buffer)) {
throw new ZipException("JarMarker doesn't expect any data");
}
return new self();
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return JarMarkerExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* @return string
*/
public function __toString()
{
return sprintf('0x%04x Jar Marker', self::HEADER_ID);
}
}

View File

@ -0,0 +1,248 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* Info-ZIP New Unix Extra Field:
* ====================================.
*
* Currently stores Unix UIDs/GIDs up to 32 bits.
* (Last Revision 20080509)
*
* Value Size Description
* ----- ---- -----------
* (UnixN) 0x7875 Short tag for this extra block type ("ux")
* TSize Short total data size for this block
* Version 1 byte version of this extra field, currently 1
* UIDSize 1 byte Size of UID field
* UID Variable UID for this entry
* GIDSize 1 byte Size of GID field
* GID Variable GID for this entry
*
* Currently Version is set to the number 1. If there is a need
* to change this field, the version will be incremented. Changes
* may not be backward compatible so this extra field should not be
* used if the version is not recognized.
*
* UIDSize is the size of the UID field in bytes. This size should
* match the size of the UID field on the target OS.
*
* UID is the UID for this entry in standard little endian format.
*
* GIDSize is the size of the GID field in bytes. This size should
* match the size of the GID field on the target OS.
*
* GID is the GID for this entry in standard little endian format.
*
* If both the old 16-bit Unix extra field (tag 0x7855, Info-ZIP Unix)
* and this extra field are present, the values in this extra field
* supercede the values in that extra field.
*/
class NewUnixExtraField implements ZipExtraField
{
/** @var int header id */
const HEADER_ID = 0x7875;
/** ID of the first non-root user created on a unix system. */
const USER_GID_PID = 1000;
/** @var int version of this extra field, currently 1 */
private $version = 1;
/** @var int User id */
private $uid;
/** @var int Group id */
private $gid;
/**
* NewUnixExtraField constructor.
*
* @param int $version
* @param int $uid
* @param int $gid
*/
public function __construct($version = 1, $uid = self::USER_GID_PID, $gid = self::USER_GID_PID)
{
$this->version = (int) $version;
$this->uid = (int) $uid;
$this->gid = (int) $gid;
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return NewUnixExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$length = \strlen($buffer);
if ($length < 3) {
throw new ZipException(sprintf('X7875_NewUnix length is too short, only %s bytes', $length));
}
$offset = 0;
$data = unpack('Cversion/CuidSize', $buffer);
$offset += 2;
$uidSize = $data['uidSize'];
$gid = self::readSizeIntegerLE(substr($buffer, $offset, $uidSize), $uidSize);
$offset += $uidSize;
$gidSize = unpack('C', $buffer[$offset])[1];
$offset++;
$uid = self::readSizeIntegerLE(substr($buffer, $offset, $gidSize), $gidSize);
return new self($data['version'], $gid, $uid);
}
/**
* Converts a signed byte into an unsigned integer representation
* (e.g., -1 becomes 255).
*
* @param int $b byte to convert to int
*
* @return int representation of the provided byte
*
* @since 1.5
*/
public static function signedByteToUnsignedInt($b)
{
if ($b >= 0) {
return $b;
}
return 256 + $b;
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return NewUnixExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
return pack(
'CCVCV',
$this->version,
4, // GIDSize
$this->gid,
4, // UIDSize
$this->uid
);
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return $this->packLocalFileData();
}
/**
* @param string $data
* @param int $size
*
* @throws ZipException
*
* @return int
*/
private static function readSizeIntegerLE($data, $size)
{
$format = [
1 => 'C', // unsigned byte
2 => 'v', // unsigned short LE
4 => 'V', // unsigned int LE
];
if (!isset($format[$size])) {
throw new ZipException(sprintf('Invalid size bytes: %d', $size));
}
return unpack($format[$size], $data)[1];
}
/**
* @return int
*/
public function getUid()
{
return $this->uid;
}
/**
* @param int $uid
*/
public function setUid($uid)
{
$this->uid = $uid & 0xffffffff;
}
/**
* @return int
*/
public function getGid()
{
return $this->gid;
}
/**
* @param int $gid
*/
public function setGid($gid)
{
$this->gid = $gid & 0xffffffff;
}
/**
* @return string
*/
public function __toString()
{
return sprintf(
'0x%04x NewUnix: UID=%d GID=%d',
self::HEADER_ID,
$this->uid,
$this->gid
);
}
}

View File

@ -0,0 +1,258 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
/**
* NTFS Extra Field.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @license MIT
*/
class NtfsExtraField implements ZipExtraField
{
/** @var int Header id */
const HEADER_ID = 0x000a;
/** @var int Tag ID */
const TIME_ATTR_TAG = 0x0001;
/** @var int Attribute size */
const TIME_ATTR_SIZE = 24; // 3 * 8
/**
* @var int A file time is a 64-bit value that represents the number of
* 100-nanosecond intervals that have elapsed since 12:00
* A.M. January 1, 1601 Coordinated Universal Time (UTC).
* this is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
*/
const EPOCH_OFFSET = -11644473600;
/** @var int Modify ntfs time */
private $modifyTime;
/** @var int Access ntfs time */
private $accessTime;
/** @var int Create ntfs time */
private $createTime;
/**
* @param int $modifyTime
* @param int $accessTime
* @param int $createTime
*/
public function __construct($modifyTime, $accessTime, $createTime)
{
$this->modifyTime = (int) $modifyTime;
$this->accessTime = (int) $accessTime;
$this->createTime = (int) $createTime;
}
/**
* @param \DateTimeInterface $mtime
* @param \DateTimeInterface $atime
* @param \DateTimeInterface $ctime
*
* @return NtfsExtraField
*/
public static function create(\DateTimeInterface $mtime, \DateTimeInterface $atime, \DateTimeInterface $ctime)
{
return new self(
self::dateTimeToNtfsTime($mtime),
self::dateTimeToNtfsTime($atime),
self::dateTimeToNtfsTime($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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return NtfsExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$buffer = substr($buffer, 4);
$modifyTime = 0;
$accessTime = 0;
$createTime = 0;
while ($buffer || $buffer !== '') {
$unpack = unpack('vtag/vsizeAttr', $buffer);
if ($unpack['tag'] === self::TIME_ATTR_TAG && $unpack['sizeAttr'] === self::TIME_ATTR_SIZE) {
// refactoring will be needed when php 5.5 support ends
$modifyTime = PackUtil::unpackLongLE(substr($buffer, 4, 8));
$accessTime = PackUtil::unpackLongLE(substr($buffer, 12, 8));
$createTime = PackUtil::unpackLongLE(substr($buffer, 20, 8));
break;
}
$buffer = substr($buffer, 4 + $unpack['sizeAttr']);
}
return new self($modifyTime, $accessTime, $createTime);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return NtfsExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
$data = pack('Vvv', 0, self::TIME_ATTR_TAG, self::TIME_ATTR_SIZE);
// refactoring will be needed when php 5.5 support ends
$data .= PackUtil::packLongLE($this->modifyTime);
$data .= PackUtil::packLongLE($this->accessTime);
$data .= PackUtil::packLongLE($this->createTime);
return $data;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return $this->packLocalFileData();
}
/**
* @return \DateTimeInterface
*/
public function getModifyDateTime()
{
return self::ntfsTimeToDateTime($this->modifyTime);
}
/**
* @param \DateTimeInterface $modifyTime
*/
public function setModifyDateTime(\DateTimeInterface $modifyTime)
{
$this->modifyTime = self::dateTimeToNtfsTime($modifyTime);
}
/**
* @return \DateTimeInterface
*/
public function getAccessDateTime()
{
return self::ntfsTimeToDateTime($this->accessTime);
}
/**
* @param \DateTimeInterface $accessTime
*/
public function setAccessDateTime(\DateTimeInterface $accessTime)
{
$this->accessTime = self::dateTimeToNtfsTime($accessTime);
}
/**
* @return \DateTimeInterface
*/
public function getCreateDateTime()
{
return self::ntfsTimeToDateTime($this->createTime);
}
/**
* @param \DateTimeInterface $createTime
*/
public function setCreateDateTime(\DateTimeInterface $createTime)
{
$this->createTime = self::dateTimeToNtfsTime($createTime);
}
/**
* @param \DateTimeInterface $dateTime
*
* @return int
*/
public static function dateTimeToNtfsTime(\DateTimeInterface $dateTime)
{
return $dateTime->getTimestamp() * 10000000 + self::EPOCH_OFFSET;
}
/**
* @param int $time
*
* @return \DateTimeInterface
*/
public static function ntfsTimeToDateTime($time)
{
$timestamp = (int) ($time / 10000000 + self::EPOCH_OFFSET);
try {
return new \DateTimeImmutable('@' . $timestamp);
} catch (\Exception $e) {
throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp, 1, $e);
}
}
/**
* @return string
*/
public function __toString()
{
$args = [self::HEADER_ID];
$format = '0x%04x NtfsExtra:';
if ($this->modifyTime !== 0) {
$format .= ' Modify:[%s]';
$args[] = $this->getModifyDateTime()->format(\DATE_ATOM);
}
if ($this->accessTime !== 0) {
$format .= ' Access:[%s]';
$args[] = $this->getAccessDateTime()->format(\DATE_ATOM);
}
if ($this->createTime !== 0) {
$format .= ' Create:[%s]';
$args[] = $this->getCreateDateTime()->format(\DATE_ATOM);
}
return vsprintf($format, $args);
}
}

View File

@ -0,0 +1,327 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* Info-ZIP Unix Extra Field (type 1):
* ==================================.
*
* The following is the layout of the old Info-ZIP extra block for
* Unix. It has been replaced by the extended-timestamp extra block
* (0x5455) and the Unix type 2 extra block (0x7855).
* (Last Revision 19970118)
*
* Local-header version:
*
* Value Size Description
* ----- ---- -----------
* (Unix1) 0x5855 Short tag for this extra block type ("UX")
* TSize Short total data size for this block
* AcTime Long time of last access (UTC/GMT)
* ModTime Long time of last modification (UTC/GMT)
* UID Short Unix user ID (optional)
* GID Short Unix group ID (optional)
*
* Central-header version:
*
* Value Size Description
* ----- ---- -----------
* (Unix1) 0x5855 Short tag for this extra block type ("UX")
* TSize Short total data size for this block
* AcTime Long time of last access (GMT/UTC)
* ModTime Long time of last modification (GMT/UTC)
*
* The file access and modification times are in standard Unix signed-
* long format, indicating the number of seconds since 1 January 1970
* 00:00:00. The times are relative to Coordinated Universal Time
* (UTC), also sometimes referred to as Greenwich Mean Time (GMT). To
* convert to local time, the software must know the local timezone
* offset from UTC/GMT. The modification time may be used by non-Unix
* systems to support inter-timezone freshening and updating of zip
* archives.
*
* The local-header extra block may optionally contain UID and GID
* info for the file. The local-header TSize value is the only
* indication of this. Note that Unix UIDs and GIDs are usually
* specific to a particular machine, and they generally require root
* access to restore.
*
* This extra field type is obsolete, but it has been in use since
* mid-1994. Therefore future archiving software should continue to
* support it.
*/
class OldUnixExtraField implements ZipExtraField
{
/** @var int Header id */
const HEADER_ID = 0x5855;
/** @var int|null Access timestamp */
private $accessTime;
/** @var int|null Modify timestamp */
private $modifyTime;
/** @var int|null User id */
private $uid;
/** @var int|null Group id */
private $gid;
/**
* @param int|null $accessTime
* @param int|null $modifyTime
* @param int|null $uid
* @param int|null $gid
*/
public function __construct($accessTime, $modifyTime, $uid, $gid)
{
$this->accessTime = $accessTime;
$this->modifyTime = $modifyTime;
$this->uid = $uid;
$this->gid = $gid;
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return OldUnixExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$length = \strlen($buffer);
$accessTime = $modifyTime = $uid = $gid = null;
if ($length >= 4) {
$accessTime = unpack('V', $buffer)[1];
}
if ($length >= 8) {
$modifyTime = unpack('V', substr($buffer, 4, 4))[1];
}
if ($length >= 10) {
$uid = unpack('v', substr($buffer, 8, 2))[1];
}
if ($length >= 12) {
$gid = unpack('v', substr($buffer, 10, 2))[1];
}
return new self($accessTime, $modifyTime, $uid, $gid);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return OldUnixExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
$length = \strlen($buffer);
$accessTime = $modifyTime = null;
if ($length >= 4) {
$accessTime = unpack('V', $buffer)[1];
}
if ($length >= 8) {
$modifyTime = unpack('V', substr($buffer, 4, 4))[1];
}
return new self($accessTime, $modifyTime, null, null);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
$data = '';
if ($this->accessTime !== null) {
$data .= pack('V', $this->accessTime);
if ($this->modifyTime !== null) {
$data .= pack('V', $this->modifyTime);
if ($this->uid !== null) {
$data .= pack('v', $this->uid);
if ($this->gid !== null) {
$data .= pack('v', $this->gid);
}
}
}
}
return $data;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
$data = '';
if ($this->accessTime !== null) {
$data .= pack('V', $this->accessTime);
if ($this->modifyTime !== null) {
$data .= pack('V', $this->modifyTime);
}
}
return $data;
}
/**
* @return int|null
*/
public function getAccessTime()
{
return $this->accessTime;
}
/**
* @param int|null $accessTime
*/
public function setAccessTime($accessTime)
{
$this->accessTime = $accessTime;
}
/**
* @return \DateTimeInterface|null
*/
public function getAccessDateTime()
{
try {
return $this->accessTime === null ? null :
new \DateTimeImmutable('@' . $this->accessTime);
} catch (\Exception $e) {
return null;
}
}
/**
* @return int|null
*/
public function getModifyTime()
{
return $this->modifyTime;
}
/**
* @param int|null $modifyTime
*/
public function setModifyTime($modifyTime)
{
$this->modifyTime = $modifyTime;
}
/**
* @return \DateTimeInterface|null
*/
public function getModifyDateTime()
{
try {
return $this->modifyTime === null ? null :
new \DateTimeImmutable('@' . $this->modifyTime);
} catch (\Exception $e) {
return null;
}
}
/**
* @return int|null
*/
public function getUid()
{
return $this->uid;
}
/**
* @param int|null $uid
*/
public function setUid($uid)
{
$this->uid = $uid;
}
/**
* @return int|null
*/
public function getGid()
{
return $this->gid;
}
/**
* @param int|null $gid
*/
public function setGid($gid)
{
$this->gid = $gid;
}
/**
* @return string
*/
public function __toString()
{
$args = [self::HEADER_ID];
$format = '0x%04x OldUnix:';
if (($modifyTime = $this->getModifyDateTime()) !== null) {
$format .= ' Modify:[%s]';
$args[] = $modifyTime->format(\DATE_ATOM);
}
if (($accessTime = $this->getAccessDateTime()) !== null) {
$format .= ' Access:[%s]';
$args[] = $accessTime->format(\DATE_ATOM);
}
if ($this->uid !== null) {
$format .= ' UID=%d';
$args[] = $this->uid;
}
if ($this->gid !== null) {
$format .= ' GID=%d';
$args[] = $this->gid;
}
return vsprintf($format, $args);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace PhpZip\Model\Extra\Fields;
/**
* Info-ZIP Unicode Comment Extra Field (0x6375):.
*
* Stores the UTF-8 version of the file comment as stored in the
* central directory header. (Last Revision 20070912)
*
* Value Size Description
* ----- ---- -----------
* (UCom) 0x6375 Short tag for this extra block type ("uc")
* TSize Short total data size for this block
* Version 1 byte version of this extra field, currently 1
* ComCRC32 4 bytes Comment Field CRC32 Checksum
* UnicodeCom Variable UTF-8 version of the entry comment
*
* Currently Version is set to the number 1. If there is a need
* to change this field, the version will be incremented. Changes
* may not be backward compatible so this extra field should not be
* used if the version is not recognized.
*
* The ComCRC32 is the standard zip CRC32 checksum of the File Comment
* field in the central directory header. This is used to verify that
* the comment field has not changed since the Unicode Comment extra field
* was created. This can happen if a utility changes the File Comment
* field but does not update the UTF-8 Comment extra field. If the CRC
* check fails, this Unicode Comment extra field should be ignored and
* the File Comment field in the header should be used instead.
*
* The UnicodeCom field is the UTF-8 version of the File Comment field
* in the header. As UnicodeCom is defined to be UTF-8, no UTF-8 byte
* order mark (BOM) is used. The length of this field is determined by
* subtracting the size of the previous fields from TSize. If both the
* File Name and Comment fields are UTF-8, the new General Purpose Bit
* Flag, bit 11 (Language encoding flag (EFS)), can be used to indicate
* both the header File Name and Comment fields are UTF-8 and, in this
* case, the Unicode Path and Unicode Comment extra fields are not
* needed and should not be created. Note that, for backward
* compatibility, bit 11 should only be used if the native character set
* of the paths and comments being zipped up are already in UTF-8. It is
* expected that the same file comment storage method, either general
* purpose bit 11 or extra fields, be used in both the Local and Central
* Directory Header for a file.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.6.8
*/
class UnicodeCommentExtraField extends AbstractUnicodeExtraField
{
const HEADER_ID = 0x6375;
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* @return string
*/
public function __toString()
{
return sprintf(
'0x%04x UnicodeComment: "%s"',
self::HEADER_ID,
$this->getUnicodeValue()
);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace PhpZip\Model\Extra\Fields;
/**
* Info-ZIP Unicode Path Extra Field (0x7075):
* ==========================================.
*
* Stores the UTF-8 version of the file name field as stored in the
* local header and central directory header. (Last Revision 20070912)
*
* Value Size Description
* ----- ---- -----------
* (UPath) 0x7075 Short tag for this extra block type ("up")
* TSize Short total data size for this block
* Version 1 byte version of this extra field, currently 1
* NameCRC32 4 bytes File Name Field CRC32 Checksum
* UnicodeName Variable UTF-8 version of the entry File Name
*
* Currently Version is set to the number 1. If there is a need
* to change this field, the version will be incremented. Changes
* may not be backward compatible so this extra field should not be
* used if the version is not recognized.
*
* The NameCRC32 is the standard zip CRC32 checksum of the File Name
* field in the header. This is used to verify that the header
* File Name field has not changed since the Unicode Path extra field
* was created. This can happen if a utility renames the File Name but
* does not update the UTF-8 path extra field. If the CRC check fails,
* this UTF-8 Path Extra Field should be ignored and the File Name field
* in the header should be used instead.
*
* The UnicodeName is the UTF-8 version of the contents of the File Name
* field in the header. As UnicodeName is defined to be UTF-8, no UTF-8
* byte order mark (BOM) is used. The length of this field is determined
* by subtracting the size of the previous fields from TSize. If both
* the File Name and Comment fields are UTF-8, the new General Purpose
* Bit Flag, bit 11 (Language encoding flag (EFS)), can be used to
* indicate that both the header File Name and Comment fields are UTF-8
* and, in this case, the Unicode Path and Unicode Comment extra fields
* are not needed and should not be created. Note that, for backward
* compatibility, bit 11 should only be used if the native character set
* of the paths and comments being zipped up are already in UTF-8. It is
* expected that the same file name storage method, either general
* purpose bit 11 or extra fields, be used in both the Local and Central
* Directory Header for a file.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.6.9
*/
class UnicodePathExtraField extends AbstractUnicodeExtraField
{
const HEADER_ID = 0x7075;
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* @return string
*/
public function __toString()
{
return sprintf(
'0x%04x UnicodePath: "%s"',
self::HEADER_ID,
$this->getUnicodeValue()
);
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* Simple placeholder for all those extra fields we don't want to deal with.
*/
class UnrecognizedExtraField implements ZipExtraField
{
/** @var int */
private $headerId;
/** @var string extra field data without Header-ID or length specifier */
private $data;
/**
* UnrecognizedExtraField constructor.
*
* @param int $headerId
* @param string $data
*/
public function __construct($headerId, $data)
{
$this->headerId = (int) $headerId;
$this->data = (string) $data;
}
/**
* @param int $headerId
*/
public function setHeaderId($headerId)
{
$this->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 function getHeaderId()
{
return $this->headerId;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
throw new \RuntimeException('Unsupport parse');
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
throw new \RuntimeException('Unsupport parse');
}
/**
* {@inheritdoc}
*/
public function packLocalFileData()
{
return $this->data;
}
/**
* {@inheritdoc}
*/
public function packCentralDirData()
{
return $this->data;
}
/**
* @return string
*/
public function getData()
{
return $this->data;
}
/**
* @param string $data
*/
public function setData($data)
{
$this->data = (string) $data;
}
/**
* @return string
*/
public function __toString()
{
$args = [$this->headerId, $this->data];
$format = '0x%04x Unrecognized Extra Field: "%s"';
return vsprintf($format, $args);
}
}

View File

@ -0,0 +1,387 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipUnsupportMethodException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
/**
* WinZip AES Extra Field.
*
* @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers
*/
class WinZipAesExtraField implements ZipExtraField
{
/** @var int Header id */
const HEADER_ID = 0x9901;
/**
* @var int Data size (currently 7, but subject to possible increase
* in the future)
*/
const DATA_SIZE = 7;
/**
* @var int The vendor ID field should always be set to the two ASCII
* characters "AE"
*/
const VENDOR_ID = 0x4541; // 'A' | ('E' << 8)
/**
* @var int Entries of this type do include the standard ZIP CRC-32 value.
* For use with {@see WinZipAesExtraField::setVendorVersion()}.
*/
const VERSION_AE1 = 1;
/**
* @var int Entries of this type do not include the standard ZIP CRC-32 value.
* For use with {@see WinZipAesExtraField::setVendorVersion().
*/
const VERSION_AE2 = 2;
/** @var int integer mode value indicating AES encryption 128-bit strength */
const KEY_STRENGTH_128BIT = 0x01;
/** @var int integer mode value indicating AES encryption 192-bit strength */
const KEY_STRENGTH_192BIT = 0x02;
/** @var int integer mode value indicating AES encryption 256-bit strength */
const KEY_STRENGTH_256BIT = 0x03;
/** @var int[] */
private static $allowVendorVersions = [
self::VERSION_AE1,
self::VERSION_AE2,
];
/** @var array<int, int> */
private static $encryptionStrengths = [
self::KEY_STRENGTH_128BIT => 128,
self::KEY_STRENGTH_192BIT => 192,
self::KEY_STRENGTH_256BIT => 256,
];
/** @var array<int, int> */
private static $MAP_KEY_STRENGTH_METHODS = [
self::KEY_STRENGTH_128BIT => ZipEncryptionMethod::WINZIP_AES_128,
self::KEY_STRENGTH_192BIT => ZipEncryptionMethod::WINZIP_AES_192,
self::KEY_STRENGTH_256BIT => ZipEncryptionMethod::WINZIP_AES_256,
];
/** @var int Integer version number specific to the zip vendor */
private $vendorVersion = self::VERSION_AE1;
/** @var int Integer mode value indicating AES encryption strength */
private $keyStrength = self::KEY_STRENGTH_256BIT;
/** @var int The actual compression method used to compress the file */
private $compressionMethod;
/**
* @param int $vendorVersion Integer version number specific to the zip vendor
* @param int $keyStrength Integer mode value indicating AES encryption strength
* @param int $compressionMethod The actual compression method used to compress the file
*
* @throws ZipUnsupportMethodException
*/
public function __construct($vendorVersion, $keyStrength, $compressionMethod)
{
$this->setVendorVersion($vendorVersion);
$this->setKeyStrength($keyStrength);
$this->setCompressionMethod($compressionMethod);
}
/**
* @param ZipEntry $entry
*
* @throws ZipUnsupportMethodException
*
* @return WinZipAesExtraField
*/
public static function create(ZipEntry $entry)
{
$keyStrength = array_search($entry->getEncryptionMethod(), self::$MAP_KEY_STRENGTH_METHODS, true);
if ($keyStrength === false) {
throw new InvalidArgumentException('Not support encryption method ' . $entry->getEncryptionMethod());
}
// WinZip 11 will continue to use AE-2, with no CRC, for very small files
// of less than 20 bytes. It will also use AE-2 for files compressed in
// BZIP2 format, because this format has internal integrity checks
// equivalent to a CRC check built in.
//
// https://www.winzip.com/win/en/aes_info.html
$vendorVersion = (
$entry->getUncompressedSize() < 20 ||
$entry->getCompressionMethod() === ZipCompressionMethod::BZIP2
) ?
self::VERSION_AE2 :
self::VERSION_AE1;
$field = new self($vendorVersion, $keyStrength, $entry->getCompressionMethod());
$entry->getLocalExtraFields()->add($field);
$entry->getCdExtraFields()->add($field);
return $field;
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return WinZipAesExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$size = \strlen($buffer);
if ($size !== self::DATA_SIZE) {
throw new ZipException(
sprintf(
'WinZip AES Extra data invalid size: %d. Must be %d',
$size,
self::DATA_SIZE
)
);
}
$data = unpack('vvendorVersion/vvendorId/ckeyStrength/vcompressionMethod', $buffer);
if ($data['vendorId'] !== self::VENDOR_ID) {
throw new ZipException(
sprintf(
'Vendor id invalid: %d. Must be %d',
$data['vendorId'],
self::VENDOR_ID
)
);
}
return new self(
$data['vendorVersion'],
$data['keyStrength'],
$data['compressionMethod']
);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return WinZipAesExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
return self::unpackLocalFileData($buffer, $entry);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
return pack(
'vvcv',
$this->vendorVersion,
self::VENDOR_ID,
$this->keyStrength,
$this->compressionMethod
);
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
return $this->packLocalFileData();
}
/**
* Returns the vendor version.
*
* @return int
*
* @see WinZipAesExtraField::VERSION_AE2
* @see WinZipAesExtraField::VERSION_AE1
*/
public function getVendorVersion()
{
return $this->vendorVersion;
}
/**
* Sets the vendor version.
*
* @param int $vendorVersion the vendor version
*
* @see WinZipAesExtraField::VERSION_AE2
* @see WinZipAesExtraField::VERSION_AE1
*/
public function setVendorVersion($vendorVersion)
{
$vendorVersion = (int) $vendorVersion;
if (!\in_array($vendorVersion, self::$allowVendorVersions, true)) {
throw new \InvalidArgumentException(
sprintf(
'Unsupport WinZip AES vendor version: %d',
$vendorVersion
)
);
}
$this->vendorVersion = $vendorVersion;
}
/**
* Returns vendor id.
*
* @return int
*/
public function getVendorId()
{
return self::VENDOR_ID;
}
/**
* @return int
*/
public function getKeyStrength()
{
return $this->keyStrength;
}
/**
* Set key strength.
*
* @param int $keyStrength
*/
public function setKeyStrength($keyStrength)
{
$keyStrength = (int) $keyStrength;
if (!isset(self::$encryptionStrengths[$keyStrength])) {
throw new \InvalidArgumentException(
sprintf(
'Key strength %d not support value. Allow values: %s',
$keyStrength,
implode(', ', array_keys(self::$encryptionStrengths))
)
);
}
$this->keyStrength = $keyStrength;
}
/**
* @return int
*/
public function getCompressionMethod()
{
return $this->compressionMethod;
}
/**
* @param int $compressionMethod
*
* @throws ZipUnsupportMethodException
*/
public function setCompressionMethod($compressionMethod)
{
$compressionMethod = (int) $compressionMethod;
ZipCompressionMethod::checkSupport($compressionMethod);
$this->compressionMethod = $compressionMethod;
}
/**
* @return int
*/
public function getEncryptionStrength()
{
return self::$encryptionStrengths[$this->keyStrength];
}
/**
* @return int
*/
public function getEncryptionMethod()
{
$keyStrength = $this->getKeyStrength();
if (!isset(self::$MAP_KEY_STRENGTH_METHODS[$keyStrength])) {
throw new InvalidArgumentException('Invalid encryption method');
}
return self::$MAP_KEY_STRENGTH_METHODS[$keyStrength];
}
/**
* @return bool
*/
public function isV1()
{
return $this->vendorVersion === self::VERSION_AE1;
}
/**
* @return bool
*/
public function isV2()
{
return $this->vendorVersion === self::VERSION_AE2;
}
/**
* @return int
*/
public function getSaltSize()
{
return (int) ($this->getEncryptionStrength() / 8 / 2);
}
/**
* @return string
*/
public function __toString()
{
return sprintf(
'0x%04x WINZIP AES: VendorVersion=%d KeyStrength=0x%02x CompressionMethod=%s',
__CLASS__,
$this->vendorVersion,
$this->keyStrength,
$this->compressionMethod
);
}
}

View File

@ -0,0 +1,311 @@
<?php
namespace PhpZip\Model\Extra\Fields;
use PhpZip\Constants\ZipConstants;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipException;
use PhpZip\Model\Extra\ZipExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
/**
* ZIP64 Extra Field.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*/
class Zip64ExtraField implements ZipExtraField
{
/** @var int The Header ID for a ZIP64 Extended Information Extra Field. */
const HEADER_ID = 0x0001;
/** @var int|null */
private $uncompressedSize;
/** @var int|null */
private $compressedSize;
/** @var int|null */
private $localHeaderOffset;
/** @var int|null */
private $diskStart;
/**
* Zip64ExtraField constructor.
*
* @param int|null $uncompressedSize
* @param int|null $compressedSize
* @param int|null $localHeaderOffset
* @param int|null $diskStart
*/
public function __construct(
$uncompressedSize = null,
$compressedSize = null,
$localHeaderOffset = null,
$diskStart = null
) {
$this->uncompressedSize = $uncompressedSize;
$this->compressedSize = $compressedSize;
$this->localHeaderOffset = $localHeaderOffset;
$this->diskStart = $diskStart;
}
/**
* 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 function getHeaderId()
{
return self::HEADER_ID;
}
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException on error
*
* @return Zip64ExtraField
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
{
$length = \strlen($buffer);
if ($length === 0) {
// no local file data at all, may happen if an archive
// only holds a ZIP64 extended information extra field
// inside the central directory but not inside the local
// file header
return new self();
}
if ($length < 16) {
throw new ZipException(
'Zip64 extended information must contain both size values in the local file header.'
);
}
$uncompressedSize = PackUtil::unpackLongLE(substr($buffer, 0, 8));
$compressedSize = PackUtil::unpackLongLE(substr($buffer, 8, 8));
return new self($uncompressedSize, $compressedSize);
}
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @throws ZipException
*
* @return Zip64ExtraField
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
{
if ($entry === null) {
throw new RuntimeException('zipEntry is null');
}
$length = \strlen($buffer);
$remaining = $length;
$uncompressedSize = null;
$compressedSize = null;
$localHeaderOffset = null;
$diskStart = null;
if ($entry->getUncompressedSize() === ZipConstants::ZIP64_MAGIC) {
if ($remaining < 8) {
throw new ZipException('ZIP64 extension corrupt (no uncompressed size).');
}
$uncompressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
$remaining -= 8;
}
if ($entry->getCompressedSize() === ZipConstants::ZIP64_MAGIC) {
if ($remaining < 8) {
throw new ZipException('ZIP64 extension corrupt (no compressed size).');
}
$compressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
$remaining -= 8;
}
if ($entry->getLocalHeaderOffset() === ZipConstants::ZIP64_MAGIC) {
if ($remaining < 8) {
throw new ZipException('ZIP64 extension corrupt (no relative local header offset).');
}
$localHeaderOffset = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
$remaining -= 8;
}
if ($remaining === 4) {
$diskStart = unpack('V', substr($buffer, $length - $remaining, 4))[1];
}
return new self($uncompressedSize, $compressedSize, $localHeaderOffset, $diskStart);
}
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData()
{
if ($this->uncompressedSize !== null || $this->compressedSize !== null) {
if ($this->uncompressedSize === null || $this->compressedSize === null) {
throw new \InvalidArgumentException(
'Zip64 extended information must contain both size values in the local file header.'
);
}
return $this->packSizes();
}
return '';
}
/**
* @return string
*/
private function packSizes()
{
$data = '';
if ($this->uncompressedSize !== null) {
$data .= PackUtil::packLongLE($this->uncompressedSize);
}
if ($this->compressedSize !== null) {
$data .= PackUtil::packLongLE($this->compressedSize);
}
return $data;
}
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData()
{
$data = $this->packSizes();
if ($this->localHeaderOffset !== null) {
$data .= PackUtil::packLongLE($this->localHeaderOffset);
}
if ($this->diskStart !== null) {
$data .= pack('V', $this->diskStart);
}
return $data;
}
/**
* @return int|null
*/
public function getUncompressedSize()
{
return $this->uncompressedSize;
}
/**
* @param int|null $uncompressedSize
*/
public function setUncompressedSize($uncompressedSize)
{
$this->uncompressedSize = $uncompressedSize;
}
/**
* @return int|null
*/
public function getCompressedSize()
{
return $this->compressedSize;
}
/**
* @param int|null $compressedSize
*/
public function setCompressedSize($compressedSize)
{
$this->compressedSize = $compressedSize;
}
/**
* @return int|null
*/
public function getLocalHeaderOffset()
{
return $this->localHeaderOffset;
}
/**
* @param int|null $localHeaderOffset
*/
public function setLocalHeaderOffset($localHeaderOffset)
{
$this->localHeaderOffset = $localHeaderOffset;
}
/**
* @return int|null
*/
public function getDiskStart()
{
return $this->diskStart;
}
/**
* @param int|null $diskStart
*/
public function setDiskStart($diskStart)
{
$this->diskStart = $diskStart;
}
/**
* @return string
*/
public function __toString()
{
$args = [self::HEADER_ID];
$format = '0x%04x ZIP64: ';
$formats = [];
if ($this->uncompressedSize !== null) {
$formats[] = 'SIZE=%d';
$args[] = $this->uncompressedSize;
}
if ($this->compressedSize !== null) {
$formats[] = 'COMP_SIZE=%d';
$args[] = $this->compressedSize;
}
if ($this->localHeaderOffset !== null) {
$formats[] = 'OFFSET=%d';
$args[] = $this->localHeaderOffset;
}
if ($this->diskStart !== null) {
$formats[] = 'DISK_START=%d';
$args[] = $this->diskStart;
}
$format .= implode(' ', $formats);
return vsprintf($format, $args);
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace PhpZip\Model\Extra;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Model\Extra\Fields\ApkAlignmentExtraField;
use PhpZip\Model\Extra\Fields\AsiExtraField;
use PhpZip\Model\Extra\Fields\ExtendedTimestampExtraField;
use PhpZip\Model\Extra\Fields\JarMarkerExtraField;
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
use PhpZip\Model\Extra\Fields\NtfsExtraField;
use PhpZip\Model\Extra\Fields\OldUnixExtraField;
use PhpZip\Model\Extra\Fields\UnicodeCommentExtraField;
use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
use PhpZip\Model\Extra\Fields\Zip64ExtraField;
/**
* Class ZipExtraManager.
*/
final class ZipExtraDriver
{
/**
* @var array<int, string>
* @psalm-var array<int, class-string<\PhpZip\Model\Extra\ZipExtraField>>
*/
private static $implementations = [
ApkAlignmentExtraField::HEADER_ID => ApkAlignmentExtraField::class,
AsiExtraField::HEADER_ID => AsiExtraField::class,
ExtendedTimestampExtraField::HEADER_ID => ExtendedTimestampExtraField::class,
JarMarkerExtraField::HEADER_ID => JarMarkerExtraField::class,
NewUnixExtraField::HEADER_ID => NewUnixExtraField::class,
NtfsExtraField::HEADER_ID => NtfsExtraField::class,
OldUnixExtraField::HEADER_ID => OldUnixExtraField::class,
UnicodeCommentExtraField::HEADER_ID => UnicodeCommentExtraField::class,
UnicodePathExtraField::HEADER_ID => UnicodePathExtraField::class,
WinZipAesExtraField::HEADER_ID => WinZipAesExtraField::class,
Zip64ExtraField::HEADER_ID => Zip64ExtraField::class,
];
private function __construct()
{
}
/**
* @param string|ZipExtraField $extraField ZipExtraField object or class name
*/
public static function register($extraField)
{
if (!is_a($extraField, ZipExtraField::class, true)) {
throw new InvalidArgumentException(
sprintf(
'$extraField "%s" is not implements interface %s',
(string) $extraField,
ZipExtraField::class
)
);
}
self::$implementations[\call_user_func([$extraField, 'getHeaderId'])] = $extraField;
}
/**
* @param int|string|ZipExtraField $extraType ZipExtraField object or class name or extra header id
*
* @return bool
*/
public static function unregister($extraType)
{
$headerId = null;
if (\is_int($extraType)) {
$headerId = $extraType;
} elseif (is_a($extraType, ZipExtraField::class, true)) {
$headerId = \call_user_func([$extraType, 'getHeaderId']);
} else {
return false;
}
if (isset(self::$implementations[$headerId])) {
unset(self::$implementations[$headerId]);
return true;
}
return false;
}
/**
* @param int $headerId
*
* @return string|null
*/
public static function getClassNameOrNull($headerId)
{
$headerId = (int) $headerId;
if ($headerId < 0 || $headerId > 0xffff) {
throw new \InvalidArgumentException('$headerId out of range: ' . $headerId);
}
if (isset(self::$implementations[$headerId])) {
return self::$implementations[$headerId];
}
return null;
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace PhpZip\Model\Extra;
use PhpZip\Model\ZipEntry;
/**
* 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/unserialize them to/from byte arrays.
*/
interface ZipExtraField
{
/**
* 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 function getHeaderId();
/**
* Populate data from this array as if it was in local file data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return static
*/
public static function unpackLocalFileData($buffer, ZipEntry $entry = null);
/**
* Populate data from this array as if it was in central directory data.
*
* @param string $buffer the buffer to read data from
* @param ZipEntry|null $entry
*
* @return static
*/
public static function unpackCentralDirData($buffer, ZipEntry $entry = null);
/**
* The actual data to put into local file data - without Header-ID
* or length specifier.
*
* @return string the data
*/
public function packLocalFileData();
/**
* The actual data to put into central directory - without Header-ID or
* length specifier.
*
* @return string the data
*/
public function packCentralDirData();
/**
* @return string
*/
public function __toString();
}

View File

@ -0,0 +1,56 @@
<?php
namespace PhpZip\Model;
/**
* Class ImmutableZipContainer.
*/
class ImmutableZipContainer implements \Countable
{
/** @var ZipEntry[] */
protected $entries;
/** @var string|null Archive comment */
protected $archiveComment;
/**
* ZipContainer constructor.
*
* @param ZipEntry[] $entries
* @param string|null $archiveComment
*/
public function __construct(array $entries, $archiveComment)
{
$this->entries = $entries;
$this->archiveComment = $archiveComment;
}
/**
* @return ZipEntry[]
*/
public function &getEntries()
{
return $this->entries;
}
/**
* @return string|null
*/
public function getArchiveComment()
{
return $this->archiveComment;
}
/**
* Count elements of an object.
*
* @see https://php.net/manual/en/countable.count.php
*
* @return int The custom count as an integer.
* The return value is cast to an integer.
*/
public function count()
{
return \count($this->entries);
}
}

381
src/Model/ZipContainer.php Normal file
View File

@ -0,0 +1,381 @@
<?php
namespace PhpZip\Model;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipEntryNotFoundException;
use PhpZip\Exception\ZipException;
/**
* Class ZipContainer.
*/
class ZipContainer extends ImmutableZipContainer
{
/** @var ImmutableZipContainer|null */
private $sourceContainer;
/**
* @var int|null Apk zipalign value
*
* @todo remove and use in ApkFileWriter
*/
private $zipAlign;
/**
* MutableZipContainer constructor.
*
* @param ImmutableZipContainer|null $sourceContainer
*/
public function __construct(ImmutableZipContainer $sourceContainer = null)
{
$entries = [];
$archiveComment = null;
if ($sourceContainer !== null) {
foreach ($sourceContainer->getEntries() as $entryName => $entry) {
$entries[$entryName] = clone $entry;
}
$archiveComment = $sourceContainer->getArchiveComment();
}
parent::__construct($entries, $archiveComment);
$this->sourceContainer = $sourceContainer;
}
/**
* @return ImmutableZipContainer|null
*/
public function getSourceContainer()
{
return $this->sourceContainer;
}
/**
* @param ZipEntry $entry
*/
public function addEntry(ZipEntry $entry)
{
$this->entries[$entry->getName()] = $entry;
}
/**
* @param string|ZipEntry $entry
*
* @return bool
*/
public function deleteEntry($entry)
{
$entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry;
if (isset($this->entries[$entry])) {
unset($this->entries[$entry]);
return true;
}
return false;
}
/**
* @param string|ZipEntry $old
* @param string|ZipEntry $new
*
* @throws ZipException
*
* @return ZipEntry New zip entry
*/
public function renameEntry($old, $new)
{
$old = $old instanceof ZipEntry ? $old->getName() : (string) $old;
$new = $new instanceof ZipEntry ? $new->getName() : (string) $new;
if (isset($this->entries[$new])) {
throw new InvalidArgumentException('New entry name ' . $new . ' is exists.');
}
$entry = $this->getEntry($old);
$newEntry = $entry->rename($new);
$this->deleteEntry($entry);
$this->addEntry($newEntry);
return $newEntry;
}
/**
* @param string|ZipEntry $entryName
*
* @throws ZipEntryNotFoundException
*
* @return ZipEntry
*/
public function getEntry($entryName)
{
$entry = $this->getEntryOrNull($entryName);
if ($entry !== null) {
return $entry;
}
throw new ZipEntryNotFoundException($entryName);
}
/**
* @param string|ZipEntry $entryName
*
* @return ZipEntry|null
*/
public function getEntryOrNull($entryName)
{
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
return isset($this->entries[$entryName]) ? $this->entries[$entryName] : null;
}
/**
* @param string|ZipEntry $entryName
*
* @return bool
*/
public function hasEntry($entryName)
{
$entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName;
return isset($this->entries[$entryName]);
}
/**
* Delete all entries.
*/
public function deleteAll()
{
$this->entries = [];
}
/**
* Delete entries by regex pattern.
*
* @param string $regexPattern Regex pattern
*
* @return ZipEntry[] Deleted entries
*/
public function deleteByRegex($regexPattern)
{
if (empty($regexPattern)) {
throw new InvalidArgumentException('The regex pattern is not specified');
}
/** @var ZipEntry[] $found */
$found = [];
foreach ($this->entries as $entryName => $entry) {
if (preg_match($regexPattern, $entryName)) {
$found[] = $entry;
}
}
foreach ($found as $entry) {
$this->deleteEntry($entry);
}
return $found;
}
/**
* Undo all changes done in the archive.
*/
public function unchangeAll()
{
$this->entries = [];
if ($this->sourceContainer !== null) {
foreach ($this->sourceContainer->getEntries() as $entry) {
$this->entries[$entry->getName()] = clone $entry;
}
}
$this->unchangeArchiveComment();
}
/**
* Undo change archive comment.
*/
public function unchangeArchiveComment()
{
$this->archiveComment = null;
if ($this->sourceContainer !== null) {
$this->archiveComment = $this->sourceContainer->archiveComment;
}
}
/**
* 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 (
$this->sourceContainer !== null &&
isset($this->entries[$entry], $this->sourceContainer->entries[$entry])
) {
$this->entries[$entry] = clone $this->sourceContainer->entries[$entry];
return true;
}
return false;
}
/**
* Entries sort by name.
*
* Example:
* ```php
* $zipContainer->sortByName(static function (string $nameA, string $nameB): int {
* return strcmp($nameA, $nameB);
* });
* ```
*
* @param callable $cmp
*/
public function sortByName(callable $cmp)
{
uksort($this->entries, $cmp);
}
/**
* Entries sort by entry.
*
* Example:
* ```php
* $zipContainer->sortByEntry(static function (ZipEntry $a, ZipEntry $b): int {
* return strcmp($a->getName(), $b->getName());
* });
* ```
*
* @param callable $cmp
*/
public function sortByEntry(callable $cmp)
{
uasort($this->entries, $cmp);
}
/**
* @param string|null $archiveComment
*/
public function setArchiveComment($archiveComment)
{
if ($archiveComment !== null && $archiveComment !== '') {
$archiveComment = (string) $archiveComment;
$length = \strlen($archiveComment);
if ($length > 0xffff) {
throw new InvalidArgumentException('Length comment out of range');
}
}
$this->archiveComment = $archiveComment;
}
/**
* @return ZipEntryMatcher
*/
public function matcher()
{
return new ZipEntryMatcher($this);
}
/**
* Specify a password for extracting files.
*
* @param string|null $password
*/
public function setReadPassword($password)
{
if ($this->sourceContainer !== null) {
foreach ($this->sourceContainer->entries 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->sourceContainer->entries[$entryName])) {
throw new ZipEntryNotFoundException($entryName);
}
if ($this->sourceContainer->entries[$entryName]->isEncrypted()) {
$this->sourceContainer->entries[$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 string|null $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);
}
/**
* @param int $encryptionMethod
*/
public function setEncryptionMethod($encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256)
{
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
}
}

28
src/Model/ZipData.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace PhpZip\Model;
use PhpZip\Exception\ZipException;
/**
* Interface ZipData.
*/
interface ZipData
{
/**
* @return string returns data as string
*/
public function getDataAsString();
/**
* @return resource returns stream data
*/
public function getDataAsStream();
/**
* @param resource $outStream
*
* @throws ZipException
*/
public function copyDataToStream($outStream);
}

1567
src/Model/ZipEntry.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@ namespace PhpZip\Model;
*/
class ZipEntryMatcher implements \Countable
{
/** @var ZipModel */
protected $zipModel;
/** @var ZipContainer */
protected $zipContainer;
/** @var array */
protected $matches = [];
@ -17,15 +17,15 @@ class ZipEntryMatcher implements \Countable
/**
* ZipEntryMatcher constructor.
*
* @param ZipModel $zipModel
* @param ZipContainer $zipContainer
*/
public function __construct(ZipModel $zipModel)
public function __construct(ZipContainer $zipContainer)
{
$this->zipModel = $zipModel;
$this->zipContainer = $zipContainer;
}
/**
* @param string|array $entries
* @param string|ZipEntry|string[]|ZipEntry[] $entries
*
* @return ZipEntryMatcher
*/
@ -46,7 +46,7 @@ class ZipEntryMatcher implements \Countable
$this->matches,
array_keys(
array_intersect_key(
$this->zipModel->getEntries(),
$this->zipContainer->getEntries(),
array_flip($entries)
)
)
@ -62,16 +62,18 @@ class ZipEntryMatcher implements \Countable
* @param string $regexp
*
* @return ZipEntryMatcher
*
* @noinspection PhpUnusedParameterInspection
*/
public function match($regexp)
{
array_walk(
$this->zipModel->getEntries(),
function (
/** @noinspection PhpUnusedParameterInspection */
$entry,
$entryName
) use ($regexp) {
$this->zipContainer->getEntries(),
/**
* @param ZipEntry $entry
* @param string $entryName
*/
function (ZipEntry $entry, $entryName) use ($regexp) {
if (preg_match($regexp, $entryName)) {
$this->matches[] = (string) $entryName;
}
@ -87,7 +89,7 @@ class ZipEntryMatcher implements \Countable
*/
public function all()
{
$this->matches = array_keys($this->zipModel->getEntries());
$this->matches = array_keys($this->zipContainer->getEntries());
return $this;
}
@ -105,6 +107,7 @@ class ZipEntryMatcher implements \Countable
if (!empty($this->matches)) {
array_walk(
$this->matches,
/** @param string $entryName */
static function ($entryName) use ($callable) {
$callable($entryName);
}
@ -124,8 +127,9 @@ class ZipEntryMatcher implements \Countable
{
array_walk(
$this->matches,
function ($entry) {
$this->zipModel->deleteEntry($entry);
/** @param string $entryName */
function ($entryName) {
$this->zipContainer->deleteEntry($entryName);
}
);
$this->matches = [];
@ -139,11 +143,12 @@ class ZipEntryMatcher implements \Countable
{
array_walk(
$this->matches,
function ($entry) use ($password, $encryptionMethod) {
$entry = $this->zipModel->getEntry($entry);
/** @param string $entryName */
function ($entryName) use ($password, $encryptionMethod) {
$entry = $this->zipContainer->getEntry($entryName);
if (!$entry->isDirectory()) {
$this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod);
$entry->setPassword($password, $encryptionMethod);
}
}
);
@ -156,11 +161,12 @@ class ZipEntryMatcher implements \Countable
{
array_walk(
$this->matches,
function ($entry) use ($encryptionMethod) {
$entry = $this->zipModel->getEntry($entry);
/** @param string $entryName */
function ($entryName) use ($encryptionMethod) {
$entry = $this->zipContainer->getEntry($entryName);
if (!$entry->isDirectory()) {
$this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod);
$entry->setEncryptionMethod($encryptionMethod);
}
}
);
@ -170,11 +176,11 @@ class ZipEntryMatcher implements \Countable
{
array_walk(
$this->matches,
function ($entry) {
$entry = $this->zipModel->getEntry($entry);
/** @param string $entryName */
function ($entryName) {
$entry = $this->zipContainer->getEntry($entryName);
if (!$entry->isDirectory()) {
$entry = $this->zipModel->getEntryForChanges($entry);
$entry->disableEncryption();
}
}

266
src/Model/ZipInfo.php Normal file
View File

@ -0,0 +1,266 @@
<?php
namespace PhpZip\Model;
use PhpZip\Constants\ZipCompressionMethod;
use PhpZip\Constants\ZipEncryptionMethod;
use PhpZip\Constants\ZipPlatform;
use PhpZip\Util\FileAttribUtil;
use PhpZip\Util\FilesUtil;
/**
* Zip info.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @deprecated Use ZipEntry
*/
class ZipInfo
{
/** @var ZipEntry */
private $entry;
/**
* ZipInfo constructor.
*
* @param ZipEntry $entry
*/
public function __construct(ZipEntry $entry)
{
$this->entry = $entry;
}
/**
* @param ZipEntry $entry
*
* @return string
*
* @deprecated Use {@see ZipPlatform::getPlatformName()}
*/
public static function getPlatformName(ZipEntry $entry)
{
return ZipPlatform::getPlatformName($entry->getExtractedOS());
}
/**
* @return string
*/
public function getName()
{
return $this->entry->getName();
}
/**
* @return bool
*/
public function isFolder()
{
return $this->entry->isDirectory();
}
/**
* @return int
*/
public function getSize()
{
return $this->entry->getUncompressedSize();
}
/**
* @return int
*/
public function getCompressedSize()
{
return $this->entry->getCompressedSize();
}
/**
* @return int
*/
public function getMtime()
{
return $this->entry->getMTime()->getTimestamp();
}
/**
* @return int|null
*/
public function getCtime()
{
$ctime = $this->entry->getCTime();
return $ctime === null ? null : $ctime->getTimestamp();
}
/**
* @return int|null
*/
public function getAtime()
{
$atime = $this->entry->getATime();
return $atime === null ? null : $atime->getTimestamp();
}
/**
* @return string
*/
public function getAttributes()
{
$externalAttributes = $this->entry->getExternalAttributes();
if ($this->entry->getCreatedOS() === ZipPlatform::OS_UNIX) {
$permission = (($externalAttributes >> 16) & 0xFFFF);
return FileAttribUtil::getUnixMode($permission);
}
return FileAttribUtil::getDosMode($externalAttributes);
}
/**
* @return bool
*/
public function isEncrypted()
{
return $this->entry->isEncrypted();
}
/**
* @return string|null
*/
public function getComment()
{
return $this->entry->getComment();
}
/**
* @return int
*/
public function getCrc()
{
return $this->entry->getCrc();
}
/**
* @return string
*
* @deprecated use \PhpZip\Model\ZipInfo::getMethodName()
*/
public function getMethod()
{
return $this->getMethodName();
}
/**
* @return string
*/
public function getMethodName()
{
return ZipCompressionMethod::getCompressionMethodName($this->entry->getCompressionMethod());
}
/**
* @return string
*/
public function getEncryptionMethodName()
{
return ZipEncryptionMethod::getEncryptionMethodName($this->entry->getEncryptionMethod());
}
/**
* @return string
*/
public function getPlatform()
{
return ZipPlatform::getPlatformName($this->entry->getExtractedOS());
}
/**
* @return int
*/
public function getVersion()
{
return $this->entry->getExtractVersion();
}
/**
* @return int|null
*/
public function getEncryptionMethod()
{
$encryptionMethod = $this->entry->getEncryptionMethod();
return $encryptionMethod === ZipEncryptionMethod::NONE ? null : $encryptionMethod;
}
/**
* @return int|null
*/
public function getCompressionLevel()
{
return $this->entry->getCompressionLevel();
}
/**
* @return int
*/
public function getCompressionMethod()
{
return $this->entry->getCompressionMethod();
}
/**
* @return array
*/
public function toArray()
{
return [
'name' => $this->getName(),
'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(),
'encryption_method_name' => $this->getEncryptionMethodName(),
'comment' => $this->getComment(),
'crc' => $this->getCrc(),
'method_name' => $this->getMethodName(),
'compression_method' => $this->getCompressionMethod(),
'platform' => $this->getPlatform(),
'version' => $this->getVersion(),
];
}
/**
* @return string
*/
public function __toString()
{
$ctime = $this->entry->getCTime();
$atime = $this->entry->getATime();
$comment = $this->getComment();
return __CLASS__ . ' {'
. 'Name="' . $this->getName() . '", '
. ($this->isFolder() ? 'Folder, ' : '')
. 'Size="' . FilesUtil::humanSize($this->getSize()) . '"'
. ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()) . '"'
. ', Modified time="' . $this->entry->getMTime()->format(\DATE_W3C) . '", '
. ($ctime !== null ? 'Created time="' . $ctime->format(\DATE_W3C) . '", ' : '')
. ($atime !== null ? 'Accessed time="' . $atime->format(\DATE_W3C) . '", ' : '')
. ($this->isEncrypted() ? 'Encrypted, ' : '')
. ($comment !== null ? 'Comment="' . $comment . '", ' : '')
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->crc) . ', ' : '')
. 'Method name="' . $this->getMethodName() . '", '
. 'Attributes="' . $this->getAttributes() . '", '
. 'Platform="' . $this->getPlatform() . '", '
. 'Version=' . $this->getVersion()
. '}';
}
}

View File

@ -1,295 +0,0 @@
<?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;
/**
* 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
*
* @throws ZipException
* @throws ZipAuthenticationException
* @throws ZipCryptoException
*
* @return string
*/
public function decrypt($content)
{
$extraFieldsCollection = $this->entry->getExtraFieldsCollection();
if (!isset($extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()])) {
throw new ZipCryptoException($this->entry->getName() . ' (missing extra field for WinZip AES entry)');
}
/**
* @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 ($size < 0) {
throw new ZipCryptoException($this->entry->getName() . ' (false positive WinZip AES entry is too short)');
}
// Load authentication code.
$authenticationCode = substr($content, $end, $footerSize);
if ($end + $footerSize !== $endPos) {
// This should never happen unless someone is writing to the
// end of the file concurrently!
throw new ZipCryptoException('Expected end of file after WinZip AES authentication code!');
}
$password = $this->entry->getPassword();
if ($password === null) {
throw new ZipException(sprintf('Password not set for entry %s', $this->entry->getName()));
}
/**
* WinZip 99-character limit.
*
* @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
*/
$password = substr($password, 0, 99);
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(\chr(0), $ctrIvSize);
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 (strpos($mac, $authenticationCode) !== 0) {
throw new ZipAuthenticationException(
$this->entry->getName() .
' (authenticated WinZip AES entry content has been tampered with)'
);
}
return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false);
}
/**
* 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);
}
if (\extension_loaded('mcrypt')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
}
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
/**
* Decrypt AES-CTR.
*
* @param string $data Encrypted data
* @param string $key Aes key
* @param string $iv Aes IV
*
* @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);
}
if (\extension_loaded('mcrypt')) {
/** @noinspection PhpComposerExtensionStubsInspection */
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
}
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
/**
* Encryption string.
*
* @param string $content
*
* @throws ZipException
*
* @return string
*/
public function encrypt($content)
{
// Init key strength.
$password = $this->entry->getPassword();
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;
try {
$salt = random_bytes($keyStrengthBytes / 2);
} catch (\Exception $e) {
throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e);
}
$keyParam = hash_pbkdf2(
'sha1',
$password,
$salt,
self::ITERATION_COUNT,
(2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8,
true
);
$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);
}
}

View File

@ -1,36 +0,0 @@
<?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
*
* @throws ZipAuthenticationException
*
* @return string
*/
public function decrypt($encryptionContent);
/**
* Encryption string.
*
* @param string $content
*
* @return string
*/
public function encrypt($content);
}

View File

@ -1,37 +0,0 @@
<?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);
}

View File

@ -1,202 +0,0 @@
<?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
*
* @throws ZipException
*
* @return ExtraFieldsCollection
*/
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 = self::create($headerId);
if ($extraField instanceof Zip64ExtraField && $entry !== null) {
$extraField->setEntry($entry);
}
if ($dataSize > 0) {
$content = substr($extra, $pos, $dataSize);
if ($content !== false) {
$extraField->deserialize($content);
$extraFieldsCollection[$headerId] = $extraField;
}
}
$pos += $dataSize;
}
}
return $extraFieldsCollection;
}
/**
* @param ExtraFieldsCollection $extraFieldsCollection
*
* @throws ZipException
*
* @return string
*/
public static function createSerializedData(ExtraFieldsCollection $extraFieldsCollection)
{
$extraData = '';
foreach ($extraFieldsCollection as $extraField) {
$data = $extraField->serialize();
$extraData .= pack('vv', $extraField::getHeaderId(), \strlen($data));
$extraData .= $data;
}
$size = \strlen($extraData);
if ($size < 0x0000 || $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
*
* @throws ZipException if headerId is out of range
*
* @return ExtraField a new Extra Field or null if not support header id
*/
public static function create($headerId)
{
if ($headerId < 0x0000 || $headerId > 0xffff) {
throw new ZipException('headerId out of range');
}
if (isset(self::getRegistry()[$headerId])) {
$extraClassName = self::getRegistry()[$headerId];
/** @var ExtraField $extraField */
$extraField = new $extraClassName();
if ($headerId !== $extraField::getHeaderId()) {
throw new ZipException('Runtime error support headerId ' . $headerId);
}
} else {
$extraField = new DefaultExtraField($headerId);
}
return $extraField;
}
/**
* 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;
}
}

View File

@ -1,115 +0,0 @@
<?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;
}
}

View File

@ -1,70 +0,0 @@
<?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 ($headerId < 0x0000 || $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;
}
}

View File

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

View File

@ -1,138 +0,0 @@
<?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;
}
}

View File

@ -1,284 +0,0 @@
<?php
namespace PhpZip\Extra\Fields;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraField;
use PhpZip\ZipFile;
/**
* WinZip AES Extra Field.
*
* @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2
* (WinZip Computing, S.L.)
* @see http://www.winzip.com/win/en/aes_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 => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128,
self::KEY_STRENGTH_192BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192,
self::KEY_STRENGTH_256BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256,
];
/**
* 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.
*
* @param int $vendorVersion the vendor version
*
* @throws ZipException unsupport vendor version
*
* @see WinZipAesEntryExtraField::VV_AE_1
* @see WinZipAesEntryExtraField::VV_AE_2
*/
public function setVendorVersion($vendorVersion)
{
if ($vendorVersion < self::VV_AE_1 || $vendorVersion > self::VV_AE_2) {
throw new ZipException($vendorVersion);
}
$this->vendorVersion = $vendorVersion;
}
/**
* Returns vendor id.
*
* @return int
*/
public function getVendorId()
{
return self::VENDOR_ID;
}
/**
* @throws ZipException
*
* @return bool|int
*/
public function getKeyStrength()
{
return self::keyStrength($this->encryptionStrength);
}
/**
* @param int $encryptionStrength encryption strength as bits
*
* @throws ZipException if unsupport encryption strength
*
* @return int
*/
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.
*
* @throws ZipException
*
* @return int
*/
public function getEncryptionMethod()
{
return isset(self::$encryptionMethods[$this->getKeyStrength()]) ?
self::$encryptionMethods[$this->getKeyStrength()] :
self::$encryptionMethods[self::KEY_STRENGTH_256BIT];
}
/**
* @param int $encryptionMethod
*
* @throws ZipException
*
* @return int
*/
public static function getKeyStrangeFromEncryptionMethod($encryptionMethod)
{
$flipKey = array_flip(self::$encryptionMethods);
if (!isset($flipKey[$encryptionMethod])) {
throw new ZipException('Unsupport encryption method ' . $encryptionMethod);
}
return $flipKey[$encryptionMethod];
}
/**
* Sets compression method.
*
* @param int $compressionMethod Compression method
*
* @throws ZipException compression method out of range
*/
public function setMethod($compressionMethod)
{
if ($compressionMethod < 0x0000 || $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 ($size !== self::DATA_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 ($unpack['vendorId'] !== self::VENDOR_ID) {
throw new ZipException('Vendor id invalid: ' . $unpack['vendorId'] . '. Must be ' . self::VENDOR_ID);
}
$this->setKeyStrength(self::keyStrength($unpack['keyStrength'])); // checked
$this->setMethod($unpack['method']);
}
}

View File

@ -1,123 +0,0 @@
<?php
namespace PhpZip\Extra\Fields;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
/**
* ZIP64 Extra Field.
*
* @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 ($size >= 0xffffffff) {
$data .= PackUtil::packLongLE($size);
}
// Write out Compressed Size.
$compressedSize = $this->entry->getCompressedSize();
if ($compressedSize >= 0xffffffff) {
$data .= PackUtil::packLongLE($compressedSize);
}
// Write out Relative Header Offset.
$offset = $this->entry->getOffset();
if ($offset >= 0xffffffff) {
$data .= PackUtil::packLongLE($offset);
}
return $data;
}
/**
* Initializes this Extra Field by deserializing a Data Block.
*
* @param string $data
*
* @throws ZipException
*/
public function deserialize($data)
{
if ($this->entry === null) {
throw new RuntimeException('entry is null');
}
$off = 0;
// Read in Uncompressed Size.
if ($this->entry->getSize() === 0xffffffff) {
$this->entry->setSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
$off += 8;
}
// Read in Compressed Size.
if ($this->entry->getCompressedSize() === 0xffffffff) {
$this->entry->setCompressedSize(PackUtil::unpackLongLE(substr($data, $off, 8)));
$off += 8;
}
// Read in Relative Header Offset.
if ($this->entry->getOffset() === 0xffffffff) {
$this->entry->setOffset(PackUtil::unpackLongLE(substr($data, $off, 8)));
}
}
}

View File

@ -1,157 +0,0 @@
<?php
namespace PhpZip\Model;
/**
* End of Central Directory.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class EndOfCentralDirectory
{
/** Zip64 End Of Central Directory Record. */
const ZIP64_END_OF_CD_RECORD_SIG = 0x06064B50;
/** Zip64 End Of Central Directory Locator. */
const ZIP64_END_OF_CD_LOCATOR_SIG = 0x07064B50;
/** End Of Central Directory Record signature. */
const END_OF_CD_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_CD_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 int Count files. */
private $entryCount;
/** @var int Central Directory Offset. */
private $cdOffset;
/** @var int */
private $cdSize;
/** @var string|null The archive comment. */
private $comment;
/** @var bool Zip64 extension */
private $zip64;
/**
* EndOfCentralDirectory constructor.
*
* @param int $entryCount
* @param int $cdOffset
* @param int $cdSize
* @param bool $zip64
* @param mixed|null $comment
*/
public function __construct($entryCount, $cdOffset, $cdSize, $zip64, $comment = null)
{
$this->entryCount = $entryCount;
$this->cdOffset = $cdOffset;
$this->cdSize = $cdSize;
$this->zip64 = $zip64;
$this->comment = $comment;
}
/**
* @param string|null $comment
*/
public function setComment($comment)
{
$this->comment = $comment;
}
/**
* @return int
*/
public function getEntryCount()
{
return $this->entryCount;
}
/**
* @return int
*/
public function getCdOffset()
{
return $this->cdOffset;
}
/**
* @return int
*/
public function getCdSize()
{
return $this->cdSize;
}
/**
* @return string|null
*/
public function getComment()
{
return $this->comment;
}
/**
* @return bool
*/
public function isZip64()
{
return $this->zip64;
}
}

View File

@ -1,48 +0,0 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Model\ZipEntry;
/**
* Entry to write to the central directory.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
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;
}
}

View File

@ -1,912 +0,0 @@
<?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\ZipFile;
/**
* Abstract ZIP entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
abstract class ZipAbstractEntry implements ZipEntry
{
/** @var string Entry name (filename in archive) */
private $name;
/** @var int Made by platform */
private $createdOS = self::UNKNOWN;
/** @var int Extracted by platform */
private $extractedOS = self::UNKNOWN;
/** @var int */
private $softwareVersion = self::UNKNOWN;
/** @var int */
private $versionNeededToExtract = self::UNKNOWN;
/** @var int Compression method */
private $method = self::UNKNOWN;
/** @var int */
private $generalPurposeBitFlags = 0;
/** @var int Dos time */
private $dosTime = self::UNKNOWN;
/** @var int Crc32 */
private $crc = self::UNKNOWN;
/** @var int Compressed size */
private $compressedSize = self::UNKNOWN;
/** @var int Uncompressed size */
private $size = self::UNKNOWN;
/** @var int Internal attributes */
private $internalAttributes = 0;
/** @var int External attributes */
private $externalAttributes = 0;
/** @var int relative Offset Of Local File Header */
private $offset = 0;
/**
* Collections of Extra Fields.
* Keys from Header ID [int] and value Extra Field [ExtraField].
* Should be null or may be empty if no Extra Fields are used.
*
* @var ExtraFieldsCollection
*/
private $extraFieldsCollection;
/** @var string|null comment field */
private $comment;
/** @var string entry password for read or write encryption data */
private $password;
/**
* Encryption method.
*
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
*
* @var int
*/
private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;
/** @var int */
private $compressionLevel = ZipFile::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->setSoftwareVersion($entry->getSoftwareVersion());
$this->setCreatedOS($entry->getCreatedOS());
$this->setExtractedOS($entry->getExtractedOS());
$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->setInternalAttributes($entry->getInternalAttributes());
$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
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setName($name)
{
$length = \strlen($name);
if ($length < 0x0000 || $length > 0xffff) {
throw new ZipException('Illegal zip entry name parameter');
}
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
$this->name = $name;
$this->externalAttributes = $this->isDirectory() ? 0x10 : 0;
return $this;
}
/**
* Sets the indexed General Purpose Bit Flag.
*
* @param int $mask
* @param bool $bit
*
* @return ZipEntry
*/
public function setGeneralPurposeBitFlag($mask, $bit)
{
if ($bit) {
$this->generalPurposeBitFlags |= $mask;
} else {
$this->generalPurposeBitFlags &= ~$mask;
}
return $this;
}
/**
* @return int Get platform
*
* @deprecated Use {@see ZipEntry::getCreatedOS()}
* @noinspection PhpUsageOfSilenceOperatorInspection
*/
public function getPlatform()
{
@trigger_error('ZipEntry::getPlatform() is deprecated. Use ZipEntry::getCreatedOS()', \E_USER_DEPRECATED);
return $this->getCreatedOS();
}
/**
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*
* @deprecated Use {@see ZipEntry::setCreatedOS()}
* @noinspection PhpUsageOfSilenceOperatorInspection
*/
public function setPlatform($platform)
{
@trigger_error('ZipEntry::setPlatform() is deprecated. Use ZipEntry::setCreatedOS()', \E_USER_DEPRECATED);
return $this->setCreatedOS($platform);
}
/**
* @return int platform
*/
public function getCreatedOS()
{
return $this->createdOS;
}
/**
* Set platform.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCreatedOS($platform)
{
$platform = (int) $platform;
if ($platform < 0x00 || $platform > 0xff) {
throw new ZipException('Platform out of range');
}
$this->createdOS = $platform;
return $this;
}
/**
* @return int
*/
public function getExtractedOS()
{
return $this->extractedOS;
}
/**
* Set extracted OS.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setExtractedOS($platform)
{
$platform = (int) $platform;
if ($platform < 0x00 || $platform > 0xff) {
throw new ZipException('Platform out of range');
}
$this->extractedOS = $platform;
return $this;
}
/**
* @return int
*/
public function getSoftwareVersion()
{
return $this->softwareVersion;
}
/**
* @param int $softwareVersion
*
* @return ZipEntry
*/
public function setSoftwareVersion($softwareVersion)
{
$this->softwareVersion = (int) $softwareVersion;
return $this;
}
/**
* Version needed to extract.
*
* @return int
*/
public function getVersionNeededToExtract()
{
if ($this->versionNeededToExtract === self::UNKNOWN) {
$method = $this->getMethod();
if ($method === self::METHOD_WINZIP_AES) {
return 51;
}
if ($method === ZipFile::METHOD_BZIP2) {
return 46;
}
if ($this->isZip64ExtensionsRequired()) {
return 45;
}
return $method === ZipFile::METHOD_DEFLATED || $this->isDirectory() ? 20 : 10;
}
return $this->versionNeededToExtract;
}
/**
* 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 $this->getCompressedSize() >= 0xffffffff
|| $this->getSize() >= 0xffffffff;
}
/**
* 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 = (int) $offset;
return $this;
}
/**
* Returns the General Purpose Bit Flags.
*
* @return int
*/
public function getGeneralPurposeBitFlags()
{
return $this->generalPurposeBitFlags & 0xffff;
}
/**
* Sets the General Purpose Bit Flags.
*
* @param mixed $general
*
* @throws ZipException
*
* @return ZipEntry
*
* @var int general
*/
public function setGeneralPurposeBitFlags($general)
{
if ($general < 0x0000 || $general > 0xffff) {
throw new ZipException('general out of range');
}
$this->generalPurposeBitFlags = $general;
if ($this->method === ZipFile::METHOD_DEFLATED) {
$bit1 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG1);
$bit2 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG2);
if ($bit1 && !$bit2) {
$this->compressionLevel = ZipFile::LEVEL_BEST_COMPRESSION;
} elseif (!$bit1 && $bit2) {
$this->compressionLevel = ZipFile::LEVEL_FAST;
} elseif ($bit1 && $bit2) {
$this->compressionLevel = ZipFile::LEVEL_SUPER_FAST;
} else {
$this->compressionLevel = ZipFile::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->generalPurposeBitFlags & $mask) !== 0;
}
/**
* Sets the encryption property to false and removes any other
* encryption artifacts.
*
* @throws ZipException
*
* @return ZipEntry
*/
public function disableEncryption()
{
$this->setEncrypted(false);
$headerId = WinZipAesEntryExtraField::getHeaderId();
if (isset($this->extraFieldsCollection[$headerId])) {
/** @var WinZipAesEntryExtraField $field */
$field = $this->extraFieldsCollection[$headerId];
if ($this->getMethod() === self::METHOD_WINZIP_AES) {
$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()
{
return $this->method;
}
/**
* Sets the compression method for this entry.
*
* @param int $method
*
* @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
*
* @return ZipEntry
*/
public function setMethod($method)
{
if ($method === self::UNKNOWN) {
$this->method = $method;
return $this;
}
if ($method < 0x0000 || $method > 0xffff) {
throw new ZipException('method out of range: ' . $method);
}
switch ($method) {
case self::METHOD_WINZIP_AES:
case ZipFile::METHOD_STORED:
case ZipFile::METHOD_DEFLATED:
case ZipFile::METHOD_BZIP2:
$this->method = $method;
break;
default:
throw new ZipException($this->name . " (unsupported compression method {$method})");
}
return $this;
}
/**
* Get Unix Timestamp.
*
* @return int
*/
public function getTime()
{
if ($this->getDosTime() === self::UNKNOWN) {
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
*
* @return ZipEntry
*/
public function setDosTime($dosTime)
{
$dosTime = (int) $dosTime;
if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
throw new ZipException('DosTime out of range');
}
$this->dosTime = $dosTime;
return $this;
}
/**
* Set time from unix timestamp.
*
* @param int $unixTimestamp
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setTime($unixTimestamp)
{
$known = $unixTimestamp !== self::UNKNOWN;
if ($known) {
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
} else {
$this->dosTime = 0;
}
return $this;
}
/**
* Returns the external file attributes.
*
* @return int the external file attributes
*/
public function getExternalAttributes()
{
return $this->externalAttributes;
}
/**
* Sets the external file attributes.
*
* @param int $externalAttributes the external file attributes
*
* @return ZipEntry
*/
public function setExternalAttributes($externalAttributes)
{
$this->externalAttributes = $externalAttributes;
return $this;
}
/**
* Sets the internal file attributes.
*
* @param int $attributes the internal file attributes
*
* @return ZipEntry
*/
public function setInternalAttributes($attributes)
{
$this->internalAttributes = (int) $attributes;
return $this;
}
/**
* Returns the internal file attributes.
*
* @return int the internal file attributes
*/
public function getInternalAttributes()
{
return $this->internalAttributes;
}
/**
* Returns true if and only if this ZIP entry represents a directory entry
* (i.e. end with '/').
*
* @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.
*
* @throws ZipException
*
* @return string
*/
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
*
* @return ZipEntry
*/
public function setExtra($data)
{
$this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);
return $this;
}
/**
* Returns comment entry.
*
* @return string
*/
public function getComment()
{
return $this->comment !== null ? $this->comment : '';
}
/**
* Set entry comment.
*
* @param string|null $comment
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setComment($comment)
{
if ($comment !== null) {
$commentLength = \strlen($comment);
if ($commentLength < 0x0000 || $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 = (int) $crc;
return $this;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set password and encryption method from entry.
*
* @param string $password
* @param int|null $encryptionMethod
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setPassword($password, $encryptionMethod = null)
{
$this->password = $password;
if ($encryptionMethod !== null) {
$this->setEncryptionMethod($encryptionMethod);
}
if (!empty($this->password)) {
$this->setEncrypted(true);
} else {
$this->disableEncryption();
}
return $this;
}
/**
* @return int
*/
public function getEncryptionMethod()
{
return $this->encryptionMethod;
}
/**
* Set encryption method.
*
* @param int $encryptionMethod
*
* @throws ZipException
*
* @return ZipEntry
*
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
*/
public function setEncryptionMethod($encryptionMethod)
{
if ($encryptionMethod !== null) {
if (
$encryptionMethod !== ZipFile::ENCRYPTION_METHOD_TRADITIONAL
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
&& $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
) {
throw new ZipException('Invalid encryption method');
}
$this->encryptionMethod = $encryptionMethod;
}
return $this;
}
/**
* @return int
*/
public function getCompressionLevel()
{
return $this->compressionLevel;
}
/**
* @param int $compressionLevel
*
* @return ZipEntry
*/
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
{
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
) {
throw new InvalidArgumentException(
'Invalid compression level. Minimum level ' .
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION
);
}
$this->compressionLevel = $compressionLevel;
return $this;
}
/**
* Clone extra fields.
*/
public function __clone()
{
$this->extraFieldsCollection = clone $this->extraFieldsCollection;
}
}

View File

@ -1,69 +0,0 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
/**
* Source Entry Changes.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
class ZipChangesEntry extends ZipAbstractEntry
{
/** @var ZipSourceEntry */
protected $entry;
/**
* ZipChangesEntry constructor.
*
* @param ZipSourceEntry $entry
*
* @throws ZipException
* @throws 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.
*
* @throws ZipException
*
* @return string|null
*/
public function getEntryContent()
{
return $this->entry->getEntryContent();
}
/**
* @return ZipSourceEntry
*/
public function getSourceEntry()
{
return $this->entry;
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
/**
* @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 string|null
*/
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;
}
/**
* 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;
}
}
}

View File

@ -1,57 +0,0 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipException;
/**
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipNewFileEntry extends ZipAbstractEntry
{
/** @var string Filename */
protected $file;
/**
* ZipNewEntry constructor.
*
* @param string $file
*
* @throws ZipException
*/
public function __construct($file)
{
parent::__construct();
if ($file === null) {
throw new InvalidArgumentException('file is null');
}
$file = (string) $file;
if (!is_file($file)) {
throw new ZipException("File {$file} does not exist.");
}
if (!is_readable($file)) {
throw new ZipException("The '{$file}' file could not be read. Check permissions.");
}
$this->file = $file;
}
/**
* Returns an string content of the given entry.
*
* @return string|null
*/
public function getEntryContent()
{
if (!is_file($this->file)) {
throw new RuntimeException("File {$this->file} does not exist.");
}
return file_get_contents($this->file);
}
}

View File

@ -1,96 +0,0 @@
<?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 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.
*
* @throws ZipException
*
* @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);
}
}
}

View File

@ -1,536 +0,0 @@
<?php
namespace PhpZip\Model;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraFieldsCollection;
use PhpZip\ZipFile;
/**
* ZIP file entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
interface ZipEntry
{
/** 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;
// (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
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setName($name);
/**
* @return int Get platform
*
* @deprecated Use {@see ZipEntry::getCreatedOS()}
*/
public function getPlatform();
/**
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*
* @deprecated Use {@see ZipEntry::setCreatedOS()}
*/
public function setPlatform($platform);
/**
* Returns created OS.
*
* @return int Get platform
*/
public function getCreatedOS();
/**
* Set created OS.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCreatedOS($platform);
/**
* @return int
*/
public function getExtractedOS();
/**
* Set extracted OS.
*
* @param int $platform
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setExtractedOS($platform);
/**
* @return int
*/
public function getSoftwareVersion();
/**
* @param int $softwareVersion
*
* @return ZipEntry
*/
public function setSoftwareVersion($softwareVersion);
/**
* Version needed to extract.
*
* @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
*
* @throws ZipException
*
* @return ZipEntry
*/
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
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setSize($size);
/**
* Return relative Offset Of Local File Header.
*
* @return int
*/
public function getOffset();
/**
* @param int $offset
*
* @throws ZipException
*
* @return ZipEntry
*/
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.
*
* @param mixed $general
*
* @throws ZipException
*
* @return ZipEntry
*
* @var int general
*/
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
*
* @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN
*
* @return ZipEntry
*/
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
*
* @return ZipEntry
*/
public function setDosTime($dosTime);
/**
* Returns the external file attributes.
*
* @return int the external file attributes
*/
public function getExternalAttributes();
/**
* Sets the internal file attributes.
*
* @param int $attributes the internal file attributes
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setInternalAttributes($attributes);
/**
* Returns the internal file attributes.
*
* @return int the internal file attributes
*/
public function getInternalAttributes();
/**
* Sets the external file attributes.
*
* @param int $externalAttributes the external file attributes
*
* @throws ZipException
*
* @return ZipEntry
*/
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
*
* @return ZipEntry
*/
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
*
* @throws ZipException
*
* @return ZipEntry
*/
public function setCrc($crc);
/**
* @return string
*/
public function getPassword();
/**
* Set password and encryption method from entry.
*
* @param string $password
* @param int|null $encryptionMethod
*
* @return ZipEntry
*/
public function setPassword($password, $encryptionMethod = null);
/**
* @return int
*/
public function getEncryptionMethod();
/**
* Set encryption method.
*
* @param int $encryptionMethod
*
* @throws ZipException
*
* @return ZipEntry
*
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192
*/
public function setEncryptionMethod($encryptionMethod);
/**
* Returns an string content of the given entry.
*
* @throws ZipException
*
* @return string|null
*/
public function getEntryContent();
/**
* @param int $compressionLevel
*
* @return ZipEntry
*/
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION);
/**
* @return int
*/
public function getCompressionLevel();
}

View File

@ -1,634 +0,0 @@
<?php
namespace PhpZip\Model;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\Fields\NtfsExtraField;
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
use PhpZip\Util\FilesUtil;
use PhpZip\ZipFile;
/**
* 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 $platformNames = [
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 $compressionMethodNames = [
ZipEntry::UNKNOWN => 'unknown',
ZipFile::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',
ZipFile::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 ZipException
* @noinspection PhpMissingBreakStatementInspection
*/
public function __construct(ZipEntry $entry)
{
$mtime = $entry->getTime();
$atime = null;
$ctime = null;
$field = $entry->getExtraFieldsCollection()->get(NtfsExtraField::getHeaderId());
if ($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 = $entry->getSize();
$this->compressedSize = $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();
$xattr = (($externalAttributes >> 16) & 0xFFFF);
switch ($entry->getCreatedOS()) {
case self::MADE_BY_MS_DOS:
case self::MADE_BY_WINDOWS_NTFS:
if ($entry->getCreatedOS() !== self::MADE_BY_MS_DOS ||
($xattr & self::UNX_IRWXU) !==
(self::UNX_IRUSR |
(!($externalAttributes & 1) << 7) |
(($externalAttributes & 0x10) << 2))
) {
$xattr = $externalAttributes & 0xFF;
$attributes = '.r.-... ';
$attributes[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
*
* @throws ZipException
*
* @return int
*/
private static function getMethodId(ZipEntry $entry)
{
$method = $entry->getMethod();
if ($entry->isEncrypted() && $entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
if ($field !== null) {
/** @var WinZipAesEntryExtraField $field */
$method = $field->getMethod();
}
}
return $method;
}
/**
* @param ZipEntry $entry
*
* @throws ZipException
*
* @return string
*/
private static function getEntryMethodName(ZipEntry $entry)
{
$return = '';
$compressionMethod = $entry->getMethod();
if ($entry->isEncrypted()) {
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
$return .= ucfirst(self::$compressionMethodNames[$entry->getMethod()]);
/** @var WinZipAesEntryExtraField|null $field */
$field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId());
if ($field !== null) {
$return .= '-' . $field->getKeyStrength();
$compressionMethod = $field->getMethod();
}
} else {
$return .= 'ZipCrypto';
}
$return .= ' ';
}
if (isset(self::$compressionMethodNames[$compressionMethod])) {
$return .= ucfirst(self::$compressionMethodNames[$compressionMethod]);
} else {
$return .= 'unknown';
}
return $return;
}
/**
* @param ZipEntry $entry
*
* @return string
*/
public static function getPlatformName(ZipEntry $entry)
{
if (isset(self::$platformNames[$entry->getCreatedOS()])) {
return self::$platformNames[$entry->getCreatedOS()];
}
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 bool
*/
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 bool
*/
public function isEncrypted()
{
return $this->encrypted;
}
/**
* @return string|null
*/
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()
. '}';
}
}

View File

@ -1,367 +0,0 @@
<?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\ZipFile;
/**
* 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 string|null
*/
public function getArchiveComment()
{
if ($this->archiveCommentChanged) {
return $this->archiveCommentChanges;
}
return $this->archiveComment;
}
/**
* @param string $comment
*/
public function setArchiveComment($comment)
{
if ($comment !== null && $comment !== '') {
$comment = (string) $comment;
$length = \strlen($comment);
if ($length > 0xffff) {
throw new InvalidArgumentException('Length comment out of range');
}
}
if ($comment !== $this->archiveComment) {
$this->archiveCommentChanges = $comment;
$this->archiveCommentChanged = true;
} else {
$this->archiveCommentChanged = false;
}
}
/**
* Specify a password for extracting files.
*
* @param string|null $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 string|null $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
*
* @throws ZipEntryNotFoundException
* @throws ZipException
*
* @return ZipChangesEntry|ZipEntry
*/
public function getEntryForChanges($entry)
{
$entry = $this->getEntry($entry);
if ($entry instanceof ZipSourceEntry) {
$entry = new ZipChangesEntry($entry);
$this->addEntry($entry);
}
return $entry;
}
/**
* @param string|ZipEntry $entryName
*
* @throws ZipEntryNotFoundException
*
* @return ZipEntry
*/
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.
*
* @see http://php.net/manual/en/countable.count.php
*
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
*
* @since 5.1.0
*/
public function count()
{
return \count($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], $this->inputEntries[$entry])) {
$this->outEntries[$entry] = $this->inputEntries[$entry];
return true;
}
return false;
}
/**
* @param int $encryptionMethod
*/
public function setEncryptionMethod($encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256)
{
$this->matcher()->all()->setEncryptionMethod($encryptionMethod);
}
/**
* @return ZipEntryMatcher
*/
public function matcher()
{
return new ZipEntryMatcher($this);
}
}

View File

@ -1,718 +0,0 @@
<?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\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\ZipFile;
/**
* Read zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipInputStream implements ZipInputStreamInterface
{
/** @var resource */
protected $in;
/** @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;
}
/**
* @throws ZipException
*
* @return ZipModel
*/
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 (
$signature !== ZipEntry::LOCAL_FILE_HEADER_SIG
&& $signature !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG
&& $signature !== EndOfCentralDirectory::END_OF_CD_SIG
) {
throw new ZipException(
'Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: ' . $signature
);
}
}
/**
* @throws ZipException
*
* @return EndOfCentralDirectory
*/
protected function readEndOfCentralDirectory()
{
if (!$this->findEndOfCentralDirectory()) {
throw new ZipException('Invalid zip file. The end of the central directory could not be found.');
}
$positionECD = ftell($this->in) - 4;
$buffer = fread($this->in, fstat($this->in)['size'] - $positionECD);
$unpack = unpack(
'vdiskNo/vcdDiskNo/vcdEntriesDisk/' .
'vcdEntries/VcdSize/VcdPos/vcommentLength',
substr($buffer, 0, 18)
);
if (
$unpack['diskNo'] !== 0 ||
$unpack['cdDiskNo'] !== 0 ||
$unpack['cdEntriesDisk'] !== $unpack['cdEntries']
) {
throw new ZipException(
'ZIP file spanning/splitting is not supported!'
);
}
// .ZIP file comment (variable sizeECD)
$comment = null;
if ($unpack['commentLength'] > 0) {
$comment = substr($buffer, 18, $unpack['commentLength']);
}
// Check for ZIP64 End Of Central Directory Locator exists.
$zip64ECDLocatorPosition = $positionECD - EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_LEN;
fseek($this->in, $zip64ECDLocatorPosition);
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
if ($zip64ECDLocatorPosition > 0 && unpack(
'V',
fread($this->in, 4)
)[1] === EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG) {
$positionECD = $this->findZip64ECDPosition();
$endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD);
$endCentralDirectory->setComment($comment);
} else {
$endCentralDirectory = new EndOfCentralDirectory(
$unpack['cdEntries'],
$unpack['cdPos'],
$unpack['cdSize'],
false,
$comment
);
}
return $endCentralDirectory;
}
/**
* @throws ZipException
*
* @return bool
*/
protected function findEndOfCentralDirectory()
{
$max = fstat($this->in)['size'] - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
if ($max < 0) {
throw new ZipException('Too short to be a zip file');
}
$min = $max >= 0xffff ? $max - 0xffff : 0;
// Search for End of central directory record.
for ($position = $max; $position >= $min; $position--) {
fseek($this->in, $position);
// end of central dir signature 4 bytes (0x06054b50)
if (unpack('V', fread($this->in, 4))[1] !== EndOfCentralDirectory::END_OF_CD_SIG) {
continue;
}
return true;
}
return false;
}
/**
* Read Zip64 end of central directory locator and returns
* Zip64 end of central directory position.
*
* number of the disk with the
* start of the zip64 end of
* central directory 4 bytes
* relative offset of the zip64
* end of central directory record 8 bytes
* total number of disks 4 bytes
*
* @throws ZipException
*
* @return int Zip64 End Of Central Directory position
*/
protected function findZip64ECDPosition()
{
$diskNo = unpack('V', fread($this->in, 4))[1];
$zip64ECDPos = PackUtil::unpackLongLE(fread($this->in, 8));
$totalDisks = unpack('V', fread($this->in, 4))[1];
if ($diskNo !== 0 || $totalDisks > 1) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
return $zip64ECDPos;
}
/**
* Read zip64 end of central directory locator and zip64 end
* of central directory record.
*
* zip64 end of central dir
* signature 4 bytes (0x06064b50)
* size of zip64 end of central
* directory record 8 bytes
* version made by 2 bytes
* version needed to extract 2 bytes
* number of this disk 4 bytes
* number of the disk with the
* start of the central directory 4 bytes
* total number of entries in the
* central directory on this disk 8 bytes
* total number of entries in the
* central directory 8 bytes
* size of the central directory 8 bytes
* offset of start of central
* directory with respect to
* the starting disk number 8 bytes
* zip64 extensible data sector (variable size)
*
* @param int $zip64ECDPosition
*
* @throws ZipException
*
* @return EndOfCentralDirectory
*/
protected function readZip64EndOfCentralDirectory($zip64ECDPosition)
{
fseek($this->in, $zip64ECDPosition);
$buffer = fread($this->in, 56 /* zip64 end of cd rec length */);
if (unpack('V', $buffer)[1] !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG) {
throw new ZipException('Expected ZIP64 End Of Central Directory Record!');
}
$data = unpack(
'VdiskNo/VcdDiskNo',
substr($buffer, 16)
);
$cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8));
$entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8));
$cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8));
$cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8));
if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) {
throw new ZipException('ZIP file spanning/splitting is not supported!');
}
if ($entryCount < 0 || $entryCount > 0x7fffffff) {
throw new ZipException('Total Number Of Entries In The Central Directory out of range!');
}
// skip zip64 extensible data sector (variable sizeEndCD)
return new EndOfCentralDirectory(
$entryCount,
$cdPos,
$cdSize,
true
);
}
/**
* 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
*
* @throws ZipException
*
* @return ZipEntry[]
*/
protected function mountCentralDirectory(EndOfCentralDirectory $endOfCentralDirectory)
{
$entries = [];
fseek($this->in, $endOfCentralDirectory->getCdOffset());
if (!($cdStream = fopen('php://temp', 'w+b'))) {
throw new ZipException('Temp resource can not open from write');
}
stream_copy_to_stream($this->in, $cdStream, $endOfCentralDirectory->getCdSize());
rewind($cdStream);
for ($numEntries = $endOfCentralDirectory->getEntryCount(); $numEntries > 0; $numEntries--) {
$entry = $this->readCentralDirectoryEntry($cdStream);
$entries[$entry->getName()] = $entry;
}
fclose($cdStream);
return $entries;
}
/**
* Read central directory entry.
*
* central file header signature 4 bytes (0x02014b50)
* version made by 2 bytes
* version needed to extract 2 bytes
* general purpose bit flag 2 bytes
* compression method 2 bytes
* last mod file time 2 bytes
* last mod file date 2 bytes
* crc-32 4 bytes
* compressed size 4 bytes
* uncompressed size 4 bytes
* file name length 2 bytes
* extra field length 2 bytes
* file comment length 2 bytes
* disk number start 2 bytes
* internal file attributes 2 bytes
* external file attributes 4 bytes
* relative offset of local header 4 bytes
*
* file name (variable size)
* extra field (variable size)
* file comment (variable size)
*
* @param resource $stream
*
* @throws ZipException
*
* @return ZipEntry
*/
public function readCentralDirectoryEntry($stream)
{
if (unpack('V', fread($stream, 4))[1] !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
throw new ZipException('Corrupt zip file. Cannot read central dir entry.');
}
$data = unpack(
'vversionMadeBy/vversionNeededToExtract/' .
'vgeneralPurposeBitFlag/vcompressionMethod/' .
'VlastModFile/Vcrc/VcompressedSize/' .
'VuncompressedSize/vfileNameLength/vextraFieldLength/' .
'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' .
'VexternalFileAttributes/VoffsetLocalHeader',
fread($stream, 42)
);
$createdOS = ($data['versionMadeBy'] & 0xFF00) >> 8;
$softwareVersion = $data['versionMadeBy'] & 0x00FF;
$extractOS = ($data['versionNeededToExtract'] & 0xFF00) >> 8;
$extractVersion = $data['versionNeededToExtract'] & 0x00FF;
$name = fread($stream, $data['fileNameLength']);
$extra = '';
if ($data['extraFieldLength'] > 0) {
$extra = fread($stream, $data['extraFieldLength']);
}
$comment = null;
if ($data['fileCommentLength'] > 0) {
$comment = fread($stream, $data['fileCommentLength']);
}
$entry = new ZipSourceEntry($this);
$entry->setName($name);
$entry->setCreatedOS($createdOS);
$entry->setSoftwareVersion($softwareVersion);
$entry->setVersionNeededToExtract($extractVersion);
$entry->setExtractedOS($extractOS);
$entry->setMethod($data['compressionMethod']);
$entry->setGeneralPurposeBitFlags($data['generalPurposeBitFlag']);
$entry->setDosTime($data['lastModFile']);
$entry->setCrc($data['crc']);
$entry->setCompressedSize($data['compressedSize']);
$entry->setSize($data['uncompressedSize']);
$entry->setInternalAttributes($data['internalFileAttributes']);
$entry->setExternalAttributes($data['externalFileAttributes']);
$entry->setOffset($data['offsetLocalHeader']);
$entry->setComment($comment);
$entry->setExtra($extra);
return $entry;
}
/**
* @param ZipEntry $entry
*
* @throws ZipException
*
* @return string
*/
public function readEntryContent(ZipEntry $entry)
{
if ($entry->isDirectory()) {
return null;
}
if (!($entry instanceof ZipSourceEntry)) {
throw new InvalidArgumentException('entry must be ' . ZipSourceEntry::class);
}
$isEncrypted = $entry->isEncrypted();
if ($isEncrypted && $entry->getPassword() === null) {
throw new ZipException('Can not password from entry ' . $entry->getName());
}
$startPos = $pos = $entry->getOffset();
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'];
if ($entry->getCrc() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('Missing crc for entry %s', $entry->getName()));
}
$method = $entry->getMethod();
fseek($this->in, $pos);
// Get raw entry content
$compressedSize = $entry->getCompressedSize();
$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(ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
}
if (!$skipCheckCrc) {
// Check CRC32 in the Local File Header or Data Descriptor.
$localCrc = null;
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
// The CRC32 is in the Data Descriptor after the compressed size.
// Note the Data Descriptor's Signature is optional:
// 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 = fread($this->in, 4)[1];
}
if (\PHP_INT_SIZE === 4) {
if (sprintf('%u', $entry->getCrc()) === sprintf('%u', $localCrc)) {
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
}
} elseif ($localCrc !== $entry->getCrc()) {
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc);
}
}
}
switch ($method) {
case ZipFile::METHOD_STORED:
break;
case ZipFile::METHOD_DEFLATED:
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$content = @gzinflate($content);
break;
case ZipFile::METHOD_BZIP2:
if (!\extension_loaded('bz2')) {
throw new ZipException('Extension bzip2 not install');
}
/** @noinspection PhpComposerExtensionStubsInspection */
$content = bzdecompress($content);
if (\is_int($content)) { // decompress error
$content = false;
}
break;
default:
throw new 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);
if (sprintf('%u', $entry->getCrc()) !== sprintf('%u', $localCrc)) {
if ($isEncrypted) {
throw new ZipAuthenticationException(
sprintf(
'Invalid password for zip entry "%s"',
$entry->getName()
)
);
}
throw new Crc32Exception($entry->getName(), $entry->getCrc(), $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();
if ($pos === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('Missing local header offset for entry %s', $entry->getName()));
}
$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() === ZipFile::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();
$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;
}
}
}

View File

@ -1,63 +0,0 @@
<?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();
/**
* Read central directory entry.
*
* @param resource $stream
*
* @throws ZipException
*
* @return ZipEntry
*/
public function readCentralDirectoryEntry($stream);
/**
* @param ZipEntry $entry
*
* @throws ZipException
*
* @return string
*/
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();
}

View File

@ -1,606 +0,0 @@
<?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\ZipFile;
/**
* 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() === ZipFile::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->getExtractedOS() << 8) | $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);
}
if ($entry->getCrc() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('No crc for entry %s', $entry->getName()));
}
if ($entry->getSize() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('No uncompressed size for entry %s', $entry->getName()));
}
if ($entry->getCompressedSize() === ZipEntry::UNKNOWN) {
throw new ZipException(sprintf('No compressed size for entry %s', $entry->getName()));
}
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
// data descriptor signature 4 bytes (0x08074b50)
// crc-32 4 bytes
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
*
* @throws ZipException
*
* @return string|null
*/
protected function entryCommitChangesAndReturnContent(ZipEntry $entry)
{
if ($entry->getCreatedOS() === ZipEntry::UNKNOWN) {
$entry->setCreatedOS(ZipEntry::PLATFORM_UNIX);
}
if ($entry->getSoftwareVersion() === ZipEntry::UNKNOWN) {
$entry->setSoftwareVersion(63);
}
if ($entry->getExtractedOS() === ZipEntry::UNKNOWN) {
$entry->setExtractedOS(ZipEntry::PLATFORM_UNIX);
}
if ($entry->getTime() === ZipEntry::UNKNOWN) {
$entry->setTime(time());
}
$method = $entry->getMethod();
$encrypted = $entry->isEncrypted();
// See appendix D of PKWARE's ZIP File Format Specification.
$utf8 = true;
if ($encrypted && $entry->getPassword() === null) {
throw new ZipException(sprintf('Password not set for entry %s', $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 ZipFile::METHOD_STORED:
break;
case ZipFile::METHOD_DEFLATED:
$entryContent = gzdeflate($entryContent, $entry->getCompressionLevel());
break;
case ZipFile::METHOD_BZIP2:
$compressionLevel = $entry->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ?
ZipEntry::LEVEL_DEFAULT_BZIP2_COMPRESSION :
$entry->getCompressionLevel();
/** @noinspection PhpComposerExtensionStubsInspection */
$entryContent = bzcompress($entryContent, $compressionLevel);
if (\is_int($entryContent)) {
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 === ZipFile::METHOD_DEFLATED) {
$bit1 = false;
$bit2 = false;
switch ($entry->getCompressionLevel()) {
case ZipFile::LEVEL_BEST_COMPRESSION:
$bit1 = true;
break;
case ZipFile::LEVEL_FAST:
$bit2 = true;
break;
case ZipFile::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(),
[
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128,
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192,
ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256,
],
true
)) {
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod(
$entry->getEncryptionMethod()
); // size bits
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
$field->setKeyStrength($keyStrength);
$field->setMethod($method);
$size = $entry->getSize();
if ($size >= 20 && $method !== ZipFile::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() === ZipFile::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
*
* @throws ZipException
*
* @return string
*/
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
{
if ($content !== null) {
$entryContent = gzdeflate($content, $entry->getCompressionLevel());
if (\strlen($entryContent) < \strlen($content)) {
$entry->setMethod(ZipFile::METHOD_DEFLATED);
return $entryContent;
}
$entry->setMethod(ZipFile::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->getCreatedOS() << 8) | $entry->getSoftwareVersion(),
// version needed to extract 2 bytes
($entry->getExtractedOS() << 8) | $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
$entry->getInternalAttributes(),
// 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());
}
}
/**
* @param int $centralDirectoryOffset
*/
protected function writeEndOfCentralDirectoryRecord($centralDirectoryOffset)
{
$cdEntriesCount = \count($this->zipModel);
$position = ftell($this->out);
$centralDirectorySize = $position - $centralDirectoryOffset;
$cdEntriesZip64 = $cdEntriesCount > 0xFFFF;
$cdSizeZip64 = $centralDirectorySize > 0xFFFFFFFF;
$cdOffsetZip64 = $centralDirectoryOffset > 0xFFFFFFFF;
$zip64Required = $cdEntriesZip64 || $cdSizeZip64 || $cdOffsetZip64;
if ($zip64Required) {
$zip64EndOfCentralDirectoryOffset = ftell($this->out);
// find max software version, version needed to extract and most common platform
list($softwareVersion, $versionNeededToExtract) = array_reduce(
$this->zipModel->getEntries(),
static function (array $carry, ZipEntry $entry) {
$carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF);
$carry[1] = max($carry[1], $entry->getVersionNeededToExtract() & 0xFF);
return $carry;
},
[10 /* simple file min ver */, 45 /* zip64 ext min ver */]
);
$createdOS = $extractedOS = ZipEntry::PLATFORM_FAT;
$versionMadeBy = ($createdOS << 8) | max($softwareVersion, 45 /* zip64 ext min ver */);
$versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, 45 /* zip64 ext min ver */);
// signature 4 bytes (0x06064b50)
fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG));
// size of zip64 end of central
// directory record 8 bytes
fwrite($this->out, PackUtil::packLongLE(44));
fwrite(
$this->out,
pack(
'vvVV',
// version made by 2 bytes
$versionMadeBy & 0xFFFF,
// version needed to extract 2 bytes
$versionExtractedBy & 0xFFFF,
// number of this disk 4 bytes
0,
// number of the disk with the
// start of the central directory 4 bytes
0
)
);
// total number of entries in the
// central directory on this disk 8 bytes
fwrite($this->out, PackUtil::packLongLE($cdEntriesCount));
// total number of entries in the
// central directory 8 bytes
fwrite($this->out, PackUtil::packLongLE($cdEntriesCount));
// size of the central directory 8 bytes
fwrite($this->out, PackUtil::packLongLE($centralDirectorySize));
// offset of start of central
// directory with respect to
// the starting disk number 8 bytes
fwrite($this->out, PackUtil::packLongLE($centralDirectoryOffset));
// write zip64 end of central directory locator
fwrite(
$this->out,
pack(
'VV',
// zip64 end of central dir locator
// signature 4 bytes (0x07064b50)
EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG,
// number of the disk with the
// start of the zip64 end of
// central directory 4 bytes
0
)
);
// relative offset of the zip64
// end of central directory record 8 bytes
fwrite($this->out, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset));
// total number of disks 4 bytes
fwrite($this->out, pack('V', 1));
}
$comment = $this->zipModel->getArchiveComment();
$commentLength = $comment !== null ? \strlen($comment) : 0;
fwrite(
$this->out,
pack(
'VvvvvVVv',
// end of central dir signature 4 bytes (0x06054b50)
EndOfCentralDirectory::END_OF_CD_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
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
// total number of entries in
// the central directory 2 bytes
$cdEntriesZip64 ? 0xFFFF : $cdEntriesCount,
// size of the central directory 4 bytes
$cdSizeZip64 ? 0xFFFFFFFF : $centralDirectorySize,
// offset of start of central
// directory with respect to
// the starting disk number 4 bytes
$cdOffsetZip64 ? 0xFFFFFFFF : $centralDirectoryOffset,
// .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;
}
}

View File

@ -1,29 +0,0 @@
<?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();
}

View File

@ -1,27 +0,0 @@
<?php
namespace PhpZip\Util;
/**
* Crypto Utils.
*
* @deprecated
*/
class CryptoUtil
{
/**
* Returns random bytes.
*
* @param int $length
*
* @throws \Exception
*
* @return string
*
* @deprecated Use random_bytes()
*/
final public static function randomBytes($length)
{
return random_bytes($length);
}
}

View File

@ -1,82 +0,0 @@
<?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 ($dosTime < self::MIN_DOS_TIME) {
$dosTime = self::MIN_DOS_TIME;
} elseif ($dosTime > self::MAX_DOS_TIME) {
$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
*
* @throws ZipException if unix timestamp is negative
*
* @return int a DOS date/time value reflecting the local time zone and
* rounded down to even seconds
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME
*/
public static function toDosTime($unixTimestamp)
{
if ($unixTimestamp < 0) {
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;
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace PhpZip\Util;
/**
* String Util.
*
* @internal
*/
final class StringUtil
{
/**
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function startsWith($haystack, $needle)
{
return $needle === '' || strrpos($haystack, $needle, -\strlen($haystack)) !== false;
}
/**
* @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);
}
}

View File

@ -1,686 +0,0 @@
<?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
*
* @throws ZipException if can't open file
*
* @return ZipFileInterface
*/
public function openFile($filename);
/**
* Open zip archive from raw string data.
*
* @param string $data
*
* @throws ZipException if can't open temp stream
*
* @return ZipFileInterface
*/
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 string|null $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
*
* @throws ZipEntryNotFoundException
*
* @return bool
*/
public function isDirectory($entryName);
/**
* Returns entry comment.
*
* @param string $entryName
*
* @throws ZipEntryNotFoundException
*
* @return string
*/
public function getEntryComment($entryName);
/**
* Set entry comment.
*
* @param string $entryName
* @param string|null $comment
*
* @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
*/
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
*
* @throws ZipEntryNotFoundException
*
* @return ZipInfo
*/
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.
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function extractTo($destination, $entries = null);
/**
* Add entry from the string.
*
* @param string $localName zip entry name
* @param string $contents string contents
* @param 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 ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFromString($localName, $contents, $compressionMethod = null);
/**
* Add entry from the file.
*
* @param string $filename destination file
* @param string|null $localName zip Entry name
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @return ZipFileInterface
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFile($filename, $localName = null, $compressionMethod = null);
/**
* Add entry from the stream.
*
* @param resource $stream stream resource
* @param string $localName zip Entry name
* @param 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 ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::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 ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::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 ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null);
/**
* Add files from glob pattern.
*
* @param string $inputDir Input directory
* @param string $globPattern glob pattern
* @param string|null $localPath add files to this directory, or the root
* @param int|null $compressionMethod Compression method.
* Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or
* ZipFile::METHOD_BZIP2. If null, then auto choosing method.
*
* @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
*
* @throws ZipEntryNotFoundException
*
* @return ZipFileInterface
*/
public function rename($oldName, $newName);
/**
* Delete entry by name.
*
* @param string $entryName zip Entry name
*
* @throws ZipEntryNotFoundException if entry not found
*
* @return ZipFileInterface
*/
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
*
* @return ZipFileInterface
*
* @see ZipFile::LEVEL_SUPER_FAST
* @see ZipFile::LEVEL_FAST
* @see ZipFile::LEVEL_BEST_COMPRESSION
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
*/
public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
/**
* @param string $entryName
* @param int $compressionLevel
*
* @throws ZipException
*
* @return ZipFileInterface
*
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
* @see ZipFile::LEVEL_SUPER_FAST
* @see ZipFile::LEVEL_FAST
* @see ZipFile::LEVEL_BEST_COMPRESSION
*/
public function setCompressionLevelEntry($entryName, $compressionLevel);
/**
* @param string $entryName
* @param int $compressionMethod
*
* @throws ZipException
*
* @return ZipFileInterface
*
* @see ZipFile::METHOD_STORED
* @see ZipFile::METHOD_DEFLATED
* @see ZipFile::METHOD_BZIP2
*/
public function setCompressionMethodEntry($entryName, $compressionMethod);
/**
* zipalign is optimization to Android application (APK) files.
*
* @param int|null $align
*
* @return ZipFileInterface
*
* @see 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 ZipFile::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 ZipFile::setPassword()
*/
public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256);
/**
* Sets a new password for all files in the archive.
*
* @param string $password
* @param 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 ZipFile::disableEncryption()
*/
public function withoutPassword();
/**
* Disable encryption for all entries that are already in the archive.
*
* @return ZipFileInterface
*/
public function disableEncryption();
/**
* Disable encryption of an entry defined by its name.
*
* @param string $entryName
*
* @return ZipFileInterface
*/
public function disableEncryptionEntry($entryName);
/**
* Undo all changes done in the archive.
*
* @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
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function saveAsFile($filename);
/**
* Save as stream.
*
* @param resource $handle Output stream resource
*
* @throws ZipException
*
* @return ZipFileInterface
*/
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.
*
* @throws ZipException
*
* @return ZipFileInterface
*/
public function rewrite();
/**
* Close zip archive and release input stream.
*/
public function close();
}

77
src/Util/CryptoUtil.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace PhpZip\Util;
use PhpZip\Exception\RuntimeException;
/**
* Crypto Utils.
*
* @internal
*/
class CryptoUtil
{
/**
* Returns random bytes.
*
* @param int $length
*
* @throws \Exception
*
* @return string
*
* @deprecated Use random_bytes()
*/
final public static function randomBytes($length)
{
return random_bytes($length);
}
/**
* Decrypt AES-CTR.
*
* @param string $data Encrypted data
* @param string $key Aes key
* @param string $iv Aes IV
*
* @return string Raw data
*/
public static function decryptAesCtr($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);
}
if (\extension_loaded('mcrypt')) {
return mcrypt_decrypt(\MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
}
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
/**
* Encrypt AES-CTR.
*
* @param string $data Raw data
* @param string $key Aes key
* @param string $iv Aes IV
*
* @return string Encrypted data
*/
public static function encryptAesCtr($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);
}
if (\extension_loaded('mcrypt')) {
return mcrypt_encrypt(\MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);
}
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace PhpZip\Util;
/**
* Convert unix timestamp values to DOS date/time values and vice versa.
*
* The DOS date/time format is a bitmask:
*
* 24 16 8 0
* +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
* |Y|Y|Y|Y|Y|Y|Y|M| |M|M|M|D|D|D|D|D| |h|h|h|h|h|m|m|m| |m|m|m|s|s|s|s|s|
* +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
* \___________/\________/\_________/ \________/\____________/\_________/
* year month day hour minute second
*
* The year is stored as an offset from 1980.
* Seconds are stored in two-second increments.
* (So if the "second" value is 15, it actually represents 30 seconds.)
*
* @see https://docs.microsoft.com/ru-ru/windows/win32/api/winbase/nf-winbase-filetimetodosdatetime?redirectedfrom=MSDN
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*
* @internal
*/
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 msDosToUnix($dosTime)
{
if ($dosTime <= self::MIN_DOS_TIME) {
$dosTime = 0;
} elseif ($dosTime > self::MAX_DOS_TIME) {
$dosTime = self::MAX_DOS_TIME;
}
// date_default_timezone_set('UTC');
return mktime(
(($dosTime >> 11) & 0x1f), // hours
(($dosTime >> 5) & 0x3f), // minutes
(($dosTime << 1) & 0x3e), // seconds
(($dosTime >> 21) & 0x0f), // month
(($dosTime >> 16) & 0x1f), // day
((($dosTime >> 25) & 0x7f) + 1980) // 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
*/
public static function unixToMsDos($unixTimestamp)
{
if ($unixTimestamp < 0) {
throw new \InvalidArgumentException('Negative unix timestamp: ' . $unixTimestamp);
}
$date = getdate($unixTimestamp);
$dosTime = (
(($date['year'] - 1980) << 25) |
($date['mon'] << 21) |
($date['mday'] << 16) |
($date['hours'] << 11) |
($date['minutes'] << 5) |
($date['seconds'] >> 1)
);
if ($dosTime <= self::MIN_DOS_TIME) {
$dosTime = 0;
}
return $dosTime;
}
}

108
src/Util/FileAttribUtil.php Normal file
View File

@ -0,0 +1,108 @@
<?php
namespace PhpZip\Util;
use PhpZip\Constants\DosAttrs;
use PhpZip\Constants\UnixStat;
/**
* Class FileAttribUtil.
*
* @internal
*/
class FileAttribUtil implements DosAttrs, UnixStat
{
/**
* Get DOS mode,.
*
* @param int $xattr
*
* @return string
*/
public static function getDosMode($xattr)
{
$xattr = (int) $xattr;
$mode = (($xattr & self::DOS_DIRECTORY) === self::DOS_DIRECTORY) ? 'd' : '-';
$mode .= (($xattr & self::DOS_ARCHIVE) === self::DOS_ARCHIVE) ? 'a' : '-';
$mode .= (($xattr & self::DOS_READ_ONLY) === self::DOS_READ_ONLY) ? 'r' : '-';
$mode .= (($xattr & self::DOS_HIDDEN) === self::DOS_HIDDEN) ? 'h' : '-';
$mode .= (($xattr & self::DOS_SYSTEM) === self::DOS_SYSTEM) ? 's' : '-';
$mode .= (($xattr & self::DOS_LABEL) === self::DOS_LABEL) ? 'l' : '-';
return $mode;
}
/**
* @param int $permission
*
* @return string
*/
public static function getUnixMode($permission)
{
$mode = '';
$permission = (int) $permission;
switch ($permission & self::UNX_IFMT) {
case self::UNX_IFDIR:
$mode .= 'd';
break;
case self::UNX_IFREG:
$mode .= '-';
break;
case self::UNX_IFLNK:
$mode .= 'l';
break;
case self::UNX_IFBLK:
$mode .= 'b';
break;
case self::UNX_IFCHR:
$mode .= 'c';
break;
case self::UNX_IFIFO:
$mode .= 'p';
break;
case self::UNX_IFSOCK:
$mode .= 's';
break;
default:
$mode .= '?';
break;
}
$mode .= ($permission & self::UNX_IRUSR) ? 'r' : '-';
$mode .= ($permission & self::UNX_IWUSR) ? 'w' : '-';
if ($permission & self::UNX_IXUSR) {
$mode .= ($permission & self::UNX_ISUID) ? 's' : 'x';
} else {
$mode .= ($permission & self::UNX_ISUID) ? 'S' : '-'; // S==undefined
}
$mode .= ($permission & self::UNX_IRGRP) ? 'r' : '-';
$mode .= ($permission & self::UNX_IWGRP) ? 'w' : '-';
if ($permission & self::UNX_IXGRP) {
$mode .= ($permission & self::UNX_ISGID) ? 's' : 'x';
} // == self::UNX_ENFMT
else {
$mode .= ($permission & self::UNX_ISGID) ? 'S' : '-';
} // SunOS 4.1.x
$mode .= ($permission & self::UNX_IROTH) ? 'r' : '-';
$mode .= ($permission & self::UNX_IWOTH) ? 'w' : '-';
if ($permission & self::UNX_IXOTH) {
$mode .= ($permission & self::UNX_ISVTX) ? 't' : 'x';
} // "sticky bit"
else {
$mode .= ($permission & self::UNX_ISVTX) ? 'T' : '-';
} // T==undefined
return $mode;
}
}

View File

@ -151,20 +151,22 @@ final class FilesUtil
*/
public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
{
$directoryIterator = $recursive ?
new \RecursiveDirectoryIterator($inputDir) :
new \DirectoryIterator($inputDir);
if ($recursive) {
$directoryIterator = new \RecursiveDirectoryIterator($inputDir);
if (!empty($ignoreFiles)) {
$directoryIterator = $recursive ?
new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles) :
new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
if (!empty($ignoreFiles)) {
$directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
}
$iterator = new \RecursiveIteratorIterator($directoryIterator);
} else {
$directoryIterator = new \DirectoryIterator($inputDir);
if (!empty($ignoreFiles)) {
$directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
}
$iterator = new \IteratorIterator($directoryIterator);
}
$iterator = $recursive ?
new \RecursiveIteratorIterator($directoryIterator) :
new \IteratorIterator($directoryIterator);
$fileList = [];
foreach ($iterator as $file) {
@ -215,10 +217,14 @@ final class FilesUtil
*/
public static function regexFileSearch($folder, $pattern, $recursive = true)
{
$directoryIterator = $recursive ? new \RecursiveDirectoryIterator($folder) : new \DirectoryIterator($folder);
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator(
$directoryIterator
);
if ($recursive) {
$directoryIterator = new \RecursiveDirectoryIterator($folder);
$iterator = new \RecursiveIteratorIterator($directoryIterator);
} else {
$directoryIterator = new \DirectoryIterator($folder);
$iterator = new \IteratorIterator($directoryIterator);
}
$regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
$fileList = [];
@ -275,4 +281,186 @@ final class FilesUtil
)
);
}
/**
* Returns whether the file path is an absolute path.
*
* @param string $file A file path
*
* @return bool
*
* @see source symfony filesystem component
*/
public static function isAbsolutePath($file)
{
return strspn($file, '/\\', 0, 1)
|| (
\strlen($file) > 3 && ctype_alpha($file[0])
&& $file[1] === ':'
&& strspn($file, '/\\', 2, 1)
)
|| parse_url($file, \PHP_URL_SCHEME) !== null;
}
/**
* @param string $linkPath
* @param string $target
*
* @return bool
*/
public static function symlink($target, $linkPath)
{
if (\DIRECTORY_SEPARATOR === '\\') {
$linkPath = str_replace('/', '\\', $linkPath);
$target = str_replace('/', '\\', $target);
$abs = null;
if (!self::isAbsolutePath($target)) {
$abs = realpath(\dirname($linkPath) . \DIRECTORY_SEPARATOR . $target);
if (\is_string($abs)) {
$target = $abs;
}
}
}
if (!symlink($target, $linkPath)) {
if (\DIRECTORY_SEPARATOR === '\\' && is_file($target)) {
return copy($target, $linkPath);
}
return false;
}
return true;
}
/**
* @param string $file
*
* @return bool
*/
public static function isBadCompressionFile($file)
{
$badCompressFileExt = [
'dic',
'dng',
'f4v',
'flipchart',
'h264',
'lrf',
'mobi',
'mts',
'nef',
'pspimage',
];
$ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION));
if (\in_array($ext, $badCompressFileExt, true)) {
return true;
}
$mimeType = self::getMimeTypeFromFile($file);
return self::isBadCompressionMimeType($mimeType);
}
/**
* @param string $mimeType
*
* @return bool
*/
public static function isBadCompressionMimeType($mimeType)
{
static $badDeflateCompMimeTypes = [
'application/epub+zip',
'application/gzip',
'application/vnd.debian.binary-package',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.text-master',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.rn-realmedia',
'application/x-7z-compressed',
'application/x-arj',
'application/x-bzip2',
'application/x-hwp',
'application/x-lzip',
'application/x-lzma',
'application/x-ms-reader',
'application/x-rar',
'application/x-rpm',
'application/x-stuffit',
'application/x-tar',
'application/x-xz',
'application/zip',
'application/zlib',
'audio/flac',
'audio/mpeg',
'audio/ogg',
'audio/vnd.dolby.dd-raw',
'audio/webm',
'audio/x-ape',
'audio/x-hx-aac-adts',
'audio/x-m4a',
'audio/x-m4a',
'audio/x-wav',
'image/gif',
'image/heic',
'image/jp2',
'image/jpeg',
'image/png',
'image/vnd.djvu',
'image/webp',
'image/x-canon-cr2',
'video/ogg',
'video/webm',
'video/x-matroska',
'video/x-ms-asf',
'x-epoc/x-sisx-app',
];
if (\in_array($mimeType, $badDeflateCompMimeTypes, true)) {
return true;
}
return false;
}
/**
* @param string $file
*
* @return string
*
* @noinspection PhpComposerExtensionStubsInspection
*/
public static function getMimeTypeFromFile($file)
{
if (\function_exists('mime_content_type')) {
return mime_content_type($file);
}
return 'application/octet-stream';
}
/**
* @param string $contents
*
* @return string
* @noinspection PhpComposerExtensionStubsInspection
*/
public static function getMimeTypeFromString($contents)
{
$contents = (string) $contents;
$finfo = new \finfo(\FILEINFO_MIME);
$mimeType = $finfo->buffer($contents);
if ($mimeType === false) {
$mimeType = 'application/octet-stream';
}
return explode(';', $mimeType)[0];
}
}

View File

@ -66,7 +66,6 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
/**
* @return IgnoreFilesRecursiveFilterIterator
* @noinspection PhpMissingParentCallCommonInspection
*/
public function getChildren()
{

View File

@ -13,13 +13,13 @@ namespace PhpZip\Util;
final class PackUtil
{
/**
* @param int|string $longValue
* @param int $longValue
*
* @return string
*/
public static function packLongLE($longValue)
{
if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) {
if (\PHP_VERSION_ID >= 506030) {
return pack('P', $longValue);
}
@ -33,13 +33,13 @@ final class PackUtil
}
/**
* @param string|int $value
* @param string $value
*
* @return int
*/
public static function unpackLongLE($value)
{
if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) {
if (\PHP_VERSION_ID >= 506030) {
return unpack('P', $value)[1];
}
$unpack = unpack('Va/Vb', $value);

Some files were not shown because too many files have changed in this diff Show More