mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-08-15 11:44:56 +02:00
Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
42c0fc59df | ||
|
6688f474b5 | ||
|
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 | ||
|
951433d0b7 | ||
|
9370f353c6 | ||
|
ac20d6fbf3 | ||
|
2729d8cbec | ||
|
4c6f27c269 | ||
|
f0d90da75c | ||
|
6e5d6f48e6 | ||
|
cf7f98b7c9 | ||
|
560a94c910 | ||
|
5c23e588ff | ||
|
fe38b442a0 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/vendor/
|
||||
/vendor
|
||||
*.iml
|
||||
/.idea/
|
||||
/.idea
|
||||
/composer.lock
|
24
.travis.yml
Normal file
24
.travis.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
language: php
|
||||
php:
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0'
|
||||
- '7.1'
|
||||
- nightly
|
||||
|
||||
# cache vendor dirs
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
|
||||
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
|
12
CHANGELOG.md
Normal file
12
CHANGELOG.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## 3.0.3 (2017-11-11)
|
||||
Fix bug issue #8 - Error if the file is empty.
|
||||
|
||||
## 3.0.0 (2017-03-15)
|
||||
Merge `ZipOutputFile` with ZipFile and optimize the zip archive update.
|
||||
|
||||
See the update instructions in README.md.
|
||||
|
||||
## 2.2.0 (2017-03-02)
|
||||
Features:
|
||||
- create output object `ZipOutputFile` from `ZipFile` in method `ZipFile::edit()`.
|
||||
- create output object `ZipOutputFile` from filename in static method `ZipOutputFile::openFromFile(string $filename)`.
|
754
README.md
754
README.md
@@ -1,250 +1,548 @@
|
||||
## Documentation
|
||||
`PhpZip`
|
||||
========
|
||||
`PhpZip` - php library for manipulating zip archives.
|
||||
|
||||
Create and manipulate zip archives. No use ZipArchive class and php-zip extension.
|
||||
[](https://travis-ci.org/Ne-Lexa/php-zip)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
[](https://php.net/)
|
||||
[](https://packagist.org/packages/nelexa/zip)
|
||||
|
||||
### class \Nelexa\Zip\ZipFile
|
||||
Initialization
|
||||
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)
|
||||
|
||||
### <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.
|
||||
|
||||
### <a name="Installation"></a> Installation
|
||||
`composer require nelexa/zip:^3.0`
|
||||
|
||||
### <a name="Examples"></a> Examples
|
||||
```php
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
// 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');
|
||||
```
|
||||
Create archive
|
||||
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
|
||||
$zip->create();
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
```
|
||||
Open archive file
|
||||
Open zip archive from data string.
|
||||
```php
|
||||
$zip->open($filename);
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFromString($stringContents);
|
||||
```
|
||||
Open archive from string
|
||||
Open zip archive from stream resource.
|
||||
```php
|
||||
$zip->openFromString($string)
|
||||
$stream = fopen($filename, 'rb');
|
||||
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$zipFile->openFromStream($stream);
|
||||
```
|
||||
Set password
|
||||
#### <a name="Documentation-Open-Zip-Entries"></a> Get Zip Entries
|
||||
Get num entries.
|
||||
```php
|
||||
$zip->setPassword($password);
|
||||
$count = count($zipFile);
|
||||
// or
|
||||
$count = $zipFile->count();
|
||||
```
|
||||
List files
|
||||
Get list files.
|
||||
```php
|
||||
$listFiles = $zip->getListFiles();
|
||||
$listFiles = $zipFile->getListFiles();
|
||||
|
||||
// Example result:
|
||||
//
|
||||
// $listFiles = [
|
||||
// 'info.txt',
|
||||
// 'path/to/file.jpg',
|
||||
// 'another path/'
|
||||
// ];
|
||||
```
|
||||
Get count files
|
||||
Get entry contents.
|
||||
```php
|
||||
$countFiles = $zip->getCountFiles();
|
||||
// $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;
|
||||
echo "Data: $dataContent" . PHP_EOL;
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
}
|
||||
```
|
||||
or
|
||||
```php
|
||||
$iterator = new \ArrayIterator($zipFile);
|
||||
while ($iterator->valid())
|
||||
{
|
||||
$entryName = $iterator->key();
|
||||
$dataContent = $iterator->current();
|
||||
|
||||
echo "Entry: $entryName" . PHP_EOL;
|
||||
echo "Data: $dataContent" . PHP_EOL;
|
||||
echo "-----------------------------" . PHP_EOL;
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
```
|
||||
Get comment archive.
|
||||
```php
|
||||
$commentArchive = $zipFile->getArchiveComment();
|
||||
```
|
||||
Get comment zip entry.
|
||||
```php
|
||||
$commentEntry = $zipFile->getEntryComment($entryName);
|
||||
```
|
||||
Set password for read encrypted entries.
|
||||
```php
|
||||
$zipFile->withReadPassword($password);
|
||||
```
|
||||
Get entry info.
|
||||
```php
|
||||
$zipInfo = $zipFile->getEntryInfo('file.txt');
|
||||
|
||||
echo $zipInfo . PHP_EOL;
|
||||
|
||||
// Output:
|
||||
// ZipInfo {Path="file.txt", Size=9.77KB, Compressed size=2.04KB, Modified time=2016-09-24T19:25:10+03:00, Crc=0x4b5ab5c7, Method="Deflate", Attributes="-rw-r--r--", Platform="UNIX", Version=20}
|
||||
|
||||
print_r($zipInfo);
|
||||
|
||||
// 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
|
||||
// (
|
||||
// ...
|
||||
// )
|
||||
//
|
||||
// [file2.txt] => PhpZip\Model\ZipInfo Object
|
||||
// (
|
||||
// ...
|
||||
// )
|
||||
//
|
||||
// ...
|
||||
//)
|
||||
|
||||
```
|
||||
#### <a name="Documentation-Add-Zip-Entries"></a> Add Zip Entries
|
||||
Adding a file to the zip-archive.
|
||||
```php
|
||||
// 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
|
||||
$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
|
||||
// $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
|
||||
$zip->addEmptyDir($dirName);
|
||||
```
|
||||
Add dir
|
||||
```php
|
||||
$directory = "/tmp";
|
||||
$ignoreFiles = array("xxx.file", "xxx2.file");
|
||||
$zip->addDir($directory); // add path /tmp to /
|
||||
$zip->addDir($directory, "var/temp"); // add path /tmp to var/temp
|
||||
$zip->addDir($directory, "var/temp", $ignoreFiles); // add path /tmp to var/temp and ignore files xxx.file and xxx2.file
|
||||
```
|
||||
Add files from glob pattern
|
||||
```php
|
||||
$zip->addGlob("music/*.mp3"); // add all mp3 files
|
||||
```
|
||||
Add files from regex pattern
|
||||
```php
|
||||
$zip->addPattern("~file[0-9]+\.jpg$~", "picture/");
|
||||
```
|
||||
Add file
|
||||
```php
|
||||
$zip->addFile($filename);
|
||||
$zip->addFile($filename, $localName);
|
||||
$zip->addFile($filename, $localName, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_STORED); // no compression
|
||||
$zip->addFile($filename, $localName, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_DEFLATED);
|
||||
```
|
||||
Add file from string
|
||||
```php
|
||||
$zip->addFromString($localName, $contents);
|
||||
$zip->addFromString($localName, $contents, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_STORED); // no compression
|
||||
$zip->addFromString($localName, $contents, \Nelexa\Zip\ZipEntry::COMPRESS_METHOD_DEFLATED);
|
||||
```
|
||||
Update timestamp for all files
|
||||
```php
|
||||
$timestamp = time(); // now time
|
||||
$zip->updateTimestamp($timestamp);
|
||||
```
|
||||
Delete files from glob pattern
|
||||
```php
|
||||
$zip->deleteGlob("*.jpg"); // remove all jpg files
|
||||
```
|
||||
Delete files from regex pattern
|
||||
```php
|
||||
$zip->deletePattern("~\.jpg$~i"); // remove all jpg files
|
||||
```
|
||||
Delete file from index
|
||||
```php
|
||||
$zip->deleteIndex(0);
|
||||
```
|
||||
Delete all files
|
||||
```php
|
||||
$zip->deleteAll();
|
||||
```
|
||||
Delete from file name
|
||||
```php
|
||||
$zip->deleteName($filename);
|
||||
```
|
||||
Extract zip archive
|
||||
```php
|
||||
$zip->extractTo($toPath)
|
||||
$zip->extractTo($toPath, array("file1", "file2")); // extract only files file1 and file2
|
||||
```
|
||||
Get archive comment
|
||||
```php
|
||||
$archiveComment = $zip->getArchiveComment();
|
||||
```
|
||||
Set archive comment
|
||||
```php
|
||||
$zip->setArchiveComment($comment)
|
||||
```
|
||||
Get comment file from index
|
||||
```php
|
||||
$commentFile = $zip->getCommentIndex($index);
|
||||
```
|
||||
Set comment file from index
|
||||
```php
|
||||
$zip->setCommentIndex($index, $comment);
|
||||
```
|
||||
Get comment file from filename
|
||||
```php
|
||||
$commentFile = $zip->getCommentName($filename);
|
||||
```
|
||||
Set comment file from filename
|
||||
```php
|
||||
$zip->setCommentName($name, $comment);
|
||||
```
|
||||
Get file content from index
|
||||
```php
|
||||
$content = $zip->getFromIndex($index);
|
||||
```
|
||||
Get file content from filename
|
||||
```php
|
||||
$content = $zip->getFromName($name);
|
||||
```
|
||||
Get filename from index
|
||||
```php
|
||||
$filename = $zip->getNameIndex($index);
|
||||
```
|
||||
Rename file from index
|
||||
```php
|
||||
$zip->renameIndex($index, $newFilename);
|
||||
```
|
||||
Rename file from filename
|
||||
```php
|
||||
$zip->renameName($oldName, $newName);
|
||||
```
|
||||
Get zip entries
|
||||
```php
|
||||
/**
|
||||
* @var \Nelexa\Zip\ZipEntry[] $zipEntries
|
||||
*/
|
||||
$zipEntries = $zip->getZipEntries();
|
||||
```
|
||||
Get zip entry from index
|
||||
```php
|
||||
/**
|
||||
* @var \Nelexa\Zip\ZipEntry $zipEntry
|
||||
*/
|
||||
$zipEntry = $zip->getZipEntryIndex($index);
|
||||
```
|
||||
Get zip entry from filename
|
||||
```php
|
||||
/**
|
||||
* @var \Nelexa\Zip\ZipEntry $zipEntry
|
||||
*/
|
||||
$zipEntry = $zip->getZipEntryName($name);
|
||||
```
|
||||
Get info from index
|
||||
```php
|
||||
$info = $zip->statIndex($index);
|
||||
// [
|
||||
// 'name' - filename
|
||||
// 'index' - index number
|
||||
// 'crc' - crc32
|
||||
// 'size' - uncompressed size
|
||||
// 'mtime' - last modify date time
|
||||
// 'comp_size' - compressed size
|
||||
// 'comp_method' - compressed method
|
||||
// ]
|
||||
```
|
||||
Get info from name
|
||||
```php
|
||||
$info = $zip->statName($name);
|
||||
// [
|
||||
// 'name' - filename
|
||||
// 'index' - index number
|
||||
// 'crc' - crc32
|
||||
// 'size' - uncompressed size
|
||||
// 'mtime' - last modify date time
|
||||
// 'comp_size' - compressed size
|
||||
// 'comp_method' - compressed method
|
||||
// ]
|
||||
```
|
||||
Get info from all files
|
||||
```php
|
||||
$info = $zip->getExtendedListFiles();
|
||||
```
|
||||
Get output contents
|
||||
```php
|
||||
$content = $zip->output();
|
||||
```
|
||||
Save opened file
|
||||
```php
|
||||
$isSuccessSave = $zip->save();
|
||||
```
|
||||
Save file as
|
||||
```php
|
||||
$zip->saveAs($outputFile);
|
||||
```
|
||||
Close archive
|
||||
```php
|
||||
$zip->close();
|
||||
```
|
||||
// $dirName = "path/to/";
|
||||
|
||||
### Example create zip archive
|
||||
```php
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->create();
|
||||
$zip->addFile("README.md");
|
||||
$zip->addFile("README.md", "folder/README");
|
||||
$zip->addFromString("folder/file.txt", "File content");
|
||||
$zip->addEmptyDir("f/o/l/d/e/r");
|
||||
$zip->setArchiveComment("Archive comment");
|
||||
$zip->setCommentIndex(0, "Comment file with index 0");
|
||||
$zip->saveAs("output.zip");
|
||||
$zip->close();
|
||||
|
||||
// $ zipinfo output.zip
|
||||
// Archive: output.zip
|
||||
// Zip file size: 912 bytes, number of entries: 4
|
||||
// -rw---- 1.0 fat 387 b- defN README.md
|
||||
// -rw---- 1.0 fat 387 b- defN folder/README
|
||||
// -rw---- 1.0 fat 12 b- defN folder/file.txt
|
||||
// -rw---- 1.0 fat 0 b- stor f/o/l/d/e/r/
|
||||
// 4 files, 786 bytes uncompressed, 448 bytes compressed: 43.0%
|
||||
$zipFile->addEmptyDir($dirName);
|
||||
// or
|
||||
$zipFile[$dirName] = null;
|
||||
```
|
||||
|
||||
### Example modification zip archive
|
||||
Add all entries form string contents.
|
||||
```php
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->open("output.zip");
|
||||
$zip->addFromString("new-file", file_get_contents(__FILE__));
|
||||
$zip->saveAs("output2.zip");
|
||||
$zip->save();
|
||||
$zip->close();
|
||||
$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
|
||||
$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 **recursively** to the archive.
|
||||
```php
|
||||
$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 files from directory iterator.
|
||||
```php
|
||||
// $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
|
||||
```
|
||||
Example add a directory to the archive with ignoring files from directory iterator.
|
||||
```php
|
||||
$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
|
||||
|
||||
$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
|
||||
$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
|
||||
|
||||
$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
|
||||
$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
|
||||
$zipFile->rename($oldName, $newName);
|
||||
```
|
||||
Delete entry by name.
|
||||
```php
|
||||
$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
|
||||
|
||||
$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
|
||||
|
||||
$zipFile->deleteFromRegex($regexPattern);
|
||||
```
|
||||
Delete all entries.
|
||||
```php
|
||||
$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 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
|
||||
$zipFile->setArchiveComment($commentArchive);
|
||||
```
|
||||
Set comment zip entry.
|
||||
```php
|
||||
$zipFile->setEntryComment($entryName, $entryComment);
|
||||
```
|
||||
Set a new password.
|
||||
```php
|
||||
$zipFile->withNewPassword($password);
|
||||
```
|
||||
Set a new password and encryption method.
|
||||
```php
|
||||
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES; // default value
|
||||
$zipFile->withNewPassword($password, $encryptionMethod);
|
||||
|
||||
// Support encryption methods:
|
||||
// ZipFile::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
|
||||
// ZipFile::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption
|
||||
```
|
||||
Remove password from all entries.
|
||||
```php
|
||||
$zipFile->withoutPassword();
|
||||
```
|
||||
#### <a name="Documentation-ZipAlign-Usage"></a> ZipAlign Usage
|
||||
Set archive alignment ([`zipalign`](https://developer.android.com/studio/command-line/zipalign.html)).
|
||||
```php
|
||||
// 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
|
||||
$zipFile->saveAsFile($filename);
|
||||
```
|
||||
Save archive to a stream.
|
||||
```php
|
||||
// $fp = fopen($filename, 'w+b');
|
||||
|
||||
$zipFile->saveAsStream($fp);
|
||||
```
|
||||
Returns the zip archive as a string.
|
||||
```php
|
||||
$rawZipArchiveBytes = $zipFile->outputAsString();
|
||||
```
|
||||
Output .ZIP archive as attachment and terminate.
|
||||
```php
|
||||
$zipFile->outputAsAttachment($outputFilename);
|
||||
// or set mime type
|
||||
$mimeType = 'application/zip'
|
||||
$zipFile->outputAsAttachment($outputFilename, $mimeType);
|
||||
```
|
||||
Rewrite and reopen zip archive.
|
||||
```php
|
||||
$zipFile->rewrite();
|
||||
```
|
||||
#### <a name="Documentation-Close-Zip-Archive"></a> Close Zip Archive
|
||||
Close zip archive.
|
||||
```php
|
||||
$zipFile->close();
|
||||
```
|
||||
### <a name="Running-Tests"></a> Running Tests
|
||||
Installing development dependencies.
|
||||
```bash
|
||||
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`
|
||||
|
||||
|
||||
// $ zipinfo output2.zip
|
||||
// Archive: output2.zip
|
||||
// Zip file size: 1331 bytes, number of entries: 5
|
||||
// -rw---- 1.0 fat 387 b- defN README.md
|
||||
// -rw---- 1.0 fat 387 b- defN folder/README
|
||||
// -rw---- 1.0 fat 12 b- defN folder/file.txt
|
||||
// -rw---- 1.0 fat 0 b- stor f/o/l/d/e/r/
|
||||
// -rw---- 1.0 fat 593 b- defN new-file
|
||||
// 5 files, 1379 bytes uncompressed, 775 bytes compressed: 43.8%
|
||||
```
|
10
bootstrap.xml
Normal file
10
bootstrap.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<phpunit bootstrap="./vendor/autoload.php" colors="true">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="PhpZip test suite">
|
||||
<directory>./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
</phpunit>
|
@@ -1,25 +1,44 @@
|
||||
{
|
||||
"name": "nelexa/zip",
|
||||
"description": "Zip create, modify and extract tool. Alternative ZipArchive.",
|
||||
"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": "^5.5"
|
||||
"phpunit/phpunit": "4.8",
|
||||
"codeclimate/php-test-reporter": "^0.4.4"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ne-Lexa",
|
||||
"email": "alexey@nelexa.ru"
|
||||
"email": "alexey@nelexa.ru",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"nelexa/buffer": "^1.0"
|
||||
"php": "^5.5 || ^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nelexa\\Zip\\": "src"
|
||||
"PhpZip\\": "src/PhpZip"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PhpZip\\": "tests/PhpZip"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
|
||||
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
|
||||
"ext-bz2": "Needed to support BZIP2 compression"
|
||||
}
|
||||
}
|
||||
|
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
class FilterFileIterator extends \FilterIterator
|
||||
{
|
||||
private $ignoreFiles;
|
||||
private static $ignoreAlways = array('..');
|
||||
|
||||
/**
|
||||
* @param \Iterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\Iterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge(self::$ignoreAlways, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.1.0)<br/>
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $value
|
||||
*/
|
||||
$value = $this->current();
|
||||
$pathName = $value->getRealPath();
|
||||
foreach ($this->ignoreFiles AS $ignoreFile) {
|
||||
if ($this->endsWith($pathName, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function endsWith($haystack, $needle)
|
||||
{
|
||||
// search forward starting from end minus needle length characters
|
||||
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== FALSE);
|
||||
}
|
||||
}
|
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);
|
||||
}
|
236
src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php
Normal file
236
src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* Traditional PKWARE Encryption Engine.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class TraditionalPkwareEncryptionEngine implements CryptoEngine
|
||||
{
|
||||
/**
|
||||
* Encryption header size
|
||||
*/
|
||||
const STD_DEC_HDR_SIZE = 12;
|
||||
|
||||
/**
|
||||
* Crc table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $CRC_TABLE = [
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
];
|
||||
|
||||
/**
|
||||
* Encryption keys
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $keys = [];
|
||||
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* ZipCryptoEngine constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
$this->initKeys($entry->getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial keys
|
||||
*
|
||||
* @param string $password
|
||||
*/
|
||||
private function initKeys($password)
|
||||
{
|
||||
$this->keys[0] = 305419896;
|
||||
$this->keys[1] = 591751049;
|
||||
$this->keys[2] = 878082192;
|
||||
foreach (unpack('C*', $password) as $b) {
|
||||
$this->updateKeys($b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update keys.
|
||||
*
|
||||
* @param string $charAt
|
||||
*/
|
||||
private function updateKeys($charAt)
|
||||
{
|
||||
$this->keys[0] = self::crc32($this->keys[0], $charAt);
|
||||
$this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff);
|
||||
$this->keys[1] = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update crc.
|
||||
*
|
||||
* @param int $oldCrc
|
||||
* @param string $charAt
|
||||
* @return int
|
||||
*/
|
||||
private function crc32($oldCrc, $charAt)
|
||||
{
|
||||
return (($oldCrc >> 8) & 0xffffff) ^ self::$CRC_TABLE[($oldCrc ^ $charAt) & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
$headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE)));
|
||||
foreach ($headerBytes as &$byte) {
|
||||
$byte = ($byte ^ $this->decryptByte()) & 0xff;
|
||||
$this->updateKeys($byte);
|
||||
}
|
||||
|
||||
if ($this->entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
// compare against the file type from extended local headers
|
||||
$checkByte = ($this->entry->getDosTime() >> 8) & 0xff;
|
||||
} else {
|
||||
// compare against the CRC otherwise
|
||||
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
|
||||
}
|
||||
if ($byte !== $checkByte) {
|
||||
throw new ZipAuthenticationException("Bad password for entry " . $this->entry->getName());
|
||||
}
|
||||
|
||||
$outputContent = "";
|
||||
foreach (unpack('C*', substr($content, self::STD_DEC_HDR_SIZE)) as $val) {
|
||||
$val = ($val ^ $this->decryptByte()) & 0xff;
|
||||
$this->updateKeys($val);
|
||||
$outputContent .= pack('c', $val);
|
||||
}
|
||||
return $outputContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt byte.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function decryptByte()
|
||||
{
|
||||
$temp = $this->keys[2] | 2;
|
||||
return (($temp * ($temp ^ 1)) >> 8) & 0xffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption data
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
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.
|
||||
$this->initKeys($this->entry->getPassword());
|
||||
$headerBytes[self::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff);
|
||||
$headerBytes[self::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff);
|
||||
|
||||
$headerBytes = $this->encryptData($headerBytes);
|
||||
return $headerBytes . $this->encryptData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws ZipCryptoException
|
||||
*/
|
||||
private function encryptData($content)
|
||||
{
|
||||
if (null === $content) {
|
||||
throw new ZipCryptoException('content is null');
|
||||
}
|
||||
$buff = '';
|
||||
foreach (unpack('C*', $content) as $val) {
|
||||
$buff .= pack('c', $this->encryptByte($val));
|
||||
}
|
||||
return $buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $byte
|
||||
* @return int
|
||||
*/
|
||||
private function encryptByte($byte)
|
||||
{
|
||||
$tempVal = $byte ^ $this->decryptByte() & 0xff;
|
||||
$this->updateKeys($byte);
|
||||
return $tempVal;
|
||||
}
|
||||
}
|
247
src/PhpZip/Crypto/WinZipAesEngine.php
Normal file
247
src/PhpZip/Crypto/WinZipAesEngine.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
namespace PhpZip\Crypto;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipAuthenticationException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\CryptoUtil;
|
||||
|
||||
/**
|
||||
* WinZip Aes Encryption Engine.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class WinZipAesEngine implements CryptoEngine
|
||||
{
|
||||
/**
|
||||
* The block size of the Advanced Encryption Specification (AES) Algorithm
|
||||
* in bits (AES_BLOCK_SIZE_BITS).
|
||||
*/
|
||||
const AES_BLOCK_SIZE_BITS = 128;
|
||||
const PWD_VERIFIER_BITS = 16;
|
||||
/**
|
||||
* The iteration count for the derived keys of the cipher, KLAC and MAC.
|
||||
*/
|
||||
const ITERATION_COUNT = 1000;
|
||||
/**
|
||||
* @var ZipEntry
|
||||
*/
|
||||
private $entry;
|
||||
|
||||
/**
|
||||
* WinZipAesEngine constructor.
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$this->entry = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt from stream resource.
|
||||
*
|
||||
* @param string $content Input stream buffer
|
||||
* @return string
|
||||
* @throws ZipAuthenticationException
|
||||
* @throws ZipCryptoException
|
||||
*/
|
||||
public function decrypt($content)
|
||||
{
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$field = $this->entry->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
if (null === $field) {
|
||||
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
|
||||
}
|
||||
|
||||
// Get key strength.
|
||||
$keyStrengthBits = $field->getKeyStrength();
|
||||
$keyStrengthBytes = $keyStrengthBits / 8;
|
||||
|
||||
$pos = $keyStrengthBytes / 2;
|
||||
$salt = substr($content, 0, $pos);
|
||||
$passwordVerifier = substr($content, $pos, self::PWD_VERIFIER_BITS / 8);
|
||||
$pos += self::PWD_VERIFIER_BITS / 8;
|
||||
|
||||
$sha1Size = 20;
|
||||
|
||||
// Init start, end and size of encrypted data.
|
||||
$start = $pos;
|
||||
$endPos = strlen($content);
|
||||
$footerSize = $sha1Size / 2;
|
||||
$end = $endPos - $footerSize;
|
||||
$size = $end - $start;
|
||||
|
||||
if (0 > $size) {
|
||||
throw new ZipCryptoException($this->entry->getName() . " (false positive WinZip AES entry is too short)");
|
||||
}
|
||||
|
||||
// Load authentication code.
|
||||
$authenticationCode = substr($content, $end, $footerSize);
|
||||
if ($end + $footerSize !== $endPos) {
|
||||
// This should never happen unless someone is writing to the
|
||||
// end of the file concurrently!
|
||||
throw new ZipCryptoException("Expected end of file after WinZip AES authentication code!");
|
||||
}
|
||||
|
||||
$password = $this->entry->getPassword();
|
||||
assert($password !== null);
|
||||
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
|
||||
|
||||
// WinZip 99-character limit
|
||||
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
|
||||
$password = substr($password, 0, 99);
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$iv = str_repeat(chr(0), $ctrIvSize);
|
||||
do {
|
||||
// Here comes the strange part about WinZip AES encryption:
|
||||
// Its unorthodox use of the Password-Based Key Derivation
|
||||
// Function 2 (PBKDF2) of PKCS #5 V2.0 alias RFC 2898.
|
||||
// Yes, the password verifier is only a 16 bit value.
|
||||
// So we must use the MAC for password verification, too.
|
||||
$keyParam = hash_pbkdf2(
|
||||
"sha1",
|
||||
$password,
|
||||
$salt,
|
||||
self::ITERATION_COUNT,
|
||||
(2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8,
|
||||
true
|
||||
);
|
||||
$key = substr($keyParam, 0, $keyStrengthBytes);
|
||||
$sha1MacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
|
||||
// Verify password.
|
||||
} while (!$passwordVerifier === substr($keyParam, 2 * $keyStrengthBytes));
|
||||
|
||||
$content = substr($content, $start, $size);
|
||||
$mac = hash_hmac('sha1', $content, $sha1MacParam, true);
|
||||
|
||||
if (substr($mac, 0, 10) !== $authenticationCode) {
|
||||
throw new ZipAuthenticationException($this->entry->getName() .
|
||||
" (authenticated WinZip AES entry content has been tampered with)");
|
||||
}
|
||||
|
||||
return self::aesCtrSegmentIntegerCounter(false, $content, $key, $iv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decryption or encryption AES-CTR with Segment Integer Count (SIC).
|
||||
*
|
||||
* @param bool $encrypted If true encryption else decryption
|
||||
* @param string $str Data
|
||||
* @param string $key Key
|
||||
* @param string $iv IV
|
||||
* @return string
|
||||
*/
|
||||
private static function aesCtrSegmentIntegerCounter($encrypted = true, $str, $key, $iv)
|
||||
{
|
||||
$numOfBlocks = ceil(strlen($str) / 16);
|
||||
$ctrStr = '';
|
||||
for ($i = 0; $i < $numOfBlocks; ++$i) {
|
||||
for ($j = 0; $j < 16; ++$j) {
|
||||
$n = ord($iv[$j]);
|
||||
if (0x100 === ++$n) {
|
||||
// overflow, set this one to 0, increment next
|
||||
$iv[$j] = chr(0);
|
||||
} else {
|
||||
// no overflow, just write incremented number back and abort
|
||||
$iv[$j] = chr($n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$data = substr($str, $i * 16, 16);
|
||||
$ctrStr .= $encrypted ?
|
||||
self::encryptCtr($data, $key, $iv) :
|
||||
self::decryptCtr($data, $key, $iv);
|
||||
}
|
||||
return $ctrStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Raw data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @return string Encrypted data
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private static function encryptCtr($data, $key, $iv)
|
||||
{
|
||||
if (extension_loaded("openssl")) {
|
||||
$numBits = strlen($key) * 8;
|
||||
return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt AES-CTR.
|
||||
*
|
||||
* @param string $data Encrypted data
|
||||
* @param string $key Aes key
|
||||
* @param string $iv Aes IV
|
||||
* @return string Raw data
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private static function decryptCtr($data, $key, $iv)
|
||||
{
|
||||
if (extension_loaded("openssl")) {
|
||||
$numBits = strlen($key) * 8;
|
||||
return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv);
|
||||
} elseif (extension_loaded("mcrypt")) {
|
||||
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption string.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($content)
|
||||
{
|
||||
// Init key strength.
|
||||
$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;
|
||||
|
||||
assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits);
|
||||
|
||||
$salt = CryptoUtil::randomBytes($keyStrengthBytes / 2);
|
||||
|
||||
$keyParam = hash_pbkdf2("sha1", $password, $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true);
|
||||
$sha1HMacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
|
||||
|
||||
// Can you believe they "forgot" the nonce in the CTR mode IV?! :-(
|
||||
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
|
||||
$iv = str_repeat(chr(0), $ctrIvSize);
|
||||
|
||||
$key = substr($keyParam, 0, $keyStrengthBytes);
|
||||
|
||||
$content = self::aesCtrSegmentIntegerCounter(true, $content, $key, $iv);
|
||||
|
||||
$mac = hash_hmac('sha1', $content, $sha1HMacParam, true);
|
||||
|
||||
return ($salt .
|
||||
substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) .
|
||||
$content .
|
||||
substr($mac, 0, 10)
|
||||
);
|
||||
}
|
||||
}
|
70
src/PhpZip/Exception/Crc32Exception.php
Normal file
70
src/PhpZip/Exception/Crc32Exception.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate a CRC32 mismatch between the declared value in the
|
||||
* Central File Header and the Data Descriptor or between the declared value
|
||||
* and the computed value from the decompressed data.
|
||||
*
|
||||
* The exception's detail message is the name of the ZIP entry.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class Crc32Exception extends ZipException
|
||||
{
|
||||
/**
|
||||
* Expected crc.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $expectedCrc;
|
||||
|
||||
/**
|
||||
* Actual crc.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $actualCrc;
|
||||
|
||||
/**
|
||||
* Crc32Exception constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $expected
|
||||
* @param int $actual
|
||||
*/
|
||||
public function __construct($name, $expected, $actual)
|
||||
{
|
||||
parent::__construct($name
|
||||
. " (expected CRC32 value 0x"
|
||||
. dechex($expected)
|
||||
. ", but is actually 0x"
|
||||
. dechex($actual)
|
||||
. ")");
|
||||
assert($expected != $actual);
|
||||
$this->expectedCrc = $expected;
|
||||
$this->actualCrc = $actual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expected crc.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getExpectedCrc()
|
||||
{
|
||||
return $this->expectedCrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns actual crc.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getActualCrc()
|
||||
{
|
||||
return $this->actualCrc;
|
||||
}
|
||||
|
||||
}
|
14
src/PhpZip/Exception/InvalidArgumentException.php
Normal file
14
src/PhpZip/Exception/InvalidArgumentException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a method has been passed an illegal or
|
||||
* inappropriate argument.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class InvalidArgumentException extends 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
|
||||
{
|
||||
|
||||
}
|
13
src/PhpZip/Exception/ZipAuthenticationException.php
Normal file
13
src/PhpZip/Exception/ZipAuthenticationException.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an authenticated ZIP entry has been tampered with.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipAuthenticationException extends ZipCryptoException
|
||||
{
|
||||
|
||||
}
|
14
src/PhpZip/Exception/ZipCryptoException.php
Normal file
14
src/PhpZip/Exception/ZipCryptoException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if there is an issue when reading or writing an encrypted ZIP file
|
||||
* or entry.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipCryptoException extends ZipException
|
||||
{
|
||||
|
||||
}
|
14
src/PhpZip/Exception/ZipException.php
Normal file
14
src/PhpZip/Exception/ZipException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Signals that a Zip exception of some sort has occurred.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @see \Exception
|
||||
*/
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
|
||||
}
|
14
src/PhpZip/Exception/ZipNotFoundEntry.php
Normal file
14
src/PhpZip/Exception/ZipNotFoundEntry.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if entry not found.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @see \Exception
|
||||
*/
|
||||
class ZipNotFoundEntry extends ZipException
|
||||
{
|
||||
|
||||
}
|
14
src/PhpZip/Exception/ZipUnsupportMethod.php
Normal file
14
src/PhpZip/Exception/ZipUnsupportMethod.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace PhpZip\Exception;
|
||||
|
||||
/**
|
||||
* Thrown if entry unsupport compression method.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
* @see \Exception
|
||||
*/
|
||||
class ZipUnsupportMethod extends ZipException
|
||||
{
|
||||
|
||||
}
|
98
src/PhpZip/Extra/DefaultExtraField.php
Normal file
98
src/PhpZip/Extra/DefaultExtraField.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Default implementation for an Extra Field in a Local or Central Header of a
|
||||
* ZIP file.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class DefaultExtraField extends ExtraField
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $headerId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* Constructs a new Extra Field.
|
||||
*
|
||||
* @param int $headerId an unsigned short integer (two bytes) indicating the
|
||||
* type of the Extra Field.
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function __construct($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
self::$headerId = $headerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return self::$headerId & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public function getDataSize()
|
||||
{
|
||||
return null !== $this->data ? strlen($this->data) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
if ($size > 0) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
$this->data = fread($handle, $size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @param int $off
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
{
|
||||
if (null !== $this->data) {
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fwrite($handle, $this->data);
|
||||
}
|
||||
}
|
||||
}
|
120
src/PhpZip/Extra/ExtraField.php
Normal file
120
src/PhpZip/Extra/ExtraField.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Abstract base class for an Extra Field in a Local or Central Header of a
|
||||
* ZIP archive.
|
||||
* It defines the common properties of all Extra Fields and how to
|
||||
* serialize/deserialize them to/from byte arrays.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class ExtraField implements ExtraFieldHeader
|
||||
{
|
||||
/** The Header ID for a ZIP64 Extended Information Extra Field. */
|
||||
const ZIP64_HEADER_ID = 0x0001;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private static $registry;
|
||||
|
||||
/**
|
||||
* A static factory method which creates a new Extra Field based on the
|
||||
* given Header ID.
|
||||
* The returned Extra Field still requires proper initialization, for
|
||||
* example by calling ExtraField::readFrom.
|
||||
*
|
||||
* @param int $headerId An unsigned short integer (two bytes) which indicates
|
||||
* the type of the returned Extra Field.
|
||||
* @return ExtraField A new Extra Field or null if not support header id.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public static function create($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ExtraField $extraField
|
||||
*/
|
||||
if (isset(self::getRegistry()[$headerId])) {
|
||||
$extraClassName = self::getRegistry()[$headerId];
|
||||
$extraField = new $extraClassName;
|
||||
if ($extraField::getHeaderId() !== $headerId) {
|
||||
throw new ZipException('Runtime error support headerId ' . $headerId);
|
||||
}
|
||||
} else {
|
||||
$extraField = new DefaultExtraField($headerId);
|
||||
}
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered extra field classes.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private static function getRegistry()
|
||||
{
|
||||
if (null === self::$registry) {
|
||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
||||
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
||||
}
|
||||
return self::$registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the Data Block.
|
||||
*
|
||||
* @return resource
|
||||
* @throws ZipException If size data block out of range.
|
||||
*/
|
||||
public function getDataBlock()
|
||||
{
|
||||
$size = $this->getDataSize();
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size data block out of range.');
|
||||
}
|
||||
$fp = fopen('php://memory', 'r+b');
|
||||
if (0 === $size) return $fp;
|
||||
$this->writeTo($fp, 0);
|
||||
rewind($fp);
|
||||
return $fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
abstract public function getDataSize();
|
||||
|
||||
/**
|
||||
* Serializes a Data Block of ExtraField::getDataSize bytes to the
|
||||
* resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
*/
|
||||
abstract public function writeTo($handle, $off);
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
*/
|
||||
abstract public function readFrom($handle, $off, $size);
|
||||
}
|
21
src/PhpZip/Extra/ExtraFieldHeader.php
Normal file
21
src/PhpZip/Extra/ExtraFieldHeader.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
/**
|
||||
* Interface ExtraFieldHeader
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ExtraFieldHeader
|
||||
{
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId();
|
||||
|
||||
}
|
200
src/PhpZip/Extra/ExtraFields.php
Normal file
200
src/PhpZip/Extra/ExtraFields.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Represents a collection of Extra Fields as they may
|
||||
* be present at several locations in ZIP files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ExtraFields
|
||||
{
|
||||
/**
|
||||
* The map of Extra Fields.
|
||||
* Maps from Header ID to Extra Field.
|
||||
* Must not be null, but may be empty if no Extra Fields are used.
|
||||
* The map is sorted by Header IDs in ascending order.
|
||||
*
|
||||
* @var ExtraField[]
|
||||
*/
|
||||
private $extra = [];
|
||||
|
||||
/**
|
||||
* Returns the number of Extra Fields in this collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function size()
|
||||
{
|
||||
return sizeof($this->extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public function get($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
if (isset($this->extra[$headerId])) {
|
||||
return $this->extra[$headerId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given Extra Field in this collection.
|
||||
*
|
||||
* @param ExtraField $extraField The Extra Field to store in this collection.
|
||||
* @return ExtraField The Extra Field previously associated with the Header ID of
|
||||
* of the given Extra Field or null if no such Extra Field existed.
|
||||
* @throws ZipException If headerId is out of range.
|
||||
*/
|
||||
public function add(ExtraField $extraField)
|
||||
{
|
||||
$headerId = $extraField::getHeaderId();
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
$this->extra[$headerId] = $extraField;
|
||||
return $extraField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Extra Field exists
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function has($headerId)
|
||||
{
|
||||
return isset($this->extra[$headerId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Extra Field with the given Header ID.
|
||||
*
|
||||
* @param int $headerId The requested Header ID.
|
||||
* @return ExtraField The Extra Field with the given Header ID or null
|
||||
* if no such Extra Field exists.
|
||||
* @throws ZipException If headerId is out of range or extra field not found.
|
||||
*/
|
||||
public function remove($headerId)
|
||||
{
|
||||
if (0x0000 > $headerId || $headerId > 0xffff) {
|
||||
throw new ZipException('headerId out of range');
|
||||
}
|
||||
if (isset($this->extra[$headerId])) {
|
||||
$ef = $this->extra[$headerId];
|
||||
unset($this->extra[$headerId]);
|
||||
return $ef;
|
||||
}
|
||||
throw new ZipException('ExtraField not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the Extra Fields.
|
||||
* null is never returned.
|
||||
*
|
||||
* @return string
|
||||
* @throws ZipException If size out of range
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
$size = $this->getExtraLength();
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
if (0 === $size) return '';
|
||||
|
||||
$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);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes required to hold the Extra Fields.
|
||||
*
|
||||
* @return int The length of the Extra Fields in bytes. May be 0.
|
||||
* @see #getExtra
|
||||
*/
|
||||
public function getExtraLength()
|
||||
{
|
||||
if (empty($this->extra)) {
|
||||
return 0;
|
||||
}
|
||||
$length = 0;
|
||||
|
||||
/**
|
||||
* @var ExtraField $extraField
|
||||
*/
|
||||
foreach ($this->extra as $extraField) {
|
||||
$length += 4 + $extraField->getDataSize();
|
||||
}
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset
|
||||
* @param int $size Size
|
||||
* @throws ZipException If size out of range
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
$map = [];
|
||||
if (null !== $handle && 0 < $size) {
|
||||
$end = $off + $size;
|
||||
while ($off < $end) {
|
||||
fseek($handle, $off);
|
||||
$unpack = unpack('vheaderId/vdataSize', fread($handle, 4));
|
||||
$off += 4;
|
||||
$extraField = ExtraField::create($unpack['headerId']);
|
||||
$extraField->readFrom($handle, $off, $unpack['dataSize']);
|
||||
$off += $unpack['dataSize'];
|
||||
$map[$unpack['headerId']] = $extraField;
|
||||
}
|
||||
assert($off === $end);
|
||||
}
|
||||
$this->extra = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* If clone extra fields.
|
||||
*/
|
||||
function __clone()
|
||||
{
|
||||
foreach ($this->extra as $k => $v) {
|
||||
$this->extra[$k] = clone $v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
176
src/PhpZip/Extra/NtfsExtraField.php
Normal file
176
src/PhpZip/Extra/NtfsExtraField.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Util\PackUtil;
|
||||
|
||||
/**
|
||||
* NTFS Extra Field
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class NtfsExtraField extends ExtraField
|
||||
{
|
||||
|
||||
/**
|
||||
* Modify time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* Access Time
|
||||
*
|
||||
* @var int Unix Timestamp
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* Create Time
|
||||
*
|
||||
* @var int Unix Time
|
||||
*/
|
||||
private $ctime;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rawData = "";
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x000a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public function getDataSize()
|
||||
{
|
||||
return 8 * 4 + strlen($this->rawData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
* @throws ZipException If size out of range
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (0x0000 > $size || $size > 0xffff) {
|
||||
throw new ZipException('size out of range');
|
||||
}
|
||||
if ($size > 0) {
|
||||
$off += 4;
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
|
||||
$unpack = unpack('vtag/vsizeAttr', fread($handle, 4));
|
||||
if (24 === $unpack['sizeAttr']) {
|
||||
$tagData = fread($handle, $unpack['sizeAttr']);
|
||||
|
||||
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
|
||||
$this->atime = PackUtil::unpackLongLE(substr($tagData, 8, 8)) / 10000000 - 11644473600;
|
||||
$this->ctime = PackUtil::unpackLongLE(substr($tagData, 16, 8)) / 10000000 - 11644473600;
|
||||
}
|
||||
$off += $unpack['sizeAttr'];
|
||||
|
||||
if ($size > $off) {
|
||||
$this->rawData .= fread($handle, $size - $off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block of ExtraField::getDataSize bytes to the
|
||||
* resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
{
|
||||
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;
|
||||
fwrite($handle, PackUtil::packLongLE($mtimeLong));
|
||||
$atimeLong = ($this->atime + 11644473600) * 10000000;
|
||||
fwrite($handle, PackUtil::packLongLE($atimeLong));
|
||||
$ctimeLong = ($this->ctime + 11644473600) * 10000000;
|
||||
fwrite($handle, PackUtil::packLongLE($ctimeLong));
|
||||
if (!empty($this->rawData)) {
|
||||
fwrite($handle, $this->rawData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mtime
|
||||
*/
|
||||
public function setMtime($mtime)
|
||||
{
|
||||
$this->mtime = (int)$mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAtime()
|
||||
{
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $atime
|
||||
*/
|
||||
public function setAtime($atime)
|
||||
{
|
||||
$this->atime = (int)$atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCtime()
|
||||
{
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ctime
|
||||
*/
|
||||
public function setCtime($ctime)
|
||||
{
|
||||
$this->ctime = (int)$ctime;
|
||||
}
|
||||
|
||||
}
|
236
src/PhpZip/Extra/WinZipAesEntryExtraField.php
Normal file
236
src/PhpZip/Extra/WinZipAesEntryExtraField.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* WinZip AES Extra Field.
|
||||
*
|
||||
* @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2 (WinZip Computing, S.L.)
|
||||
* @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers (WinZip Computing, S.L.)
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class WinZipAesEntryExtraField extends ExtraField
|
||||
{
|
||||
const DATA_SIZE = 7;
|
||||
const VENDOR_ID = 17729; // 'A' | ('E' << 8);
|
||||
|
||||
/**
|
||||
* Entries of this type <em>do</em> include the standard ZIP CRC-32 value.
|
||||
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion().
|
||||
*/
|
||||
const VV_AE_1 = 1;
|
||||
|
||||
/**
|
||||
* Entries of this type do <em>not</em> include the standard ZIP CRC-32 value.
|
||||
* For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion().
|
||||
*/
|
||||
const VV_AE_2 = 2;
|
||||
|
||||
const KEY_STRENGTH_128BIT = 128;
|
||||
const KEY_STRENGTH_192BIT = 192;
|
||||
const KEY_STRENGTH_256BIT = 256;
|
||||
|
||||
private static $keyStrengths = [
|
||||
self::KEY_STRENGTH_128BIT => 0x01,
|
||||
self::KEY_STRENGTH_192BIT => 0x02,
|
||||
self::KEY_STRENGTH_256BIT => 0x03
|
||||
];
|
||||
|
||||
/**
|
||||
* Vendor version.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $vendorVersion = self::VV_AE_1;
|
||||
|
||||
/**
|
||||
* Encryption strength.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $encryptionStrength = self::KEY_STRENGTH_256BIT;
|
||||
|
||||
/**
|
||||
* Zip compression method.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* Returns the Header ID (type) of this Extra Field.
|
||||
* The Header ID is an unsigned short integer (two bytes)
|
||||
* which must be constant during the life cycle of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getHeaderId()
|
||||
{
|
||||
return 0x9901;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Data Size of this Extra Field.
|
||||
* The Data Size is an unsigned short integer (two bytes)
|
||||
* which indicates the length of the Data Block in bytes and does not
|
||||
* include its own size in this Extra Field.
|
||||
* This property may be initialized by calling ExtraField::readFrom.
|
||||
*
|
||||
* @return int The size of the Data Block in bytes
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public function getDataSize()
|
||||
{
|
||||
return self::DATA_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vendor version.
|
||||
*
|
||||
* @see WinZipAesEntryExtraField::VV_AE_1
|
||||
* @see WinZipAesEntryExtraField::VV_AE_2
|
||||
*/
|
||||
public function getVendorVersion()
|
||||
{
|
||||
return $this->vendorVersion & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vendor version.
|
||||
*
|
||||
* @see WinZipAesEntryExtraField::VV_AE_1
|
||||
* @see WinZipAesEntryExtraField::VV_AE_2
|
||||
* @param int $vendorVersion the vendor version.
|
||||
* @throws ZipException Unsupport vendor version.
|
||||
*/
|
||||
public function setVendorVersion($vendorVersion)
|
||||
{
|
||||
if ($vendorVersion < self::VV_AE_1 || self::VV_AE_2 < $vendorVersion) {
|
||||
throw new ZipException($vendorVersion);
|
||||
}
|
||||
$this->vendorVersion = $vendorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns vendor id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVendorId()
|
||||
{
|
||||
return self::VENDOR_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|int
|
||||
*/
|
||||
public function getKeyStrength()
|
||||
{
|
||||
return self::keyStrength($this->encryptionStrength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $encryptionStrength Encryption strength as bits.
|
||||
* @return int
|
||||
* @throws ZipException If unsupport encryption strength.
|
||||
*/
|
||||
public static function keyStrength($encryptionStrength)
|
||||
{
|
||||
$flipKeyStrength = array_flip(self::$keyStrengths);
|
||||
if (!isset($flipKeyStrength[$encryptionStrength])) {
|
||||
throw new ZipException("Unsupport encryption strength " . $encryptionStrength);
|
||||
}
|
||||
return $flipKeyStrength[$encryptionStrength];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns compression method.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets compression method.
|
||||
*
|
||||
* @param int $compressionMethod Compression method
|
||||
* @throws ZipException Compression method out of range.
|
||||
*/
|
||||
public function setMethod($compressionMethod)
|
||||
{
|
||||
if (0x0000 > $compressionMethod || $compressionMethod > 0xffff) {
|
||||
throw new ZipException('Compression method out of range');
|
||||
}
|
||||
$this->method = $compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this Extra Field by deserializing a Data Block of
|
||||
* size bytes $size from the resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
* @param int $size Size
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readFrom($handle, $off, $size)
|
||||
{
|
||||
if (self::DATA_SIZE != $size)
|
||||
throw new ZipException();
|
||||
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
/**
|
||||
* @var int $vendorVersion
|
||||
* @var int $vendorId
|
||||
* @var int $keyStrength
|
||||
* @var int $method
|
||||
*/
|
||||
$unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', fread($handle, 7));
|
||||
extract($unpack);
|
||||
$this->setVendorVersion($vendorVersion);
|
||||
if (self::VENDOR_ID != $vendorId) {
|
||||
throw new ZipException();
|
||||
}
|
||||
$this->setKeyStrength(self::keyStrength($keyStrength)); // checked
|
||||
$this->setMethod($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key strength.
|
||||
*
|
||||
* @param int $keyStrength
|
||||
*/
|
||||
public function setKeyStrength($keyStrength)
|
||||
{
|
||||
$this->encryptionStrength = self::encryptionStrength($keyStrength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encryption strength.
|
||||
*
|
||||
* @param int $keyStrength Key strength in bits.
|
||||
* @return int
|
||||
*/
|
||||
public static function encryptionStrength($keyStrength)
|
||||
{
|
||||
return isset(self::$keyStrengths[$keyStrength]) ? self::$keyStrengths[$keyStrength] : self::$keyStrengths[self::KEY_STRENGTH_128BIT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Data Block of ExtraField::getDataSize bytes to the
|
||||
* resource $handle at the zero based offset $off.
|
||||
*
|
||||
* @param resource $handle
|
||||
* @param int $off Offset bytes
|
||||
*/
|
||||
public function writeTo($handle, $off)
|
||||
{
|
||||
fseek($handle, $off, SEEK_SET);
|
||||
fwrite($handle, pack('vvcv', $this->vendorVersion, self::VENDOR_ID, $this->encryptionStrength, $this->method));
|
||||
}
|
||||
}
|
42
src/PhpZip/Mapper/OffsetPositionMapper.php
Normal file
42
src/PhpZip/Mapper/OffsetPositionMapper.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace PhpZip\Mapper;
|
||||
|
||||
/**
|
||||
* Adds a offset value to the given position.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class OffsetPositionMapper extends PositionMapper
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offset;
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
*/
|
||||
public function __construct($offset)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function map($position)
|
||||
{
|
||||
return parent::map($position) + $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function unmap($position)
|
||||
{
|
||||
return parent::unmap($position) - $this->offset;
|
||||
}
|
||||
}
|
29
src/PhpZip/Mapper/PositionMapper.php
Normal file
29
src/PhpZip/Mapper/PositionMapper.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace PhpZip\Mapper;
|
||||
|
||||
/**
|
||||
* Maps a given position.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class PositionMapper
|
||||
{
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function map($position)
|
||||
{
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return int
|
||||
*/
|
||||
public function unmap($position)
|
||||
{
|
||||
return $position;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
330
src/PhpZip/Model/Entry/ZipReadEntry.php
Normal file
330
src/PhpZip/Model/Entry/ZipReadEntry.php
Normal file
@@ -0,0 +1,330 @@
|
||||
<?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 = '';
|
||||
if ($this->getCompressedSize() > 0) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
452
src/PhpZip/Model/ZipEntry.php
Normal file
452
src/PhpZip/Model/ZipEntry.php
Normal file
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraField;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* ZIP file entry.
|
||||
*
|
||||
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
interface ZipEntry
|
||||
{
|
||||
// Bit masks for initialized fields.
|
||||
const BIT_PLATFORM = 1,
|
||||
BIT_METHOD = 2 /* 1 << 1 */,
|
||||
BIT_CRC = 2 /* 1 << 2 */,
|
||||
BIT_DATE_TIME = 64 /* 1 << 6 */,
|
||||
BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/
|
||||
;
|
||||
|
||||
/** The unknown value for numeric properties. */
|
||||
const UNKNOWN = -1;
|
||||
|
||||
/** Windows platform. */
|
||||
const PLATFORM_FAT = 0;
|
||||
/** Unix platform. */
|
||||
const PLATFORM_UNIX = 3;
|
||||
/** MacOS platform */
|
||||
const PLATFORM_OS_X = 19;
|
||||
|
||||
/**
|
||||
* Pseudo compression method for WinZip AES encrypted entries.
|
||||
* Require php extension openssl or mcrypt.
|
||||
*/
|
||||
const METHOD_WINZIP_AES = 99;
|
||||
|
||||
/** General Purpose Bit Flag mask for encrypted data. */
|
||||
const GPBF_ENCRYPTED = 1;
|
||||
/** General Purpose Bit Flag mask for data descriptor. */
|
||||
const GPBF_DATA_DESCRIPTOR = 8; // 1 << 3;
|
||||
/** General Purpose Bit Flag mask for UTF-8. */
|
||||
const GPBF_UTF8 = 2048;
|
||||
|
||||
/** Local File Header signature. */
|
||||
const LOCAL_FILE_HEADER_SIG = 0x04034B50;
|
||||
/** Data Descriptor signature. */
|
||||
const DATA_DESCRIPTOR_SIG = 0x08074B50;
|
||||
/**
|
||||
* The minimum length of the Local File Header record.
|
||||
*
|
||||
* local file header signature 4
|
||||
* version needed to extract 2
|
||||
* general purpose bit flag 2
|
||||
* compression method 2
|
||||
* last mod file time 2
|
||||
* last mod file date 2
|
||||
* crc-32 4
|
||||
* compressed size 4
|
||||
* uncompressed size 4
|
||||
* file name length 2
|
||||
* extra field length 2
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_MIN_LEN = 30;
|
||||
/**
|
||||
* Local File Header signature 4
|
||||
* Version Needed To Extract 2
|
||||
* General Purpose Bit Flags 2
|
||||
* Compression Method 2
|
||||
* Last Mod File Time 2
|
||||
* Last Mod File Date 2
|
||||
* CRC-32 4
|
||||
* Compressed Size 4
|
||||
* Uncompressed Size 4
|
||||
*/
|
||||
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26; // 1 << 11;
|
||||
|
||||
|
||||
/**
|
||||
* @return CentralDirectory
|
||||
*/
|
||||
public function getCentralDirectory();
|
||||
|
||||
/**
|
||||
* @param CentralDirectory $centralDirectory
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setCentralDirectory(CentralDirectory $centralDirectory);
|
||||
|
||||
/**
|
||||
* Returns the ZIP entry name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Set entry name.
|
||||
*
|
||||
* @param string $name New entry name
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* @return int Get platform
|
||||
*/
|
||||
public function getPlatform();
|
||||
|
||||
/**
|
||||
* Set platform
|
||||
*
|
||||
* @param int $platform
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setPlatform($platform);
|
||||
|
||||
/**
|
||||
* Version needed to extract.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionNeededToExtract();
|
||||
|
||||
/**
|
||||
* Set version needed to extract.
|
||||
*
|
||||
* @param int $version
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setVersionNeededToExtract($version);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isZip64ExtensionsRequired();
|
||||
|
||||
/**
|
||||
* Returns the compressed size of this entry.
|
||||
*
|
||||
* @see int
|
||||
*/
|
||||
public function getCompressedSize();
|
||||
|
||||
/**
|
||||
* Sets the compressed size of this entry.
|
||||
*
|
||||
* @param int $compressedSize The Compressed Size.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCompressedSize($compressedSize);
|
||||
|
||||
/**
|
||||
* Returns the uncompressed size of this entry.
|
||||
*
|
||||
* @see ZipEntry::setCompressedSize
|
||||
*/
|
||||
public function getSize();
|
||||
|
||||
/**
|
||||
* Sets the uncompressed size of this entry.
|
||||
*
|
||||
* @param int $size The (Uncompressed) Size.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setSize($size);
|
||||
|
||||
/**
|
||||
* Return relative Offset Of Local File Header.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset();
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setOffset($offset);
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry represents a directory entry
|
||||
* (i.e. end with '/').
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory();
|
||||
|
||||
/**
|
||||
* Returns the General Purpose Bit Flags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlags();
|
||||
|
||||
/**
|
||||
* Sets the General Purpose Bit Flags.
|
||||
*
|
||||
* @var int general
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setGeneralPurposeBitFlags($general);
|
||||
|
||||
/**
|
||||
* Returns the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @return bool
|
||||
*/
|
||||
public function getGeneralPurposeBitFlag($mask);
|
||||
|
||||
/**
|
||||
* Sets the indexed General Purpose Bit Flag.
|
||||
*
|
||||
* @param int $mask
|
||||
* @param bool $bit
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setGeneralPurposeBitFlag($mask, $bit);
|
||||
|
||||
/**
|
||||
* Returns true if and only if this ZIP entry is encrypted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted();
|
||||
|
||||
/**
|
||||
* Sets the encryption property to false and removes any other
|
||||
* encryption artifacts.
|
||||
*
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function clearEncryption();
|
||||
|
||||
/**
|
||||
* Sets the encryption flag for this ZIP entry.
|
||||
*
|
||||
* @param bool $encrypted
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setEncrypted($encrypted);
|
||||
|
||||
/**
|
||||
* Returns the compression method for this entry.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMethod();
|
||||
|
||||
/**
|
||||
* Sets the compression method for this entry.
|
||||
*
|
||||
* @param int $method
|
||||
* @return ZipEntry
|
||||
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
|
||||
*/
|
||||
public function setMethod($method);
|
||||
|
||||
/**
|
||||
* Get Unix Timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTime();
|
||||
|
||||
/**
|
||||
* Set time from unix timestamp.
|
||||
*
|
||||
* @param int $unixTimestamp
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setTime($unixTimestamp);
|
||||
|
||||
/**
|
||||
* Get Dos Time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDosTime();
|
||||
|
||||
/**
|
||||
* Set Dos Time
|
||||
* @param int $dosTime
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setDosTime($dosTime);
|
||||
|
||||
/**
|
||||
* Returns the external file attributes.
|
||||
*
|
||||
* @return int The external file attributes.
|
||||
*/
|
||||
public function getExternalAttributes();
|
||||
|
||||
/**
|
||||
* Sets the external file attributes.
|
||||
*
|
||||
* @param int $externalAttributes the external file attributes.
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes);
|
||||
|
||||
/**
|
||||
* Return extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
public function getExtraField($headerId);
|
||||
|
||||
/**
|
||||
* Add extra field.
|
||||
*
|
||||
* @param ExtraField $field
|
||||
* @return ExtraField
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function addExtraField($field);
|
||||
|
||||
/**
|
||||
* Return exists extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExtraField($headerId);
|
||||
|
||||
/**
|
||||
* Remove extra field from header id.
|
||||
*
|
||||
* @param int $headerId
|
||||
* @return ExtraField|null
|
||||
*/
|
||||
public function removeExtraField($headerId);
|
||||
|
||||
/**
|
||||
* Returns a protective copy of the serialized Extra Fields.
|
||||
*
|
||||
* @return string A new byte array holding the serialized Extra Fields.
|
||||
* null is never returned.
|
||||
*/
|
||||
public function getExtra();
|
||||
|
||||
/**
|
||||
* Sets the serialized Extra Fields by making a protective copy.
|
||||
* Note that this method parses the serialized Extra Fields according to
|
||||
* the ZIP File Format Specification and limits its size to 64 KB.
|
||||
* Therefore, this property cannot not be used to hold arbitrary
|
||||
* (application) data.
|
||||
* Consider storing such data in a separate entry instead.
|
||||
*
|
||||
* @param string $data The byte array holding the serialized Extra Fields.
|
||||
* @throws ZipException if the serialized Extra Fields exceed 64 KB
|
||||
* @return ZipEntry
|
||||
* or do not conform to the ZIP File Format Specification
|
||||
*/
|
||||
public function setExtra($data);
|
||||
|
||||
/**
|
||||
* Returns comment entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment();
|
||||
|
||||
/**
|
||||
* Set entry comment.
|
||||
*
|
||||
* @param $comment
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setComment($comment);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDataDescriptorRequired();
|
||||
|
||||
/**
|
||||
* Return crc32 content or 0 for WinZip AES v2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc();
|
||||
|
||||
/**
|
||||
* Set crc32 content.
|
||||
*
|
||||
* @param int $crc
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCrc($crc);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword();
|
||||
|
||||
/**
|
||||
* Set password and encryption method from entry
|
||||
*
|
||||
* @param string $password
|
||||
* @param null|int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
*/
|
||||
public function setPassword($password, $encryptionMethod = null);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getEncryptionMethod();
|
||||
|
||||
/**
|
||||
* Set encryption method
|
||||
*
|
||||
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
|
||||
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
|
||||
*
|
||||
* @param int $encryptionMethod
|
||||
* @return ZipEntry
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setEncryptionMethod($encryptionMethod);
|
||||
|
||||
/**
|
||||
* Returns an string content of the given entry.
|
||||
*
|
||||
* @return null|string
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function getEntryContent();
|
||||
|
||||
/**
|
||||
* Write local file header, encryption header, file data and data descriptor to output stream.
|
||||
*
|
||||
* @param resource $outputStream
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeEntry($outputStream);
|
||||
}
|
509
src/PhpZip/Model/ZipInfo.php
Normal file
509
src/PhpZip/Model/ZipInfo.php
Normal file
@@ -0,0 +1,509 @@
|
||||
<?php
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Extra\NtfsExtraField;
|
||||
use PhpZip\Extra\WinZipAesEntryExtraField;
|
||||
use PhpZip\Util\FilesUtil;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Zip info
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class ZipInfo
|
||||
{
|
||||
// made by constants
|
||||
const MADE_BY_MS_DOS = 0;
|
||||
const MADE_BY_AMIGA = 1;
|
||||
const MADE_BY_OPEN_VMS = 2;
|
||||
const MADE_BY_UNIX = 3;
|
||||
const MADE_BY_VM_CMS = 4;
|
||||
const MADE_BY_ATARI = 5;
|
||||
const MADE_BY_OS_2 = 6;
|
||||
const MADE_BY_MACINTOSH = 7;
|
||||
const MADE_BY_Z_SYSTEM = 8;
|
||||
const MADE_BY_CP_M = 9;
|
||||
const MADE_BY_WINDOWS_NTFS = 10;
|
||||
const MADE_BY_MVS = 11;
|
||||
const MADE_BY_VSE = 12;
|
||||
const MADE_BY_ACORN_RISC = 13;
|
||||
const MADE_BY_VFAT = 14;
|
||||
const MADE_BY_ALTERNATE_MVS = 15;
|
||||
const MADE_BY_BEOS = 16;
|
||||
const MADE_BY_TANDEM = 17;
|
||||
const MADE_BY_OS_400 = 18;
|
||||
const MADE_BY_OS_X = 19;
|
||||
const MADE_BY_UNKNOWN = 20;
|
||||
|
||||
const UNX_IFMT = 0170000; /* Unix file type mask */
|
||||
const UNX_IFREG = 0100000; /* Unix regular file */
|
||||
const UNX_IFSOCK = 0140000; /* Unix socket (BSD, not SysV or Amiga) */
|
||||
const UNX_IFLNK = 0120000; /* Unix symbolic link (not SysV, Amiga) */
|
||||
const UNX_IFBLK = 0060000; /* Unix block special (not Amiga) */
|
||||
const UNX_IFDIR = 0040000; /* Unix directory */
|
||||
const UNX_IFCHR = 0020000; /* Unix character special (not Amiga) */
|
||||
const UNX_IFIFO = 0010000; /* Unix fifo (BCC, not MSC or Amiga) */
|
||||
const UNX_ISUID = 04000; /* Unix set user id on execution */
|
||||
const UNX_ISGID = 02000; /* Unix set group id on execution */
|
||||
const UNX_ISVTX = 01000; /* Unix directory permissions control */
|
||||
const UNX_ENFMT = self::UNX_ISGID; /* Unix record locking enforcement flag */
|
||||
const UNX_IRWXU = 00700; /* Unix read, write, execute: owner */
|
||||
const UNX_IRUSR = 00400; /* Unix read permission: owner */
|
||||
const UNX_IWUSR = 00200; /* Unix write permission: owner */
|
||||
const UNX_IXUSR = 00100; /* Unix execute permission: owner */
|
||||
const UNX_IRWXG = 00070; /* Unix read, write, execute: group */
|
||||
const UNX_IRGRP = 00040; /* Unix read permission: group */
|
||||
const UNX_IWGRP = 00020; /* Unix write permission: group */
|
||||
const UNX_IXGRP = 00010; /* Unix execute permission: group */
|
||||
const UNX_IRWXO = 00007; /* Unix read, write, execute: other */
|
||||
const UNX_IROTH = 00004; /* Unix read permission: other */
|
||||
const UNX_IWOTH = 00002; /* Unix write permission: other */
|
||||
const UNX_IXOTH = 00001; /* Unix execute permission: other */
|
||||
|
||||
private static $valuesMadeBy = [
|
||||
self::MADE_BY_MS_DOS => 'FAT',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
self::MADE_BY_OPEN_VMS => 'OpenVMS',
|
||||
self::MADE_BY_UNIX => 'UNIX',
|
||||
self::MADE_BY_VM_CMS => 'VM/CMS',
|
||||
self::MADE_BY_ATARI => 'Atari ST',
|
||||
self::MADE_BY_OS_2 => 'OS/2 H.P.F.S.',
|
||||
self::MADE_BY_MACINTOSH => 'Macintosh',
|
||||
self::MADE_BY_Z_SYSTEM => 'Z-System',
|
||||
self::MADE_BY_CP_M => 'CP/M',
|
||||
self::MADE_BY_WINDOWS_NTFS => 'Windows NTFS',
|
||||
self::MADE_BY_MVS => 'MVS (OS/390 - Z/OS)',
|
||||
self::MADE_BY_VSE => 'VSE',
|
||||
self::MADE_BY_ACORN_RISC => 'Acorn Risc',
|
||||
self::MADE_BY_VFAT => 'VFAT',
|
||||
self::MADE_BY_ALTERNATE_MVS => 'Alternate MVS',
|
||||
self::MADE_BY_BEOS => 'BeOS',
|
||||
self::MADE_BY_TANDEM => 'Tandem',
|
||||
self::MADE_BY_OS_400 => 'OS/400',
|
||||
self::MADE_BY_OS_X => 'Mac OS X',
|
||||
];
|
||||
|
||||
private static $valuesCompressionMethod = [
|
||||
ZipFile::METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
4 => 'reduce level 3',
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
ZipFile::METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
12 => 'bzip2',
|
||||
13 => 'reserved by PKWARE',
|
||||
14 => 'LZMA (EFS)',
|
||||
15 => 'reserved by PKWARE',
|
||||
16 => 'reserved by PKWARE',
|
||||
17 => 'reserved by PKWARE',
|
||||
18 => 'IBM TERSE',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
97 => 'WavPack',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
ZipEntry::METHOD_WINZIP_AES => 'WinZip AES',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $folder;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $compressedSize;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $mtime;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $ctime;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $atime;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $encrypted;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $comment;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $crc;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $platform;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* ZipInfo constructor.
|
||||
*
|
||||
* @param ZipEntry $entry
|
||||
*/
|
||||
public function __construct(ZipEntry $entry)
|
||||
{
|
||||
$mtime = $entry->getTime();
|
||||
$atime = null;
|
||||
$ctime = null;
|
||||
|
||||
$field = $entry->getExtraField(NtfsExtraField::getHeaderId());
|
||||
if (null !== $field && $field instanceof NtfsExtraField) {
|
||||
/**
|
||||
* @var NtfsExtraField $field
|
||||
*/
|
||||
$atime = $field->getAtime();
|
||||
$ctime = $field->getCtime();
|
||||
}
|
||||
|
||||
$this->path = $entry->getName();
|
||||
$this->folder = $entry->isDirectory();
|
||||
$this->size = $entry->getSize();
|
||||
$this->compressedSize = $entry->getCompressedSize();
|
||||
$this->mtime = $mtime;
|
||||
$this->ctime = $ctime;
|
||||
$this->atime = $atime;
|
||||
$this->encrypted = $entry->isEncrypted();
|
||||
$this->comment = $entry->getComment();
|
||||
$this->crc = $entry->getCrc();
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
*/
|
||||
public static function getMethodName(ZipEntry $entry)
|
||||
{
|
||||
$return = '';
|
||||
if ($entry->isEncrypted()) {
|
||||
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
|
||||
$field = $entry->getExtraField(WinZipAesEntryExtraField::getHeaderId());
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
if (null !== $field) {
|
||||
/**
|
||||
* @var WinZipAesEntryExtraField $field
|
||||
*/
|
||||
$return .= '-' . $field->getKeyStrength();
|
||||
if (isset(self::$valuesCompressionMethod[$field->getMethod()])) {
|
||||
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$field->getMethod()]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$return .= 'ZipCrypto';
|
||||
if (isset(self::$valuesCompressionMethod[$entry->getMethod()])) {
|
||||
$return .= ' ' . ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
}
|
||||
}
|
||||
} elseif (isset(self::$valuesCompressionMethod[$entry->getMethod()])) {
|
||||
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
|
||||
} else {
|
||||
$return = 'unknown';
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipEntry $entry
|
||||
* @return string
|
||||
*/
|
||||
public static function getPlatformName(ZipEntry $entry)
|
||||
{
|
||||
if (isset(self::$valuesMadeBy[$entry->getPlatform()])) {
|
||||
return self::$valuesMadeBy[$entry->getPlatform()];
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'path' => $this->getPath(),
|
||||
'folder' => $this->isFolder(),
|
||||
'size' => $this->getSize(),
|
||||
'compressed_size' => $this->getCompressedSize(),
|
||||
'modified' => $this->getMtime(),
|
||||
'created' => $this->getCtime(),
|
||||
'accessed' => $this->getAtime(),
|
||||
'attributes' => $this->getAttributes(),
|
||||
'encrypted' => $this->isEncrypted(),
|
||||
'comment' => $this->getComment(),
|
||||
'crc' => $this->getCrc(),
|
||||
'method' => $this->getMethod(),
|
||||
'platform' => $this->getPlatform(),
|
||||
'version' => $this->getVersion()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFolder()
|
||||
{
|
||||
return $this->folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressedSize()
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCtime()
|
||||
{
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getAtime()
|
||||
{
|
||||
return $this->atime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc()
|
||||
{
|
||||
return $this->crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function __toString()
|
||||
{
|
||||
return 'ZipInfo {'
|
||||
. 'Path="' . $this->getPath() . '", '
|
||||
. ($this->isFolder() ? 'Folder, ' : '')
|
||||
. 'Size=' . FilesUtil::humanSize($this->getSize())
|
||||
. ', Compressed size=' . FilesUtil::humanSize($this->getCompressedSize())
|
||||
. ', Modified time=' . date(DATE_W3C, $this->getMtime()) . ', '
|
||||
. ($this->getCtime() !== null ? 'Created time=' . date(DATE_W3C, $this->getCtime()) . ', ' : '')
|
||||
. ($this->getAtime() !== null ? 'Accessed time=' . date(DATE_W3C, $this->getAtime()) . ', ' : '')
|
||||
. ($this->isEncrypted() ? 'Encrypted, ' : '')
|
||||
. (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '')
|
||||
. (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '')
|
||||
. 'Method="' . $this->getMethod() . '", '
|
||||
. 'Attributes="' . $this->getAttributes() . '", '
|
||||
. 'Platform="' . $this->getPlatform() . '", '
|
||||
. 'Version=' . $this->getVersion()
|
||||
. '}';
|
||||
}
|
||||
|
||||
|
||||
}
|
33
src/PhpZip/Util/CryptoUtil.php
Normal file
33
src/PhpZip/Util/CryptoUtil.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Crypto Utils
|
||||
*/
|
||||
class CryptoUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns random bytes.
|
||||
*
|
||||
* @param int $length
|
||||
* @return string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static final function randomBytes($length)
|
||||
{
|
||||
$length = (int)$length;
|
||||
if (function_exists('random_bytes')) {
|
||||
return random_bytes($length);
|
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return openssl_random_pseudo_bytes($length);
|
||||
} elseif (function_exists('mcrypt_create_iv')) {
|
||||
return mcrypt_create_iv($length);
|
||||
} else {
|
||||
throw new RuntimeException('Extension openssl or mcrypt not loaded');
|
||||
}
|
||||
}
|
||||
}
|
77
src/PhpZip/Util/DateTimeConverter.php
Normal file
77
src/PhpZip/Util/DateTimeConverter.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Convert unix timestamp values to DOS date/time values and vice versa.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class DateTimeConverter
|
||||
{
|
||||
/**
|
||||
* Smallest supported DOS date/time value in a ZIP file,
|
||||
* which is January 1st, 1980 AD 00:00:00 local time.
|
||||
*/
|
||||
const MIN_DOS_TIME = 0x210000; // (1 << 21) | (1 << 16)
|
||||
|
||||
/**
|
||||
* Largest supported DOS date/time value in a ZIP file,
|
||||
* which is December 31st, 2107 AD 23:59:58 local time.
|
||||
*/
|
||||
const MAX_DOS_TIME = 0xff9fbf7d; // ((2107 - 1980) << 25) | (12 << 21) | (31 << 16) | (23 << 11) | (59 << 5) | (58 >> 1);
|
||||
|
||||
/**
|
||||
* Convert a 32 bit integer DOS date/time value to a UNIX timestamp value.
|
||||
*
|
||||
* @param int $dosTime Dos date/time
|
||||
* @return int Unix timestamp
|
||||
*/
|
||||
public static function toUnixTimestamp($dosTime)
|
||||
{
|
||||
if (self::MIN_DOS_TIME > $dosTime) {
|
||||
$dosTime = self::MIN_DOS_TIME;
|
||||
} elseif (self::MAX_DOS_TIME < $dosTime) {
|
||||
$dosTime = self::MAX_DOS_TIME;
|
||||
}
|
||||
|
||||
return mktime(
|
||||
($dosTime >> 11) & 0x1f, // hour
|
||||
($dosTime >> 5) & 0x3f, // minute
|
||||
2 * ($dosTime & 0x1f), // second
|
||||
($dosTime >> 21) & 0x0f, // month
|
||||
($dosTime >> 16) & 0x1f, // day
|
||||
1980 + (($dosTime >> 25) & 0x7f) // year
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UNIX timestamp value to a DOS date/time value.
|
||||
*
|
||||
* @param int $unixTimestamp The number of seconds since midnight, January 1st,
|
||||
* 1970 AD UTC.
|
||||
* @return int A DOS date/time value reflecting the local time zone and
|
||||
* rounded down to even seconds
|
||||
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME.
|
||||
* @throws ZipException If unix timestamp is negative.
|
||||
*/
|
||||
public static function toDosTime($unixTimestamp)
|
||||
{
|
||||
if (0 > $unixTimestamp) {
|
||||
throw new ZipException("Negative unix timestamp: " . $unixTimestamp);
|
||||
}
|
||||
|
||||
$date = getdate($unixTimestamp);
|
||||
|
||||
if ($date['year'] < 1980) {
|
||||
return self::MIN_DOS_TIME;
|
||||
}
|
||||
|
||||
$date['year'] -= 1980;
|
||||
return ($date['year'] << 25 | $date['mon'] << 21 |
|
||||
$date['mday'] << 16 | $date['hours'] << 11 |
|
||||
$date['minutes'] << 5 | $date['seconds'] >> 1);
|
||||
}
|
||||
}
|
222
src/PhpZip/Util/FilesUtil.php
Normal file
222
src/PhpZip/Util/FilesUtil.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
|
||||
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Files util.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class FilesUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Is empty directory
|
||||
*
|
||||
* @param string $dir Directory
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEmptyDir($dir)
|
||||
{
|
||||
if (!is_readable($dir)) {
|
||||
return false;
|
||||
}
|
||||
return count(scandir($dir)) === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove recursive directory.
|
||||
*
|
||||
* @param string $dir Directory path.
|
||||
*/
|
||||
public static function removeDir($dir)
|
||||
{
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($files as $fileInfo) {
|
||||
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$function($fileInfo->getRealPath());
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert glob pattern to regex pattern.
|
||||
*
|
||||
* @param string $globPattern
|
||||
* @return string
|
||||
*/
|
||||
public static function convertGlobToRegEx($globPattern)
|
||||
{
|
||||
// Remove beginning and ending * globs because they're useless
|
||||
$globPattern = trim($globPattern, '*');
|
||||
$escaping = false;
|
||||
$inCurrent = 0;
|
||||
$chars = str_split($globPattern);
|
||||
$regexPattern = '';
|
||||
foreach ($chars AS $currentChar) {
|
||||
switch ($currentChar) {
|
||||
case '*':
|
||||
$regexPattern .= ($escaping ? "\\*" : '.*');
|
||||
$escaping = false;
|
||||
break;
|
||||
case '?':
|
||||
$regexPattern .= ($escaping ? "\\?" : '.');
|
||||
$escaping = false;
|
||||
break;
|
||||
case '.':
|
||||
case '(':
|
||||
case ')':
|
||||
case '+':
|
||||
case '|':
|
||||
case '^':
|
||||
case '$':
|
||||
case '@':
|
||||
case '%':
|
||||
$regexPattern .= '\\' . $currentChar;
|
||||
$escaping = false;
|
||||
break;
|
||||
case '\\':
|
||||
if ($escaping) {
|
||||
$regexPattern .= "\\\\";
|
||||
$escaping = false;
|
||||
} else {
|
||||
$escaping = true;
|
||||
}
|
||||
break;
|
||||
case '{':
|
||||
if ($escaping) {
|
||||
$regexPattern .= "\\{";
|
||||
} else {
|
||||
$regexPattern = '(';
|
||||
$inCurrent++;
|
||||
}
|
||||
$escaping = false;
|
||||
break;
|
||||
case '}':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= ')';
|
||||
$inCurrent--;
|
||||
} else if ($escaping)
|
||||
$regexPattern = "\\}";
|
||||
else
|
||||
$regexPattern = "}";
|
||||
$escaping = false;
|
||||
break;
|
||||
case ',':
|
||||
if ($inCurrent > 0 && !$escaping) {
|
||||
$regexPattern .= '|';
|
||||
} else if ($escaping)
|
||||
$regexPattern .= "\\,";
|
||||
else
|
||||
$regexPattern = ",";
|
||||
break;
|
||||
default:
|
||||
$escaping = false;
|
||||
$regexPattern .= $currentChar;
|
||||
}
|
||||
}
|
||||
return $regexPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files.
|
||||
*
|
||||
* @param string $inputDir
|
||||
* @param bool $recursive
|
||||
* @param array $ignoreFiles
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
|
||||
{
|
||||
$directoryIterator = $recursive ?
|
||||
new \RecursiveDirectoryIterator($inputDir) :
|
||||
new \DirectoryIterator($inputDir);
|
||||
|
||||
if (!empty($ignoreFiles)) {
|
||||
$directoryIterator = $recursive ?
|
||||
new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles) :
|
||||
new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
|
||||
}
|
||||
|
||||
$iterator = $recursive ?
|
||||
new \RecursiveIteratorIterator($directoryIterator) :
|
||||
new \IteratorIterator($directoryIterator);
|
||||
|
||||
$fileList = [];
|
||||
foreach ($iterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files from glob pattern.
|
||||
*
|
||||
* @param string $globPattern
|
||||
* @param int $flags
|
||||
* @param bool $recursive
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
|
||||
{
|
||||
$flags = (int)$flags;
|
||||
$recursive = (bool)$recursive;
|
||||
$files = glob($globPattern, $flags);
|
||||
if (!$recursive) {
|
||||
return $files;
|
||||
}
|
||||
foreach (glob(dirname($globPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
|
||||
$files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive));
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search files from regex pattern.
|
||||
*
|
||||
* @param string $folder
|
||||
* @param string $pattern
|
||||
* @param bool $recursive
|
||||
* @return array Searched file list
|
||||
*/
|
||||
public static function regexFileSearch($folder, $pattern, $recursive = true)
|
||||
{
|
||||
$directoryIterator = $recursive ? new \RecursiveDirectoryIterator($folder) : new \DirectoryIterator($folder);
|
||||
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator($directoryIterator);
|
||||
$regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
|
||||
$fileList = [];
|
||||
foreach ($regexIterator as $file) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$fileList[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to human size.
|
||||
*
|
||||
* @param int $size Size bytes
|
||||
* @param string|null $unit Unit support 'GB', 'MB', 'KB'
|
||||
* @return string
|
||||
*/
|
||||
public static function humanSize($size, $unit = null)
|
||||
{
|
||||
if (($unit === null && $size >= 1 << 30) || $unit === "GB")
|
||||
return number_format($size / (1 << 30), 2) . "GB";
|
||||
if (($unit === null && $size >= 1 << 20) || $unit === "MB")
|
||||
return number_format($size / (1 << 20), 2) . "MB";
|
||||
if (($unit === null && $size >= 1 << 10) || $unit === "KB")
|
||||
return number_format($size / (1 << 10), 2) . "KB";
|
||||
return number_format($size) . " bytes";
|
||||
}
|
||||
}
|
60
src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php
Normal file
60
src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Iterator for ignore files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class IgnoreFilesFilterIterator extends \FilterIterator
|
||||
{
|
||||
/**
|
||||
* Ignore list files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignoreFiles = ['..'];
|
||||
|
||||
/**
|
||||
* @param \Iterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\Iterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $fileInfo
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handler filename
|
||||
if (StringUtil::endsWith($pathname, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace PhpZip\Util\Iterator;
|
||||
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* Recursive iterator for ignore files.
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
|
||||
/**
|
||||
* Ignore list files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignoreFiles = ['..'];
|
||||
|
||||
/**
|
||||
* @param \RecursiveIterator $iterator
|
||||
* @param array $ignoreFiles
|
||||
*/
|
||||
public function __construct(\RecursiveIterator $iterator, array $ignoreFiles)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
/**
|
||||
* @var \SplFileInfo $fileInfo
|
||||
*/
|
||||
$fileInfo = $this->current();
|
||||
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
|
||||
foreach ($this->ignoreFiles as $ignoreFile) {
|
||||
// handler dir and sub dir
|
||||
if ($fileInfo->isDir()
|
||||
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
|
||||
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handler filename
|
||||
if (StringUtil::endsWith($pathname, $ignoreFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IgnoreFilesRecursiveFilterIterator
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
|
||||
}
|
||||
}
|
48
src/PhpZip/Util/PackUtil.php
Normal file
48
src/PhpZip/Util/PackUtil.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace PhpZip\Util;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
/**
|
||||
* Pack util
|
||||
*
|
||||
* @author Ne-Lexa alexey@nelexa.ru
|
||||
* @license MIT
|
||||
*/
|
||||
class PackUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int|string $longValue
|
||||
* @return string
|
||||
*/
|
||||
public static function packLongLE($longValue)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.6.3') >= 0) {
|
||||
return pack("P", $longValue);
|
||||
}
|
||||
|
||||
$left = 0xffffffff00000000;
|
||||
$right = 0x00000000ffffffff;
|
||||
|
||||
$r = ($longValue & $left) >> 32;
|
||||
$l = $longValue & $right;
|
||||
|
||||
return pack('VV', $l, $r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $value
|
||||
* @return int
|
||||
* @throws ZipException
|
||||
*/
|
||||
public static function unpackLongLE($value)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.6.3') >= 0) {
|
||||
return current(unpack('P', $value));
|
||||
}
|
||||
$unpack = unpack('Va/Vb', $value);
|
||||
return $unpack['a'] + ($unpack['b'] << 32);
|
||||
}
|
||||
|
||||
}
|
30
src/PhpZip/Util/StringUtil.php
Normal file
30
src/PhpZip/Util/StringUtil.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace PhpZip\Util;
|
||||
|
||||
/**
|
||||
* String Util
|
||||
*/
|
||||
class StringUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0
|
||||
&& strpos($haystack, $needle, $temp) !== false);
|
||||
}
|
||||
}
|
1260
src/PhpZip/ZipFile.php
Normal file
1260
src/PhpZip/ZipFile.php
Normal file
File diff suppressed because it is too large
Load Diff
905
src/ZipEntry.php
905
src/ZipEntry.php
@@ -1,905 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
use Nelexa\Buffer\Buffer;
|
||||
use Nelexa\Buffer\StringBuffer;
|
||||
|
||||
class ZipEntry
|
||||
{
|
||||
// made by constants
|
||||
const MADE_BY_MS_DOS = 0;
|
||||
const MADE_BY_AMIGA = 1;
|
||||
const MADE_BY_OPEN_VMS = 2;
|
||||
const MADE_BY_UNIX = 3;
|
||||
const MADE_BY_VM_CMS = 4;
|
||||
const MADE_BY_ATARI = 5;
|
||||
const MADE_BY_OS_2 = 6;
|
||||
const MADE_BY_MACINTOSH = 7;
|
||||
const MADE_BY_Z_SYSTEM = 8;
|
||||
const MADE_BY_CP_M = 9;
|
||||
const MADE_BY_WINDOWS_NTFS = 10;
|
||||
const MADE_BY_MVS = 11;
|
||||
const MADE_BY_VSE = 12;
|
||||
const MADE_BY_ACORN_RISC = 13;
|
||||
const MADE_BY_VFAT = 14;
|
||||
const MADE_BY_ALTERNATE_MVS = 15;
|
||||
const MADE_BY_BEOS = 16;
|
||||
const MADE_BY_TANDEM = 17;
|
||||
const MADE_BY_OS_400 = 18;
|
||||
const MADE_BY_OS_X = 19;
|
||||
const MADE_BY_UNKNOWN = 20;
|
||||
|
||||
private static $valuesMadeBy = array(
|
||||
self::MADE_BY_MS_DOS => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)',
|
||||
self::MADE_BY_AMIGA => 'Amiga',
|
||||
self::MADE_BY_OPEN_VMS => 'OpenVMS',
|
||||
self::MADE_BY_UNIX => 'UNIX',
|
||||
self::MADE_BY_VM_CMS => 'VM/CMS',
|
||||
self::MADE_BY_ATARI => 'Atari ST',
|
||||
self::MADE_BY_OS_2 => 'OS/2 H.P.F.S.',
|
||||
self::MADE_BY_MACINTOSH => 'Macintosh',
|
||||
self::MADE_BY_Z_SYSTEM => 'Z-System',
|
||||
self::MADE_BY_CP_M => 'CP/M',
|
||||
self::MADE_BY_WINDOWS_NTFS => 'Windows NTFS',
|
||||
self::MADE_BY_MVS => 'MVS (OS/390 - Z/OS)',
|
||||
self::MADE_BY_VSE => 'VSE',
|
||||
self::MADE_BY_ACORN_RISC => 'Acorn Risc',
|
||||
self::MADE_BY_VFAT => 'VFAT',
|
||||
self::MADE_BY_ALTERNATE_MVS => 'alternate MVS',
|
||||
self::MADE_BY_BEOS => 'BeOS',
|
||||
self::MADE_BY_TANDEM => 'Tandem',
|
||||
self::MADE_BY_OS_400 => 'OS/400',
|
||||
self::MADE_BY_OS_X => 'OS X (Darwin)',
|
||||
);
|
||||
|
||||
// constants version by extract
|
||||
const EXTRACT_VERSION_10 = 10;
|
||||
const EXTRACT_VERSION_11 = 11;
|
||||
const EXTRACT_VERSION_20 = 20;
|
||||
//1.0 - Default value
|
||||
//1.1 - File is a volume label
|
||||
//2.0 - File is a folder (directory)
|
||||
//2.0 - File is compressed using Deflate compression
|
||||
//2.0 - File is encrypted using traditional PKWARE encryption
|
||||
//2.1 - File is compressed using Deflate64(tm)
|
||||
//2.5 - File is compressed using PKWARE DCL Implode
|
||||
//2.7 - File is a patch data set
|
||||
//4.5 - File uses ZIP64 format extensions
|
||||
//4.6 - File is compressed using BZIP2 compression*
|
||||
//5.0 - File is encrypted using DES
|
||||
//5.0 - File is encrypted using 3DES
|
||||
//5.0 - File is encrypted using original RC2 encryption
|
||||
//5.0 - File is encrypted using RC4 encryption
|
||||
//5.1 - File is encrypted using AES encryption
|
||||
//5.1 - File is encrypted using corrected RC2 encryption**
|
||||
//5.2 - File is encrypted using corrected RC2-64 encryption**
|
||||
//6.1 - File is encrypted using non-OAEP key wrapping***
|
||||
//6.2 - Central directory encryption
|
||||
//6.3 - File is compressed using LZMA
|
||||
//6.3 - File is compressed using PPMd+
|
||||
//6.3 - File is encrypted using Blowfish
|
||||
//6.3 - File is encrypted using Twofish
|
||||
|
||||
const FLAG_ENCRYPTION = 0;
|
||||
const FLAG_DATA_DESCRIPTION = 3;
|
||||
const FLAG_UTF8 = 11;
|
||||
private static $valuesFlag = array(
|
||||
self::FLAG_ENCRYPTION => 'encrypted file', // 1 << 0
|
||||
1 => 'compression option', // 1 << 1
|
||||
2 => 'compression option', // 1 << 2
|
||||
self::FLAG_DATA_DESCRIPTION => 'data descriptor', // 1 << 3
|
||||
4 => 'enhanced deflation', // 1 << 4
|
||||
5 => 'compressed patched data', // 1 << 5
|
||||
6 => 'strong encryption', // 1 << 6
|
||||
7 => 'unused', // 1 << 7
|
||||
8 => 'unused', // 1 << 8
|
||||
9 => 'unused', // 1 << 9
|
||||
10 => 'unused', // 1 << 10
|
||||
self::FLAG_UTF8 => 'language encoding', // 1 << 11
|
||||
12 => 'reserved', // 1 << 12
|
||||
13 => 'mask header values', // 1 << 13
|
||||
14 => 'reserved', // 1 << 14
|
||||
15 => 'reserved', // 1 << 15
|
||||
);
|
||||
|
||||
// compression method constants
|
||||
const COMPRESS_METHOD_STORED = 0;
|
||||
const COMPRESS_METHOD_DEFLATED = 8;
|
||||
const COMPRESS_METHOD_AES = 99;
|
||||
|
||||
private static $valuesCompressionMethod = array(
|
||||
self::COMPRESS_METHOD_STORED => 'no compression',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce level 1',
|
||||
3 => 'reduce level 2',
|
||||
4 => 'reduce level 3',
|
||||
5 => 'reduce level 4',
|
||||
6 => 'implode',
|
||||
7 => 'reserved for Tokenizing compression algorithm',
|
||||
self::COMPRESS_METHOD_DEFLATED => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
|
||||
11 => 'reserved by PKWARE',
|
||||
12 => 'bzip2',
|
||||
13 => 'reserved by PKWARE',
|
||||
14 => 'LZMA (EFS)',
|
||||
15 => 'reserved by PKWARE',
|
||||
16 => 'reserved by PKWARE',
|
||||
17 => 'reserved by PKWARE',
|
||||
18 => 'IBM TERSE',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
97 => 'WavPack',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
self::COMPRESS_METHOD_AES => 'AES Encryption',
|
||||
);
|
||||
|
||||
const INTERNAL_ATTR_DEFAULT = 0;
|
||||
const EXTERNAL_ATTR_DEFAULT = 0;
|
||||
|
||||
/*
|
||||
* Extra field header ID
|
||||
*/
|
||||
const EXTID_ZIP64 = 0x0001; // Zip64
|
||||
const EXTID_NTFS = 0x000a; // NTFS (for storing full file times information)
|
||||
const EXTID_UNIX = 0x000d; // UNIX
|
||||
const EXTID_EXTT = 0x5455; // Info-ZIP Extended Timestamp
|
||||
const EXTID_UNICODE_FILENAME = 0x7075; // for Unicode filenames
|
||||
const EXTID_UNICODE_ = 0x6375; // for Unicode file comments
|
||||
const EXTID_STORING_STRINGS = 0x5A4C; // for storing strings code pages and Unicode filenames using custom Unicode implementation (see Unicode Support: Using Non-English Characters in Filenames, Comments and Passwords).
|
||||
const EXTID_OFFSETS_COMPRESS_DATA = 0x5A4D; // for saving offsets array from seekable compressed data
|
||||
const EXTID_AES_ENCRYPTION = 0x9901; // WinZip AES encryption (http://www.winzip.com/aes_info.htm)
|
||||
|
||||
/**
|
||||
* entry name
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* version made by
|
||||
* @var int
|
||||
*/
|
||||
private $versionMadeBy = self::MADE_BY_WINDOWS_NTFS;
|
||||
/**
|
||||
* version needed to extract
|
||||
* @var int
|
||||
*/
|
||||
private $versionExtract = self::EXTRACT_VERSION_20;
|
||||
/**
|
||||
* general purpose bit flag
|
||||
* @var int
|
||||
*/
|
||||
private $flag = 0;
|
||||
/**
|
||||
* compression method
|
||||
* @var int
|
||||
*/
|
||||
private $compressionMethod = self::COMPRESS_METHOD_DEFLATED;
|
||||
/**
|
||||
* last mod file datetime
|
||||
* @var int Unix timestamp
|
||||
*/
|
||||
private $lastModDateTime;
|
||||
/**
|
||||
* crc-32
|
||||
* @var int
|
||||
*/
|
||||
private $crc32;
|
||||
/**
|
||||
* compressed size
|
||||
* @var int
|
||||
*/
|
||||
private $compressedSize;
|
||||
/**
|
||||
* uncompressed size
|
||||
* @var int
|
||||
*/
|
||||
private $unCompressedSize;
|
||||
/**
|
||||
* disk number start
|
||||
* @var int
|
||||
*/
|
||||
private $diskNumber = 0;
|
||||
/**
|
||||
* internal file attributes
|
||||
* @var int
|
||||
*/
|
||||
private $internalAttributes = self::INTERNAL_ATTR_DEFAULT;
|
||||
/**
|
||||
* external file attributes
|
||||
* @var int
|
||||
*/
|
||||
private $externalAttributes = self::EXTERNAL_ATTR_DEFAULT;
|
||||
/**
|
||||
* relative offset of local header
|
||||
* @var int
|
||||
*/
|
||||
private $offsetOfLocal;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offsetOfCentral;
|
||||
|
||||
/**
|
||||
* optional extra field data for entry
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $extraCentral = "";
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $extraLocal = "";
|
||||
/**
|
||||
* optional comment string for entry
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $comment = "";
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getLengthOfLocal()
|
||||
{
|
||||
return $this->getLengthLocalHeader() + $this->compressedSize + ($this->hasDataDescriptor() ? 12 : 0);
|
||||
}
|
||||
|
||||
public function getLengthLocalHeader()
|
||||
{
|
||||
return 30 + strlen($this->name) + strlen($this->extraLocal);
|
||||
}
|
||||
|
||||
public function getLengthOfCentral()
|
||||
{
|
||||
return 46 + strlen($this->name) + strlen($this->extraCentral) + strlen($this->comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Buffer $buffer
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function readCentralHeader(Buffer $buffer)
|
||||
{
|
||||
$signature = $buffer->getUnsignedInt(); // after offset 4
|
||||
if ($signature !== ZipFile::SIGNATURE_CENTRAL_DIR) {
|
||||
throw new ZipException("Can not read central directory. Bad signature: " . $signature);
|
||||
}
|
||||
$this->versionMadeBy = $buffer->getUnsignedShort(); // after offset 6
|
||||
$this->versionExtract = $buffer->getUnsignedShort(); // after offset 8
|
||||
$this->flag = $buffer->getUnsignedShort(); // after offset 10
|
||||
$this->compressionMethod = $buffer->getUnsignedShort(); // after offset 12
|
||||
$lastModTime = $buffer->getUnsignedShort(); // after offset 14
|
||||
$lastModDate = $buffer->getUnsignedShort(); // after offset 16
|
||||
$this->setLastModifyDosDatetime($lastModTime, $lastModDate);
|
||||
$this->crc32 = $buffer->getUnsignedInt(); // after offset 20
|
||||
$this->compressedSize = $buffer->getUnsignedInt(); // after offset 24
|
||||
$this->unCompressedSize = $buffer->getUnsignedInt(); // after offset 28
|
||||
$fileNameLength = $buffer->getUnsignedShort(); // after offset 30
|
||||
$extraCentralLength = $buffer->getUnsignedShort(); // after offset 32
|
||||
$fileCommentLength = $buffer->getUnsignedShort(); // after offset 34
|
||||
$this->diskNumber = $buffer->getUnsignedShort(); // after offset 36
|
||||
$this->internalAttributes = $buffer->getUnsignedShort(); // after offset 38
|
||||
$this->externalAttributes = $buffer->getUnsignedInt(); // after offset 42
|
||||
$this->offsetOfLocal = $buffer->getUnsignedInt(); // after offset 46
|
||||
$this->name = $buffer->getString($fileNameLength);
|
||||
$this->setExtra($buffer->getString($extraCentralLength));
|
||||
$this->comment = $buffer->getString($fileCommentLength);
|
||||
|
||||
$currentPos = $buffer->position();
|
||||
$buffer->setPosition($this->offsetOfLocal + 28);
|
||||
$extraLocalLength = $buffer->getUnsignedShort();
|
||||
$buffer->skip($fileNameLength);
|
||||
$this->extraLocal = $buffer->getString($extraLocalLength);
|
||||
$buffer->setPosition($currentPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional extra field data for the entry.
|
||||
*
|
||||
* @param string $extra the extra field data bytes
|
||||
* @throws ZipException
|
||||
*/
|
||||
private function setExtra($extra)
|
||||
{
|
||||
if (!empty($extra)) {
|
||||
$len = strlen($extra);
|
||||
if ($len > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$buffer = new StringBuffer($extra);
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
// extra fields are in "HeaderID(2)DataSize(2)Data... format
|
||||
while ($buffer->position() + 4 < $len) {
|
||||
$tag = $buffer->getUnsignedShort();
|
||||
$sz = $buffer->getUnsignedShort();
|
||||
if ($buffer->position() + $sz > $len) // invalid data
|
||||
break;
|
||||
switch ($tag) {
|
||||
case self::EXTID_ZIP64:
|
||||
// not support zip64
|
||||
break;
|
||||
case self::EXTID_NTFS:
|
||||
$buffer->skip(4); // reserved 4 bytes
|
||||
if ($buffer->getUnsignedShort() != 0x0001 || $buffer->getUnsignedShort() != 24)
|
||||
break;
|
||||
// $mtime = winTimeToFileTime($buffer->getLong());
|
||||
// $atime = winTimeToFileTime($buffer->getLong());
|
||||
// $ctime = winTimeToFileTime($buffer->getLong());
|
||||
break;
|
||||
case self::EXTID_EXTT:
|
||||
$flag = $buffer->getUnsignedByte();
|
||||
$sz0 = 1;
|
||||
// The CEN-header extra field contains the modification
|
||||
// time only, or no timestamp at all. 'sz' is used to
|
||||
// flag its presence or absence. But if mtime is present
|
||||
// in LOC it must be present in CEN as well.
|
||||
if (($flag & 0x1) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$mtime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
if (($flag & 0x2) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$atime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
if (($flag & 0x4) != 0 && ($sz0 + 4) <= $sz) {
|
||||
$ctime = $buffer->getUnsignedInt();
|
||||
$sz0 += 4;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->extraCentral = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Buffer
|
||||
*/
|
||||
public function writeLocalHeader()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
$buffer->insertInt(ZipFile::SIGNATURE_LOCAL_HEADER);
|
||||
$buffer->insertShort($this->versionExtract);
|
||||
$buffer->insertShort($this->flag);
|
||||
$buffer->insertShort($this->compressionMethod);
|
||||
$buffer->insertShort($this->getLastModifyDosTime());
|
||||
$buffer->insertShort($this->getLastModifyDosDate());
|
||||
if ($this->hasDataDescriptor()) {
|
||||
$buffer->insertInt(0);
|
||||
$buffer->insertInt(0);
|
||||
$buffer->insertInt(0);
|
||||
} else {
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
}
|
||||
$buffer->insertShort(strlen($this->name));
|
||||
$buffer->insertShort(strlen($this->extraLocal)); // offset 30
|
||||
$buffer->insertString($this->name);
|
||||
$buffer->insertString($this->extraLocal);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bit
|
||||
* @return bool
|
||||
*/
|
||||
public function setFlagBit($bit)
|
||||
{
|
||||
if ($bit < 0 || $bit > 15) {
|
||||
return false;
|
||||
}
|
||||
$this->flag |= 1 << $bit;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bit
|
||||
* @return bool
|
||||
*/
|
||||
public function testFlagBit($bit)
|
||||
{
|
||||
return (($this->flag & (1 << $bit)) !== 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDataDescriptor()
|
||||
{
|
||||
return $this->testFlagBit(self::FLAG_DATA_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->testFlagBit(self::FLAG_ENCRYPTION);
|
||||
}
|
||||
|
||||
public function writeDataDescriptor()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Buffer
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function writeCentralHeader()
|
||||
{
|
||||
$buffer = new StringBuffer();
|
||||
$buffer->setOrder(Buffer::LITTLE_ENDIAN);
|
||||
|
||||
$buffer->insertInt(ZipFile::SIGNATURE_CENTRAL_DIR);
|
||||
$buffer->insertShort($this->versionMadeBy);
|
||||
$buffer->insertShort($this->versionExtract);
|
||||
$buffer->insertShort($this->flag);
|
||||
$buffer->insertShort($this->compressionMethod);
|
||||
$buffer->insertShort($this->getLastModifyDosTime());
|
||||
$buffer->insertShort($this->getLastModifyDosDate());
|
||||
$buffer->insertInt($this->crc32);
|
||||
$buffer->insertInt($this->compressedSize);
|
||||
$buffer->insertInt($this->unCompressedSize);
|
||||
$buffer->insertShort(strlen($this->name));
|
||||
$buffer->insertShort(strlen($this->extraCentral));
|
||||
$buffer->insertShort(strlen($this->comment));
|
||||
$buffer->insertShort($this->diskNumber);
|
||||
$buffer->insertShort($this->internalAttributes);
|
||||
$buffer->insertInt($this->externalAttributes);
|
||||
$buffer->insertInt($this->offsetOfLocal);
|
||||
$buffer->insertString($this->name);
|
||||
$buffer->insertString($this->extraCentral);
|
||||
$buffer->insertString($this->comment);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory()
|
||||
{
|
||||
return $this->name[strlen($this->name) - 1] === "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesMadeBy()
|
||||
{
|
||||
return self::$valuesMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesMadeBy
|
||||
*/
|
||||
public static function setValuesMadeBy($valuesMadeBy)
|
||||
{
|
||||
self::$valuesMadeBy = $valuesMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesFlag()
|
||||
{
|
||||
return self::$valuesFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesFlag
|
||||
*/
|
||||
public static function setValuesFlag($valuesFlag)
|
||||
{
|
||||
self::$valuesFlag = $valuesFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getValuesCompressionMethod()
|
||||
{
|
||||
return self::$valuesCompressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $valuesCompressionMethod
|
||||
*/
|
||||
public static function setValuesCompressionMethod($valuesCompressionMethod)
|
||||
{
|
||||
self::$valuesCompressionMethod = $valuesCompressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
if (strlen($name) > 0xFFFF) {
|
||||
throw new ZipException("entry name too long");
|
||||
}
|
||||
$this->name = $name;
|
||||
$encoding = mb_detect_encoding($this->name, "ASCII, UTF-8", true);
|
||||
if ($encoding === 'UTF-8') {
|
||||
$this->setFlagBit(self::FLAG_UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionMadeBy()
|
||||
{
|
||||
return $this->versionMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionMadeBy
|
||||
*/
|
||||
public function setVersionMadeBy($versionMadeBy)
|
||||
{
|
||||
$this->versionMadeBy = $versionMadeBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionExtract()
|
||||
{
|
||||
return $this->versionExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionExtract
|
||||
*/
|
||||
public function setVersionExtract($versionExtract)
|
||||
{
|
||||
$this->versionExtract = $versionExtract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFlag()
|
||||
{
|
||||
return $this->flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $flag
|
||||
*/
|
||||
public function setFlag($flag)
|
||||
{
|
||||
$this->flag = $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressionMethod()
|
||||
{
|
||||
return $this->compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressionMethod
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCompressionMethod($compressionMethod)
|
||||
{
|
||||
if (!isset(self::$valuesCompressionMethod[$compressionMethod])) {
|
||||
throw new ZipException("invalid compression method " . $compressionMethod);
|
||||
}
|
||||
$this->compressionMethod = $compressionMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModDateTime()
|
||||
{
|
||||
return $this->lastModDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastModDateTime
|
||||
*/
|
||||
public function setLastModDateTime($lastModDateTime)
|
||||
{
|
||||
$this->lastModDateTime = $lastModDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCrc32()
|
||||
{
|
||||
return $this->crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $crc32
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setCrc32($crc32)
|
||||
{
|
||||
if ($crc32 < 0 || $crc32 > 0xFFFFFFFF) {
|
||||
throw new ZipException("invalid entry crc-32");
|
||||
}
|
||||
$this->crc32 = $crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCompressedSize()
|
||||
{
|
||||
return $this->compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $compressedSize
|
||||
*/
|
||||
public function setCompressedSize($compressedSize)
|
||||
{
|
||||
$this->compressedSize = $compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUnCompressedSize()
|
||||
{
|
||||
return $this->unCompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $unCompressedSize
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setUnCompressedSize($unCompressedSize)
|
||||
{
|
||||
if ($unCompressedSize < 0 || $unCompressedSize > 0xFFFFFFFF) {
|
||||
throw new ZipException("invalid entry size");
|
||||
}
|
||||
$this->unCompressedSize = $unCompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDiskNumber()
|
||||
{
|
||||
return $this->diskNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $diskNumber
|
||||
*/
|
||||
public function setDiskNumber($diskNumber)
|
||||
{
|
||||
$this->diskNumber = $diskNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getInternalAttributes()
|
||||
{
|
||||
return $this->internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $internalAttributes
|
||||
*/
|
||||
public function setInternalAttributes($internalAttributes)
|
||||
{
|
||||
$this->internalAttributes = $internalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExternalAttributes()
|
||||
{
|
||||
return $this->externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $externalAttributes
|
||||
*/
|
||||
public function setExternalAttributes($externalAttributes)
|
||||
{
|
||||
$this->externalAttributes = $externalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetOfLocal()
|
||||
{
|
||||
return $this->offsetOfLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offsetOfLocal
|
||||
*/
|
||||
public function setOffsetOfLocal($offsetOfLocal)
|
||||
{
|
||||
$this->offsetOfLocal = $offsetOfLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetOfCentral()
|
||||
{
|
||||
return $this->offsetOfCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offsetOfCentral
|
||||
*/
|
||||
public function setOffsetOfCentral($offsetOfCentral)
|
||||
{
|
||||
$this->offsetOfCentral = $offsetOfCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraCentral()
|
||||
{
|
||||
return $this->extraCentral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExtraCentral($extra)
|
||||
{
|
||||
if ($extra !== null && strlen($extra) > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$this->extraCentral = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extra
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function setExtraLocal($extra)
|
||||
{
|
||||
if ($extra !== null && strlen($extra) > 0xFFFF) {
|
||||
throw new ZipException("invalid extra field length");
|
||||
}
|
||||
$this->extraLocal = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraLocal()
|
||||
{
|
||||
return $this->extraLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastModTime
|
||||
* @param int $lastModDate
|
||||
*/
|
||||
private function setLastModifyDosDatetime($lastModTime, $lastModDate)
|
||||
{
|
||||
$hour = ($lastModTime & 0xF800) >> 11;
|
||||
$minute = ($lastModTime & 0x07E0) >> 5;
|
||||
$seconds = ($lastModTime & 0x001F) * 2;
|
||||
|
||||
$year = (($lastModDate & 0xFE00) >> 9) + 1980;
|
||||
$month = ($lastModDate & 0x01E0) >> 5;
|
||||
$day = $lastModDate & 0x001F;
|
||||
|
||||
// ----- Get UNIX date format
|
||||
$this->lastModDateTime = mktime($hour, $minute, $seconds, $month, $day, $year);
|
||||
}
|
||||
|
||||
public function getLastModifyDosTime()
|
||||
{
|
||||
$date = getdate($this->lastModDateTime);
|
||||
return ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2;
|
||||
}
|
||||
|
||||
public function getLastModifyDosDate()
|
||||
{
|
||||
$date = getdate($this->lastModDateTime);
|
||||
return (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'];
|
||||
}
|
||||
|
||||
public function versionMadeToString()
|
||||
{
|
||||
if (isset(self::$valuesMadeBy[$this->versionMadeBy])) {
|
||||
return self::$valuesMadeBy[$this->versionMadeBy];
|
||||
} else return "unknown";
|
||||
}
|
||||
|
||||
public function compressionMethodToString()
|
||||
{
|
||||
if (isset(self::$valuesCompressionMethod[$this->compressionMethod])) {
|
||||
return self::$valuesCompressionMethod[$this->compressionMethod];
|
||||
} else return "unknown";
|
||||
}
|
||||
|
||||
public function flagToString()
|
||||
{
|
||||
$return = array();
|
||||
foreach (self::$valuesFlag AS $bit => $value) {
|
||||
if ($this->testFlagBit($bit)) {
|
||||
$return[] = $value;
|
||||
}
|
||||
}
|
||||
if (!empty($return)) {
|
||||
return implode(', ', $return);
|
||||
} else if ($this->flag === 0) {
|
||||
return "default";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return __CLASS__ . '{' .
|
||||
'name="' . $this->name . '"' .
|
||||
', versionMadeBy={' . $this->versionMadeBy . ' => "' . $this->versionMadeToString() . '"}' .
|
||||
', versionExtract="' . $this->versionExtract . '"' .
|
||||
', flag={' . $this->flag . ' => ' . $this->flagToString() . '}' .
|
||||
', compressionMethod={' . $this->compressionMethod . ' => ' . $this->compressionMethodToString() . '}' .
|
||||
', lastModify=' . date("Y-m-d H:i:s", $this->lastModDateTime) .
|
||||
', crc32=0x' . dechex($this->crc32) .
|
||||
', compressedSize=' . ZipUtils::humanSize($this->compressedSize) .
|
||||
', unCompressedSize=' . ZipUtils::humanSize($this->unCompressedSize) .
|
||||
', diskNumber=' . $this->diskNumber .
|
||||
', internalAttributes=' . $this->internalAttributes .
|
||||
', externalAttributes=' . $this->externalAttributes .
|
||||
', offsetOfLocal=' . $this->offsetOfLocal .
|
||||
', offsetOfCentral=' . $this->offsetOfCentral .
|
||||
', extraCentral="' . $this->extraCentral . '"' .
|
||||
', extraLocal="' . $this->extraLocal . '"' .
|
||||
', comment="' . $this->comment . '"' .
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
class ZipException extends \Exception
|
||||
{
|
||||
|
||||
}
|
1374
src/ZipFile.php
1374
src/ZipFile.php
File diff suppressed because it is too large
Load Diff
104
src/ZipUtils.php
104
src/ZipUtils.php
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
namespace Nelexa\Zip;
|
||||
|
||||
use Nelexa\Buffer\MathHelper;
|
||||
|
||||
class ZipUtils
|
||||
{
|
||||
const DECRYPT_HEADER_SIZE = 12;
|
||||
public static $CFH_SIGNATURE = array(0x50, 0x4b, 0x01, 0x02);
|
||||
public static $LFH_SIGNATURE = array(0x50, 0x4b, 0x03, 0x04);
|
||||
public static $ECD_SIGNATURE = array(0x50, 0x4b, 0x05, 0x06);
|
||||
public static $DD_SIGNATURE = array(0x50, 0x4b, 0x07, 0x08);
|
||||
|
||||
private static $CRC_TABLE = array(
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
|
||||
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
|
||||
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
|
||||
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
|
||||
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
|
||||
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
|
||||
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
|
||||
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
|
||||
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
|
||||
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
|
||||
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
|
||||
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
|
||||
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
|
||||
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
|
||||
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
|
||||
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
|
||||
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
|
||||
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
|
||||
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
|
||||
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
|
||||
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
|
||||
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
||||
);
|
||||
|
||||
/**
|
||||
* @param int $charAt
|
||||
* @param int[] $keys
|
||||
* @return array
|
||||
*/
|
||||
public static function updateKeys($charAt, array $keys)
|
||||
{
|
||||
$keys[0] = self::crc32($keys[0], $charAt);
|
||||
$keys[1] = MathHelper::add($keys[1], MathHelper::bitwiseAnd($keys[0], 0xff));
|
||||
$keys[1] = MathHelper::castToInt(MathHelper::add(MathHelper::mul($keys[1], 134775813), 1));
|
||||
$keys[2] = self::crc32($keys[2], MathHelper::rightShift32($keys[1], 24));
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alg: ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff])
|
||||
*
|
||||
* @param int $oldCrc
|
||||
* @param int $charAt
|
||||
* @return int|string
|
||||
*/
|
||||
public static function crc32($oldCrc, $charAt)
|
||||
{
|
||||
return MathHelper::castToInt(MathHelper::bitwiseXor(MathHelper::unsignedRightShift32($oldCrc, 8), self::$CRC_TABLE[MathHelper::bitwiseAnd(MathHelper::bitwiseXor($oldCrc, $charAt), 0xff)]));
|
||||
}
|
||||
|
||||
public static function decryptByte($byte)
|
||||
{
|
||||
$temp = $byte | 2;
|
||||
return MathHelper::unsignedRightShift32($temp * ($temp ^ 1), 8);
|
||||
}
|
||||
|
||||
public static function humanSize($size, $unit = "")
|
||||
{
|
||||
if ((!$unit && $size >= 1 << 30) || $unit == "GB")
|
||||
return number_format($size / (1 << 30), 2) . "GB";
|
||||
if ((!$unit && $size >= 1 << 20) || $unit == "MB")
|
||||
return number_format($size / (1 << 20), 2) . "MB";
|
||||
if ((!$unit && $size >= 1 << 10) || $unit == "KB")
|
||||
return number_format($size / (1 << 10), 2) . "KB";
|
||||
return number_format($size) . " bytes";
|
||||
}
|
||||
|
||||
|
||||
}
|
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
1853
tests/PhpZip/ZipFileTest.php
Normal file
1853
tests/PhpZip/ZipFileTest.php
Normal file
File diff suppressed because it is too large
Load Diff
129
tests/PhpZip/ZipTestCase.php
Normal file
129
tests/PhpZip/ZipTestCase.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?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 string $filename
|
||||
* @param string|null $password
|
||||
*/
|
||||
public static function assertCorrectZipArchive($filename, $password = null)
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert correct empty zip archive.
|
||||
*
|
||||
* @param $filename
|
||||
*/
|
||||
public static function assertCorrectEmptyZip($filename)
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR !== '\\' && `which zipinfo`) {
|
||||
exec("zipinfo " . escapeshellarg($filename), $output, $returnCode);
|
||||
|
||||
$output = implode(PHP_EOL, $output);
|
||||
|
||||
self::assertContains('Empty zipfile', $output);
|
||||
}
|
||||
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
|
||||
self::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, $showErrors = false)
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR !== '\\' && `which zipalign`) {
|
||||
exec("zipalign -c -v 4 " . escapeshellarg($filename), $output, $returnCode);
|
||||
if ($showErrors && $returnCode !== 0) fwrite(STDERR, implode(PHP_EOL, $output));
|
||||
return $returnCode === 0;
|
||||
} else {
|
||||
fwrite(STDERR, 'Can not find program "zipalign" for test');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,244 +0,0 @@
|
||||
<?php
|
||||
|
||||
class TestZipFile extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$output = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-create.zip';
|
||||
$extractOutputDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-create';
|
||||
|
||||
$listFilename = 'files.txt';
|
||||
$listFileContent = implode(PHP_EOL, glob('*'));
|
||||
$dirName = 'src/';
|
||||
$archiveComment = 'Archive comment - 😀';
|
||||
$commentIndex0 = basename(__FILE__);
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->create();
|
||||
$zip->addFile(__FILE__);
|
||||
$zip->addFromString($listFilename, $listFileContent);
|
||||
$zip->addEmptyDir($dirName);
|
||||
$zip->setArchiveComment($archiveComment);
|
||||
$zip->setCommentIndex(0, $commentIndex0);
|
||||
$zip->saveAs($output);
|
||||
$zip->close();
|
||||
|
||||
$this->assertTrue(file_exists($output));
|
||||
$this->assertCorrectZipArchive($output);
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile();
|
||||
$zip->open($output);
|
||||
$listFiles = $zip->getListFiles();
|
||||
|
||||
$this->assertEquals(sizeof($listFiles), 3);
|
||||
$filenameIndex0 = basename(__FILE__);
|
||||
$this->assertEquals($listFiles[0], $filenameIndex0);
|
||||
$this->assertEquals($listFiles[1], $listFilename);
|
||||
$this->assertEquals($listFiles[2], $dirName);
|
||||
|
||||
$this->assertEquals($zip->getFromIndex(0), $zip->getFromName(basename(__FILE__)));
|
||||
$this->assertEquals($zip->getFromIndex(0), file_get_contents(__FILE__));
|
||||
$this->assertEquals($zip->getFromIndex(1), $zip->getFromName($listFilename));
|
||||
$this->assertEquals($zip->getFromIndex(1), $listFileContent);
|
||||
|
||||
$this->assertEquals($zip->getArchiveComment(), $archiveComment);
|
||||
$this->assertEquals($zip->getCommentIndex(0), $commentIndex0);
|
||||
|
||||
if (!file_exists($extractOutputDir)) {
|
||||
$this->assertTrue(mkdir($extractOutputDir, 0755, true));
|
||||
}
|
||||
|
||||
$zip->extractTo($extractOutputDir);
|
||||
|
||||
$this->assertTrue(file_exists($extractOutputDir . DIRECTORY_SEPARATOR . $filenameIndex0));
|
||||
$this->assertEquals(md5_file($extractOutputDir . DIRECTORY_SEPARATOR . $filenameIndex0), md5_file(__FILE__));
|
||||
|
||||
$this->assertTrue(file_exists($extractOutputDir . DIRECTORY_SEPARATOR . $listFilename));
|
||||
$this->assertEquals(file_get_contents($extractOutputDir . DIRECTORY_SEPARATOR . $listFilename), $listFileContent);
|
||||
|
||||
$zip->close();
|
||||
|
||||
unlink($output);
|
||||
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($extractOutputDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($files as $fileInfo) {
|
||||
$todo = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$todo($fileInfo->getRealPath());
|
||||
}
|
||||
|
||||
rmdir($extractOutputDir);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function testUpdate()
|
||||
{
|
||||
$file = __DIR__ . '/res/file.apk';
|
||||
$privateKey = __DIR__ . '/res/private.pem';
|
||||
$publicKey = __DIR__ . '/res/public.pem';
|
||||
$outputFile = sys_get_temp_dir() . '/test-update.apk';
|
||||
|
||||
$zip = new \Nelexa\Zip\ZipFile($file);
|
||||
$zip->open($file);
|
||||
|
||||
// signed apk file
|
||||
$certList = array();
|
||||
$manifestMf = new Manifest();
|
||||
$manifestMf->appendLine("Manifest-Version: 1.0");
|
||||
$manifestMf->appendLine("Created-By: 1.0 (Android)");
|
||||
$manifestMf->appendLine('');
|
||||
for ($i = 0, $length = $zip->getCountFiles(); $i < $length; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if ($name[strlen($name) - 1] === '/') continue; // is path
|
||||
$content = $zip->getFromIndex($i);
|
||||
|
||||
$certManifest = $this->createSha1EncodeEntryManifest($name, $content);
|
||||
$manifestMf->appendManifest($certManifest);
|
||||
$certList[$name] = $certManifest;
|
||||
}
|
||||
$manifestMf = $manifestMf->getContent();
|
||||
|
||||
$certSf = new Manifest();
|
||||
$certSf->appendLine('Signature-Version: 1.0');
|
||||
$certSf->appendLine('Created-By: 1.0 (Android)');
|
||||
$certSf->appendLine('SHA1-Digest-Manifest: ' . base64_encode(sha1($manifestMf, 1)));
|
||||
$certSf->appendLine('');
|
||||
foreach ($certList AS $filename => $content) {
|
||||
$certManifest = $this->createSha1EncodeEntryManifest($filename, $content->getContent());
|
||||
$certSf->appendManifest($certManifest);
|
||||
}
|
||||
$certSf = $certSf->getContent();
|
||||
unset($certList);
|
||||
|
||||
$zip->addFromString('META-INF/MANIFEST.MF', $manifestMf);
|
||||
$zip->addFromString('META-INF/CERT.SF', $certSf);
|
||||
|
||||
if (`which openssl`) {
|
||||
$openssl_cmd = 'printf ' . escapeshellarg($certSf) . ' | openssl smime -md sha1 -sign -inkey ' . escapeshellarg($privateKey) . ' -signer ' . $publicKey . ' -binary -outform DER -noattr';
|
||||
|
||||
ob_start();
|
||||
passthru($openssl_cmd, $error);
|
||||
$rsaContent = ob_get_clean();
|
||||
$this->assertEquals($error, 0);
|
||||
|
||||
$zip->addFromString('META-INF/CERT.RSA', $rsaContent);
|
||||
}
|
||||
|
||||
$zip->saveAs($outputFile);
|
||||
$zip->close();
|
||||
|
||||
$this->assertCorrectZipArchive($outputFile);
|
||||
|
||||
if (`which jarsigner`) {
|
||||
ob_start();
|
||||
passthru('jarsigner -verify -verbose -certs ' . escapeshellarg($outputFile), $error);
|
||||
$verifedResult = ob_get_clean();
|
||||
|
||||
$this->assertEquals($error, 0);
|
||||
$this->assertContains('jar verified', $verifedResult);
|
||||
}
|
||||
|
||||
unlink($outputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $filename
|
||||
*/
|
||||
private function assertCorrectZipArchive($filename)
|
||||
{
|
||||
exec("zip -T " . escapeshellarg($filename), $output, $returnCode);
|
||||
$this->assertEquals($returnCode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $content
|
||||
* @return Manifest
|
||||
*/
|
||||
private function createSha1EncodeEntryManifest($filename, $content)
|
||||
{
|
||||
$manifest = new Manifest();
|
||||
$manifest->appendLine('Name: ' . $filename);
|
||||
$manifest->appendLine('SHA1-Digest: ' . base64_encode(sha1($content, 1)));
|
||||
return $manifest;
|
||||
}
|
||||
}
|
||||
|
||||
class Manifest
|
||||
{
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return trim($this->content) . "\r\n\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a long manifest line and add continuation if required
|
||||
* @param $line string
|
||||
* @return Manifest
|
||||
*/
|
||||
public function appendLine($line)
|
||||
{
|
||||
$begin = 0;
|
||||
$sb = '';
|
||||
$lineLength = mb_strlen($line, "UTF-8");
|
||||
for ($end = 70; $lineLength - $begin > 70; $end += 69) {
|
||||
$sb .= mb_substr($line, $begin, $end - $begin, "UTF-8") . "\r\n ";
|
||||
$begin = $end;
|
||||
}
|
||||
$this->content .= $sb . mb_substr($line, $begin, $lineLength, "UTF-8") . "\r\n";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function appendManifest(Manifest $manifest)
|
||||
{
|
||||
$this->content .= $manifest->getContent();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->content = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $manifestContent
|
||||
* @return Manifest
|
||||
*/
|
||||
public static function createFromManifest($manifestContent)
|
||||
{
|
||||
$manifestContent = trim($manifestContent);
|
||||
$lines = explode("\n", $manifestContent);
|
||||
|
||||
// normalize manifest
|
||||
$content = '';
|
||||
$trim = array("\r", "\n");
|
||||
foreach ($lines AS $line) {
|
||||
|
||||
$line = str_replace($trim, '', $line);
|
||||
if ($line[0] === ' ') {
|
||||
$content = rtrim($content, "\n\r");
|
||||
$line = ltrim($line);
|
||||
}
|
||||
$content .= $line . "\r\n";
|
||||
}
|
||||
|
||||
$manifset = new self;
|
||||
$lines = explode("\n", $content);
|
||||
foreach ($lines AS $line) {
|
||||
$line = trim($line, "\n\r");
|
||||
$manifset->appendLine($line);
|
||||
}
|
||||
return $manifset;
|
||||
}
|
||||
}
|
Binary file not shown.
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwiURw5w8TAS0G
|
||||
iWaBzJqbQyacFsIzKc+orHDzBdBdKlrx/aUlw3fBA310aP7343hc7vMm6koar/1n
|
||||
8iEh2W4retDHzNf9j3IXETa5/D+3WJY4aVJkYcFr8v6OEnuSXIwKZduMeL4BpQwM
|
||||
Xu/z6gkoTa9o+Dzhl46l71UX8umdPIx/4YWwX2oSm6EGklcGJyYdqMvwOXVXDE/J
|
||||
qqPzC7ZQiu422cDDvqBYt32CVyjLKCo5YjWBb24jxjtl5M4y8xPOcHlVEG2WwPBU
|
||||
sv2aw4Z9/Q0erZ35gMyzu+Y+2623B3tuAbfsswgBINq0bl2v+M17Kpv2tjhMKj1H
|
||||
ds6CK/BVAgMBAAECggEAdTUt86f1IjENq+Fd5Z/qplsXL1sM5NtFvD+BXljl1nVg
|
||||
nHpDQ6dbwxKGINv1LLAiIdGkLpovSTi/jlv8E3VA6C1KoN0oKnkqzpXnN+R6iUiP
|
||||
tDR5N5yPxxQ2Xi13Td2UPPMTqVghDwZ90VjXB6LDIbcyVwc5pK3zT8hvPs9Qu8t1
|
||||
S2pCEKcowvTRSB1DMTZ3lrNjEEIMdV0H8Qik3lf7ognRGoDywu5pA1bc/Yg+XlmP
|
||||
/ZmQinFeg3izNQzDdP6Ppo1i/QFeVXVuMs2ergMMHJRNUhBXKz8iNyVupqfroE8a
|
||||
xRpD3eO+KvSNb0TJR5TXf64t62zEEpHaRsmgACEMAQKBgQDXo0jVUa67oqfJBFBU
|
||||
3zfHIlgcrydRE4Av+LJ0hdEnFpMwVpUihJXUaJijawTzTKOgpVImhxfr/T1HMalm
|
||||
MTXH5Tc7inJTiB9A1IffLPqgoOr2JRwQ2q8lgWkQPkq1ySd+q0vhkj1tuAe3qI1i
|
||||
jiMo1Vb9zdVjcxmvPnZRKJgiIQKBgQDRlFm6PKc2Zx46BXeNPtXnHhSduUBJf2iO
|
||||
n9/pKTANQuDlPwC3Q4edSKe44fZ/oj4KRAnzX254wXBMX+ktKX/kqXbwEanxcd/v
|
||||
Lnvgv8QhsEKO3Ye09yasAfC2lYsSVSwHv+dYurb0nZ2JEPL1IP+V76RgTbdeMdic
|
||||
Mt53jN/vtQKBgQC+D+mOO+Sq9X61qtuzMtvS5O6MucUJrQp7PdTs51WmAjvRiz7/
|
||||
oaT+BwMiZp2CZLaETbLOypvHIPn12kvZCt7ARcQc8rY58ey6E5l+mAJ/udXfBm5q
|
||||
XJWrlRipfH4VJCtvdkP3mhIStvX2ZtXXXDiZMRDvu5CtizHESGW4uvL8gQKBgCI7
|
||||
ukBagfG3/E7776BJwETlO/bbeK3Iuvp5EOkUCj5QS04G8YX96Nv/Ly5a8pm8lae1
|
||||
n256ix/8cOx4yizPV42xRLVIHVtL/4khLajzigT6tpSBiRY9PLriAkDAwpu2/98w
|
||||
MIjkzte8Gyx1cUorHrSOFWqJp0cim0BAauhaQYX1AoGAPvb5TG/A6LhYKgCWUMOH
|
||||
lucrnV3Ns1BzaaMmOaokuYGtyTv2loVlrv+7QGdC9UBRXDz46DTE7CPHBwReNnWB
|
||||
R7YW50VwB9YfD7dqRM24Y08F3B7RCNhqsAnpAtVgXf+/01o2nfJbzxTty/STiBNB
|
||||
OjjxKHnAgIfhe7xAIiY2eow=
|
||||
-----END PRIVATE KEY-----
|
@@ -1,21 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDeTCCAmGgAwIBAgIEDeVWNjANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJT
|
||||
QTEPMA0GA1UECBMGUml5YWRoMQ8wDQYDVQQHEwZSaXlhZGgxEDAOBgNVBAoTB1lv
|
||||
dXR5cGUxEDAOBgNVBAsTB1lvdXR5cGUxGDAWBgNVBAMTD0x1Y2llbm5lIEFuc2Vs
|
||||
YTAeFw0xNjA5MDgxNDM2MjJaFw00NDAxMjUxNDM2MjJaMG0xCzAJBgNVBAYTAlNB
|
||||
MQ8wDQYDVQQIEwZSaXlhZGgxDzANBgNVBAcTBlJpeWFkaDEQMA4GA1UEChMHWW91
|
||||
dHlwZTEQMA4GA1UECxMHWW91dHlwZTEYMBYGA1UEAxMPTHVjaWVubmUgQW5zZWxh
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsIlEcOcPEwEtBolmgcya
|
||||
m0MmnBbCMynPqKxw8wXQXSpa8f2lJcN3wQN9dGj+9+N4XO7zJupKGq/9Z/IhIdlu
|
||||
K3rQx8zX/Y9yFxE2ufw/t1iWOGlSZGHBa/L+jhJ7klyMCmXbjHi+AaUMDF7v8+oJ
|
||||
KE2vaPg84ZeOpe9VF/LpnTyMf+GFsF9qEpuhBpJXBicmHajL8Dl1VwxPyaqj8wu2
|
||||
UIruNtnAw76gWLd9glcoyygqOWI1gW9uI8Y7ZeTOMvMTznB5VRBtlsDwVLL9msOG
|
||||
ff0NHq2d+YDMs7vmPtuttwd7bgG37LMIASDatG5dr/jNeyqb9rY4TCo9R3bOgivw
|
||||
VQIDAQABoyEwHzAdBgNVHQ4EFgQUEPoIQyYzpjseEK7hqm6UALvjJj8wDQYJKoZI
|
||||
hvcNAQEFBQADggEBAD/C/48B4MvF2WzhMtLIAWuhtp73xBy6GCQBKT1dn9dgtXfD
|
||||
LuHAvkx28CoOTso4Ia+JhWuu7jGfYdtL00ezV8d8Ma1k/SJfWyHpgDDk1MEhvn+h
|
||||
tOoUQpt0S+QhKFxDm+INv2zw/P/TDIIodHQqkX+YVSLQMhUGRTq3vhDnfJqedAUr
|
||||
QIhZjCZx9VjjiM4yhcabKEHpxqLQOcoeHB8zchnP1j/N+QSIW6hICqjcPLPLzpPu
|
||||
M0RmEuRYz3EJ2P3jINhaCLFRLHTnoN2lVDS32v5Cr+IC7A1hPUcHG+07junRMEiG
|
||||
uTYj9+UYI6phGJBABfFp7/oxs080RXCrKUhR+Go=
|
||||
-----END CERTIFICATE-----
|
Reference in New Issue
Block a user