mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-01-17 07:18:14 +01:00
solving problems with lack of memory, the new ZipReader and ZipWriter class, adds .phpstorm.meta.php
#13 #16 #27 #31 #41
This commit is contained in:
parent
28e220718c
commit
f377b889b5
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -2,5 +2,6 @@
|
||||
.github export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
.php_cs export-ignore
|
||||
phpunit.xml export-ignore
|
||||
tests export-ignore
|
||||
tests export-ignore
|
||||
|
3
.php_cs
3
.php_cs
@ -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',
|
||||
|
62
.phpstorm.meta.php
Normal file
62
.phpstorm.meta.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?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"));
|
||||
|
||||
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"));
|
||||
|
||||
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"));
|
||||
|
||||
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\ZipFileI::outputAsResponse(), 2, argumentsSet("zip_mime_types"));
|
||||
expectedArguments(\PhpZip\ZipFileI::outputAsResponse(), 3, argumentsSet("bool"));
|
||||
}
|
42
CHANGELOG.md
42
CHANGELOG.md
@ -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)`.
|
234
README.RU.md
234
README.RU.md
@ -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
300
README.md
@ -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
|
||||
|
@ -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": "*",
|
||||
"phpunit/phpunit": "^4.8|^5.7",
|
||||
"zendframework/zend-diactoros": "^1.4"
|
||||
"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": {
|
||||
|
11
phpunit.xml
11
phpunit.xml
@ -10,9 +10,16 @@
|
||||
</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>
|
||||
|
33
src/Constants/DosAttrs.php
Normal file
33
src/Constants/DosAttrs.php
Normal 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;
|
||||
}
|
105
src/Constants/DosCodePage.php
Normal file
105
src/Constants/DosCodePage.php
Normal 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;
|
||||
}
|
||||
}
|
71
src/Constants/GeneralPurposeBitFlag.php
Normal file
71
src/Constants/GeneralPurposeBitFlag.php
Normal 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
|
||||
}
|
84
src/Constants/UnixStat.php
Normal file
84
src/Constants/UnixStat.php
Normal 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;
|
||||
}
|
54
src/Constants/ZipCompressionLevel.php
Normal file
54
src/Constants/ZipCompressionLevel.php
Normal 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;
|
||||
}
|
102
src/Constants/ZipCompressionMethod.php
Normal file
102
src/Constants/ZipCompressionMethod.php
Normal 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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
99
src/Constants/ZipConstants.php
Normal file
99
src/Constants/ZipConstants.php
Normal 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;
|
||||
}
|
93
src/Constants/ZipEncryptionMethod.php
Normal file
93
src/Constants/ZipEncryptionMethod.php
Normal 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
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
29
src/Constants/ZipOptions.php
Normal file
29
src/Constants/ZipOptions.php
Normal 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';
|
||||
}
|
53
src/Constants/ZipPlatform.php
Normal file
53
src/Constants/ZipPlatform.php
Normal 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';
|
||||
}
|
||||
}
|
81
src/Constants/ZipVersion.php
Normal file
81
src/Constants/ZipVersion.php
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ namespace PhpZip\Exception;
|
||||
/**
|
||||
* Class ZipUnsupportMethodException.
|
||||
*/
|
||||
class ZipUnsupportMethodException extends RuntimeException
|
||||
class ZipUnsupportMethodException extends ZipException
|
||||
{
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Crypto;
|
||||
namespace PhpZip\IO\Filter\Cipher\Traditional;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
118
src/IO/Filter/Cipher/Traditional/PKDecryptionStreamFilter.php
Normal file
118
src/IO/Filter/Cipher/Traditional/PKDecryptionStreamFilter.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\Traditional;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
128
src/IO/Filter/Cipher/Traditional/PKEncryptionStreamFilter.php
Normal file
128
src/IO/Filter/Cipher/Traditional/PKEncryptionStreamFilter.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\IO\Filter\Cipher\Traditional;
|
||||
|
||||
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->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;
|
||||
}
|
||||
}
|
166
src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
Normal file
166
src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
309
src/IO/Stream/ZipEntryStreamWrapper.php
Normal file
309
src/IO/Stream/ZipEntryStreamWrapper.php
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
885
src/IO/ZipReader.php
Normal file
885
src/IO/ZipReader.php
Normal file
@ -0,0 +1,885 @@
|
||||
<?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\Traditional\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;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$data = unpack('VdiskNo/Pzip64ECDPos/VtotalDisks', fread($this->inStream, 16));
|
||||
|
||||
if ($data['diskNo'] !== 0 || $data['totalDisks'] > 1) {
|
||||
throw new ZipException('ZIP file spanning/splitting is not supported!');
|
||||
}
|
||||
|
||||
return $data['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/PcdEntriesDisk/PentryCount/PcdSize/PcdPos',
|
||||
substr($buffer, 16)
|
||||
);
|
||||
|
||||
// $platform = ZipPlatform::fromValue(($data['versionMadeBy'] & 0xFF00) >> 8);
|
||||
// $softwareVersion = $data['versionMadeBy'] & 0x00FF;
|
||||
|
||||
if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $data['entryCount'] !== $data['cdEntriesDisk']) {
|
||||
throw new ZipException('ZIP file spanning/splitting is not supported!');
|
||||
}
|
||||
|
||||
if ($data['entryCount'] < 0 || $data['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(
|
||||
$data['entryCount'],
|
||||
$data['cdPos'],
|
||||
$data['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->getZipEntry();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
886
src/IO/ZipWriter.php
Normal file
886
src/IO/ZipWriter.php
Normal file
@ -0,0 +1,886 @@
|
||||
<?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\Traditional\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();
|
||||
|
||||
// todo check on 32bit system
|
||||
$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 && !$this->zipContainer->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();
|
||||
|
||||
// todo check on 32bit system
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
78
src/Model/Data/ZipFileData.php
Normal file
78
src/Model/Data/ZipFileData.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
88
src/Model/Data/ZipNewData.php
Normal file
88
src/Model/Data/ZipNewData.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
155
src/Model/Data/ZipSourceFileData.php
Normal file
155
src/Model/Data/ZipSourceFileData.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?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 $zipEntry;
|
||||
|
||||
/** @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->zipEntry = $zipEntry;
|
||||
$this->compressedSize = $zipEntry->getCompressedSize();
|
||||
$this->uncompressedSize = $zipEntry->getUncompressedSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 getZipEntry()
|
||||
{
|
||||
return $this->zipEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
93
src/Model/EndOfCentralDirectory.php
Normal file
93
src/Model/EndOfCentralDirectory.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
137
src/Model/Extra/Fields/AbstractUnicodeExtraField.php
Normal file
137
src/Model/Extra/Fields/AbstractUnicodeExtraField.php
Normal 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();
|
||||
}
|
||||
}
|
176
src/Model/Extra/Fields/ApkAlignmentExtraField.php
Normal file
176
src/Model/Extra/Fields/ApkAlignmentExtraField.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
300
src/Model/Extra/Fields/AsiExtraField.php
Normal file
300
src/Model/Extra/Fields/AsiExtraField.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
425
src/Model/Extra/Fields/ExtendedTimestampExtraField.php
Normal file
425
src/Model/Extra/Fields/ExtendedTimestampExtraField.php
Normal 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);
|
||||
}
|
||||
}
|
118
src/Model/Extra/Fields/JarMarkerExtraField.php
Normal file
118
src/Model/Extra/Fields/JarMarkerExtraField.php
Normal 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);
|
||||
}
|
||||
}
|
248
src/Model/Extra/Fields/NewUnixExtraField.php
Normal file
248
src/Model/Extra/Fields/NewUnixExtraField.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
242
src/Model/Extra/Fields/NtfsExtraField.php
Normal file
242
src/Model/Extra/Fields/NtfsExtraField.php
Normal file
@ -0,0 +1,242 @@
|
||||
<?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 time timestamp */
|
||||
private $modifyTime;
|
||||
|
||||
/** @var int Access time timestamp */
|
||||
private $accessTime;
|
||||
|
||||
/** @var int Create time timestamp */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $this->ntfsTimeToDateTime($this->modifyTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface $modifyTime
|
||||
*/
|
||||
public function setModifyDateTime(\DateTimeInterface $modifyTime)
|
||||
{
|
||||
$this->modifyTime = $this->dateTimeToNtfsTime($modifyTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getAccessDateTime()
|
||||
{
|
||||
return $this->ntfsTimeToDateTime($this->accessTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface $accessTime
|
||||
*/
|
||||
public function setAccessDateTime(\DateTimeInterface $accessTime)
|
||||
{
|
||||
$this->accessTime = $this->dateTimeToNtfsTime($accessTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getCreateDateTime()
|
||||
{
|
||||
return $this->ntfsTimeToDateTime($this->createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface $createTime
|
||||
*/
|
||||
public function setCreateDateTime(\DateTimeInterface $createTime)
|
||||
{
|
||||
$this->createTime = $this->dateTimeToNtfsTime($createTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface $dateTime
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function dateTimeToNtfsTime(\DateTimeInterface $dateTime)
|
||||
{
|
||||
return $dateTime->getTimestamp() * 10000000 + self::EPOCH_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $time
|
||||
*
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
protected 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);
|
||||
}
|
||||
}
|
327
src/Model/Extra/Fields/OldUnixExtraField.php
Normal file
327
src/Model/Extra/Fields/OldUnixExtraField.php
Normal 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);
|
||||
}
|
||||
}
|
76
src/Model/Extra/Fields/UnicodeCommentExtraField.php
Normal file
76
src/Model/Extra/Fields/UnicodeCommentExtraField.php
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
77
src/Model/Extra/Fields/UnicodePathExtraField.php
Normal file
77
src/Model/Extra/Fields/UnicodePathExtraField.php
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
115
src/Model/Extra/Fields/UnrecognizedExtraField.php
Normal file
115
src/Model/Extra/Fields/UnrecognizedExtraField.php
Normal 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);
|
||||
}
|
||||
}
|
387
src/Model/Extra/Fields/WinZipAesExtraField.php
Normal file
387
src/Model/Extra/Fields/WinZipAesExtraField.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
311
src/Model/Extra/Fields/Zip64ExtraField.php
Normal file
311
src/Model/Extra/Fields/Zip64ExtraField.php
Normal 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);
|
||||
}
|
||||
}
|
107
src/Model/Extra/ZipExtraDriver.php
Normal file
107
src/Model/Extra/ZipExtraDriver.php
Normal 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;
|
||||
}
|
||||
}
|
63
src/Model/Extra/ZipExtraField.php
Normal file
63
src/Model/Extra/ZipExtraField.php
Normal 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();
|
||||
}
|
56
src/Model/ImmutableZipContainer.php
Normal file
56
src/Model/ImmutableZipContainer.php
Normal 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);
|
||||
}
|
||||
}
|
409
src/Model/ZipContainer.php
Normal file
409
src/Model/ZipContainer.php
Normal file
@ -0,0 +1,409 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRecompressData(ZipEntry $entry)
|
||||
{
|
||||
// todo test with rename, check exists ZipSourceData
|
||||
if ($this->sourceContainer && isset($this->sourceContainer->entries[$entry->getName()])) {
|
||||
$sourceEntry = $this->sourceContainer->entries[$entry->getName()];
|
||||
|
||||
if (
|
||||
$sourceEntry->getCompressionLevel() !== $entry->getCompressionLevel() ||
|
||||
$sourceEntry->getCompressionMethod() !== $entry->getCompressionMethod() ||
|
||||
$sourceEntry->isEncrypted() !== $entry->isEncrypted() ||
|
||||
$sourceEntry->getEncryptionMethod() !== $entry->getEncryptionMethod() ||
|
||||
$sourceEntry->getPassword() !== $entry->getPassword() ||
|
||||
$sourceEntry->getCompressedSize() !== $entry->getCompressedSize() ||
|
||||
$sourceEntry->getUncompressedSize() !== $entry->getUncompressedSize() ||
|
||||
$sourceEntry->getCrc() !== $entry->getCrc()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
28
src/Model/ZipData.php
Normal 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);
|
||||
}
|
1466
src/Model/ZipEntry.php
Normal file
1466
src/Model/ZipEntry.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
266
src/Model/ZipInfo.php
Normal 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()
|
||||
. '}';
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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']);
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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()
|
||||
. '}';
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
77
src/Util/CryptoUtil.php
Normal 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');
|
||||
}
|
||||
}
|
@ -2,13 +2,13 @@
|
||||
|
||||
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
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DateTimeConverter
|
||||
{
|
||||
@ -55,8 +55,6 @@ class DateTimeConverter
|
||||
* @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
|
||||
@ -64,7 +62,7 @@ class DateTimeConverter
|
||||
public static function toDosTime($unixTimestamp)
|
||||
{
|
||||
if ($unixTimestamp < 0) {
|
||||
throw new ZipException('Negative unix timestamp: ' . $unixTimestamp);
|
||||
throw new \InvalidArgumentException('Negative unix timestamp: ' . $unixTimestamp);
|
||||
}
|
||||
|
||||
$date = getdate($unixTimestamp);
|
108
src/Util/FileAttribUtil.php
Normal file
108
src/Util/FileAttribUtil.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
@ -66,7 +66,6 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
|
||||
/**
|
||||
* @return IgnoreFilesRecursiveFilterIterator
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
@ -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);
|
@ -31,4 +31,24 @@ final class StringUtil
|
||||
return $needle === '' || (($temp = \strlen($haystack) - \strlen($needle)) >= 0
|
||||
&& strpos($haystack, $needle, $temp) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isBinary($string)
|
||||
{
|
||||
return strpos($string, "\0") !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isASCII($name)
|
||||
{
|
||||
return preg_match('~[^\x20-\x7e]~', (string) $name) === 0;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
897
src/ZipFileInterface.php
Normal file
897
src/ZipFileInterface.php
Normal file
@ -0,0 +1,897 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Constants\ZipCompressionLevel;
|
||||
use PhpZip\Constants\ZipCompressionMethod;
|
||||
use PhpZip\Constants\ZipEncryptionMethod;
|
||||
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;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Create, open .ZIP files, modify, get info and extract files.
|
||||
*
|
||||
* Implemented support traditional PKWARE encryption and WinZip AES encryption.
|
||||
* Implemented support ZIP64.
|
||||
* Support ZipAlign functional.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*
|
||||
* @deprecated will be removed in version 4.0. Use the {@see ZipFile} class.
|
||||
*/
|
||||
interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator
|
||||
{
|
||||
/**
|
||||
* Method for Stored (uncompressed) entries.
|
||||
*
|
||||
* @see ZipEntry::setCompressionMethod()
|
||||
* @deprecated Use {@see ZipCompressionMethod::STORED}
|
||||
*/
|
||||
const METHOD_STORED = ZipCompressionMethod::STORED;
|
||||
|
||||
/**
|
||||
* Method for Deflated compressed entries.
|
||||
*
|
||||
* @see ZipEntry::setCompressionMethod()
|
||||
* @deprecated Use {@see ZipCompressionMethod::DEFLATED}
|
||||
*/
|
||||
const METHOD_DEFLATED = ZipCompressionMethod::DEFLATED;
|
||||
|
||||
/**
|
||||
* Method for BZIP2 compressed entries.
|
||||
* Require php extension bz2.
|
||||
*
|
||||
* @see ZipEntry::setCompressionMethod()
|
||||
* @deprecated Use {@see ZipCompressionMethod::BZIP2}
|
||||
*/
|
||||
const METHOD_BZIP2 = ZipCompressionMethod::BZIP2;
|
||||
|
||||
/**
|
||||
* @var int default compression level
|
||||
*
|
||||
* @deprecated Use {@see ZipCompressionLevel::NORMAL}
|
||||
*/
|
||||
const LEVEL_DEFAULT_COMPRESSION = ZipCompressionLevel::NORMAL;
|
||||
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*
|
||||
* @deprecated Use {@see ZipCompressionLevel::FAST}
|
||||
*/
|
||||
const LEVEL_FAST = ZipCompressionLevel::FAST;
|
||||
|
||||
/**
|
||||
* Compression level for fastest compression.
|
||||
*
|
||||
* @deprecated Use {@see ZipCompressionLevel::SUPER_FAST}
|
||||
*/
|
||||
const LEVEL_BEST_SPEED = ZipCompressionLevel::SUPER_FAST;
|
||||
|
||||
/** @deprecated Use {@see ZipCompressionLevel::SUPER_FAST} */
|
||||
const LEVEL_SUPER_FAST = ZipCompressionLevel::SUPER_FAST;
|
||||
|
||||
/**
|
||||
* Compression level for best compression.
|
||||
*
|
||||
* @deprecated Use {@see ZipCompressionLevel::MAXIMUM}
|
||||
*/
|
||||
const LEVEL_BEST_COMPRESSION = ZipCompressionLevel::MAXIMUM;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to Traditional PKWARE encryption.
|
||||
*
|
||||
* @deprecated Use {@see ZipEncryptionMethod::PKWARE}
|
||||
*/
|
||||
const ENCRYPTION_METHOD_TRADITIONAL = ZipEncryptionMethod::PKWARE;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption.
|
||||
* Default value 256 bit.
|
||||
*
|
||||
* @deprecated Use {@see ZipEncryptionMethod::WINZIP_AES_256}
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES = ZipEncryptionMethod::WINZIP_AES_256;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 128 bit.
|
||||
*
|
||||
* @deprecated Use {@see ZipEncryptionMethod::WINZIP_AES_128}
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_128 = ZipEncryptionMethod::WINZIP_AES_128;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 194 bit.
|
||||
*
|
||||
* @deprecated Use {@see ZipEncryptionMethod::WINZIP_AES_192}
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_192 = ZipEncryptionMethod::WINZIP_AES_192;
|
||||
|
||||
/**
|
||||
* No specified method for set encryption method to WinZip AES encryption 256 bit.
|
||||
*
|
||||
* @deprecated Use {@see ZipEncryptionMethod::WINZIP_AES_256}
|
||||
*/
|
||||
const ENCRYPTION_METHOD_WINZIP_AES_256 = ZipEncryptionMethod::WINZIP_AES_256;
|
||||
|
||||
/**
|
||||
* Open zip archive from file.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param array $options
|
||||
*
|
||||
* @throws ZipException if can't open file
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function openFile($filename, array $options = []);
|
||||
|
||||
/**
|
||||
* Open zip archive from raw string data.
|
||||
*
|
||||
* @param string $data
|
||||
* @param array $options
|
||||
*
|
||||
* @throws ZipException if can't open temp stream
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function openFromString($data, array $options = []);
|
||||
|
||||
/**
|
||||
* Open zip archive from stream resource.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param array $options
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function openFromStream($handle, array $options = []);
|
||||
|
||||
/**
|
||||
* @return string[] returns the list files
|
||||
*/
|
||||
public function getListFiles();
|
||||
|
||||
/**
|
||||
* @return int returns the number of entries in this ZIP file
|
||||
*/
|
||||
public function count();
|
||||
|
||||
/**
|
||||
* Returns the file comment.
|
||||
*
|
||||
* @return string|null the file comment
|
||||
*/
|
||||
public function getArchiveComment();
|
||||
|
||||
/**
|
||||
* Set archive comment.
|
||||
*
|
||||
* @param string|null $comment
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function setArchiveComment($comment = null);
|
||||
|
||||
/**
|
||||
* Checks if there is an entry in the archive.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEntry($entryName);
|
||||
|
||||
/**
|
||||
* Returns ZipEntry object.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function getEntry($entryName);
|
||||
|
||||
/**
|
||||
* 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 ZipException
|
||||
* @throws ZipEntryNotFoundException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryComment($entryName);
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string|null $comment
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function setEntryComment($entryName, $comment = null);
|
||||
|
||||
/**
|
||||
* Returns the entry contents.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContents($entryName);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getEntryStream($entryName);
|
||||
|
||||
/**
|
||||
* Get info by entry.
|
||||
*
|
||||
* @param string|ZipEntry $entryName
|
||||
*
|
||||
* @throws ZipException
|
||||
* @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 (unzip).
|
||||
*
|
||||
* Extract the complete archive or the given files to the specified destination.
|
||||
*
|
||||
* @param string $destDir 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 ZipFile
|
||||
*/
|
||||
public function extractTo($destDir, $entries = null);
|
||||
|
||||
/**
|
||||
* Add entry from the string.
|
||||
*
|
||||
* @param string $entryName zip entry name
|
||||
* @param string $contents string contents
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function addFromString($entryName, $contents, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* @param Finder $finder
|
||||
* @param array $options
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry[]
|
||||
*/
|
||||
public function addFromFinder(Finder $finder, array $options = []);
|
||||
|
||||
/**
|
||||
* @param \SplFileInfo $file
|
||||
* @param string|null $entryName
|
||||
* @param array $options
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function addSplFile(\SplFileInfo $file, $entryName = null, array $options = []);
|
||||
|
||||
/**
|
||||
* Add entry from the file.
|
||||
*
|
||||
* @param string $filename destination file
|
||||
* @param string|null $entryName zip Entry name
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function addFile($filename, $entryName = null, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add entry from the stream.
|
||||
*
|
||||
* @param resource $stream stream resource
|
||||
* @param string $entryName zip Entry name
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}.
|
||||
* If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function addFromStream($stream, $entryName, $compressionMethod = null);
|
||||
|
||||
/**
|
||||
* Add an empty directory in the zip archive.
|
||||
*
|
||||
* @param string $dirName
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
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 {@see ZipCompressionMethod::STORED}, {@see
|
||||
* ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
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 {@see ZipCompressionMethod::STORED}, {@see
|
||||
* ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @see ZipCompressionMethod::STORED
|
||||
* @see ZipCompressionMethod::DEFLATED
|
||||
* @see ZipCompressionMethod::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 {@see ZipCompressionMethod::STORED}, {@see
|
||||
* ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @see ZipCompressionMethod::STORED
|
||||
* @see ZipCompressionMethod::DEFLATED
|
||||
* @see ZipCompressionMethod::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 $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
* @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 $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
* @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 $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @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 $localPath add files to this directory, or the root
|
||||
* @param int|null $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED},
|
||||
* {@see ZipCompressionMethod::DEFLATED} or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @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 ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function rename($oldName, $newName);
|
||||
|
||||
/**
|
||||
* Delete entry by name.
|
||||
*
|
||||
* @param string $entryName zip Entry name
|
||||
*
|
||||
* @throws ZipEntryNotFoundException if entry not found
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function deleteFromName($entryName);
|
||||
|
||||
/**
|
||||
* Delete entries by glob pattern.
|
||||
*
|
||||
* @param string $globPattern Glob pattern
|
||||
*
|
||||
* @return ZipFile
|
||||
* @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 ZipFile
|
||||
*/
|
||||
public function deleteFromRegex($regexPattern);
|
||||
|
||||
/**
|
||||
* Delete all entries.
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function deleteAll();
|
||||
|
||||
/**
|
||||
* Set compression level for new entries.
|
||||
*
|
||||
* @param int $compressionLevel
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @see ZipCompressionLevel::NORMAL
|
||||
* @see ZipCompressionLevel::SUPER_FAST
|
||||
* @see ZipCompressionLevel::FAST
|
||||
* @see ZipCompressionLevel::MAXIMUM
|
||||
*/
|
||||
public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionLevel
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @see ZipCompressionLevel::NORMAL
|
||||
* @see ZipCompressionLevel::SUPER_FAST
|
||||
* @see ZipCompressionLevel::FAST
|
||||
* @see ZipCompressionLevel::MAXIMUM
|
||||
*/
|
||||
public function setCompressionLevelEntry($entryName, $compressionLevel);
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @param int $compressionMethod Compression method.
|
||||
* Use {@see ZipCompressionMethod::STORED}, {@see ZipCompressionMethod::DEFLATED}
|
||||
* or
|
||||
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @see ZipCompressionMethod::STORED
|
||||
* @see ZipCompressionMethod::DEFLATED
|
||||
* @see ZipCompressionMethod::BZIP2
|
||||
*/
|
||||
public function setCompressionMethodEntry($entryName, $compressionMethod);
|
||||
|
||||
/**
|
||||
* zipalign is optimization to Android application (APK) files.
|
||||
*
|
||||
* @param int|null $align
|
||||
*
|
||||
* @return ZipFile
|
||||
*
|
||||
* @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 ZipFile
|
||||
*/
|
||||
public function setReadPassword($password);
|
||||
|
||||
/**
|
||||
* Set password to concrete input entry.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $password Password
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function setReadPasswordEntry($entryName, $password);
|
||||
|
||||
/**
|
||||
* Sets a new password for all files in the archive.
|
||||
*
|
||||
* @param string $password Password
|
||||
* @param int|null $encryptionMethod Encryption method
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256);
|
||||
|
||||
/**
|
||||
* Sets a new password of an entry defined by its name.
|
||||
*
|
||||
* @param string $entryName
|
||||
* @param string $password
|
||||
* @param int|null $encryptionMethod
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function setPasswordEntry($entryName, $password, $encryptionMethod = null);
|
||||
|
||||
/**
|
||||
* Disable encryption for all entries that are already in the archive.
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function disableEncryption();
|
||||
|
||||
/**
|
||||
* Disable encryption of an entry defined by its name.
|
||||
*
|
||||
* @param string $entryName
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function disableEncryptionEntry($entryName);
|
||||
|
||||
/**
|
||||
* Undo all changes done in the archive.
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function unchangeAll();
|
||||
|
||||
/**
|
||||
* Undo change archive comment.
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function unchangeArchiveComment();
|
||||
|
||||
/**
|
||||
* Revert all changes done to an entry with the given name.
|
||||
*
|
||||
* @param string|ZipEntry $entry Entry name or ZipEntry
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function unchangeEntry($entry);
|
||||
|
||||
/**
|
||||
* Save as file.
|
||||
*
|
||||
* @param string $filename Output filename
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function saveAsFile($filename);
|
||||
|
||||
/**
|
||||
* Save as stream.
|
||||
*
|
||||
* @param resource $handle Output stream resource
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @throws ZipException
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function outputAsResponse(
|
||||
ResponseInterface $response,
|
||||
$outputFilename,
|
||||
$mimeType = null,
|
||||
$attachment = true
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the zip archive as a string.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function outputAsString();
|
||||
|
||||
/**
|
||||
* Close zip archive and release input stream.
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* Save and reopen zip archive.
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return ZipFile
|
||||
*/
|
||||
public function rewrite();
|
||||
|
||||
/**
|
||||
* Offset to set.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
*
|
||||
* @param string $entryName the offset to assign the value to
|
||||
* @param string|\DirectoryIterator|\SplFileInfo|resource $contents the value to set
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @see ZipFile::addFromString
|
||||
* @see ZipFile::addEmptyDir
|
||||
* @see ZipFile::addFile
|
||||
* @see ZipFile::addFilesFromIterator
|
||||
*/
|
||||
public function offsetSet($entryName, $contents);
|
||||
|
||||
/**
|
||||
* Offset to unset.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
*
|
||||
* @param string $entryName the offset to unset
|
||||
*
|
||||
* @throws ZipEntryNotFoundException
|
||||
*/
|
||||
public function offsetUnset($entryName);
|
||||
|
||||
/**
|
||||
* Return the current element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.current.php
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return mixed can return any type
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function current();
|
||||
|
||||
/**
|
||||
* Offset to retrieve.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
*
|
||||
* @param string $entryName the offset to retrieve
|
||||
*
|
||||
* @throws ZipException
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function offsetGet($entryName);
|
||||
|
||||
/**
|
||||
* Return the key of the current element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.key.php
|
||||
*
|
||||
* @return mixed scalar on success, or null on failure
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function key();
|
||||
|
||||
/**
|
||||
* Move forward to next element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.next.php
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function next();
|
||||
|
||||
/**
|
||||
* Checks if current position is valid.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.valid.php
|
||||
*
|
||||
* @return bool The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function valid();
|
||||
|
||||
/**
|
||||
* Whether a offset exists.
|
||||
*
|
||||
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
*
|
||||
* @param string $entryName an offset to check for
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
*/
|
||||
public function offsetExists($entryName);
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element.
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.rewind.php
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function rewind();
|
||||
}
|
216
tests/Internal/DummyFileSystemStream.php
Normal file
216
tests/Internal/DummyFileSystemStream.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Tests\Internal;
|
||||
|
||||
/**
|
||||
* Try to load using dummy stream.
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper
|
||||
*/
|
||||
class DummyFileSystemStream
|
||||
{
|
||||
/** @var resource */
|
||||
private $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
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @see https://www.php.net/streamwrapper.stream-open
|
||||
*
|
||||
* @noinspection PhpUsageOfSilenceOperatorInspection
|
||||
*/
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
$parsedUrl = parse_url($path);
|
||||
$uri = str_replace('//', '/', $parsedUrl['path']);
|
||||
$this->fp = @fopen($uri, $mode);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
fclose($this->fp);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user