mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-08-15 11:44:56 +02:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
810b7ca741 | ||
|
115dfd3b52 | ||
|
183274d6da | ||
|
f99c0278fd | ||
|
1b065c4cca | ||
|
67fc1ea7ad | ||
|
9716976002 | ||
|
4ca1717979 | ||
|
560649b1e8 | ||
|
3ab98532a0 | ||
|
0dbdc0faeb | ||
|
1e4b14177a | ||
|
eb183c9da0 | ||
|
72ecdca941 | ||
|
b958cb7e19 | ||
|
e4650b7de2 | ||
|
f6fc289102 | ||
|
af4b66bb6e | ||
|
bcd7949efe | ||
|
3d8be5c339 | ||
|
46654e3e8d | ||
|
16c214b8a4 | ||
|
db50dd8e46 | ||
|
08c890ba24 | ||
|
6691858b95 | ||
|
f802861d86 | ||
|
d8e40ee3f1 | ||
|
cc75f44949 | ||
|
58e9f4bf73 | ||
|
39f8616336 | ||
|
8d140cc1a1 | ||
|
11a7c16f1b | ||
|
f2ffdae0c2 | ||
|
676eca7f87 | ||
|
2c402157ca | ||
|
464050ff85 | ||
|
294e3d54ef | ||
|
015166d165 | ||
|
47d308605e | ||
|
9370f353c6 |
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/
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/vendor/
|
||||
/vendor
|
||||
*.iml
|
||||
/.idea/
|
||||
/.idea
|
||||
/composer.lock
|
32
.travis.yml
Normal file
32
.travis.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0'
|
||||
- '7.1'
|
||||
- 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
|
||||
|
9
CHANGELOG.md
Normal file
9
CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 3.0.0 (2017-03-15)
|
||||
Merge `ZipOutputFile` with ZipFile and optimize the zip archive update.
|
||||
|
||||
See the update instructions in README.md.
|
||||
|
||||
## 2.2.0 (2017-03-02)
|
||||
Features:
|
||||
- create output object `ZipOutputFile` from `ZipFile` in method `ZipFile::edit()`.
|
||||
- create output object `ZipOutputFile` from filename in static method `ZipOutputFile::openFromFile(string $filename)`.
|
667
README.md
667
README.md
@@ -1,50 +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)
|
||||
|
||||
The library does not require extension `php-xml` and class `ZipArchive`.
|
||||
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)
|
||||
|
||||
Requirements
|
||||
------------
|
||||
- `PHP` >= 5.4 (64 bit)
|
||||
- Php-extension `mbstring`
|
||||
### <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).
|
||||
|
||||
### <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;
|
||||
@@ -52,7 +145,7 @@ foreach($zipFile as $entryName => $dataContent){
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
}
|
||||
```
|
||||
Iterator zip entries.
|
||||
or
|
||||
```php
|
||||
$iterator = new \ArrayIterator($zipFile);
|
||||
while ($iterator->valid())
|
||||
@@ -67,57 +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;
|
||||
// ZipInfo {Path="file.txt", Size=9.77KB, Compressed size=2.04KB, Modified time=2016-09-24T19:25:10+03:00, Crc=0x4b5ab5c7, Method="Deflate", Platform="UNIX", Version=20}
|
||||
|
||||
// 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
|
||||
//)
|
||||
|
||||
// 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
|
||||
@@ -132,329 +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);
|
||||
```
|
||||
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
|
||||
// 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);
|
||||
```
|
||||
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, 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)
|
||||
$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)
|
||||
// $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 = \PhpZip\ZipOutputFile::openFromZipFile($zipFile); // 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`
|
||||
|
||||
|
||||
|
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "nelexa/zip",
|
||||
"description": "Zip files CRUD. Open, create, update, extract and get info tool. Support read and write encrypted archives. Support ZIP64 ext and zip align. Alternative ZipArchive. It does not require php-zip extension.",
|
||||
"description": "Zip files CRUD. Open, create, update, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"zip",
|
||||
"unzip",
|
||||
"archive",
|
||||
"extract",
|
||||
"winzip",
|
||||
"zipalign"
|
||||
],
|
||||
"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": "^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
|
||||
@@ -105,9 +106,26 @@ class TraditionalPkwareEncryptionEngine
|
||||
private function updateKeys($charAt)
|
||||
{
|
||||
$this->keys[0] = self::crc32($this->keys[0], $charAt);
|
||||
$this->keys[1] = ($this->keys[1] + ($this->keys[0] & 0xff)) & 4294967295;
|
||||
$this->keys[1] = ($this->keys[1] * 134775813 + 1) & 4294967295;
|
||||
$this->keys[2] = self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff);
|
||||
$this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff);
|
||||
$this->keys[1] = self::toInt($this->keys[1] * 134775813 + 1);
|
||||
$this->keys[2] = self::toInt(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast to int
|
||||
*
|
||||
* @param $i
|
||||
* @return int
|
||||
*/
|
||||
private static function toInt($i)
|
||||
{
|
||||
$i = (int)($i & 0xffffffff);
|
||||
if ($i > 2147483647) {
|
||||
return -(-$i & 0xffffffff);
|
||||
} elseif ($i < -2147483648) {
|
||||
return $i & -2147483648;
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,12 +155,12 @@ 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->getDosTime() >> 8) & 0xff;
|
||||
} else {
|
||||
// compare against the CRC otherwise
|
||||
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
|
||||
}
|
||||
if ($headerBytes[11] !== $checkByte) {
|
||||
if ($byte !== $checkByte) {
|
||||
throw new ZipAuthenticationException("Bad password for entry " . $this->entry->getName());
|
||||
}
|
||||
|
||||
@@ -170,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->getDosTime() & 0x0000ffff) << 16 :
|
||||
$this->entry->getCrc();
|
||||
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
|
||||
|
||||
// Initialize again since the generated bytes were encrypted.
|
||||
@@ -189,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) {
|
||||
@@ -206,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,38 +81,47 @@ 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!");
|
||||
}
|
||||
|
||||
do {
|
||||
assert($this->entry->getPassword() !== null);
|
||||
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
|
||||
$password = $this->entry->getPassword();
|
||||
assert($password !== null);
|
||||
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
|
||||
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$iv = str_repeat(chr(0), $ctrIvSize);
|
||||
do {
|
||||
// Here comes the strange part about WinZip AES encryption:
|
||||
// Its unorthodox use of the Password-Based Key Derivation
|
||||
// Function 2 (PBKDF2) of PKCS #5 V2.0 alias RFC 2898.
|
||||
// Yes, the password verifier is only a 16 bit value.
|
||||
// So we must use the MAC for password verification, too.
|
||||
$keyParam = hash_pbkdf2("sha1", $this->entry->getPassword(), $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);
|
||||
@@ -133,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 {
|
||||
@@ -157,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)
|
||||
{
|
||||
@@ -166,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,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)
|
||||
{
|
||||
@@ -186,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +214,10 @@ class WinZipAesEngine
|
||||
$password = $this->entry->getPassword();
|
||||
assert($password !== null);
|
||||
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
|
||||
$keyStrengthBytes = 32;
|
||||
$keyStrengthBits = $keyStrengthBytes * 8;
|
||||
|
||||
|
@@ -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;
|
||||
|
482
src/PhpZip/Model/CentralDirectory.php
Normal file
482
src/PhpZip/Model/CentralDirectory.php
Normal file
@@ -0,0 +1,482 @@
|
||||
<?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];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entryName
|
||||
* @return ZipEntry
|
||||
* @throws ZipNotFoundEntry
|
||||
*/
|
||||
public function getModifiedEntry($entryName){
|
||||
if (!isset($this->modifiedEntries[$entryName])) {
|
||||
throw new ZipNotFoundEntry('Zip modified entry ' . $entryName . ' not found');
|
||||
}
|
||||
return $this->modifiedEntries[$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->getDosTime(),
|
||||
// crc-32 4 bytes
|
||||
$entry->getCrc(),
|
||||
// compressed size 4 bytes
|
||||
$entry->getCompressedSize(),
|
||||
// uncompressed size 4 bytes
|
||||
$entry->getSize(),
|
||||
// file name length 2 bytes
|
||||
strlen($entry->getName()),
|
||||
// extra field length 2 bytes
|
||||
$extraSize,
|
||||
// file comment length 2 bytes
|
||||
$commentLength,
|
||||
// disk number start 2 bytes
|
||||
0,
|
||||
// internal file attributes 2 bytes
|
||||
0,
|
||||
// external file attributes 4 bytes
|
||||
$entry->getExternalAttributes(),
|
||||
// relative offset of local header 4 bytes
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
953
src/PhpZip/Model/Entry/ZipAbstractEntry.php
Normal file
953
src/PhpZip/Model/Entry/ZipAbstractEntry.php
Normal file
@@ -0,0 +1,953 @@
|
||||
<?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) {
|
||||
$compressedSize = sprintf('%u', $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) {
|
||||
$size = sprintf('%u', $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)
|
||||
{
|
||||
$offset = sprintf('%u', $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->getDosTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDosTime()
|
||||
{
|
||||
return $this->dosTime & 0xffffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Dos Time
|
||||
* @param int $dosTime
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setDosTime($dosTime)
|
||||
{
|
||||
$dosTime = sprintf('%u', $dosTime);
|
||||
if (0x00000000 > $dosTime || $dosTime > 0xffffffff) {
|
||||
throw new ZipException('DosTime out of range');
|
||||
}
|
||||
$this->dosTime = $dosTime;
|
||||
$this->setInit(self::BIT_DATE_TIME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$externalAttributes = sprintf('%u', $externalAttributes);
|
||||
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)
|
||||
{
|
||||
$crc = sprintf('%u', $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->getDosTime(),
|
||||
// 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->setDosTime($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->getDosTime(),
|
||||
// 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
|
||||
@@ -36,6 +37,31 @@ class ZipInfo
|
||||
const MADE_BY_OS_X = 19;
|
||||
const MADE_BY_UNKNOWN = 20;
|
||||
|
||||
const UNX_IFMT = 0170000; /* Unix file type mask */
|
||||
const UNX_IFREG = 0100000; /* Unix regular file */
|
||||
const UNX_IFSOCK = 0140000; /* Unix socket (BSD, not SysV or Amiga) */
|
||||
const UNX_IFLNK = 0120000; /* Unix symbolic link (not SysV, Amiga) */
|
||||
const UNX_IFBLK = 0060000; /* Unix block special (not Amiga) */
|
||||
const UNX_IFDIR = 0040000; /* Unix directory */
|
||||
const UNX_IFCHR = 0020000; /* Unix character special (not Amiga) */
|
||||
const UNX_IFIFO = 0010000; /* Unix fifo (BCC, not MSC or Amiga) */
|
||||
const UNX_ISUID = 04000; /* Unix set user id on execution */
|
||||
const UNX_ISGID = 02000; /* Unix set group id on execution */
|
||||
const UNX_ISVTX = 01000; /* Unix directory permissions control */
|
||||
const UNX_ENFMT = self::UNX_ISGID; /* Unix record locking enforcement flag */
|
||||
const UNX_IRWXU = 00700; /* Unix read, write, execute: owner */
|
||||
const UNX_IRUSR = 00400; /* Unix read permission: owner */
|
||||
const UNX_IWUSR = 00200; /* Unix write permission: owner */
|
||||
const UNX_IXUSR = 00100; /* Unix execute permission: owner */
|
||||
const UNX_IRWXG = 00070; /* Unix read, write, execute: group */
|
||||
const UNX_IRGRP = 00040; /* Unix read permission: group */
|
||||
const UNX_IWGRP = 00020; /* Unix write permission: group */
|
||||
const UNX_IXGRP = 00010; /* Unix execute permission: group */
|
||||
const UNX_IRWXO = 00007; /* Unix read, write, execute: other */
|
||||
const UNX_IROTH = 00004; /* Unix read permission: other */
|
||||
const UNX_IWOTH = 00002; /* Unix write permission: other */
|
||||
const UNX_IXOTH = 00001; /* Unix execute permission: other */
|
||||
|
||||
private static $valuesMadeBy = [
|
||||
self::MADE_BY_MS_DOS => 'FAT',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
@@ -60,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',
|
||||
@@ -68,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',
|
||||
@@ -82,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',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -150,6 +176,11 @@ class ZipInfo
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* ZipInfo constructor.
|
||||
*
|
||||
@@ -162,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
|
||||
*/
|
||||
@@ -183,6 +214,90 @@ class ZipInfo
|
||||
$this->method = self::getMethodName($entry);
|
||||
$this->platform = self::getPlatformName($entry);
|
||||
$this->version = $entry->getVersionNeededToExtract();
|
||||
|
||||
$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 |
|
||||
(!($externalAttributes & 1) << 7) |
|
||||
(($externalAttributes & 0x10) << 2))
|
||||
) {
|
||||
$xattr = $externalAttributes & 0xFF;
|
||||
$attributes = ".r.-... ";
|
||||
$attributes[2] = ($xattr & 0x01) ? '-' : 'w';
|
||||
$attributes[5] = ($xattr & 0x02) ? 'h' : '-';
|
||||
$attributes[6] = ($xattr & 0x04) ? 's' : '-';
|
||||
$attributes[4] = ($xattr & 0x20) ? 'a' : '-';
|
||||
if ($xattr & 0x10) {
|
||||
$attributes[0] = 'd';
|
||||
$attributes[3] = 'x';
|
||||
} else
|
||||
$attributes[0] = '-';
|
||||
if ($xattr & 0x08)
|
||||
$attributes[0] = 'V';
|
||||
else {
|
||||
$ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) {
|
||||
$attributes[3] = 'x';
|
||||
}
|
||||
}
|
||||
break;
|
||||
} /* else: fall through! */
|
||||
|
||||
default: /* assume Unix-like */
|
||||
switch ($xattr & self::UNX_IFMT) {
|
||||
case self::UNX_IFDIR:
|
||||
$attributes[0] = 'd';
|
||||
break;
|
||||
case self::UNX_IFREG:
|
||||
$attributes[0] = '-';
|
||||
break;
|
||||
case self::UNX_IFLNK:
|
||||
$attributes[0] = 'l';
|
||||
break;
|
||||
case self::UNX_IFBLK:
|
||||
$attributes[0] = 'b';
|
||||
break;
|
||||
case self::UNX_IFCHR:
|
||||
$attributes[0] = 'c';
|
||||
break;
|
||||
case self::UNX_IFIFO:
|
||||
$attributes[0] = 'p';
|
||||
break;
|
||||
case self::UNX_IFSOCK:
|
||||
$attributes[0] = 's';
|
||||
break;
|
||||
default:
|
||||
$attributes[0] = '?';
|
||||
break;
|
||||
}
|
||||
$attributes[1] = ($xattr & self::UNX_IRUSR) ? 'r' : '-';
|
||||
$attributes[4] = ($xattr & self::UNX_IRGRP) ? 'r' : '-';
|
||||
$attributes[7] = ($xattr & self::UNX_IROTH) ? 'r' : '-';
|
||||
$attributes[2] = ($xattr & self::UNX_IWUSR) ? 'w' : '-';
|
||||
$attributes[5] = ($xattr & self::UNX_IWGRP) ? 'w' : '-';
|
||||
$attributes[8] = ($xattr & self::UNX_IWOTH) ? 'w' : '-';
|
||||
|
||||
if ($xattr & self::UNX_IXUSR)
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
|
||||
else
|
||||
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; /* S==undefined */
|
||||
if ($xattr & self::UNX_IXGRP)
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; /* == UNX_ENFMT */
|
||||
else
|
||||
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; /* SunOS 4.1.x */
|
||||
if ($xattr & self::UNX_IXOTH)
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; /* "sticky bit" */
|
||||
else
|
||||
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; /* T==undefined */
|
||||
}
|
||||
$this->attributes = trim($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,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
|
||||
*/
|
||||
@@ -245,6 +360,7 @@ class ZipInfo
|
||||
'modified' => $this->getMtime(),
|
||||
'created' => $this->getCtime(),
|
||||
'accessed' => $this->getAtime(),
|
||||
'attributes' => $this->getAttributes(),
|
||||
'encrypted' => $this->isEncrypted(),
|
||||
'comment' => $this->getComment(),
|
||||
'crc' => $this->getCrc(),
|
||||
@@ -358,6 +474,14 @@ class ZipInfo
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -375,6 +499,7 @@ class ZipInfo
|
||||
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
|
||||
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')
|
||||
. 'Method="' . $this->getMethod() . '", '
|
||||
. 'Attributes="' . $this->getAttributes() . '", '
|
||||
. 'Platform="' . $this->getPlatform() . '", '
|
||||
. 'Version=' . $this->getVersion()
|
||||
. '}';
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
1839
tests/PhpZip/ZipFileTest.php
Normal file
1839
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,26 +1,93 @@
|
||||
<?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.
|
||||
*
|
||||
* @param $filename
|
||||
* @param string $filename
|
||||
* @param string|null $password
|
||||
*/
|
||||
public static function assertCorrectZipArchive($filename)
|
||||
public static function assertCorrectZipArchive($filename, $password = null)
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR !== '\\' && `which zip`) {
|
||||
exec("zip -T " . escapeshellarg($filename), $output, $returnCode);
|
||||
if (DIRECTORY_SEPARATOR !== '\\' && `which unzip`) {
|
||||
$command = "unzip";
|
||||
if ($password !== null) {
|
||||
$command .= " -P " . escapeshellarg($password);
|
||||
}
|
||||
$command .= " -t " . escapeshellarg($filename);
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
self::assertEquals($returnCode, 0);
|
||||
self::assertNotContains('zip error', $output);
|
||||
self::assertContains(' OK', $output);
|
||||
if ($password !== null && $returnCode === 81) {
|
||||
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);
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
self::assertEquals($returnCode, 0);
|
||||
self::assertNotContains(' Errors', $output);
|
||||
self::assertContains(' Ok', $output);
|
||||
} else {
|
||||
fwrite(STDERR, 'Program unzip cannot support this function.' . PHP_EOL);
|
||||
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
self::assertEquals($returnCode, 0, $output);
|
||||
self::assertNotContains('incorrect password', $output);
|
||||
self::assertContains(' OK', $output);
|
||||
self::assertContains('No errors', $output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,21 +105,22 @@ 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 {
|
||||
echo 'Can not find program "zipalign" for test' . PHP_EOL;
|
||||
fwrite(STDERR, 'Can not find program "zipalign" for test');
|
||||
return null;
|
||||
}
|
||||
|
Reference in New Issue
Block a user