mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-03-20 22:50:00 +01:00
Merge branch 'feature/3.0.0-dev' into develop
This commit is contained in:
commit
4ca1717979
9
.codeclimate.yml
Normal file
9
.codeclimate.yml
Normal file
@ -0,0 +1,9 @@
|
||||
engines:
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
- php
|
||||
exclude_paths:
|
||||
- tests/
|
||||
- vendor/
|
33
.travis.yml
Normal file
33
.travis.yml
Normal file
@ -0,0 +1,33 @@
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0'
|
||||
- '7.1'
|
||||
- hhvm
|
||||
- nightly
|
||||
|
||||
# cache vendor dirs
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token: 486a09d58d663450146c53c81c6c64938bcf3bb0b7c8ddebdc125fe97c18213a
|
||||
|
||||
install:
|
||||
- travis_retry composer self-update && composer --version
|
||||
- travis_retry composer install --prefer-dist --no-interaction
|
||||
|
||||
before_script:
|
||||
- sudo apt-get install p7zip-full
|
||||
|
||||
script:
|
||||
- composer validate --no-check-lock
|
||||
- vendor/bin/phpunit -v -c bootstrap.xml --coverage-clover build/logs/clover.xml
|
||||
|
||||
after_success:
|
||||
- vendor/bin/test-reporter
|
||||
|
680
README.md
680
README.md
@ -1,52 +1,143 @@
|
||||
`PhpZip` Version 2
|
||||
================
|
||||
`PhpZip` - is to create, update, opening and unpacking ZIP archives in pure PHP.
|
||||
`PhpZip`
|
||||
====================
|
||||
`PhpZip` - php library for manipulating zip archives.
|
||||
|
||||
The library supports `ZIP64`, `zipalign`, `Traditional PKWARE Encryption` and `WinZIP AES Encryption`.
|
||||
[](https://travis-ci.org/Ne-Lexa/php-zip)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
[](https://php.net/)
|
||||
[](https://codeclimate.com/github/Ne-Lexa/php-zip/coverage)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
|
||||
ZIP64 extensions are automatically and transparently activated when reading or writing ZIP files of more than 4 GB size.
|
||||
Table of contents
|
||||
-----------------
|
||||
- [Features](#Features)
|
||||
- [Requirements](#Requirements)
|
||||
- [Installation](#Installation)
|
||||
- [Examples](#Examples)
|
||||
- [Documentation](#Documentation)
|
||||
+ [Open Zip Archive](#Documentation-Open-Zip-Archive)
|
||||
+ [Get Zip Entries](#Documentation-Open-Zip-Entries)
|
||||
+ [Add Zip Entries](#Documentation-Add-Zip-Entries)
|
||||
+ [ZipAlign Usage](#Documentation-ZipAlign-Usage)
|
||||
+ [Save Zip File or Output](#Documentation-Save-Or-Output-Entries)
|
||||
+ [Close Zip Archive](#Documentation-Close-Zip-Archive)
|
||||
- [Running Tests](#Running-Tests)
|
||||
- [Upgrade version 2 to version 3](#Upgrade)
|
||||
|
||||
The library does not require extension `php-zip` and class `ZipArchive`.
|
||||
### <a name="Features"></a> Features
|
||||
- Opening and unzipping zip files.
|
||||
- Create zip files.
|
||||
- Update zip files.
|
||||
- Pure php (not require extension `php-zip` and class `\ZipArchive`).
|
||||
- Output the modified archive as a string or output to the browser without saving the result to disk.
|
||||
- Support archive comment and entries comments.
|
||||
- Get info of zip entries.
|
||||
- Support zip password for PHP 5.5, include update and remove password.
|
||||
- Support encryption method `Traditional PKWARE Encryption (ZipCrypto)` and `WinZIP AES Encryption`.
|
||||
- Support `ZIP64` (size > 4 GiB or files > 65535 in a .ZIP archive).
|
||||
- Support archive alignment functional [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
||||
|
||||
Requirements
|
||||
------------
|
||||
- `PHP` >= 5.4 (64 bit)
|
||||
- PHP-extension `mbstring`
|
||||
### <a name="Requirements"></a> Requirements
|
||||
- `PHP` >= 5.5 (64 bit)
|
||||
- Optional php-extension `bzip2` for BZIP2 compression.
|
||||
- Optional php-extension `openssl` or `mcrypt` for `WinZip Aes Encryption` support.
|
||||
|
||||
Installation
|
||||
------------
|
||||
`composer require nelexa/zip`
|
||||
### <a name="Installation"></a> Installation
|
||||
`composer require nelexa/zip:^3.0`
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
#### Class `\PhpZip\ZipFile` (open, extract, info)
|
||||
### <a name="Examples"></a> Examples
|
||||
```php
|
||||
// create new archive
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile
|
||||
->addFromString("zip/entry/filename", "Is file content")
|
||||
->addFile("/path/to/file", "data/tofile")
|
||||
->addDir(__DIR__, "to/path/")
|
||||
->saveAsFile($outputFilename)
|
||||
->close();
|
||||
|
||||
// open archive, extract, add files, set password and output to browser.
|
||||
$zipFile
|
||||
->openFile($outputFilename)
|
||||
->extractTo($outputDirExtract)
|
||||
->deleteFromRegex('~^\.~') // delete all hidden (Unix) files
|
||||
->addFromString('dir/file.txt', 'Test file')
|
||||
->withNewPassword('password')
|
||||
->outputAsAttachment('library.jar');
|
||||
```
|
||||
Other examples can be found in the `tests/` folder
|
||||
|
||||
### <a name="Documentation"></a> Documentation:
|
||||
#### <a name="Documentation-Open-Zip-Archive"></a> Open Zip Archive
|
||||
Open zip archive from file.
|
||||
```php
|
||||
$zipFile = \PhpZip\ZipFile::openFromFile($filename);
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
```
|
||||
Open zip archive from data string.
|
||||
```php
|
||||
$data = file_get_contents($filename);
|
||||
$zipFile = \PhpZip\ZipFile::openFromString($data);
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFromString($stringContents);
|
||||
```
|
||||
Open zip archive from stream resource.
|
||||
```php
|
||||
$stream = fopen($filename, 'rb');
|
||||
$zipFile = \PhpZip\ZipFile::openFromStream($stream);
|
||||
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFromStream($stream);
|
||||
```
|
||||
#### <a name="Documentation-Open-Zip-Entries"></a> Get Zip Entries
|
||||
Get num entries.
|
||||
```php
|
||||
$count = $zipFile->count();
|
||||
// or
|
||||
$count = count($zipFile);
|
||||
// or
|
||||
$count = $zipFile->count();
|
||||
```
|
||||
Get list files.
|
||||
```php
|
||||
$listFiles = $zipFile->getListFiles();
|
||||
|
||||
// Example result:
|
||||
//
|
||||
// $listFiles = [
|
||||
// 'info.txt',
|
||||
// 'path/to/file.jpg',
|
||||
// 'another path/'
|
||||
// ];
|
||||
```
|
||||
Foreach zip entries.
|
||||
Get entry contents.
|
||||
```php
|
||||
// $entryName = 'path/to/example-entry-name.txt';
|
||||
|
||||
$contents = $zipFile[$entryName];
|
||||
```
|
||||
Checks whether a entry exists.
|
||||
```php
|
||||
// $entryName = 'path/to/example-entry-name.txt';
|
||||
|
||||
$hasEntry = isset($zipFile[$entryName]);
|
||||
```
|
||||
Check whether the directory entry.
|
||||
```php
|
||||
// $entryName = 'path/to/';
|
||||
|
||||
$isDirectory = $zipFile->isDirectory($entryName);
|
||||
```
|
||||
Extract all files to directory.
|
||||
```php
|
||||
$zipFile->extractTo($directory);
|
||||
```
|
||||
Extract some files to directory.
|
||||
```php
|
||||
$extractOnlyFiles = [
|
||||
"filename1",
|
||||
"filename2",
|
||||
"dir/dir/dir/"
|
||||
];
|
||||
$zipFile->extractTo($directory, $extractOnlyFiles);
|
||||
```
|
||||
Iterate zip entries.
|
||||
```php
|
||||
foreach($zipFile as $entryName => $dataContent){
|
||||
echo "Entry: $entryName" . PHP_EOL;
|
||||
@ -54,7 +145,7 @@ foreach($zipFile as $entryName => $dataContent){
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
}
|
||||
```
|
||||
Iterator zip entries.
|
||||
or
|
||||
```php
|
||||
$iterator = new \ArrayIterator($zipFile);
|
||||
while ($iterator->valid())
|
||||
@ -69,58 +160,54 @@ while ($iterator->valid())
|
||||
$iterator->next();
|
||||
}
|
||||
```
|
||||
Checks whether a entry exists.
|
||||
```php
|
||||
$boolValue = $zipFile->hasEntry($entryName);
|
||||
```
|
||||
Check whether the directory entry.
|
||||
```php
|
||||
$boolValue = $zipFile->isDirectory($entryName);
|
||||
```
|
||||
Set password to all encrypted entries.
|
||||
```php
|
||||
$zipFile->setPassword($password);
|
||||
```
|
||||
Set password to concrete zip entry.
|
||||
```php
|
||||
$zipFile->setEntryPassword($entryName, $password);
|
||||
```
|
||||
Get comment archive.
|
||||
```php
|
||||
$commentArchive = $zipFile->getComment();
|
||||
$commentArchive = $zipFile->getArchiveComment();
|
||||
```
|
||||
Get comment zip entry.
|
||||
```php
|
||||
$commentEntry = $zipFile->getEntryComment($entryName);
|
||||
```
|
||||
Set password for read encrypted entries.
|
||||
```php
|
||||
$zipFile->withReadPassword($password);
|
||||
```
|
||||
Get entry info.
|
||||
```php
|
||||
$zipInfo = $zipFile->getEntryInfo('file.txt');
|
||||
|
||||
echo $zipInfo . PHP_EOL;
|
||||
|
||||
// Output:
|
||||
// ZipInfo {Path="file.txt", Size=9.77KB, Compressed size=2.04KB, Modified time=2016-09-24T19:25:10+03:00, Crc=0x4b5ab5c7, Method="Deflate", Attributes="-rw-r--r--", Platform="UNIX", Version=20}
|
||||
|
||||
print_r($zipInfo);
|
||||
//PhpZip\Model\ZipInfo Object
|
||||
//(
|
||||
// [path:PhpZip\Model\ZipInfo:private] => file.txt
|
||||
// [folder:PhpZip\Model\ZipInfo:private] =>
|
||||
// [size:PhpZip\Model\ZipInfo:private] => 10000
|
||||
// [compressedSize:PhpZip\Model\ZipInfo:private] => 2086
|
||||
// [mtime:PhpZip\Model\ZipInfo:private] => 1474734310
|
||||
// [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] => 1264235975
|
||||
// [method:PhpZip\Model\ZipInfo:private] => Deflate
|
||||
// [platform:PhpZip\Model\ZipInfo:private] => UNIX
|
||||
// [version:PhpZip\Model\ZipInfo:private] => 20
|
||||
// [attributes:PhpZip\Model\ZipInfo:private] => -rw-r--r--
|
||||
//)
|
||||
|
||||
// Output:
|
||||
// PhpZip\Model\ZipInfo Object
|
||||
// (
|
||||
// [path:PhpZip\Model\ZipInfo:private] => file.txt
|
||||
// [folder:PhpZip\Model\ZipInfo:private] =>
|
||||
// [size:PhpZip\Model\ZipInfo:private] => 10000
|
||||
// [compressedSize:PhpZip\Model\ZipInfo:private] => 2086
|
||||
// [mtime:PhpZip\Model\ZipInfo:private] => 1474734310
|
||||
// [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] => 1264235975
|
||||
// [method:PhpZip\Model\ZipInfo:private] => Deflate
|
||||
// [platform:PhpZip\Model\ZipInfo:private] => UNIX
|
||||
// [version:PhpZip\Model\ZipInfo:private] => 20
|
||||
// [attributes:PhpZip\Model\ZipInfo:private] => -rw-r--r--
|
||||
// )
|
||||
```
|
||||
Get info for all entries.
|
||||
```php
|
||||
$zipAllInfo = $zipFile->getAllInfo();
|
||||
|
||||
print_r($zipAllInfo);
|
||||
|
||||
//Array
|
||||
//(
|
||||
// [file.txt] => PhpZip\Model\ZipInfo Object
|
||||
@ -135,343 +222,328 @@ print_r($zipAllInfo);
|
||||
//
|
||||
// ...
|
||||
//)
|
||||
```
|
||||
Extract all files to directory.
|
||||
```php
|
||||
$zipFile->extractTo($directory);
|
||||
```
|
||||
Extract some files to directory.
|
||||
```php
|
||||
$extractOnlyFiles = ["filename1", "filename2", "dir/dir/dir/"];
|
||||
$zipFile->extractTo($directory, $extractOnlyFiles);
|
||||
```
|
||||
Get entry content.
|
||||
```php
|
||||
$data = $zipFile->getEntryContent($entryName);
|
||||
```
|
||||
Edit zip archive
|
||||
```php
|
||||
$zipOutputFile = $zipFile->edit();
|
||||
```
|
||||
Close zip archive.
|
||||
```php
|
||||
$zipFile->close();
|
||||
```
|
||||
#### Class `\PhpZip\ZipOutputFile` (create, update, extract)
|
||||
Create zip archive.
|
||||
```php
|
||||
$zipOutputFile = new \PhpZip\ZipOutputFile();
|
||||
// or
|
||||
$zipOutputFile = \PhpZip\ZipOutputFile::create();
|
||||
```
|
||||
Open zip file from update.
|
||||
```php
|
||||
$filename = "file.zip";
|
||||
$zipOutputFile = \PhpZip\ZipOutputFile::openFromFile($filename);
|
||||
```
|
||||
or
|
||||
```php
|
||||
// initial ZipFile
|
||||
$zipFile = \PhpZip\ZipFile::openFromFile($filename);
|
||||
|
||||
// Create output stream from update zip file
|
||||
$zipOutputFile = new \PhpZip\ZipOutputFile($zipFile);
|
||||
// or
|
||||
$zipOutputFile = \PhpZip\ZipOutputFile::openFromZipFile($zipFile);
|
||||
// or
|
||||
$zipOutputFile = $zipFile->edit();
|
||||
```
|
||||
Add entry from file.
|
||||
#### <a name="Documentation-Add-Zip-Entries"></a> Add Zip Entries
|
||||
Adding a file to the zip-archive.
|
||||
```php
|
||||
$zipOutputFile->addFromFile($filename); // $entryName == basename($filename);
|
||||
$zipOutputFile->addFromFile($filename, $entryName);
|
||||
$zipOutputFile->addFromFile($filename, $entryName, ZipEntry::METHOD_DEFLATED);
|
||||
$zipOutputFile->addFromFile($filename, $entryName, ZipEntry::METHOD_STORED); // no compress
|
||||
$zipOutputFile->addFromFile($filename, null, ZipEntry::METHOD_BZIP2); // $entryName == basename($filename);
|
||||
// entry name is file basename.
|
||||
$zipFile->addFile($filename);
|
||||
// or
|
||||
$zipFile->addFile($filename, null);
|
||||
|
||||
// with entry name
|
||||
$zipFile->addFile($filename, $entryName);
|
||||
// or
|
||||
$zipFile[$entryName] = new \SplFileInfo($filename);
|
||||
|
||||
// with compression method
|
||||
$zipFile->addFile($filename, $entryName, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFile($filename, $entryName, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFile($filename, null, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add entry from string data.
|
||||
```php
|
||||
$zipOutputFile->addFromString($entryName, $data);
|
||||
$zipOutputFile->addFromString($entryName, $data, ZipEntry::METHOD_DEFLATED);
|
||||
$zipOutputFile->addFromString($entryName, $data, ZipEntry::METHOD_STORED); // no compress
|
||||
$zipFile[$entryName] = $data;
|
||||
// or
|
||||
$zipFile->addFromString($entryName, $data);
|
||||
|
||||
// with compression method
|
||||
$zipFile->addFromString($entryName, $data, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFromString($entryName, $data, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFromString($entryName, $data, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add entry from stream.
|
||||
```php
|
||||
$zipOutputFile->addFromStream($stream, $entryName);
|
||||
$zipOutputFile->addFromStream($stream, $entryName, ZipEntry::METHOD_DEFLATED);
|
||||
$zipOutputFile->addFromStream($stream, $entryName, ZipEntry::METHOD_STORED); // no compress
|
||||
// $stream = fopen(...);
|
||||
|
||||
$zipFile->addFromStream($stream, $entryName);
|
||||
|
||||
// with compression method
|
||||
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add empty dir
|
||||
```php
|
||||
$zipOutputFile->addEmptyDir($dirName);
|
||||
```
|
||||
Add a directory **recursively** to the archive.
|
||||
```php
|
||||
$zipOutputFile->addDir($dirName);
|
||||
// $dirName = "path/to/";
|
||||
|
||||
$zipFile->addEmptyDir($dirName);
|
||||
// or
|
||||
$zipOutputFile->addDir($dirName, true);
|
||||
$zipFile[$dirName] = null;
|
||||
```
|
||||
Add all entries form string contents.
|
||||
```php
|
||||
$mapData = [
|
||||
'file.txt' => 'file contents',
|
||||
'path/to/file.txt' => 'another file contents',
|
||||
'empty dir/' => null,
|
||||
];
|
||||
|
||||
$zipFile->addAll($mapData);
|
||||
```
|
||||
Add a directory **not recursively** to the archive.
|
||||
```php
|
||||
$zipOutputFile->addDir($dirName, false);
|
||||
$zipFile->addDir($dirName);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addDir($dirName, $localPath);
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add a directory to the archive by path `$moveToPath`
|
||||
Add a directory **recursively** to the archive.
|
||||
```php
|
||||
$moveToPath = 'dir/subdir/';
|
||||
$zipOutputFile->addDir($dirName, $boolResursive, $moveToPath);
|
||||
$zipFile->addDirRecursive($dirName);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addDirRecursive($dirName, $localPath);
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add a directory to the archive with ignoring files.
|
||||
Add a files from directory iterator.
|
||||
```php
|
||||
$ignoreFiles = ["file_ignore.txt", "dir_ignore/sub dir ignore/"];
|
||||
$zipOutputFile->addDir($dirName, $boolResursive, $moveToPath, $ignoreFiles);
|
||||
// $directoryIterator = new \DirectoryIterator($dir); // not recursive
|
||||
// $directoryIterator = new \RecursiveDirectoryIterator($dir); // recursive
|
||||
|
||||
$zipFile->addFilesFromIterator($directoryIterator);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
// or
|
||||
$zipFile[$localPath] = $directoryIterator;
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add a directory and set compression method.
|
||||
Example add a directory to the archive with ignoring files from directory iterator.
|
||||
```php
|
||||
$compressionMethod = ZipEntry::METHOD_DEFLATED;
|
||||
$zipOutputFile->addDir($dirName, $boolRecursive, $moveToPath, $ignoreFiles, $compressionMethod);
|
||||
$ignoreFiles = [
|
||||
"file_ignore.txt",
|
||||
"dir_ignore/sub dir ignore/"
|
||||
];
|
||||
|
||||
// use \DirectoryIterator for not recursive
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($dir);
|
||||
|
||||
// use IgnoreFilesFilterIterator for not recursive
|
||||
$ignoreIterator = new IgnoreFilesRecursiveFilterIterator(
|
||||
$directoryIterator,
|
||||
$ignoreFiles
|
||||
);
|
||||
|
||||
$zipFile->addFilesFromIterator($ignoreIterator);
|
||||
```
|
||||
Add a files **recursively** from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive.
|
||||
```php
|
||||
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
|
||||
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern);
|
||||
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath);
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath), ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath), ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath), ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add a files **not recursively** from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive.
|
||||
```php
|
||||
$recursive = false;
|
||||
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern, $recursive);
|
||||
```
|
||||
Add a files from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive by path `$moveToPath`.
|
||||
```php
|
||||
$moveToPath = 'dir/dir2/dir3';
|
||||
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern, $recursive = true, $moveToPath);
|
||||
```
|
||||
Add a files from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive and set compression method.
|
||||
```php
|
||||
$compressionMethod = ZipEntry::METHOD_DEFLATED;
|
||||
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern, $recursive, $moveToPath, $compressionMethod);
|
||||
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
|
||||
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath);
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath), ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath), ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath), ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add a files **recursively** from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive.
|
||||
```php
|
||||
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
|
||||
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern);
|
||||
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath);
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Add a files **not recursively** from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive.
|
||||
```php
|
||||
$recursive = false;
|
||||
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern, $recursive);
|
||||
```
|
||||
Add a files from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive by path `$moveToPath`.
|
||||
```php
|
||||
$moveToPath = 'dir/dir2/dir3';
|
||||
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern, $recursive = true, $moveToPath);
|
||||
```
|
||||
Add a files from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive and set compression method.
|
||||
```php
|
||||
$compressionMethod = ZipEntry::METHOD_DEFLATED;
|
||||
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern, $recursive, $moveToPath, $compressionMethod);
|
||||
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
|
||||
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern);
|
||||
|
||||
// with entry path
|
||||
$localPath = "to/path/";
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath);
|
||||
|
||||
// with compression method for all files
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // No compression
|
||||
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
|
||||
```
|
||||
Rename entry name.
|
||||
```php
|
||||
$zipOutputFile->rename($oldName, $newName);
|
||||
$zipFile->rename($oldName, $newName);
|
||||
```
|
||||
Delete entry by name.
|
||||
```php
|
||||
$zipOutputFile->deleteFromName($entryName);
|
||||
$zipFile->deleteFromName($entryName);
|
||||
```
|
||||
Delete entries from [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
|
||||
$zipOutputFile->deleteFromGlob($globPattern);
|
||||
|
||||
$zipFile->deleteFromGlob($globPattern);
|
||||
```
|
||||
Delete entries from [RegEx (Regular Expression) 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
|
||||
$zipOutputFile->deleteFromRegex($regexPattern);
|
||||
|
||||
$zipFile->deleteFromRegex($regexPattern);
|
||||
```
|
||||
Delete all entries.
|
||||
```php
|
||||
$zipOutputFile->deleteAll();
|
||||
```
|
||||
Get num entries.
|
||||
```php
|
||||
$count = $zipOutputFile->count();
|
||||
// or
|
||||
$count = count($zipOutputFile);
|
||||
```
|
||||
Get list files.
|
||||
```php
|
||||
$listFiles = $zipOutputFile->getListFiles();
|
||||
```
|
||||
Get the compression level for entries.
|
||||
```php
|
||||
$compressionLevel = $zipOutputFile->getLevel();
|
||||
$zipFile->deleteAll();
|
||||
```
|
||||
Sets the compression level for entries.
|
||||
```php
|
||||
// This property is only used if the effective compression method is DEFLATED or BZIP2.
|
||||
// Legal values are ZipOutputFile::LEVEL_DEFAULT_COMPRESSION or range from
|
||||
// ZipOutputFile::LEVEL_BEST_SPEED to ZipOutputFile::LEVEL_BEST_COMPRESSION.
|
||||
$compressionMethod = ZipOutputFile::LEVEL_BEST_COMPRESSION;
|
||||
$zipOutputFile->setLevel($compressionLevel);
|
||||
```
|
||||
Get comment archive.
|
||||
```php
|
||||
$commentArchive = $zipOutputFile->getComment();
|
||||
// Legal values are ZipFile::LEVEL_DEFAULT_COMPRESSION or range from
|
||||
// ZipFile::LEVEL_BEST_SPEED to ZipFile::LEVEL_BEST_COMPRESSION.
|
||||
|
||||
$compressionMethod = ZipFile::LEVEL_BEST_COMPRESSION;
|
||||
|
||||
$zipFile->setCompressionLevel($compressionLevel);
|
||||
```
|
||||
Set comment archive.
|
||||
```php
|
||||
$zipOutputFile->setComment($commentArchive);
|
||||
```
|
||||
Get comment zip entry.
|
||||
```php
|
||||
$commentEntry = $zipOutputFile->getEntryComment($entryName);
|
||||
$zipFile->setArchiveComment($commentArchive);
|
||||
```
|
||||
Set comment zip entry.
|
||||
```php
|
||||
$zipOutputFile->setEntryComment($entryName, $entryComment);
|
||||
$zipFile->setEntryComment($entryName, $entryComment);
|
||||
```
|
||||
Set compression method for zip entry.
|
||||
Set a new password.
|
||||
```php
|
||||
$compressionMethod = ZipEntry::METHOD_DEFLATED;
|
||||
$zipOutputMethod->setCompressionMethod($entryName, $compressionMethod);
|
||||
|
||||
// Support compression methods:
|
||||
// ZipEntry::METHOD_STORED - no compression
|
||||
// ZipEntry::METHOD_DEFLATED - deflate compression
|
||||
// ZipEntry::METHOD_BZIP2 - bzip2 compression (need bz2 extension)
|
||||
$zipFile->withNewPassword($password);
|
||||
```
|
||||
Set a password for all previously added entries.
|
||||
Set a new password and encryption method.
|
||||
```php
|
||||
$zipOutputFile->setPassword($password);
|
||||
```
|
||||
Set a password and encryption method for all previously added entries.
|
||||
```php
|
||||
$encryptionMethod = ZipEntry::ENCRYPTION_METHOD_WINZIP_AES; // default value
|
||||
$zipOutputFile->setPassword($password, $encryptionMethod);
|
||||
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES; // default value
|
||||
$zipFile->withNewPassword($password, $encryptionMethod);
|
||||
|
||||
// Support encryption methods:
|
||||
// ZipEntry::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
|
||||
// ZipEntry::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption
|
||||
```
|
||||
Set a password for a concrete entry.
|
||||
```php
|
||||
$zipOutputFile->setEntryPassword($entryName, $password);
|
||||
```
|
||||
Set a password and encryption method for a concrete entry.
|
||||
```php
|
||||
$zipOutputFile->setEntryPassword($entryName, $password, $encryptionMethod);
|
||||
|
||||
// Support encryption methods:
|
||||
// ZipEntry::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
|
||||
// ZipEntry::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption (default value)
|
||||
// ZipFile::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
|
||||
// ZipFile::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption
|
||||
```
|
||||
Remove password from all entries.
|
||||
```php
|
||||
$zipOutputFile->removePasswordAllEntries();
|
||||
$zipFile->withoutPassword();
|
||||
```
|
||||
Remove password for concrete zip entry.
|
||||
#### <a name="Documentation-ZipAlign-Usage"></a> ZipAlign Usage
|
||||
Set archive alignment ([`zipalign`](https://developer.android.com/studio/command-line/zipalign.html)).
|
||||
```php
|
||||
$zipOutputFile->removePasswordFromEntry($entryName);
|
||||
// before save or output
|
||||
$zipFile->setAlign(4); // alternative command: zipalign -f -v 4 filename.zip
|
||||
```
|
||||
#### <a name="Documentation-Save-Or-Output-Entries"></a> Save Zip File or Output
|
||||
Save archive to a file.
|
||||
```php
|
||||
$zipOutputFile->saveAsFile($filename);
|
||||
$zipFile->saveAsFile($filename);
|
||||
```
|
||||
Save archive to a stream.
|
||||
```php
|
||||
$handle = fopen($filename, 'w+b');
|
||||
$autoCloseResource = true;
|
||||
$zipOutputFile->saveAsStream($handle, $autoCloseResource);
|
||||
if(!$autoCloseResource){
|
||||
fclose($handle);
|
||||
}
|
||||
// $fp = fopen($filename, 'w+b');
|
||||
|
||||
$zipFile->saveAsStream($fp);
|
||||
```
|
||||
Returns the zip archive as a string.
|
||||
```php
|
||||
$rawZipArchiveBytes = $zipOutputFile->outputAsString();
|
||||
$rawZipArchiveBytes = $zipFile->outputAsString();
|
||||
```
|
||||
Output .ZIP archive as attachment and terminate.
|
||||
```php
|
||||
$zipOutputFile->outputAsAttachment($outputFilename);
|
||||
$zipFile->outputAsAttachment($outputFilename);
|
||||
// or set mime type
|
||||
$zipOutputFile->outputAsAttachment($outputFilename = 'output.zip', $mimeType = 'application/zip');
|
||||
$mimeType = 'application/zip'
|
||||
$zipFile->outputAsAttachment($outputFilename, $mimeType);
|
||||
```
|
||||
Extract all files to directory.
|
||||
Rewrite and reopen zip archive.
|
||||
```php
|
||||
$zipOutputFile->extractTo($directory);
|
||||
```
|
||||
Extract some files to directory.
|
||||
```php
|
||||
$extractOnlyFiles = ["filename1", "filename2", "dir/dir/dir/"];
|
||||
$zipOutputFile->extractTo($directory, $extractOnlyFiles);
|
||||
```
|
||||
Get entry contents.
|
||||
```php
|
||||
$data = $zipOutputFile->getEntryContent($entryName);
|
||||
```
|
||||
Foreach zip entries.
|
||||
```php
|
||||
foreach($zipOutputFile as $entryName => $dataContent){
|
||||
echo "Entry: $entryName" . PHP_EOL;
|
||||
echo "Data: $dataContent" . PHP_EOL;
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
}
|
||||
```
|
||||
Iterator zip entries.
|
||||
```php
|
||||
$iterator = new \ArrayIterator($zipOutputFile);
|
||||
while ($iterator->valid())
|
||||
{
|
||||
$entryName = $iterator->key();
|
||||
$dataContent = $iterator->current();
|
||||
|
||||
echo "Entry: $entryName" . PHP_EOL;
|
||||
echo "Data: $dataContent" . PHP_EOL;
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
```
|
||||
Set zip alignment (alternate program `zipalign`).
|
||||
```php
|
||||
// before save or output
|
||||
$zipOutputFile->setAlign(4); // alternative cmd: zipalign -f -v 4 filename.zip
|
||||
$zipFile->rewrite();
|
||||
```
|
||||
#### <a name="Documentation-Close-Zip-Archive"></a> Close Zip Archive
|
||||
Close zip archive.
|
||||
```php
|
||||
$zipOutputFile->close();
|
||||
$zipFile->close();
|
||||
```
|
||||
Examples
|
||||
--------
|
||||
Create, open, extract and update archive.
|
||||
```php
|
||||
$outputFilename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'output.zip';
|
||||
$outputDirExtract = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'extract';
|
||||
|
||||
if(!is_dir($outputDirExtract)){
|
||||
mkdir($outputDirExtract, 0755, true);
|
||||
}
|
||||
|
||||
$zipOutputFile = \PhpZip\ZipOutputFile::create(); // create archive
|
||||
$zipOutputFile->addDir(__DIR__, true); // add this dir to archive
|
||||
$zipOutputFile->saveAsFile($outputFilename); // save as file
|
||||
$zipOutputFile->close(); // close output file, release all streams
|
||||
|
||||
$zipFile = \PhpZip\ZipFile::openFromFile($outputFilename); // open zip archive from file
|
||||
$zipFile->extractTo($outputDirExtract); // extract files to dir
|
||||
|
||||
$zipOutputFile = $zipFile->edit(); // create zip output archive for update
|
||||
$zipOutputFile->deleteFromRegex('~^\.~'); // delete all hidden (Unix) files
|
||||
$zipOutputFile->addFromString('dir/file.txt', 'Test file'); // add files from string contents
|
||||
$zipOutputFile->saveAsFile($outputFilename); // update zip file
|
||||
$zipOutputFile->close(); // close output file, release all streams
|
||||
|
||||
$zipFile->close(); // close input file, release all streams
|
||||
```
|
||||
Other examples can be found in the `tests/` folder
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
### <a name="Running-Tests"></a> Running Tests
|
||||
Installing development dependencies.
|
||||
```bash
|
||||
vendor/bin/phpunit -v --tap -c bootstrap.xml
|
||||
```
|
||||
composer install --dev
|
||||
```
|
||||
Run tests
|
||||
```bash
|
||||
vendor/bin/phpunit -v -c bootstrap.xml
|
||||
```
|
||||
### <a name="Upgrade"></a> Upgrade version 2 to version 3
|
||||
Update to the New Major Version via Composer
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"nelexa/zip": "^3.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
Next, use Composer to download new versions of the libraries:
|
||||
```bash
|
||||
composer update nelexa/zip
|
||||
```
|
||||
Update your Code to Work with the New Version:
|
||||
- Class `ZipOutputFile` merged to `ZipFile` and removed.
|
||||
+ `new \PhpZip\ZipOutputFile()` to `new \PhpZip\ZipFile()`
|
||||
- Static initialization methods are now not static.
|
||||
+ `\PhpZip\ZipFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);`
|
||||
+ `\PhpZip\ZipOutputFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);`
|
||||
+ `\PhpZip\ZipFile::openFromString($contents);` to `(new \PhpZip\ZipFile())->openFromString($contents);`
|
||||
+ `\PhpZip\ZipFile::openFromStream($stream);` to `(new \PhpZip\ZipFile())->openFromStream($stream);`
|
||||
+ `\PhpZip\ZipOutputFile::create()` to `new \PhpZip\ZipFile()`
|
||||
+ `\PhpZip\ZipOutputFile::openFromZipFile(\PhpZip\ZipFile $zipFile)` > `(new \PhpZip\ZipFile())->openFile($filename);`
|
||||
- Rename methods:
|
||||
+ `addFromFile` to `addFile`
|
||||
+ `setLevel` to `setCompressionLevel`
|
||||
+ `ZipFile::setPassword` to `ZipFile::withReadPassword`
|
||||
+ `ZipOutputFile::setPassword` to `ZipFile::withNewPassword`
|
||||
+ `ZipOutputFile::removePasswordAllEntries` to `ZipFile::withoutPassword`
|
||||
+ `ZipOutputFile::setComment` to `ZipFile::setArchiveComment`
|
||||
+ `ZipFile::getComment` to `ZipFile::getArchiveComment`
|
||||
- Changed signature for methods `addDir`, `addFilesFromGlob`, `addFilesFromRegex`.
|
||||
- Remove methods
|
||||
+ `getLevel`
|
||||
+ `setCompressionMethod`
|
||||
+ `setEntryPassword`
|
||||
|
||||
|
||||
|
@ -4,13 +4,15 @@
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"zip",
|
||||
"unzip",
|
||||
"archive",
|
||||
"extract",
|
||||
"winzip",
|
||||
"zipalign"
|
||||
],
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.8"
|
||||
"phpunit/phpunit": "4.8",
|
||||
"codeclimate/php-test-reporter": "^0.4.4"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
@ -22,17 +24,21 @@
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require": {
|
||||
"php-64bit": "^5.4 || ^7.0",
|
||||
"ext-mbstring": "*"
|
||||
"php-64bit": "^5.5 || ^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"": "src/"
|
||||
"PhpZip\\": "src/PhpZip"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"": "tests/"
|
||||
"PhpZip\\": "tests/PhpZip"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
|
||||
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
|
||||
"ext-bz2": "Needed to support BZIP2 compression"
|
||||
}
|
||||
}
|
||||
|
24
src/PhpZip/Crypto/CryptoEngine.php
Normal file
24
src/PhpZip/Crypto/CryptoEngine.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
|
||||
interface CryptoEngine
|
||||
{
|
||||
/**
|
||||
* Decryption string.
|
||||
*
|
||||
* @param string $encryptionContent
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function decrypt($encryptionContent);
|
||||
|
||||
/**
|
||||
* Encryption string.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($content);
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
@ -12,7 +13,7 @@ use PhpZip\Util\CryptoUtil;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class TraditionalPkwareEncryptionEngine
|
||||
class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
{
|
||||
/**
|
||||
* Encryption header size
|
||||
@ -154,7 +155,7 @@ class TraditionalPkwareEncryptionEngine
|
||||
|
||||
if ($this->entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// compare against the file type from extended local headers
|
||||
$checkByte = ($this->entry->getRawTime() >> 8) & 0xff;
|
||||
$checkByte = ($this->entry->getTime() >> 8) & 0xff;
|
||||
} else {
|
||||
// compare against the CRC otherwise
|
||||
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
|
||||
@ -187,11 +188,13 @@ class TraditionalPkwareEncryptionEngine
|
||||
* Encryption data
|
||||
*
|
||||
* @param string $data
|
||||
* @param int $crc
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($data, $crc)
|
||||
public function encrypt($data)
|
||||
{
|
||||
$crc = ($this->entry->isDataDescriptorRequired() ?
|
||||
($this->entry->getTime() & 0x0000ffff) << 16 :
|
||||
$this->entry->getCrc());
|
||||
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
|
||||
|
||||
// Initialize again since the generated bytes were encrypted.
|
||||
@ -206,11 +209,12 @@ class TraditionalPkwareEncryptionEngine
|
||||
/**
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws ZipCryptoException
|
||||
*/
|
||||
private function encryptData($content)
|
||||
{
|
||||
if ($content === null) {
|
||||
throw new \RuntimeException();
|
||||
if (null === $content) {
|
||||
throw new ZipCryptoException('content is null');
|
||||
}
|
||||
$buff = '';
|
||||
foreach (unpack('C*', $content) as $val) {
|
||||
@ -223,7 +227,7 @@ class TraditionalPkwareEncryptionEngine
|
||||
* @param int $byte
|
||||
* @return int
|
||||
*/
|
||||
protected function encryptByte($byte)
|
||||
private function encryptByte($byte)
|
||||
{
|
||||
$tempVal = $byte ^ $this->decryptByte() & 0xff;
|
||||
$this->updateKeys($byte);
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
@ -13,7 +14,7 @@ use PhpZip\Util\CryptoUtil;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class WinZipAesEngine
|
||||
class WinZipAesEngine implements CryptoEngine
|
||||
{
|
||||
/**
|
||||
* The block size of the Advanced Encryption Specification (AES) Algorithm
|
||||
@ -42,12 +43,12 @@ class WinZipAesEngine
|
||||
/**
|
||||
* Decrypt from stream resource.
|
||||
*
|
||||
* @param resource $stream Input stream resource
|
||||
* @param string $content Input stream buffer
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
* @throws ZipCryptoException
|
||||
*/
|
||||
public function decrypt($stream)
|
||||
public function decrypt($content)
|
||||
{
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
@ -57,20 +58,20 @@ class WinZipAesEngine
|
||||
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
|
||||
}
|
||||
|
||||
$pos = ftell($stream);
|
||||
|
||||
// Get key strength.
|
||||
$keyStrengthBits = $field->getKeyStrength();
|
||||
$keyStrengthBytes = $keyStrengthBits / 8;
|
||||
|
||||
$salt = fread($stream, $keyStrengthBytes / 2);
|
||||
$passwordVerifier = fread($stream, self::PWD_VERIFIER_BITS / 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.
|
||||
$endPos = $pos + $this->entry->getCompressedSize();
|
||||
$start = ftell($stream);
|
||||
$start = $pos;
|
||||
$endPos = strlen($content);
|
||||
$footerSize = $sha1Size / 2;
|
||||
$end = $endPos - $footerSize;
|
||||
$size = $end - $start;
|
||||
@ -80,9 +81,8 @@ class WinZipAesEngine
|
||||
}
|
||||
|
||||
// Load authentication code.
|
||||
fseek($stream, $end, SEEK_SET);
|
||||
$authenticationCode = fread($stream, $footerSize);
|
||||
if (ftell($stream) !== $endPos) {
|
||||
$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!");
|
||||
@ -95,27 +95,33 @@ class WinZipAesEngine
|
||||
// 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);
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$iv = str_repeat(chr(0), $ctrIvSize);
|
||||
|
||||
$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 = stream_get_contents($stream, $size, $start);
|
||||
$content = substr($content, $start, $size);
|
||||
$mac = hash_hmac('sha1', $content, $sha1MacParam, true);
|
||||
|
||||
if ($authenticationCode !== substr($mac, 0, 10)) {
|
||||
throw new ZipAuthenticationException($this->entry->getName() . " (authenticated WinZip AES entry content has been tampered with)");
|
||||
if (substr($mac, 0, 10) !== $authenticationCode) {
|
||||
throw new ZipAuthenticationException($this->entry->getName() .
|
||||
" (authenticated WinZip AES entry content has been tampered with)");
|
||||
}
|
||||
|
||||
return self::aesCtrSegmentIntegerCounter(false, $content, $key, $iv);
|
||||
@ -137,7 +143,7 @@ class WinZipAesEngine
|
||||
for ($i = 0; $i < $numOfBlocks; ++$i) {
|
||||
for ($j = 0; $j < 16; ++$j) {
|
||||
$n = ord($iv[$j]);
|
||||
if (++$n === 0x100) {
|
||||
if (0x100 === ++$n) {
|
||||
// overflow, set this one to 0, increment next
|
||||
$iv[$j] = chr(0);
|
||||
} else {
|
||||
@ -161,6 +167,7 @@ class WinZipAesEngine
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @return string Encrypted data
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private static function encryptCtr($data, $key, $iv)
|
||||
{
|
||||
@ -170,7 +177,7 @@ class WinZipAesEngine
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new \RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +188,7 @@ class WinZipAesEngine
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @return string Raw data
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private static function decryptCtr($data, $key, $iv)
|
||||
{
|
||||
@ -190,7 +198,7 @@ class WinZipAesEngine
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new \RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace PhpZip\Exception;
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class IllegalArgumentException extends ZipException
|
||||
class InvalidArgumentException extends ZipException
|
||||
{
|
||||
|
||||
}
|
13
src/PhpZip/Exception/RuntimeException.php
Normal file
13
src/PhpZip/Exception/RuntimeException.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Runtime exception.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class RuntimeException extends ZipException
|
||||
{
|
||||
|
||||
}
|
@ -45,7 +45,7 @@ abstract class ExtraField implements ExtraFieldHeader
|
||||
if (isset(self::getRegistry()[$headerId])) {
|
||||
$extraClassName = self::getRegistry()[$headerId];
|
||||
$extraField = new $extraClassName;
|
||||
if ($headerId !== $extraField::getHeaderId()) {
|
||||
if ($extraField::getHeaderId() !== $headerId) {
|
||||
throw new ZipException('Runtime error support headerId ' . $headerId);
|
||||
}
|
||||
} else {
|
||||
@ -61,9 +61,9 @@ abstract class ExtraField implements ExtraFieldHeader
|
||||
*/
|
||||
private static function getRegistry()
|
||||
{
|
||||
if (self::$registry === null) {
|
||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = '\PhpZip\Extra\WinZipAesEntryExtraField';
|
||||
self::$registry[NtfsExtraField::getHeaderId()] = '\PhpZip\Extra\NtfsExtraField';
|
||||
if (null === self::$registry) {
|
||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
||||
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
||||
}
|
||||
return self::$registry;
|
||||
}
|
||||
@ -80,7 +80,7 @@ abstract class ExtraField implements ExtraFieldHeader
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size data block out of range.');
|
||||
}
|
||||
$fp = fopen('php://temp', 'r+b');
|
||||
$fp = fopen('php://memory', 'r+b');
|
||||
if (0 === $size) return $fp;
|
||||
$this->writeTo($fp, 0);
|
||||
rewind($fp);
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
@ -118,8 +117,17 @@ class ExtraFields
|
||||
}
|
||||
if (0 === $size) return '';
|
||||
|
||||
$fp = fopen('php://temp', 'r+b');
|
||||
$this->writeTo($fp, 0);
|
||||
$fp = fopen('php://memory', 'r+b');
|
||||
$offset = 0;
|
||||
/**
|
||||
* @var ExtraField $ef
|
||||
*/
|
||||
foreach ($this->extra as $ef) {
|
||||
fwrite($fp, pack('vv', $ef::getHeaderId(), $ef->getDataSize()));
|
||||
$offset += 4;
|
||||
fwrite($fp, $ef->writeTo($fp, $offset));
|
||||
$offset += $ef->getDataSize();
|
||||
}
|
||||
rewind($fp);
|
||||
$content = stream_get_contents($fp);
|
||||
fclose($fp);
|
||||
@ -148,27 +156,6 @@ class ExtraFields
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a list of Extra Fields of ExtraField::getExtraLength bytes to the
|
||||
* stream resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset
|
||||
*/
|
||||
private function writeTo($handle, $off)
|
||||
{
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
/**
|
||||
* @var ExtraField $ef
|
||||
*/
|
||||
foreach ($this->extra as $ef) {
|
||||
fwrite($handle, pack('vv', $ef::getHeaderId(), $ef->getDataSize()));
|
||||
$off += 4;
|
||||
fwrite($handle, $ef->writeTo($handle, $off));
|
||||
$off += $ef->getDataSize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
@ -187,7 +174,7 @@ class ExtraFields
|
||||
if (null !== $handle && 0 < $size) {
|
||||
$end = $off + $size;
|
||||
while ($off < $end) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fseek($handle, $off);
|
||||
$unpack = unpack('vheaderId/vdataSize', fread($handle, 4));
|
||||
$off += 4;
|
||||
$extraField = ExtraField::create($unpack['headerId']);
|
||||
|
@ -86,7 +86,7 @@ class NtfsExtraField extends ExtraField
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
|
||||
$unpack = unpack('vtag/vsizeAttr', fread($handle, 4));
|
||||
if ($unpack['sizeAttr'] === 24) {
|
||||
if (24 === $unpack['sizeAttr']) {
|
||||
$tagData = fread($handle, $unpack['sizeAttr']);
|
||||
|
||||
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
|
||||
@ -110,7 +110,7 @@ class NtfsExtraField extends ExtraField
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
{
|
||||
if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) {
|
||||
if (null !== $this->mtime && null !== $this->atime && null !== $this->ctime) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fwrite($handle, pack('Vvv', 0, 1, 8 * 3 + strlen($this->rawData)));
|
||||
$mtimeLong = ($this->mtime + 11644473600) * 10000000;
|
||||
|
470
src/PhpZip/Model/CentralDirectory.php
Normal file
470
src/PhpZip/Model/CentralDirectory.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipNotFoundEntry;
|
||||
use PhpZip\Model\Entry\ZipNewStringEntry;
|
||||
use PhpZip\Model\Entry\ZipReadEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Read Central Directory
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class CentralDirectory
|
||||
{
|
||||
/** Central File Header signature. */
|
||||
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
|
||||
/**
|
||||
* @var EndOfCentralDirectory End of Central Directory
|
||||
*/
|
||||
private $endOfCentralDirectory;
|
||||
/**
|
||||
* @var ZipEntry[] Maps entry names to zip entries.
|
||||
*/
|
||||
private $entries = [];
|
||||
/**
|
||||
* @var ZipEntry[] New and modified entries
|
||||
*/
|
||||
private $modifiedEntries = [];
|
||||
/**
|
||||
* @var int Default compression level for the methods DEFLATED and BZIP2.
|
||||
*/
|
||||
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
|
||||
/**
|
||||
* @var int|null ZipAlign setting
|
||||
*/
|
||||
private $zipAlign;
|
||||
/**
|
||||
* @var string New password
|
||||
*/
|
||||
private $password;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionMethod;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $clearPassword;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->endOfCentralDirectory = new EndOfCentralDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 resource $inputStream
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function mountCentralDirectory($inputStream)
|
||||
{
|
||||
$this->modifiedEntries = [];
|
||||
$this->checkZipFileSignature($inputStream);
|
||||
$this->endOfCentralDirectory->findCentralDirectory($inputStream);
|
||||
|
||||
$numEntries = $this->endOfCentralDirectory->getCentralDirectoryEntriesSize();
|
||||
$entries = [];
|
||||
for (; $numEntries > 0; $numEntries--) {
|
||||
$entry = new ZipReadEntry($inputStream);
|
||||
$entry->setCentralDirectory($this);
|
||||
// Re-load virtual offset after ZIP64 Extended Information
|
||||
// Extra Field may have been parsed, map it to the real
|
||||
// offset and conditionally update the preamble size from it.
|
||||
$lfhOff = $this->endOfCentralDirectory->getMapper()->map($entry->getOffset());
|
||||
if ($lfhOff < $this->endOfCentralDirectory->getPreamble()) {
|
||||
$this->endOfCentralDirectory->setPreamble($lfhOff);
|
||||
}
|
||||
$entries[$entry->getName()] = $entry;
|
||||
}
|
||||
|
||||
if (0 !== $numEntries % 0x10000) {
|
||||
throw new ZipException("Expected " . abs($numEntries) .
|
||||
($numEntries > 0 ? " more" : " less") .
|
||||
" entries in the Central Directory!");
|
||||
}
|
||||
$this->entries = $entries;
|
||||
|
||||
if ($this->endOfCentralDirectory->getPreamble() + $this->endOfCentralDirectory->getPostamble() >= fstat($inputStream)['size']) {
|
||||
assert(0 === $numEntries);
|
||||
$this->checkZipFileSignature($inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check zip file signature
|
||||
*
|
||||
* @param resource $inputStream
|
||||
* @throws ZipException if this not .ZIP file.
|
||||
*/
|
||||
private function checkZipFileSignature($inputStream)
|
||||
{
|
||||
rewind($inputStream);
|
||||
// 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($inputStream, 4);
|
||||
if (strlen($signatureBytes) < 4) {
|
||||
throw new ZipException("Invalid zip file.");
|
||||
}
|
||||
$signature = unpack('V', $signatureBytes)[1];
|
||||
if (
|
||||
ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature
|
||||
&& EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
&& EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
|
||||
) {
|
||||
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set compression method for new or rewrites entries.
|
||||
* @param int $compressionLevel
|
||||
* @throws InvalidArgumentException
|
||||
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
|
||||
* @see ZipFile::LEVEL_BEST_SPEED
|
||||
* @see ZipFile::LEVEL_BEST_COMPRESSION
|
||||
*/
|
||||
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 ZipEntry[]
|
||||
*/
|
||||
public function &getEntries()
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @return ZipEntry
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getEntry($entryName)
|
||||
{
|
||||
if (!isset($this->entries[$entryName])) {
|
||||
throw new ZipNotFoundEntry('Zip entry ' . $entryName . ' not found');
|
||||
}
|
||||
return $this->entries[$entryName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EndOfCentralDirectory
|
||||
*/
|
||||
public function getEndOfCentralDirectory()
|
||||
{
|
||||
return $this->endOfCentralDirectory;
|
||||
}
|
||||
|
||||
public function getArchiveComment()
|
||||
{
|
||||
return null === $this->endOfCentralDirectory->getComment() ?
|
||||
'' :
|
||||
$this->endOfCentralDirectory->getComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry comment
|
||||
* @param string $entryName
|
||||
* @param string|null $comment
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function setEntryComment($entryName, $comment)
|
||||
{
|
||||
if (isset($this->modifiedEntries[$entryName])) {
|
||||
$this->modifiedEntries[$entryName]->setComment($comment);
|
||||
} elseif (isset($this->entries[$entryName])) {
|
||||
$entry = clone $this->entries[$entryName];
|
||||
$entry->setComment($comment);
|
||||
$this->putInModified($entryName, $entry);
|
||||
} else {
|
||||
throw new ZipNotFoundEntry("Not found entry " . $entryName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $password
|
||||
* @param int|null $encryptionMethod
|
||||
*/
|
||||
public function setNewPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
$this->password = $password;
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
$this->clearPassword = $password === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getZipAlign()
|
||||
{
|
||||
return $this->zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $zipAlign
|
||||
*/
|
||||
public function setZipAlign($zipAlign = null)
|
||||
{
|
||||
if (null === $zipAlign) {
|
||||
$this->zipAlign = null;
|
||||
return;
|
||||
}
|
||||
$this->zipAlign = (int)$zipAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put modification or new entries.
|
||||
*
|
||||
* @param $entryName
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function putInModified($entryName, ZipEntry $entry)
|
||||
{
|
||||
$this->modifiedEntries[$entryName] = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function deleteEntry($entryName)
|
||||
{
|
||||
if (isset($this->entries[$entryName])) {
|
||||
$this->modifiedEntries[$entryName] = null;
|
||||
} elseif (isset($this->modifiedEntries[$entryName])) {
|
||||
unset($this->modifiedEntries[$entryName]);
|
||||
} else {
|
||||
throw new ZipNotFoundEntry("Not found entry " . $entryName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $regexPattern
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteEntriesFromRegex($regexPattern)
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($this->modifiedEntries as $entryName => &$entry) {
|
||||
if (preg_match($regexPattern, $entryName)) {
|
||||
unset($entry);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
foreach ($this->entries as $entryName => $entry) {
|
||||
if (preg_match($regexPattern, $entryName)) {
|
||||
$this->modifiedEntries[$entryName] = null;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $oldName
|
||||
* @param string $newName
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function rename($oldName, $newName)
|
||||
{
|
||||
$oldName = (string)$oldName;
|
||||
$newName = (string)$newName;
|
||||
|
||||
if (isset($this->entries[$newName]) || isset($this->modifiedEntries[$newName])) {
|
||||
throw new InvalidArgumentException("New entry name " . $newName . ' is exists.');
|
||||
}
|
||||
|
||||
if (isset($this->modifiedEntries[$oldName]) || isset($this->entries[$oldName])) {
|
||||
$newEntry = clone (isset($this->modifiedEntries[$oldName]) ?
|
||||
$this->modifiedEntries[$oldName] :
|
||||
$this->entries[$oldName]);
|
||||
$newEntry->setName($newName);
|
||||
|
||||
$this->modifiedEntries[$oldName] = null;
|
||||
$this->modifiedEntries[$newName] = $newEntry;
|
||||
return;
|
||||
}
|
||||
throw new ZipNotFoundEntry("Not found entry " . $oldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all entries.
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
$this->modifiedEntries = [];
|
||||
foreach ($this->entries as $entry) {
|
||||
$this->modifiedEntries[$entry->getName()] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $outputStream
|
||||
*/
|
||||
public function writeArchive($outputStream)
|
||||
{
|
||||
/**
|
||||
* @var ZipEntry[] $memoryEntriesResult
|
||||
*/
|
||||
$memoryEntriesResult = [];
|
||||
foreach ($this->entries as $entryName => $entry) {
|
||||
if (isset($this->modifiedEntries[$entryName])) continue;
|
||||
|
||||
if (
|
||||
(null !== $this->password || $this->clearPassword) &&
|
||||
$entry->isEncrypted() &&
|
||||
$entry->getPassword() !== null &&
|
||||
(
|
||||
$entry->getPassword() !== $this->password ||
|
||||
$entry->getEncryptionMethod() !== $this->encryptionMethod
|
||||
)
|
||||
) {
|
||||
$prototypeEntry = new ZipNewStringEntry($entry->getEntryContent());
|
||||
$prototypeEntry->setName($entry->getName());
|
||||
$prototypeEntry->setMethod($entry->getMethod());
|
||||
$prototypeEntry->setTime($entry->getTime());
|
||||
$prototypeEntry->setExternalAttributes($entry->getExternalAttributes());
|
||||
$prototypeEntry->setExtra($entry->getExtra());
|
||||
$prototypeEntry->setPassword($this->password, $this->encryptionMethod);
|
||||
if ($this->clearPassword) {
|
||||
$prototypeEntry->clearEncryption();
|
||||
}
|
||||
} else {
|
||||
$prototypeEntry = clone $entry;
|
||||
}
|
||||
$memoryEntriesResult[$entryName] = $prototypeEntry;
|
||||
}
|
||||
|
||||
foreach ($this->modifiedEntries as $entryName => $outputEntry) {
|
||||
if (null === $outputEntry) { // remove marked entry
|
||||
unset($memoryEntriesResult[$entryName]);
|
||||
} else {
|
||||
if (null !== $this->password) {
|
||||
$outputEntry->setPassword($this->password, $this->encryptionMethod);
|
||||
}
|
||||
$memoryEntriesResult[$entryName] = $outputEntry;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($memoryEntriesResult as $key => $outputEntry) {
|
||||
$outputEntry->setCentralDirectory($this);
|
||||
$outputEntry->writeEntry($outputStream);
|
||||
}
|
||||
$centralDirectoryOffset = ftell($outputStream);
|
||||
foreach ($memoryEntriesResult as $key => $outputEntry) {
|
||||
if (!$this->writeCentralFileHeader($outputStream, $outputEntry)) {
|
||||
unset($memoryEntriesResult[$key]);
|
||||
}
|
||||
}
|
||||
$centralDirectoryEntries = sizeof($memoryEntriesResult);
|
||||
$this->getEndOfCentralDirectory()->writeEndOfCentralDirectory(
|
||||
$outputStream,
|
||||
$centralDirectoryEntries,
|
||||
$centralDirectoryOffset
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Central File Header record.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
* @param ZipEntry $entry
|
||||
* @return bool false if and only if the record has been skipped,
|
||||
* i.e. not written for some other reason than an I/O error.
|
||||
*/
|
||||
private function writeCentralFileHeader($outputStream, ZipEntry $entry)
|
||||
{
|
||||
$compressedSize = $entry->getCompressedSize();
|
||||
$size = $entry->getSize();
|
||||
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
|
||||
// UNKNOWN!
|
||||
if (ZipEntry::UNKNOWN === ($compressedSize | $size)) {
|
||||
return false;
|
||||
}
|
||||
$extra = $entry->getExtra();
|
||||
$extraSize = strlen($extra);
|
||||
|
||||
$commentLength = strlen($entry->getComment());
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack(
|
||||
'VvvvvVVVVvvvvvVV',
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
self::CENTRAL_FILE_HEADER_SIG,
|
||||
// version made by 2 bytes
|
||||
($entry->getPlatform() << 8) | 63,
|
||||
// version needed to extract 2 bytes
|
||||
$entry->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$entry->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$entry->getMethod(),
|
||||
// last mod file datetime 4 bytes
|
||||
$entry->getTime(),
|
||||
// crc-32 4 bytes
|
||||
$entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
strlen($entry->getName()),
|
||||
// extra field length 2 bytes
|
||||
$extraSize,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
0,
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$entry->getOffset()
|
||||
)
|
||||
);
|
||||
// file name (variable size)
|
||||
fwrite($outputStream, $entry->getName());
|
||||
if (0 < $extraSize) {
|
||||
// extra field (variable size)
|
||||
fwrite($outputStream, $extra);
|
||||
}
|
||||
if (0 < $commentLength) {
|
||||
// file comment (variable size)
|
||||
fwrite($outputStream, $entry->getComment());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function release()
|
||||
{
|
||||
unset($this->entries);
|
||||
unset($this->modifiedEntries);
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->release();
|
||||
}
|
||||
|
||||
}
|
419
src/PhpZip/Model/EndOfCentralDirectory.php
Normal file
419
src/PhpZip/Model/EndOfCentralDirectory.php
Normal file
@ -0,0 +1,419 @@
|
||||
<?php
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Mapper\OffsetPositionMapper;
|
||||
use PhpZip\Mapper\PositionMapper;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* Read End of Central Directory
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class EndOfCentralDirectory
|
||||
{
|
||||
/** Zip64 End Of Central Directory Record. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
|
||||
/** Zip64 End Of Central Directory Locator. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
|
||||
/** End Of Central Directory Record signature. */
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
|
||||
/**
|
||||
* The minimum length of the End Of Central Directory Record.
|
||||
*
|
||||
* end of central dir signature 4
|
||||
* number of this disk 2
|
||||
* number of the disk with the
|
||||
* start of the central directory 2
|
||||
* total number of entries in the
|
||||
* central directory on this disk 2
|
||||
* total number of entries in
|
||||
* the central directory 2
|
||||
* size of the central directory 4
|
||||
* offset of start of central *
|
||||
* directory with respect to *
|
||||
* the starting disk number 4
|
||||
* zipfile comment length 2
|
||||
*/
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
|
||||
/**
|
||||
* The length of the Zip64 End Of Central Directory Locator.
|
||||
* zip64 end of central dir locator
|
||||
* signature 4
|
||||
* number of the disk with the
|
||||
* start of the zip64 end of
|
||||
* central directory 4
|
||||
* relative offset of the zip64
|
||||
* end of central directory record 8
|
||||
* total number of disks 4
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
|
||||
/**
|
||||
* The minimum length of the Zip64 End Of Central Directory Record.
|
||||
*
|
||||
* zip64 end of central dir
|
||||
* signature 4
|
||||
* size of zip64 end of central
|
||||
* directory record 8
|
||||
* version made by 2
|
||||
* version needed to extract 2
|
||||
* number of this disk 4
|
||||
* number of the disk with the
|
||||
* start of the central directory 4
|
||||
* total number of entries in the
|
||||
* central directory on this disk 8
|
||||
* total number of entries in
|
||||
* the central directory 8
|
||||
* size of the central directory 8
|
||||
* offset of start of central
|
||||
* directory with respect to
|
||||
* the starting disk number 8
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
|
||||
/**
|
||||
* @var string|null The archive comment.
|
||||
*/
|
||||
private $comment;
|
||||
/**
|
||||
* @var int The number of bytes in the preamble of this ZIP file.
|
||||
*/
|
||||
private $preamble;
|
||||
/**
|
||||
* @var int The number of bytes in the postamble of this ZIP file.
|
||||
*/
|
||||
private $postamble;
|
||||
/**
|
||||
* @var PositionMapper Maps offsets specified in the ZIP file to real offsets in the file.
|
||||
*/
|
||||
private $mapper;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $centralDirectoryEntriesSize;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $zip64 = false;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $newComment;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $modified;
|
||||
|
||||
/**
|
||||
* EndOfCentralDirectory constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->mapper = new PositionMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the file pointer at the first Central File Header.
|
||||
* Performs some means to check that this is really a ZIP file.
|
||||
*
|
||||
* @param resource $inputStream
|
||||
* @throws ZipException If the file is not compatible to the ZIP File
|
||||
* Format Specification.
|
||||
*/
|
||||
public function findCentralDirectory($inputStream)
|
||||
{
|
||||
// Search for End of central directory record.
|
||||
$stats = fstat($inputStream);
|
||||
$size = $stats['size'];
|
||||
$max = $size - self::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN;
|
||||
$min = $max >= 0xffff ? $max - 0xffff : 0;
|
||||
for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) {
|
||||
fseek($inputStream, $endOfCentralDirRecordPos, SEEK_SET);
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
if (self::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($inputStream, 4))[1])
|
||||
continue;
|
||||
|
||||
// number of this disk - 2 bytes
|
||||
// number of the disk with the start of the
|
||||
// central directory - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory on this disk - 2 bytes
|
||||
// total number of entries in the central
|
||||
// directory - 2 bytes
|
||||
// size of the central directory - 4 bytes
|
||||
// offset of start of central directory with
|
||||
// respect to the starting disk number - 4 bytes
|
||||
// ZIP file comment length - 2 bytes
|
||||
$data = unpack(
|
||||
'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength',
|
||||
fread($inputStream, 18)
|
||||
);
|
||||
|
||||
if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) {
|
||||
throw new ZipException(
|
||||
"ZIP file spanning/splitting is not supported!"
|
||||
);
|
||||
}
|
||||
// .ZIP file comment (variable size)
|
||||
if (0 < $data['commentLength']) {
|
||||
$this->comment = fread($inputStream, $data['commentLength']);
|
||||
}
|
||||
$this->preamble = $endOfCentralDirRecordPos;
|
||||
$this->postamble = $size - ftell($inputStream);
|
||||
|
||||
// Check for ZIP64 End Of Central Directory Locator.
|
||||
$endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - self::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN;
|
||||
|
||||
fseek($inputStream, $endOfCentralDirLocatorPos, SEEK_SET);
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
if (
|
||||
0 > $endOfCentralDirLocatorPos ||
|
||||
ftell($inputStream) === $size ||
|
||||
self::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($inputStream, 4))[1]
|
||||
) {
|
||||
// Seek and check first CFH, probably requiring an offset mapper.
|
||||
$offset = $endOfCentralDirRecordPos - $data['cdSize'];
|
||||
fseek($inputStream, $offset, SEEK_SET);
|
||||
$offset -= $data['cdPos'];
|
||||
if (0 !== $offset) {
|
||||
$this->mapper = new OffsetPositionMapper($offset);
|
||||
}
|
||||
$this->centralDirectoryEntriesSize = $data['cdEntries'];
|
||||
return;
|
||||
}
|
||||
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
$zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($inputStream, 4))[1];
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
$zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
// total number of disks 4 bytes
|
||||
$totalDisks = unpack('V', fread($inputStream, 4))[1];
|
||||
if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
fseek($inputStream, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET);
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
$zip64EndOfCentralDirSig = unpack('V', fread($inputStream, 4))[1];
|
||||
if (self::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) {
|
||||
throw new ZipException("Expected ZIP64 End Of Central Directory Record!");
|
||||
}
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
fseek($inputStream, 12, SEEK_CUR);
|
||||
// number of this disk 4 bytes
|
||||
$diskNo = unpack('V', fread($inputStream, 4))[1];
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
$cdDiskNo = unpack('V', fread($inputStream, 4))[1];
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
$cdEntriesDisk = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
$cdEntries = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) {
|
||||
throw new ZipException("ZIP file spanning/splitting is not supported!");
|
||||
}
|
||||
if ($cdEntries < 0 || 0x7fffffff < $cdEntries) {
|
||||
throw new ZipException("Total Number Of Entries In The Central Directory out of range!");
|
||||
}
|
||||
// size of the central directory 8 bytes
|
||||
fseek($inputStream, 8, SEEK_CUR);
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
$cdPos = PackUtil::unpackLongLE(fread($inputStream, 8));
|
||||
// zip64 extensible data sector (variable size)
|
||||
fseek($inputStream, $cdPos, SEEK_SET);
|
||||
$this->preamble = $zip64EndOfCentralDirectoryRecordPos;
|
||||
$this->centralDirectoryEntriesSize = $cdEntries;
|
||||
$this->zip64 = true;
|
||||
return;
|
||||
}
|
||||
// Start recovering file entries from min.
|
||||
$this->preamble = $min;
|
||||
$this->postamble = $size - $min;
|
||||
$this->centralDirectoryEntriesSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCentralDirectoryEntriesSize()
|
||||
{
|
||||
return $this->centralDirectoryEntriesSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZip64()
|
||||
{
|
||||
return $this->zip64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPreamble()
|
||||
{
|
||||
return $this->preamble;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPostamble()
|
||||
{
|
||||
return $this->postamble;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PositionMapper
|
||||
*/
|
||||
public function getMapper()
|
||||
{
|
||||
return $this->mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $preamble
|
||||
*/
|
||||
public function setPreamble($preamble)
|
||||
{
|
||||
$this->preamble = $preamble;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set archive comment
|
||||
* @param string|null $comment
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setComment($comment = null)
|
||||
{
|
||||
if (null !== $comment && strlen($comment) !== 0) {
|
||||
$comment = (string)$comment;
|
||||
$length = strlen($comment);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new InvalidArgumentException('Length comment out of range');
|
||||
}
|
||||
}
|
||||
$this->modified = $comment !== $this->comment;
|
||||
$this->newComment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write end of central directory.
|
||||
*
|
||||
* @param resource $outputStream Output stream
|
||||
* @param int $centralDirectoryEntries Size entries
|
||||
* @param int $centralDirectoryOffset Offset central directory
|
||||
*/
|
||||
public function writeEndOfCentralDirectory($outputStream, $centralDirectoryEntries, $centralDirectoryOffset)
|
||||
{
|
||||
$position = ftell($outputStream);
|
||||
$centralDirectorySize = $position - $centralDirectoryOffset;
|
||||
$centralDirectoryEntriesZip64 = $centralDirectoryEntries > 0xffff;
|
||||
$centralDirectorySizeZip64 = $centralDirectorySize > 0xffffffff;
|
||||
$centralDirectoryOffsetZip64 = $centralDirectoryOffset > 0xffffffff;
|
||||
$centralDirectoryEntries16 = $centralDirectoryEntriesZip64 ? 0xffff : (int)$centralDirectoryEntries;
|
||||
$centralDirectorySize32 = $centralDirectorySizeZip64 ? 0xffffffff : $centralDirectorySize;
|
||||
$centralDirectoryOffset32 = $centralDirectoryOffsetZip64 ? 0xffffffff : $centralDirectoryOffset;
|
||||
$zip64 // ZIP64 extensions?
|
||||
= $centralDirectoryEntriesZip64
|
||||
|| $centralDirectorySizeZip64
|
||||
|| $centralDirectoryOffsetZip64;
|
||||
if ($zip64) {
|
||||
// relative offset of the zip64 end of central directory record
|
||||
$zip64EndOfCentralDirectoryOffset = $position;
|
||||
// zip64 end of central dir
|
||||
// signature 4 bytes (0x06064b50)
|
||||
fwrite($outputStream, pack('V', self::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG));
|
||||
// size of zip64 end of central
|
||||
// directory record 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE(self::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN - 12));
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// due to potential use of BZIP2 compression
|
||||
// number of this disk 4 bytes
|
||||
// number of the disk with the
|
||||
// start of the central directory 4 bytes
|
||||
fwrite($outputStream, pack('vvVV', 63, 46, 0, 0));
|
||||
// total number of entries in the
|
||||
// central directory on this disk 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectoryEntries));
|
||||
// total number of entries in the
|
||||
// central directory 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectoryEntries));
|
||||
// size of the central directory 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectorySize));
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($centralDirectoryOffset));
|
||||
// zip64 extensible data sector (variable size)
|
||||
//
|
||||
// zip64 end of central dir locator
|
||||
// signature 4 bytes (0x07064b50)
|
||||
// number of the disk with the
|
||||
// start of the zip64 end of
|
||||
// central directory 4 bytes
|
||||
fwrite($outputStream, pack('VV', self::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG, 0));
|
||||
// relative offset of the zip64
|
||||
// end of central directory record 8 bytes
|
||||
fwrite($outputStream, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset));
|
||||
// total number of disks 4 bytes
|
||||
fwrite($outputStream, pack('V', 1));
|
||||
}
|
||||
$comment = $this->modified ? $this->newComment : $this->comment;
|
||||
$commentLength = strlen($comment);
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack('VvvvvVVv',
|
||||
// end of central dir signature 4 bytes (0x06054b50)
|
||||
self::END_OF_CENTRAL_DIRECTORY_RECORD_SIG,
|
||||
// number of this disk 2 bytes
|
||||
0,
|
||||
// number of the disk with the
|
||||
// start of the central directory 2 bytes
|
||||
0,
|
||||
// total number of entries in the
|
||||
// central directory on this disk 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// total number of entries in
|
||||
// the central directory 2 bytes
|
||||
$centralDirectoryEntries16,
|
||||
// size of the central directory 4 bytes
|
||||
$centralDirectorySize32,
|
||||
// offset of start of central
|
||||
// directory with respect to
|
||||
// the starting disk number 4 bytes
|
||||
$centralDirectoryOffset32,
|
||||
// .ZIP file comment length 2 bytes
|
||||
$commentLength
|
||||
)
|
||||
);
|
||||
if ($commentLength > 0) {
|
||||
// .ZIP file comment (variable size)
|
||||
fwrite($outputStream, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
922
src/PhpZip/Model/Entry/ZipAbstractEntry.php
Normal file
922
src/PhpZip/Model/Entry/ZipAbstractEntry.php
Normal file
@ -0,0 +1,922 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\DefaultExtraField;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\Extra\ExtraFields;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\CentralDirectory;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\DateTimeConverter;
|
||||
use PhpZip\Util\PackUtil;
|
||||
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 CentralDirectory
|
||||
*/
|
||||
private $centralDirectory;
|
||||
|
||||
/**
|
||||
* @var int Bit flags for init state.
|
||||
*/
|
||||
private $init;
|
||||
|
||||
/**
|
||||
* @var string Entry name (filename in archive)
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* @var int Made by platform
|
||||
*/
|
||||
private $platform;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $versionNeededToExtract = 20;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $general;
|
||||
/**
|
||||
* @var int Compression method
|
||||
*/
|
||||
private $method;
|
||||
/**
|
||||
* @var int Dos time
|
||||
*/
|
||||
private $dosTime;
|
||||
/**
|
||||
* @var int Crc32
|
||||
*/
|
||||
private $crc;
|
||||
/**
|
||||
* @var int Compressed size
|
||||
*/
|
||||
private $compressedSize = self::UNKNOWN;
|
||||
/**
|
||||
* @var int Uncompressed size
|
||||
*/
|
||||
private $size = self::UNKNOWN;
|
||||
/**
|
||||
* @var int External attributes
|
||||
*/
|
||||
private $externalAttributes;
|
||||
/**
|
||||
* @var int Relative Offset Of Local File Header.
|
||||
*/
|
||||
private $offset = self::UNKNOWN;
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID [Integer] to Extra Field [ExtraField].
|
||||
* Should be null or may be empty if no Extra Fields are used.
|
||||
*
|
||||
* @var ExtraFields
|
||||
*/
|
||||
private $fields;
|
||||
/**
|
||||
* @var string 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
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
private function isInit($mask)
|
||||
{
|
||||
return 0 !== ($this->init & $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mask
|
||||
* @param bool $init
|
||||
*/
|
||||
private function setInit($mask, $init)
|
||||
{
|
||||
if ($init) {
|
||||
$this->init |= $mask;
|
||||
} else {
|
||||
$this->init &= ~$mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CentralDirectory
|
||||
*/
|
||||
public function getCentralDirectory()
|
||||
{
|
||||
return $this->centralDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CentralDirectory $centralDirectory
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCentralDirectory(CentralDirectory $centralDirectory)
|
||||
{
|
||||
$this->centralDirectory = $centralDirectory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ZIP entry name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry name.
|
||||
*
|
||||
* @param string $name New entry name
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$length = strlen($name);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new ZipException('Illegal zip entry name parameter');
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->isInit(self::BIT_PLATFORM) ? $this->platform & 0xffff : self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set platform
|
||||
*
|
||||
* @param int $platform
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setPlatform($platform)
|
||||
{
|
||||
$known = self::UNKNOWN !== $platform;
|
||||
if ($known) {
|
||||
if (0x00 > $platform || $platform > 0xff) {
|
||||
throw new ZipException("Platform out of range");
|
||||
}
|
||||
$this->platform = $platform;
|
||||
} else {
|
||||
$this->platform = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_PLATFORM, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionNeededToExtract()
|
||||
{
|
||||
return $this->versionNeededToExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set version needed to extract.
|
||||
*
|
||||
* @param int $version
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setVersionNeededToExtract($version)
|
||||
{
|
||||
$this->versionNeededToExtract = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZip64ExtensionsRequired()
|
||||
{
|
||||
// Offset MUST be considered in decision about ZIP64 format - see
|
||||
// description of Data Descriptor in ZIP File Format Specification!
|
||||
return 0xffffffff <= $this->getCompressedSize()
|
||||
|| 0xffffffff <= $this->getSize()
|
||||
|| 0xffffffff <= $this->getOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCompressedSize($compressedSize)
|
||||
{
|
||||
if (self::UNKNOWN != $compressedSize) {
|
||||
if (0 > $compressedSize || $compressedSize > 0x7fffffffffffffff) {
|
||||
throw new ZipException("Compressed size out of range - " . $this->name);
|
||||
}
|
||||
}
|
||||
$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
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setSize($size)
|
||||
{
|
||||
if (self::UNKNOWN != $size) {
|
||||
if (0 > $size || $size > 0x7fffffffffffffff) {
|
||||
throw new ZipException("Uncompressed Size out of range - " . $this->name);
|
||||
}
|
||||
}
|
||||
$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
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setOffset($offset)
|
||||
{
|
||||
if (0 > $offset || $offset > 0x7fffffffffffffff) {
|
||||
throw new ZipException("Offset out of range - " . $this->name);
|
||||
}
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory()
|
||||
{
|
||||
return $this->name[strlen($this->name) - 1] === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags()
|
||||
{
|
||||
return $this->general & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the General Purpose Bit Flags.
|
||||
*
|
||||
* @var int general
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setGeneralPurposeBitFlags($general)
|
||||
{
|
||||
if (0x0000 > $general || $general > 0xffff) {
|
||||
throw new ZipException('general out of range');
|
||||
}
|
||||
$this->general = $general;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask)
|
||||
{
|
||||
return 0 !== ($this->general & $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit)
|
||||
{
|
||||
if ($bit)
|
||||
$this->general |= $mask;
|
||||
else
|
||||
$this->general &= ~$mask;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry is encrypted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->getGeneralPurposeBitFlag(self::GPBF_ENCRYPTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function clearEncryption()
|
||||
{
|
||||
$this->setEncrypted(false);
|
||||
if (null !== $this->fields) {
|
||||
$field = $this->fields->get(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$this->removeExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
}
|
||||
if (self::METHOD_WINZIP_AES === $this->getMethod()) {
|
||||
$this->setMethod(null === $field ? self::UNKNOWN : $field->getMethod());
|
||||
}
|
||||
}
|
||||
$this->password = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
* @param bool $encrypted
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted)
|
||||
{
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compression method for this entry.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->isInit(self::BIT_METHOD) ? $this->method & 0xffff : self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression method for this entry.
|
||||
*
|
||||
* @param int $method
|
||||
* @return ZipEntry
|
||||
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
|
||||
*/
|
||||
public function setMethod($method)
|
||||
{
|
||||
if (0x0000 > $method || $method > 0xffff) {
|
||||
throw new ZipException('method out of range');
|
||||
}
|
||||
switch ($method) {
|
||||
case self::METHOD_WINZIP_AES:
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, true);
|
||||
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
break;
|
||||
|
||||
case ZipFile::METHOD_STORED:
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
$this->method = $method;
|
||||
$this->setInit(self::BIT_METHOD, true);
|
||||
break;
|
||||
|
||||
case self::UNKNOWN:
|
||||
$this->method = ZipFile::METHOD_STORED;
|
||||
$this->setInit(self::BIT_METHOD, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ZipException($this->name . " (unsupported compression method $method)");
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unix Timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTime()
|
||||
{
|
||||
if (!$this->isInit(self::BIT_DATE_TIME)) {
|
||||
return self::UNKNOWN;
|
||||
}
|
||||
return DateTimeConverter::toUnixTimestamp($this->dosTime & 0xffffffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp)
|
||||
{
|
||||
$known = self::UNKNOWN != $unixTimestamp;
|
||||
if ($known) {
|
||||
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
|
||||
} else {
|
||||
$this->dosTime = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_DATE_TIME, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the external file attributes.
|
||||
*
|
||||
* @return int The external file attributes.
|
||||
*/
|
||||
public function getExternalAttributes()
|
||||
{
|
||||
if (!$this->isInit(self::BIT_EXTERNAL_ATTR)) {
|
||||
return $this->isDirectory() ? 0x10 : 0;
|
||||
}
|
||||
return $this->externalAttributes & 0xffffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the external file attributes.
|
||||
*
|
||||
* @param int $externalAttributes the external file attributes.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes)
|
||||
{
|
||||
$known = self::UNKNOWN != $externalAttributes;
|
||||
if ($known) {
|
||||
if (0x00000000 > $externalAttributes || $externalAttributes > 0xffffffff) {
|
||||
throw new ZipException("external file attributes out of range - " . $this->name);
|
||||
}
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
} else {
|
||||
$this->externalAttributes = 0;
|
||||
}
|
||||
$this->setInit(self::BIT_EXTERNAL_ATTR, $known);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
public function getExtraField($headerId)
|
||||
{
|
||||
return $this->fields === null ? null : $this->fields->get($headerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra field.
|
||||
*
|
||||
* @param ExtraField $field
|
||||
* @return ExtraField
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function addExtraField($field)
|
||||
{
|
||||
if (null === $field) {
|
||||
throw new ZipException("extra field null");
|
||||
}
|
||||
if (null === $this->fields) {
|
||||
$this->fields = new ExtraFields();
|
||||
}
|
||||
return $this->fields->add($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return exists extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExtraField($headerId)
|
||||
{
|
||||
return $this->fields === null ? false : $this->fields->has($headerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
public function removeExtraField($headerId)
|
||||
{
|
||||
return null !== $this->fields ? $this->fields->remove($headerId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
return $this->getExtraFields(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $zip64
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function getExtraFields($zip64)
|
||||
{
|
||||
if ($zip64) {
|
||||
$field = $this->composeZip64ExtraField();
|
||||
if (null !== $field) {
|
||||
if (null === $this->fields) {
|
||||
$this->fields = new ExtraFields();
|
||||
}
|
||||
$this->fields->add($field);
|
||||
}
|
||||
} else {
|
||||
assert(null === $this->fields || null === $this->fields->get(ExtraField::ZIP64_HEADER_ID));
|
||||
}
|
||||
return null === $this->fields ? null : $this->fields->getExtra();
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a ZIP64 Extended Information Extra Field from the properties
|
||||
* of this entry.
|
||||
* If no ZIP64 Extended Information Extra Field is required it is removed
|
||||
* from the collection of Extra Fields.
|
||||
*
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
private function composeZip64ExtraField()
|
||||
{
|
||||
$handle = fopen('php://memory', 'r+b');
|
||||
// Write out Uncompressed Size.
|
||||
$size = $this->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
fwrite($handle, PackUtil::packLongLE($size));
|
||||
}
|
||||
// Write out Compressed Size.
|
||||
$compressedSize = $this->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
fwrite($handle, PackUtil::packLongLE($compressedSize));
|
||||
}
|
||||
// Write out Relative Header Offset.
|
||||
$offset = $this->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
fwrite($handle, PackUtil::packLongLE($offset));
|
||||
}
|
||||
// Create ZIP64 Extended Information Extra Field from serialized data.
|
||||
$field = null;
|
||||
if (ftell($handle) > 0) {
|
||||
$field = new DefaultExtraField(ExtraField::ZIP64_HEADER_ID);
|
||||
$field->readFrom($handle, 0, ftell($handle));
|
||||
} else {
|
||||
$field = null;
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* or do not conform to the ZIP File Format Specification
|
||||
*/
|
||||
public function setExtra($data)
|
||||
{
|
||||
if (null !== $data) {
|
||||
$length = strlen($data);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
throw new ZipException("Extra Fields too large");
|
||||
}
|
||||
}
|
||||
if (null === $data || strlen($data) <= 0) {
|
||||
$this->fields = null;
|
||||
} else {
|
||||
$this->setExtraFields($data, false);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $zip64
|
||||
*/
|
||||
private function setExtraFields($data, $zip64)
|
||||
{
|
||||
if (null === $this->fields) {
|
||||
$this->fields = new ExtraFields();
|
||||
}
|
||||
$handle = fopen('php://memory', 'r+b');
|
||||
fwrite($handle, $data);
|
||||
rewind($handle);
|
||||
|
||||
$this->fields->readFrom($handle, 0, strlen($data));
|
||||
$result = false;
|
||||
if ($zip64) {
|
||||
$result = $this->parseZip64ExtraField();
|
||||
}
|
||||
if ($result) {
|
||||
$this->fields->remove(ExtraField::ZIP64_HEADER_ID);
|
||||
if ($this->fields->size() <= 0) {
|
||||
if (0 !== $this->fields->size()) {
|
||||
$this->fields = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the properties of this entry from the ZIP64 Extended Information
|
||||
* Extra Field, if present.
|
||||
* The ZIP64 Extended Information Extra Field is not removed.
|
||||
*
|
||||
* @return bool
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function parseZip64ExtraField()
|
||||
{
|
||||
if (null === $this->fields) {
|
||||
return false;
|
||||
}
|
||||
$ef = $this->fields->get(ExtraField::ZIP64_HEADER_ID);
|
||||
if (null === $ef) {
|
||||
return false;
|
||||
}
|
||||
$dataBlockHandle = $ef->getDataBlock();
|
||||
$off = 0;
|
||||
// Read in Uncompressed Size.
|
||||
$size = $this->getSize();
|
||||
if (0xffffffff <= $size) {
|
||||
assert(0xffffffff === $size);
|
||||
fseek($dataBlockHandle, $off);
|
||||
$this->setSize(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
// Read in Compressed Size.
|
||||
$compressedSize = $this->getCompressedSize();
|
||||
if (0xffffffff <= $compressedSize) {
|
||||
assert(0xffffffff === $compressedSize);
|
||||
fseek($dataBlockHandle, $off);
|
||||
$this->setCompressedSize(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
|
||||
$off += 8;
|
||||
}
|
||||
// Read in Relative Header Offset.
|
||||
$offset = $this->getOffset();
|
||||
if (0xffffffff <= $offset) {
|
||||
assert(0xffffffff, $offset);
|
||||
fseek($dataBlockHandle, $off);
|
||||
$this->setOffset(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
|
||||
//$off += 8;
|
||||
}
|
||||
fclose($dataBlockHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comment entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return null != $this->comment ? $this->comment : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param $comment
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
if (null !== $comment) {
|
||||
$commentLength = strlen($comment);
|
||||
if (0x0000 > $commentLength || $commentLength > 0xffff) {
|
||||
throw new ZipException("Comment too long");
|
||||
}
|
||||
}
|
||||
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
|
||||
$this->comment = $comment;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDataDescriptorRequired()
|
||||
{
|
||||
return self::UNKNOWN == ($this->getCrc() | $this->getCompressedSize() | $this->getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return crc32 content or 0 for WinZip AES v2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc()
|
||||
{
|
||||
return $this->crc & 0xffffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set crc32 content.
|
||||
*
|
||||
* @param int $crc
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCrc($crc)
|
||||
{
|
||||
if (0x00000000 > $crc || $crc > 0xffffffff) {
|
||||
throw new ZipException("CRC-32 out of range - " . $this->name);
|
||||
}
|
||||
$this->crc = $crc;
|
||||
$this->setInit(self::BIT_CRC, true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword()
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password and encryption method from entry
|
||||
*
|
||||
* @param string $password
|
||||
* @param null|int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null)
|
||||
{
|
||||
$this->password = $password;
|
||||
if (null !== $encryptionMethod) {
|
||||
$this->setEncryptionMethod($encryptionMethod);
|
||||
}
|
||||
$this->setEncrypted(!empty($this->password));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getEncryptionMethod()
|
||||
{
|
||||
return $this->encryptionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionLevel()
|
||||
{
|
||||
return $this->compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressionLevel
|
||||
* @return ZipEntry
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod)
|
||||
{
|
||||
if (
|
||||
ZipFile::ENCRYPTION_METHOD_TRADITIONAL !== $encryptionMethod &&
|
||||
ZipFile::ENCRYPTION_METHOD_WINZIP_AES !== $encryptionMethod
|
||||
) {
|
||||
throw new ZipException('Invalid encryption method');
|
||||
}
|
||||
$this->encryptionMethod = $encryptionMethod;
|
||||
$this->setEncrypted(true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone extra fields
|
||||
*/
|
||||
function __clone()
|
||||
{
|
||||
$this->fields = $this->fields !== null ? clone $this->fields : null;
|
||||
}
|
||||
}
|
26
src/PhpZip/Model/Entry/ZipNewEmptyDirEntry.php
Normal file
26
src/PhpZip/Model/Entry/ZipNewEmptyDirEntry.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* New zip entry from empty dir.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewEmptyDirEntry extends ZipNewEntry
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
268
src/PhpZip/Model/Entry/ZipNewEntry.php
Normal file
268
src/PhpZip/Model/Entry/ZipNewEntry.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Abstract class for new zip entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class ZipNewEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* Default compression level for bzip2
|
||||
*/
|
||||
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionNeededToExtract()
|
||||
{
|
||||
$method = $this->getMethod();
|
||||
return self::METHOD_WINZIP_AES === $method ? 51 :
|
||||
(ZipFile::METHOD_BZIP2 === $method ? 46 :
|
||||
($this->isZip64ExtensionsRequired() ? 45 :
|
||||
(ZipFile::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write local file header, encryption header, file data and data descriptor to output stream.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeEntry($outputStream)
|
||||
{
|
||||
$nameLength = strlen($this->getName());
|
||||
$size = $nameLength + strlen($this->getExtra()) + strlen($this->getComment());
|
||||
if (0xffff < $size) {
|
||||
throw new ZipException($this->getName()
|
||||
. " (the total size of "
|
||||
. $size
|
||||
. " bytes for the name, extra fields and comment exceeds the maximum size of "
|
||||
. 0xffff . " bytes)");
|
||||
}
|
||||
|
||||
if (self::UNKNOWN === $this->getPlatform()) {
|
||||
$this->setPlatform(self::PLATFORM_UNIX);
|
||||
}
|
||||
if (self::UNKNOWN === $this->getTime()) {
|
||||
$this->setTime(time());
|
||||
}
|
||||
$method = $this->getMethod();
|
||||
if (self::UNKNOWN === $method) {
|
||||
$this->setMethod($method = ZipFile::METHOD_DEFLATED);
|
||||
}
|
||||
$skipCrc = false;
|
||||
|
||||
$encrypted = $this->isEncrypted();
|
||||
$dd = $this->isDataDescriptorRequired();
|
||||
// Compose General Purpose Bit Flag.
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$utf8 = true;
|
||||
$general = ($encrypted ? self::GPBF_ENCRYPTED : 0)
|
||||
| ($dd ? self::GPBF_DATA_DESCRIPTOR : 0)
|
||||
| ($utf8 ? self::GPBF_UTF8 : 0);
|
||||
|
||||
$entryContent = $this->getEntryContent();
|
||||
|
||||
$this->setSize(strlen($entryContent));
|
||||
$this->setCrc(crc32($entryContent));
|
||||
|
||||
if ($encrypted && null === $this->getPassword()) {
|
||||
throw new ZipException("Can not password from entry " . $this->getName());
|
||||
}
|
||||
|
||||
if (
|
||||
$encrypted &&
|
||||
(
|
||||
self::METHOD_WINZIP_AES === $method ||
|
||||
$this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
)
|
||||
) {
|
||||
$field = null;
|
||||
$method = $this->getMethod();
|
||||
$keyStrength = 256; // bits
|
||||
|
||||
$compressedSize = $this->getCompressedSize();
|
||||
|
||||
if (self::METHOD_WINZIP_AES === $method) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null !== $field) {
|
||||
$method = $field->getMethod();
|
||||
if (self::UNKNOWN !== $compressedSize) {
|
||||
$compressedSize -= $field->getKeyStrength() / 2 // salt value
|
||||
+ 2 // password verification value
|
||||
+ 10; // authentication code
|
||||
}
|
||||
$this->setMethod($method);
|
||||
}
|
||||
}
|
||||
if (null === $field) {
|
||||
$field = new WinZipAesEntryExtraField();
|
||||
}
|
||||
$field->setKeyStrength($keyStrength);
|
||||
$field->setMethod($method);
|
||||
$size = $this->getSize();
|
||||
if (20 <= $size && ZipFile::METHOD_BZIP2 !== $method) {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
||||
} else {
|
||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
||||
$skipCrc = true;
|
||||
}
|
||||
$this->addExtraField($field);
|
||||
if (self::UNKNOWN !== $compressedSize) {
|
||||
$compressedSize += $field->getKeyStrength() / 2 // salt value
|
||||
+ 2 // password verification value
|
||||
+ 10; // authentication code
|
||||
$this->setCompressedSize($compressedSize);
|
||||
}
|
||||
if ($skipCrc) {
|
||||
$this->setCrc(0);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFile::METHOD_STORED:
|
||||
break;
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
$entryContent = gzdeflate($entryContent, $this->getCompressionLevel());
|
||||
break;
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
$compressionLevel = $this->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ?
|
||||
self::LEVEL_DEFAULT_BZIP2_COMPRESSION :
|
||||
$this->getCompressionLevel();
|
||||
$entryContent = bzcompress($entryContent, $compressionLevel);
|
||||
if (is_int($entryContent)) {
|
||||
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ZipException($this->getName() . " (unsupported compression method " . $method . ")");
|
||||
}
|
||||
|
||||
if ($encrypted) {
|
||||
if ($this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_WINZIP_AES) {
|
||||
if ($skipCrc) {
|
||||
$this->setCrc(0);
|
||||
}
|
||||
$this->setMethod(self::METHOD_WINZIP_AES);
|
||||
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$winZipAesEngine = new WinZipAesEngine($this, $field);
|
||||
$entryContent = $winZipAesEngine->encrypt($entryContent);
|
||||
} elseif ($this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_TRADITIONAL) {
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($this);
|
||||
$entryContent = $zipCryptoEngine->encrypt($entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
$compressedSize = strlen($entryContent);
|
||||
$this->setCompressedSize($compressedSize);
|
||||
|
||||
$offset = ftell($outputStream);
|
||||
|
||||
// Commit changes.
|
||||
$this->setGeneralPurposeBitFlags($general);
|
||||
$this->setOffset($offset);
|
||||
|
||||
$extra = $this->getExtra();
|
||||
|
||||
// zip align
|
||||
$padding = 0;
|
||||
$zipAlign = $this->getCentralDirectory()->getZipAlign();
|
||||
$extraLength = strlen($extra);
|
||||
if ($zipAlign !== null && !$this->isEncrypted() && $this->getMethod() === ZipFile::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$zipAlign -
|
||||
(
|
||||
$offset +
|
||||
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||
$nameLength + $extraLength
|
||||
) % $zipAlign
|
||||
) % $zipAlign;
|
||||
}
|
||||
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
self::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$this->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$general,
|
||||
// compression method 2 bytes
|
||||
$this->getMethod(),
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$this->getTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $this->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $this->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $this->getSize(),
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength + $padding
|
||||
)
|
||||
);
|
||||
fwrite($outputStream, $this->getName());
|
||||
if ($extraLength > 0) {
|
||||
fwrite($outputStream, $extra);
|
||||
}
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($outputStream, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
if (null !== $entryContent) {
|
||||
fwrite($outputStream, $entryContent);
|
||||
}
|
||||
|
||||
assert(self::UNKNOWN !== $this->getCrc());
|
||||
assert(self::UNKNOWN !== $this->getSize());
|
||||
if ($this->getGeneralPurposeBitFlag(self::GPBF_DATA_DESCRIPTOR)) {
|
||||
// data descriptor signature 4 bytes (0x08074b50)
|
||||
// crc-32 4 bytes
|
||||
fwrite($outputStream, pack('VV', self::DATA_DESCRIPTOR_SIG, $this->getCrc()));
|
||||
// compressed size 4 or 8 bytes
|
||||
// uncompressed size 4 or 8 bytes
|
||||
if ($this->isZip64ExtensionsRequired()) {
|
||||
fwrite($outputStream, PackUtil::packLongLE($compressedSize));
|
||||
fwrite($outputStream, PackUtil::packLongLE($this->getSize()));
|
||||
} else {
|
||||
fwrite($outputStream, pack('VV', $this->getCompressedSize(), $this->getSize()));
|
||||
}
|
||||
} elseif ($this->getCompressedSize() !== $compressedSize) {
|
||||
throw new ZipException($this->getName()
|
||||
. " (expected compressed entry size of "
|
||||
. $this->getCompressedSize() . " bytes, but is actually " . $compressedSize . " bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
55
src/PhpZip/Model/Entry/ZipNewStreamEntry.php
Normal file
55
src/PhpZip/Model/Entry/ZipNewStreamEntry.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* New zip entry from stream.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewStreamEntry extends ZipNewEntry
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* ZipNewStreamEntry constructor.
|
||||
* @param resource $stream
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($stream)
|
||||
{
|
||||
if (!is_resource($stream)) {
|
||||
throw new InvalidArgumentException('stream is not resource');
|
||||
}
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return stream_get_contents($this->stream, -1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release stream resource.
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if (null !== $this->stream) {
|
||||
fclose($this->stream);
|
||||
$this->stream = null;
|
||||
}
|
||||
}
|
||||
}
|
39
src/PhpZip/Model/Entry/ZipNewStringEntry.php
Normal file
39
src/PhpZip/Model/Entry/ZipNewStringEntry.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* New zip entry from string.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipNewStringEntry extends ZipNewEntry
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $entryContent;
|
||||
|
||||
/**
|
||||
* ZipNewStringEntry constructor.
|
||||
* @param string $entryContent
|
||||
*/
|
||||
public function __construct($entryContent)
|
||||
{
|
||||
$this->entryContent = $entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return $this->entryContent;
|
||||
}
|
||||
}
|
327
src/PhpZip/Model/Entry/ZipReadEntry.php
Normal file
327
src/PhpZip/Model/Entry/ZipReadEntry.php
Normal file
@ -0,0 +1,327 @@
|
||||
<?php
|
||||
namespace PhpZip\Model\Entry;
|
||||
|
||||
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
|
||||
use PhpZip\Crypto\WinZipAesEngine;
|
||||
use PhpZip\Exception\Crc32Exception;
|
||||
use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethod;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\CentralDirectory;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* 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 ZipReadEntry extends ZipAbstractEntry
|
||||
{
|
||||
/**
|
||||
* Max size cached content in memory.
|
||||
*/
|
||||
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 3145728; // 3 mb
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $inputStream;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $charset;
|
||||
/**
|
||||
* @var string|resource Cached entry content.
|
||||
*/
|
||||
private $entryContent;
|
||||
|
||||
/**
|
||||
* ZipFileEntry constructor.
|
||||
* @param $inputStream
|
||||
*/
|
||||
public function __construct($inputStream)
|
||||
{
|
||||
$this->inputStream = $inputStream;
|
||||
$this->readZipEntry($inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $inputStream
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function readZipEntry($inputStream)
|
||||
{
|
||||
// central file header signature 4 bytes (0x02014b50)
|
||||
$fileHeaderSig = unpack('V', fread($inputStream, 4))[1];
|
||||
if (CentralDirectory::CENTRAL_FILE_HEADER_SIG !== $fileHeaderSig) {
|
||||
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
|
||||
}
|
||||
|
||||
// version made by 2 bytes
|
||||
// version needed to extract 2 bytes
|
||||
// general purpose bit flag 2 bytes
|
||||
// compression method 2 bytes
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
// crc-32 4 bytes
|
||||
// compressed size 4 bytes
|
||||
// uncompressed size 4 bytes
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
// file comment length 2 bytes
|
||||
// disk number start 2 bytes
|
||||
// internal file attributes 2 bytes
|
||||
// external file attributes 4 bytes
|
||||
// relative offset of local header 4 bytes
|
||||
$data = unpack(
|
||||
'vversionMadeBy/vversionNeededToExtract/vgpbf/vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' .
|
||||
'VrawSize/vfileLength/vextraLength/vcommentLength/VrawInternalAttributes/VrawExternalAttributes/VlfhOff',
|
||||
fread($inputStream, 42)
|
||||
);
|
||||
|
||||
$utf8 = 0 !== ($data['gpbf'] & self::GPBF_UTF8);
|
||||
if ($utf8) {
|
||||
$this->charset = "UTF-8";
|
||||
}
|
||||
|
||||
// See appendix D of PKWARE's ZIP File Format Specification.
|
||||
$name = fread($inputStream, $data['fileLength']);
|
||||
|
||||
$this->setName($name);
|
||||
$this->setVersionNeededToExtract($data['versionNeededToExtract']);
|
||||
$this->setPlatform($data['versionMadeBy'] >> 8);
|
||||
$this->setGeneralPurposeBitFlags($data['gpbf']);
|
||||
$this->setMethod($data['rawMethod']);
|
||||
$this->setTime($data['rawTime']);
|
||||
$this->setCrc($data['rawCrc']);
|
||||
$this->setCompressedSize($data['rawCompressedSize']);
|
||||
$this->setSize($data['rawSize']);
|
||||
$this->setExternalAttributes($data['rawExternalAttributes']);
|
||||
$this->setOffset($data['lfhOff']); // must be unmapped!
|
||||
if (0 < $data['extraLength']) {
|
||||
$this->setExtra(fread($inputStream, $data['extraLength']));
|
||||
}
|
||||
if (0 < $data['commentLength']) {
|
||||
$this->setComment(fread($inputStream, $data['commentLength']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
if (null === $this->entryContent) {
|
||||
if ($this->isDirectory()) {
|
||||
$this->entryContent = null;
|
||||
return $this->entryContent;
|
||||
}
|
||||
$isEncrypted = $this->isEncrypted();
|
||||
$password = $this->getPassword();
|
||||
if ($isEncrypted && empty($password)) {
|
||||
throw new ZipException("Not set password");
|
||||
}
|
||||
|
||||
$pos = $this->getOffset();
|
||||
assert(self::UNKNOWN !== $pos);
|
||||
$startPos = $pos = $this->getCentralDirectory()->getEndOfCentralDirectory()->getMapper()->map($pos);
|
||||
fseek($this->inputStream, $startPos);
|
||||
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
if (self::LOCAL_FILE_HEADER_SIG !== unpack('V', fread($this->inputStream, 4))[1]) {
|
||||
throw new ZipException($this->getName() . " (expected Local File Header)");
|
||||
}
|
||||
fseek($this->inputStream, $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->inputStream, 4));
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
|
||||
|
||||
assert(self::UNKNOWN !== $this->getCrc());
|
||||
|
||||
$method = $this->getMethod();
|
||||
|
||||
fseek($this->inputStream, $pos);
|
||||
|
||||
// Get raw entry content
|
||||
$content = fread($this->inputStream, $this->getCompressedSize());
|
||||
|
||||
// Strong Encryption Specification - WinZip AES
|
||||
if ($this->isEncrypted()) {
|
||||
if (self::METHOD_WINZIP_AES === $method) {
|
||||
$winZipAesEngine = new WinZipAesEngine($this);
|
||||
$content = $winZipAesEngine->decrypt($content);
|
||||
// Disable redundant CRC-32 check.
|
||||
$isEncrypted = false;
|
||||
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$method = $field->getMethod();
|
||||
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
|
||||
} else {
|
||||
// Traditional PKWARE Decryption
|
||||
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($this);
|
||||
$content = $zipCryptoEngine->decrypt($content);
|
||||
|
||||
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
|
||||
}
|
||||
}
|
||||
if ($isEncrypted) {
|
||||
// Check CRC32 in the Local File Header or Data Descriptor.
|
||||
$localCrc = null;
|
||||
if ($this->getGeneralPurposeBitFlag(self::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->inputStream, $pos + $this->getCompressedSize());
|
||||
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
|
||||
if (self::DATA_DESCRIPTOR_SIG === $localCrc) {
|
||||
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
|
||||
}
|
||||
} else {
|
||||
fseek($this->inputStream, $startPos + 14);
|
||||
// The CRC32 in the Local File Header.
|
||||
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
|
||||
}
|
||||
if ($this->getCrc() !== $localCrc) {
|
||||
throw new Crc32Exception($this->getName(), $this->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case ZipFile::METHOD_STORED:
|
||||
break;
|
||||
case ZipFile::METHOD_DEFLATED:
|
||||
$content = gzinflate($content);
|
||||
break;
|
||||
case ZipFile::METHOD_BZIP2:
|
||||
if (!extension_loaded('bz2')) {
|
||||
throw new ZipException('Extension bzip2 not install');
|
||||
}
|
||||
$content = bzdecompress($content);
|
||||
break;
|
||||
default:
|
||||
throw new ZipUnsupportMethod($this->getName()
|
||||
. " (compression method "
|
||||
. $method
|
||||
. " is not supported)");
|
||||
}
|
||||
if ($isEncrypted) {
|
||||
$localCrc = crc32($content);
|
||||
if ($this->getCrc() !== $localCrc) {
|
||||
if ($this->isEncrypted()) {
|
||||
throw new ZipCryptoException("Wrong password");
|
||||
}
|
||||
throw new Crc32Exception($this->getName(), $this->getCrc(), $localCrc);
|
||||
}
|
||||
}
|
||||
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
|
||||
$this->entryContent = $content;
|
||||
} else {
|
||||
$this->entryContent = fopen('php://temp', 'rb');
|
||||
fwrite($this->entryContent, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
if (is_resource($this->entryContent)) {
|
||||
return stream_get_contents($this->entryContent, -1, 0);
|
||||
}
|
||||
return $this->entryContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write local file header, encryption header, file data and data descriptor to output stream.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
*/
|
||||
public function writeEntry($outputStream)
|
||||
{
|
||||
$pos = $this->getOffset();
|
||||
assert(ZipEntry::UNKNOWN !== $pos);
|
||||
$pos = $this->getCentralDirectory()->getEndOfCentralDirectory()->getMapper()->map($pos);
|
||||
$pos += ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS;
|
||||
|
||||
$this->setOffset(ftell($outputStream));
|
||||
// zip align
|
||||
$padding = 0;
|
||||
$zipAlign = $this->getCentralDirectory()->getZipAlign();
|
||||
$extra = $this->getExtra();
|
||||
$extraLength = strlen($extra);
|
||||
$nameLength = strlen($this->getName());
|
||||
if ($zipAlign !== null && !$this->isEncrypted() && $this->getMethod() === ZipFile::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$zipAlign -
|
||||
($this->getOffset() + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength)
|
||||
% $zipAlign
|
||||
) % $zipAlign;
|
||||
}
|
||||
$dd = $this->isDataDescriptorRequired();
|
||||
|
||||
fwrite(
|
||||
$outputStream,
|
||||
pack(
|
||||
'VvvvVVVVvv',
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
self::LOCAL_FILE_HEADER_SIG,
|
||||
// version needed to extract 2 bytes
|
||||
$this->getVersionNeededToExtract(),
|
||||
// general purpose bit flag 2 bytes
|
||||
$this->getGeneralPurposeBitFlags(),
|
||||
// compression method 2 bytes
|
||||
$this->getMethod(),
|
||||
// last mod file time 2 bytes
|
||||
// last mod file date 2 bytes
|
||||
$this->getTime(),
|
||||
// crc-32 4 bytes
|
||||
$dd ? 0 : $this->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$dd ? 0 : $this->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$dd ? 0 : $this->getSize(),
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength + $padding
|
||||
)
|
||||
);
|
||||
fwrite($outputStream, $this->getName());
|
||||
if ($extraLength > 0) {
|
||||
fwrite($outputStream, $extra);
|
||||
}
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($outputStream, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
fseek($this->inputStream, $pos);
|
||||
$data = unpack('vfileLength/vextraLength', fread($this->inputStream, 4));
|
||||
fseek($this->inputStream, $data['fileLength'] + $data['extraLength'], SEEK_CUR);
|
||||
|
||||
$length = $this->getCompressedSize();
|
||||
if ($this->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
$length += 12;
|
||||
if ($this->isZip64ExtensionsRequired()) {
|
||||
$length += 8;
|
||||
}
|
||||
}
|
||||
stream_copy_to_stream($this->inputStream, $outputStream, $length);
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
if (null !== $this->entryContent && is_resource($this->entryContent)) {
|
||||
fclose($this->entryContent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ namespace PhpZip\Model;
|
||||
use PhpZip\Extra\NtfsExtraField;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Zip info
|
||||
@ -85,7 +86,7 @@ class ZipInfo
|
||||
];
|
||||
|
||||
private static $valuesCompressionMethod = [
|
||||
ZipEntry::METHOD_STORED => 'no compression',
|
||||
ZipFile::METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
@ -93,7 +94,7 @@ class ZipInfo
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
ZipEntry::METHOD_DEFLATED => 'deflate',
|
||||
ZipFile::METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
@ -107,7 +108,7 @@ class ZipInfo
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
97 => 'WavPack',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
ZipEntry::WINZIP_AES => 'WinZip AES',
|
||||
ZipEntry::METHOD_WINZIP_AES => 'WinZip AES',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -192,7 +193,7 @@ class ZipInfo
|
||||
$ctime = null;
|
||||
|
||||
$field = $entry->getExtraField(NtfsExtraField::getHeaderId());
|
||||
if ($field !== null && $field instanceof NtfsExtraField) {
|
||||
if (null !== $field && $field instanceof NtfsExtraField) {
|
||||
/**
|
||||
* @var NtfsExtraField $field
|
||||
*/
|
||||
@ -214,34 +215,36 @@ class ZipInfo
|
||||
$this->platform = self::getPlatformName($entry);
|
||||
$this->version = $entry->getVersionNeededToExtract();
|
||||
|
||||
$attribs = str_repeat(" ", 12);
|
||||
$xattr = (($entry->getRawExternalAttributes() >> 16) & 0xFFFF);
|
||||
$attributes = str_repeat(" ", 12);
|
||||
$externalAttributes = $entry->getExternalAttributes();
|
||||
$xattr = (($externalAttributes >> 16) & 0xFFFF);
|
||||
switch ($entry->getPlatform()) {
|
||||
case self::MADE_BY_MS_DOS:
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case self::MADE_BY_WINDOWS_NTFS:
|
||||
if ($entry->getPlatform() != self::MADE_BY_MS_DOS ||
|
||||
($xattr & 0700) !=
|
||||
(0400 |
|
||||
(!($entry->getRawExternalAttributes() & 1) << 7) |
|
||||
(($entry->getRawExternalAttributes() & 0x10) << 2))
|
||||
(!($externalAttributes & 1) << 7) |
|
||||
(($externalAttributes & 0x10) << 2))
|
||||
) {
|
||||
$xattr = $entry->getRawExternalAttributes() & 0xFF;
|
||||
$attribs = ".r.-... ";
|
||||
$attribs[2] = ($xattr & 0x01) ? '-' : 'w';
|
||||
$attribs[5] = ($xattr & 0x02) ? 'h' : '-';
|
||||
$attribs[6] = ($xattr & 0x04) ? 's' : '-';
|
||||
$attribs[4] = ($xattr & 0x20) ? 'a' : '-';
|
||||
$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) {
|
||||
$attribs[0] = 'd';
|
||||
$attribs[3] = 'x';
|
||||
$attributes[0] = 'd';
|
||||
$attributes[3] = 'x';
|
||||
} else
|
||||
$attribs[0] = '-';
|
||||
$attributes[0] = '-';
|
||||
if ($xattr & 0x08)
|
||||
$attribs[0] = 'V';
|
||||
$attributes[0] = 'V';
|
||||
else {
|
||||
$ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) {
|
||||
$attribs[3] = 'x';
|
||||
$attributes[3] = 'x';
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -250,51 +253,51 @@ class ZipInfo
|
||||
default: /* assume Unix-like */
|
||||
switch ($xattr & self::UNX_IFMT) {
|
||||
case self::UNX_IFDIR:
|
||||
$attribs[0] = 'd';
|
||||
$attributes[0] = 'd';
|
||||
break;
|
||||
case self::UNX_IFREG:
|
||||
$attribs[0] = '-';
|
||||
$attributes[0] = '-';
|
||||
break;
|
||||
case self::UNX_IFLNK:
|
||||
$attribs[0] = 'l';
|
||||
$attributes[0] = 'l';
|
||||
break;
|
||||
case self::UNX_IFBLK:
|
||||
$attribs[0] = 'b';
|
||||
$attributes[0] = 'b';
|
||||
break;
|
||||
case self::UNX_IFCHR:
|
||||
$attribs[0] = 'c';
|
||||
$attributes[0] = 'c';
|
||||
break;
|
||||
case self::UNX_IFIFO:
|
||||
$attribs[0] = 'p';
|
||||
$attributes[0] = 'p';
|
||||
break;
|
||||
case self::UNX_IFSOCK:
|
||||
$attribs[0] = 's';
|
||||
$attributes[0] = 's';
|
||||
break;
|
||||
default:
|
||||
$attribs[0] = '?';
|
||||
$attributes[0] = '?';
|
||||
break;
|
||||
}
|
||||
$attribs[1] = ($xattr & self::UNX_IRUSR) ? 'r' : '-';
|
||||
$attribs[4] = ($xattr & self::UNX_IRGRP) ? 'r' : '-';
|
||||
$attribs[7] = ($xattr & self::UNX_IROTH) ? 'r' : '-';
|
||||
$attribs[2] = ($xattr & self::UNX_IWUSR) ? 'w' : '-';
|
||||
$attribs[5] = ($xattr & self::UNX_IWGRP) ? 'w' : '-';
|
||||
$attribs[8] = ($xattr & self::UNX_IWOTH) ? 'w' : '-';
|
||||
$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)
|
||||
$attribs[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
|
||||
else
|
||||
$attribs[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; /* S==undefined */
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; /* S==undefined */
|
||||
if ($xattr & self::UNX_IXGRP)
|
||||
$attribs[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; /* == UNX_ENFMT */
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; /* == UNX_ENFMT */
|
||||
else
|
||||
$attribs[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; /* SunOS 4.1.x */
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; /* SunOS 4.1.x */
|
||||
if ($xattr & self::UNX_IXOTH)
|
||||
$attribs[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; /* "sticky bit" */
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; /* "sticky bit" */
|
||||
else
|
||||
$attribs[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; /* T==undefined */
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; /* T==undefined */
|
||||
}
|
||||
$this->attributes = trim($attribs);
|
||||
$this->attributes = trim($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,10 +308,10 @@ class ZipInfo
|
||||
{
|
||||
$return = '';
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::WINZIP_AES) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
if ($field !== null) {
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Output;
|
||||
|
||||
/**
|
||||
* Zip output entry for empty dir.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputEmptyDirEntry extends ZipOutputEntry
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns entry data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Output;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Zip output Entry
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class ZipOutputEntry
|
||||
{
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
if ($entry === null) {
|
||||
throw new \RuntimeException('entry is null');
|
||||
}
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns zip entry
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function getEntry()
|
||||
{
|
||||
return $this->entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entry data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getEntryContent();
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Output;
|
||||
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Zip output entry for stream resource.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputStreamEntry extends ZipOutputEntry
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct($stream, ZipEntry $entry)
|
||||
{
|
||||
parent::__construct($entry);
|
||||
if (!is_resource($stream)) {
|
||||
throw new RuntimeException('stream is not resource');
|
||||
}
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entry data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
rewind($this->stream);
|
||||
return stream_get_contents($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release stream resource.
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if ($this->stream !== null) {
|
||||
fclose($this->stream);
|
||||
$this->stream = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Output;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
|
||||
/**
|
||||
* Zip output entry for string data.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputStringEntry extends ZipOutputEntry
|
||||
{
|
||||
/**
|
||||
* Data content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param ZipEntry $entry
|
||||
* @throws ZipException If data empty.
|
||||
*/
|
||||
public function __construct($data, ZipEntry $entry)
|
||||
{
|
||||
parent::__construct($entry);
|
||||
$data = (string)$data;
|
||||
if ($data === null) {
|
||||
throw new ZipException("data is null");
|
||||
}
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entry data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip\Output;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Zip output entry for input zip file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipOutputZipFileEntry extends ZipOutputEntry
|
||||
{
|
||||
/**
|
||||
* Input zip file.
|
||||
*
|
||||
* @var ZipFile
|
||||
*/
|
||||
private $inputZipFile;
|
||||
|
||||
/**
|
||||
* Input entry name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $inputEntryName;
|
||||
|
||||
/**
|
||||
* ZipOutputZipFileEntry constructor.
|
||||
* @param ZipFile $zipFile
|
||||
* @param ZipEntry $zipEntry
|
||||
* @throws ZipException If input zip file is null.
|
||||
*/
|
||||
public function __construct(ZipFile $zipFile, ZipEntry $zipEntry)
|
||||
{
|
||||
if ($zipFile === null) {
|
||||
throw new ZipException('ZipFile is null');
|
||||
}
|
||||
parent::__construct(clone $zipEntry);
|
||||
|
||||
$this->inputZipFile = $zipFile;
|
||||
$this->inputEntryName = $zipEntry->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns entry data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntryContent()
|
||||
{
|
||||
return $this->inputZipFile->getEntryContent($this->inputEntryName);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
@ -14,7 +15,7 @@ class CryptoUtil
|
||||
*
|
||||
* @param int $length
|
||||
* @return string
|
||||
* @throws ZipException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static final function randomBytes($length)
|
||||
{
|
||||
@ -26,7 +27,7 @@ class CryptoUtil
|
||||
} elseif (function_exists('mcrypt_create_iv')) {
|
||||
return mcrypt_create_iv($length);
|
||||
} else {
|
||||
throw new ZipException('Extension openssl or mcrypt not loaded');
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<?php
|
||||
namespace PhpZip;
|
||||
|
||||
/**
|
||||
* Constants for ZIP files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipConstants
|
||||
{
|
||||
/** Local File Header signature. */
|
||||
const LOCAL_FILE_HEADER_SIG = 0x04034B50;
|
||||
|
||||
/** Data Descriptor signature. */
|
||||
const DATA_DESCRIPTOR_SIG = 0x08074B50;
|
||||
|
||||
/** Central File Header signature. */
|
||||
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
|
||||
|
||||
/** Zip64 End Of Central Directory Record. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
|
||||
|
||||
/** Zip64 End Of Central Directory Locator. */
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
|
||||
|
||||
/** End Of Central Directory Record signature. */
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
|
||||
|
||||
/**
|
||||
* The minimum length of the 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;
|
||||
|
||||
/**
|
||||
* The minimum length of the End Of Central Directory Record.
|
||||
*
|
||||
* end of central dir signature 4
|
||||
* number of this disk 2
|
||||
* number of the disk with the
|
||||
* start of the central directory 2
|
||||
* total number of entries in the
|
||||
* central directory on this disk 2
|
||||
* total number of entries in
|
||||
* the central directory 2
|
||||
* size of the central directory 4
|
||||
* offset of start of central *
|
||||
* directory with respect to *
|
||||
* the starting disk number 4
|
||||
* zipfile comment length 2
|
||||
*/
|
||||
const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
|
||||
|
||||
/**
|
||||
* The length of the Zip64 End Of Central Directory Locator.
|
||||
* zip64 end of central dir locator
|
||||
* signature 4
|
||||
* number of the disk with the
|
||||
* start of the zip64 end of
|
||||
* central directory 4
|
||||
* relative offset of the zip64
|
||||
* end of central directory record 8
|
||||
* total number of disks 4
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
|
||||
|
||||
/**
|
||||
* The minimum length of the Zip64 End Of Central Directory Record.
|
||||
*
|
||||
* zip64 end of central dir
|
||||
* signature 4
|
||||
* size of zip64 end of central
|
||||
* directory record 8
|
||||
* version made by 2
|
||||
* version needed to extract 2
|
||||
* number of this disk 4
|
||||
* number of the disk with the
|
||||
* start of the central directory 4
|
||||
* total number of entries in the
|
||||
* central directory on this disk 8
|
||||
* total number of entries in
|
||||
* the central directory 8
|
||||
* size of the central directory 8
|
||||
* offset of start of central
|
||||
* directory with respect to
|
||||
* the starting disk number 8
|
||||
*/
|
||||
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
359
tests/PhpZip/ZipFileAddDirTest.php
Normal file
359
tests/PhpZip/ZipFileAddDirTest.php
Normal file
@ -0,0 +1,359 @@
|
||||
<?php
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Test add directory to zip archive.
|
||||
*/
|
||||
class ZipFileAddDirTest extends ZipTestCase
|
||||
{
|
||||
private static $files = [
|
||||
'.hidden' => 'Hidden file',
|
||||
'text file.txt' => 'Text file',
|
||||
'Текстовый документ.txt' => 'Текстовый документ',
|
||||
'empty dir/' => null,
|
||||
'empty dir2/ещё пустой каталог/' => null,
|
||||
'catalog/New File' => 'New Catalog File',
|
||||
'catalog/New File 2' => 'New Catalog File 2',
|
||||
'catalog/Empty Dir/' => null,
|
||||
'category/list.txt' => 'Category list',
|
||||
'category/Pictures/128x160/Car/01.jpg' => 'File 01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg' => 'File 02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg' => 'File 01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg' => 'File 02.jpg',
|
||||
];
|
||||
|
||||
/**
|
||||
* Before test
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->fillDirectory();
|
||||
}
|
||||
|
||||
protected function fillDirectory()
|
||||
{
|
||||
foreach (self::$files as $name => $content) {
|
||||
$fullName = $this->outputDirname . '/' . $name;
|
||||
if ($content === null) {
|
||||
if (!is_dir($fullName)) {
|
||||
mkdir($fullName, 0755, true);
|
||||
}
|
||||
} else {
|
||||
$dirname = dirname($fullName);
|
||||
if (!is_dir($dirname)) {
|
||||
mkdir($dirname, 0755, true);
|
||||
}
|
||||
file_put_contents($fullName, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static function assertFilesResult(ZipFile $zipFile, array $actualResultFiles = [], $localPath = '/')
|
||||
{
|
||||
$localPath = rtrim($localPath, '/');
|
||||
$localPath = empty($localPath) ? "" : $localPath . '/';
|
||||
self::assertEquals(sizeof($zipFile), sizeof($actualResultFiles));
|
||||
$actualResultFiles = array_flip($actualResultFiles);
|
||||
foreach (self::$files as $file => $content) {
|
||||
$zipEntryName = $localPath . $file;
|
||||
if (isset($actualResultFiles[$file])) {
|
||||
self::assertTrue(isset($zipFile[$zipEntryName]));
|
||||
self::assertEquals($zipFile[$zipEntryName], $content);
|
||||
unset($actualResultFiles[$file]);
|
||||
} else {
|
||||
self::assertFalse(isset($zipFile[$zipEntryName]));
|
||||
}
|
||||
}
|
||||
self::assertEmpty($actualResultFiles);
|
||||
}
|
||||
|
||||
public function testAddDirWithLocalPath()
|
||||
{
|
||||
$localPath = 'to/path';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDir($this->outputDirname, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddDirWithoutLocalPath()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDir($this->outputDirname);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
]);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddFilesFromIterator()
|
||||
{
|
||||
$localPath = 'to/project';
|
||||
|
||||
$directoryIterator = new \DirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddFilesFromRecursiveIterator()
|
||||
{
|
||||
$localPath = 'to/project';
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddRecursiveDirWithLocalPath()
|
||||
{
|
||||
$localPath = 'to/path';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDirRecursive($this->outputDirname, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddRecursiveDirWithoutLocalPath()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addDirRecursive($this->outputDirname);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, array_keys(self::$files));
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddFilesFromIteratorWithIgnoreFiles(){
|
||||
$localPath = 'to/project';
|
||||
$ignoreFiles = [
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/'
|
||||
];
|
||||
|
||||
$directoryIterator = new \DirectoryIterator($this->outputDirname);
|
||||
$ignoreIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($ignoreIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'.hidden',
|
||||
'text file.txt',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testAddFilesFromRecursiveIteratorWithIgnoreFiles(){
|
||||
$localPath = 'to/project';
|
||||
$ignoreFiles = [
|
||||
'.hidden',
|
||||
'empty dir2/ещё пустой каталог/',
|
||||
'list.txt',
|
||||
'category/Pictures/240x320',
|
||||
];
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($this->outputDirname);
|
||||
$ignoreIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromIterator($ignoreIterator, $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'empty dir/',
|
||||
'catalog/New File',
|
||||
'catalog/New File 2',
|
||||
'catalog/Empty Dir/',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files from glob pattern
|
||||
*/
|
||||
public function testAddFilesFromGlob()
|
||||
{
|
||||
$localPath = '/';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromGlob($this->outputDirname, '**.{txt,jpg}', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add recursively files from glob pattern
|
||||
*/
|
||||
public function testAddFilesFromGlobRecursive()
|
||||
{
|
||||
$localPath = '/';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromGlobRecursive($this->outputDirname, '**.{txt,jpg}', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files from regex pattern
|
||||
*/
|
||||
public function testAddFilesFromRegex()
|
||||
{
|
||||
$localPath = 'path';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromRegex($this->outputDirname, '~\.(txt|jpe?g)$~i', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive and add files recursively from regex pattern
|
||||
*/
|
||||
public function testAddFilesFromRegexRecursive()
|
||||
{
|
||||
$localPath = '/';
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->addFilesFromRegexRecursive($this->outputDirname, '~\.(txt|jpe?g)$~i', $localPath);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, [
|
||||
'text file.txt',
|
||||
'Текстовый документ.txt',
|
||||
'category/list.txt',
|
||||
'category/Pictures/128x160/Car/01.jpg',
|
||||
'category/Pictures/128x160/Car/02.jpg',
|
||||
'category/Pictures/240x320/Car/01.jpg',
|
||||
'category/Pictures/240x320/Car/02.jpg',
|
||||
], $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
public function testArrayAccessAddDir()
|
||||
{
|
||||
$localPath = 'path/to';
|
||||
$iterator = new \RecursiveDirectoryIterator($this->outputDirname);
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile[$localPath] = $iterator;
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
|
||||
}
|
1850
tests/PhpZip/ZipFileTest.php
Normal file
1850
tests/PhpZip/ZipFileTest.php
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,51 @@
|
||||
<?php
|
||||
namespace PhpZip;
|
||||
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
|
||||
/**
|
||||
* PHPUnit test case and helper methods.
|
||||
*/
|
||||
class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $outputFilename;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $outputDirname;
|
||||
|
||||
/**
|
||||
* Before test
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$id = uniqid('phpzip');
|
||||
$this->outputFilename = sys_get_temp_dir() . '/' . $id . '.zip';
|
||||
$this->outputDirname = sys_get_temp_dir() . '/' . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* After test
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
if ($this->outputFilename !== null && file_exists($this->outputFilename)) {
|
||||
unlink($this->outputFilename);
|
||||
}
|
||||
if ($this->outputDirname !== null && is_dir($this->outputDirname)) {
|
||||
FilesUtil::removeDir($this->outputDirname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert correct zip archive.
|
||||
*
|
||||
@ -25,12 +65,12 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
if ($password !== null && $returnCode === 81) {
|
||||
if(`which 7z`){
|
||||
if (`which 7z`) {
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$command = "7z t -p" . escapeshellarg($password). " " . escapeshellarg($filename);
|
||||
$command = "7z t -p" . escapeshellarg($password) . " " . escapeshellarg($filename);
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
@ -38,14 +78,12 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
self::assertEquals($returnCode, 0);
|
||||
self::assertNotContains(' Errors', $output);
|
||||
self::assertContains(' Ok', $output);
|
||||
} else {
|
||||
fwrite(STDERR, 'Program unzip cannot support this function.' . PHP_EOL);
|
||||
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . PHP_EOL);
|
||||
}
|
||||
else{
|
||||
fwrite(STDERR, 'Program unzip cannot support this function.'.PHP_EOL);
|
||||
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full'.PHP_EOL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::assertEquals($returnCode, 0);
|
||||
} else {
|
||||
self::assertEquals($returnCode, 0, $output);
|
||||
self::assertNotContains('incorrect password', $output);
|
||||
self::assertContains(' OK', $output);
|
||||
self::assertContains('No errors', $output);
|
||||
@ -67,18 +105,20 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
|
||||
self::assertContains('Empty zipfile', $output);
|
||||
}
|
||||
$actualEmptyZipData = pack('VVVVVv', ZipConstants::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
|
||||
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
|
||||
self::assertEquals(file_get_contents($filename), $actualEmptyZipData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param bool $showErrors
|
||||
* @return bool|null If null - can not install zipalign
|
||||
*/
|
||||
public static function doZipAlignVerify($filename)
|
||||
public static function doZipAlignVerify($filename, $showErrors = false)
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR !== '\\' && `which zipalign`) {
|
||||
exec("zipalign -c -v 4 " . escapeshellarg($filename), $output, $returnCode);
|
||||
if ($showErrors && $returnCode !== 0) fwrite(STDERR, implode(PHP_EOL, $output));
|
||||
return $returnCode === 0;
|
||||
} else {
|
||||
fwrite(STDERR, 'Can not find program "zipalign" for test');
|
||||
|
Loading…
x
Reference in New Issue
Block a user