1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-03-20 22:50:00 +01:00

Merge branch 'feature/3.0.0-dev' into develop

This commit is contained in:
Ne-Lexa 2017-03-15 15:27:19 +03:00
commit 4ca1717979
35 changed files with 6455 additions and 4834 deletions

9
.codeclimate.yml Normal file
View File

@ -0,0 +1,9 @@
engines:
duplication:
enabled: true
config:
languages:
- php
exclude_paths:
- tests/
- vendor/

33
.travis.yml Normal file
View File

@ -0,0 +1,33 @@
language: php
php:
- '5.5'
- '5.6'
- '7.0'
- '7.1'
- hhvm
- nightly
# cache vendor dirs
cache:
directories:
- vendor
- $HOME/.composer/cache
addons:
code_climate:
repo_token: 486a09d58d663450146c53c81c6c64938bcf3bb0b7c8ddebdc125fe97c18213a
install:
- travis_retry composer self-update && composer --version
- travis_retry composer install --prefer-dist --no-interaction
before_script:
- sudo apt-get install p7zip-full
script:
- composer validate --no-check-lock
- vendor/bin/phpunit -v -c bootstrap.xml --coverage-clover build/logs/clover.xml
after_success:
- vendor/bin/test-reporter

680
README.md
View File

@ -1,52 +1,143 @@
`PhpZip` Version 2
================
`PhpZip` - is to create, update, opening and unpacking ZIP archives in pure PHP.
`PhpZip`
====================
`PhpZip` - php library for manipulating zip archives.
The library supports `ZIP64`, `zipalign`, `Traditional PKWARE Encryption` and `WinZIP AES Encryption`.
[![Build Status](https://travis-ci.org/Ne-Lexa/php-zip.svg?branch=feature/3.0.0-dev)](https://travis-ci.org/Ne-Lexa/php-zip)
[![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip)
[![Total Downloads](https://poser.pugx.org/nelexa/zip/downloads)](https://packagist.org/packages/nelexa/zip)
[![Minimum PHP Version](http://img.shields.io/badge/php%2064bit-%3E%3D%205.5-8892BF.svg)](https://php.net/)
[![Test Coverage](https://codeclimate.com/github/Ne-Lexa/php-zip/badges/coverage.svg)](https://codeclimate.com/github/Ne-Lexa/php-zip/coverage)
[![License](https://poser.pugx.org/nelexa/zip/license)](https://packagist.org/packages/nelexa/zip)
ZIP64 extensions are automatically and transparently activated when reading or writing ZIP files of more than 4 GB size.
Table of contents
-----------------
- [Features](#Features)
- [Requirements](#Requirements)
- [Installation](#Installation)
- [Examples](#Examples)
- [Documentation](#Documentation)
+ [Open Zip Archive](#Documentation-Open-Zip-Archive)
+ [Get Zip Entries](#Documentation-Open-Zip-Entries)
+ [Add Zip Entries](#Documentation-Add-Zip-Entries)
+ [ZipAlign Usage](#Documentation-ZipAlign-Usage)
+ [Save Zip File or Output](#Documentation-Save-Or-Output-Entries)
+ [Close Zip Archive](#Documentation-Close-Zip-Archive)
- [Running Tests](#Running-Tests)
- [Upgrade version 2 to version 3](#Upgrade)
The library does not require extension `php-zip` and class `ZipArchive`.
### <a name="Features"></a> Features
- Opening and unzipping zip files.
- Create zip files.
- Update zip files.
- Pure php (not require extension `php-zip` and class `\ZipArchive`).
- Output the modified archive as a string or output to the browser without saving the result to disk.
- Support archive comment and entries comments.
- Get info of zip entries.
- Support zip password for PHP 5.5, include update and remove password.
- Support encryption method `Traditional PKWARE Encryption (ZipCrypto)` and `WinZIP AES Encryption`.
- Support `ZIP64` (size > 4 GiB or files > 65535 in a .ZIP archive).
- Support archive alignment functional [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
Requirements
------------
- `PHP` >= 5.4 (64 bit)
- PHP-extension `mbstring`
### <a name="Requirements"></a> Requirements
- `PHP` >= 5.5 (64 bit)
- Optional php-extension `bzip2` for BZIP2 compression.
- Optional php-extension `openssl` or `mcrypt` for `WinZip Aes Encryption` support.
Installation
------------
`composer require nelexa/zip`
### <a name="Installation"></a> Installation
`composer require nelexa/zip:^3.0`
Documentation
-------------
#### Class `\PhpZip\ZipFile` (open, extract, info)
### <a name="Examples"></a> Examples
```php
// create new archive
$zipFile = new \PhpZip\ZipFile();
$zipFile
->addFromString("zip/entry/filename", "Is file content")
->addFile("/path/to/file", "data/tofile")
->addDir(__DIR__, "to/path/")
->saveAsFile($outputFilename)
->close();
// open archive, extract, add files, set password and output to browser.
$zipFile
->openFile($outputFilename)
->extractTo($outputDirExtract)
->deleteFromRegex('~^\.~') // delete all hidden (Unix) files
->addFromString('dir/file.txt', 'Test file')
->withNewPassword('password')
->outputAsAttachment('library.jar');
```
Other examples can be found in the `tests/` folder
### <a name="Documentation"></a> Documentation:
#### <a name="Documentation-Open-Zip-Archive"></a> Open Zip Archive
Open zip archive from file.
```php
$zipFile = \PhpZip\ZipFile::openFromFile($filename);
$zipFile = new \PhpZip\ZipFile();
$zipFile->openFile($filename);
```
Open zip archive from data string.
```php
$data = file_get_contents($filename);
$zipFile = \PhpZip\ZipFile::openFromString($data);
$zipFile = new \PhpZip\ZipFile();
$zipFile->openFromString($stringContents);
```
Open zip archive from stream resource.
```php
$stream = fopen($filename, 'rb');
$zipFile = \PhpZip\ZipFile::openFromStream($stream);
$zipFile = new \PhpZip\ZipFile();
$zipFile->openFromStream($stream);
```
#### <a name="Documentation-Open-Zip-Entries"></a> Get Zip Entries
Get num entries.
```php
$count = $zipFile->count();
// or
$count = count($zipFile);
// or
$count = $zipFile->count();
```
Get list files.
```php
$listFiles = $zipFile->getListFiles();
// Example result:
//
// $listFiles = [
// 'info.txt',
// 'path/to/file.jpg',
// 'another path/'
// ];
```
Foreach zip entries.
Get entry contents.
```php
// $entryName = 'path/to/example-entry-name.txt';
$contents = $zipFile[$entryName];
```
Checks whether a entry exists.
```php
// $entryName = 'path/to/example-entry-name.txt';
$hasEntry = isset($zipFile[$entryName]);
```
Check whether the directory entry.
```php
// $entryName = 'path/to/';
$isDirectory = $zipFile->isDirectory($entryName);
```
Extract all files to directory.
```php
$zipFile->extractTo($directory);
```
Extract some files to directory.
```php
$extractOnlyFiles = [
"filename1",
"filename2",
"dir/dir/dir/"
];
$zipFile->extractTo($directory, $extractOnlyFiles);
```
Iterate zip entries.
```php
foreach($zipFile as $entryName => $dataContent){
echo "Entry: $entryName" . PHP_EOL;
@ -54,7 +145,7 @@ foreach($zipFile as $entryName => $dataContent){
echo "-----------------------------" . PHP_EOL;
}
```
Iterator zip entries.
or
```php
$iterator = new \ArrayIterator($zipFile);
while ($iterator->valid())
@ -69,58 +160,54 @@ while ($iterator->valid())
$iterator->next();
}
```
Checks whether a entry exists.
```php
$boolValue = $zipFile->hasEntry($entryName);
```
Check whether the directory entry.
```php
$boolValue = $zipFile->isDirectory($entryName);
```
Set password to all encrypted entries.
```php
$zipFile->setPassword($password);
```
Set password to concrete zip entry.
```php
$zipFile->setEntryPassword($entryName, $password);
```
Get comment archive.
```php
$commentArchive = $zipFile->getComment();
$commentArchive = $zipFile->getArchiveComment();
```
Get comment zip entry.
```php
$commentEntry = $zipFile->getEntryComment($entryName);
```
Set password for read encrypted entries.
```php
$zipFile->withReadPassword($password);
```
Get entry info.
```php
$zipInfo = $zipFile->getEntryInfo('file.txt');
echo $zipInfo . PHP_EOL;
// Output:
// ZipInfo {Path="file.txt", Size=9.77KB, Compressed size=2.04KB, Modified time=2016-09-24T19:25:10+03:00, Crc=0x4b5ab5c7, Method="Deflate", Attributes="-rw-r--r--", Platform="UNIX", Version=20}
print_r($zipInfo);
//PhpZip\Model\ZipInfo Object
//(
// [path:PhpZip\Model\ZipInfo:private] => file.txt
// [folder:PhpZip\Model\ZipInfo:private] =>
// [size:PhpZip\Model\ZipInfo:private] => 10000
// [compressedSize:PhpZip\Model\ZipInfo:private] => 2086
// [mtime:PhpZip\Model\ZipInfo:private] => 1474734310
// [ctime:PhpZip\Model\ZipInfo:private] =>
// [atime:PhpZip\Model\ZipInfo:private] =>
// [encrypted:PhpZip\Model\ZipInfo:private] =>
// [comment:PhpZip\Model\ZipInfo:private] =>
// [crc:PhpZip\Model\ZipInfo:private] => 1264235975
// [method:PhpZip\Model\ZipInfo:private] => Deflate
// [platform:PhpZip\Model\ZipInfo:private] => UNIX
// [version:PhpZip\Model\ZipInfo:private] => 20
// [attributes:PhpZip\Model\ZipInfo:private] => -rw-r--r--
//)
// Output:
// PhpZip\Model\ZipInfo Object
// (
// [path:PhpZip\Model\ZipInfo:private] => file.txt
// [folder:PhpZip\Model\ZipInfo:private] =>
// [size:PhpZip\Model\ZipInfo:private] => 10000
// [compressedSize:PhpZip\Model\ZipInfo:private] => 2086
// [mtime:PhpZip\Model\ZipInfo:private] => 1474734310
// [ctime:PhpZip\Model\ZipInfo:private] =>
// [atime:PhpZip\Model\ZipInfo:private] =>
// [encrypted:PhpZip\Model\ZipInfo:private] =>
// [comment:PhpZip\Model\ZipInfo:private] =>
// [crc:PhpZip\Model\ZipInfo:private] => 1264235975
// [method:PhpZip\Model\ZipInfo:private] => Deflate
// [platform:PhpZip\Model\ZipInfo:private] => UNIX
// [version:PhpZip\Model\ZipInfo:private] => 20
// [attributes:PhpZip\Model\ZipInfo:private] => -rw-r--r--
// )
```
Get info for all entries.
```php
$zipAllInfo = $zipFile->getAllInfo();
print_r($zipAllInfo);
//Array
//(
// [file.txt] => PhpZip\Model\ZipInfo Object
@ -135,343 +222,328 @@ print_r($zipAllInfo);
//
// ...
//)
```
Extract all files to directory.
```php
$zipFile->extractTo($directory);
```
Extract some files to directory.
```php
$extractOnlyFiles = ["filename1", "filename2", "dir/dir/dir/"];
$zipFile->extractTo($directory, $extractOnlyFiles);
```
Get entry content.
```php
$data = $zipFile->getEntryContent($entryName);
```
Edit zip archive
```php
$zipOutputFile = $zipFile->edit();
```
Close zip archive.
```php
$zipFile->close();
```
#### Class `\PhpZip\ZipOutputFile` (create, update, extract)
Create zip archive.
```php
$zipOutputFile = new \PhpZip\ZipOutputFile();
// or
$zipOutputFile = \PhpZip\ZipOutputFile::create();
```
Open zip file from update.
```php
$filename = "file.zip";
$zipOutputFile = \PhpZip\ZipOutputFile::openFromFile($filename);
```
or
```php
// initial ZipFile
$zipFile = \PhpZip\ZipFile::openFromFile($filename);
// Create output stream from update zip file
$zipOutputFile = new \PhpZip\ZipOutputFile($zipFile);
// or
$zipOutputFile = \PhpZip\ZipOutputFile::openFromZipFile($zipFile);
// or
$zipOutputFile = $zipFile->edit();
```
Add entry from file.
#### <a name="Documentation-Add-Zip-Entries"></a> Add Zip Entries
Adding a file to the zip-archive.
```php
$zipOutputFile->addFromFile($filename); // $entryName == basename($filename);
$zipOutputFile->addFromFile($filename, $entryName);
$zipOutputFile->addFromFile($filename, $entryName, ZipEntry::METHOD_DEFLATED);
$zipOutputFile->addFromFile($filename, $entryName, ZipEntry::METHOD_STORED); // no compress
$zipOutputFile->addFromFile($filename, null, ZipEntry::METHOD_BZIP2); // $entryName == basename($filename);
// entry name is file basename.
$zipFile->addFile($filename);
// or
$zipFile->addFile($filename, null);
// with entry name
$zipFile->addFile($filename, $entryName);
// or
$zipFile[$entryName] = new \SplFileInfo($filename);
// with compression method
$zipFile->addFile($filename, $entryName, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFile($filename, $entryName, ZipFile::METHOD_STORED); // No compression
$zipFile->addFile($filename, null, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add entry from string data.
```php
$zipOutputFile->addFromString($entryName, $data);
$zipOutputFile->addFromString($entryName, $data, ZipEntry::METHOD_DEFLATED);
$zipOutputFile->addFromString($entryName, $data, ZipEntry::METHOD_STORED); // no compress
$zipFile[$entryName] = $data;
// or
$zipFile->addFromString($entryName, $data);
// with compression method
$zipFile->addFromString($entryName, $data, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFromString($entryName, $data, ZipFile::METHOD_STORED); // No compression
$zipFile->addFromString($entryName, $data, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add entry from stream.
```php
$zipOutputFile->addFromStream($stream, $entryName);
$zipOutputFile->addFromStream($stream, $entryName, ZipEntry::METHOD_DEFLATED);
$zipOutputFile->addFromStream($stream, $entryName, ZipEntry::METHOD_STORED); // no compress
// $stream = fopen(...);
$zipFile->addFromStream($stream, $entryName);
// with compression method
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_STORED); // No compression
$zipFile->addFromStream($stream, $entryName, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add empty dir
```php
$zipOutputFile->addEmptyDir($dirName);
```
Add a directory **recursively** to the archive.
```php
$zipOutputFile->addDir($dirName);
// $dirName = "path/to/";
$zipFile->addEmptyDir($dirName);
// or
$zipOutputFile->addDir($dirName, true);
$zipFile[$dirName] = null;
```
Add all entries form string contents.
```php
$mapData = [
'file.txt' => 'file contents',
'path/to/file.txt' => 'another file contents',
'empty dir/' => null,
];
$zipFile->addAll($mapData);
```
Add a directory **not recursively** to the archive.
```php
$zipOutputFile->addDir($dirName, false);
$zipFile->addDir($dirName);
// with entry path
$localPath = "to/path/";
$zipFile->addDir($dirName, $localPath);
// with compression method for all files
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addDir($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add a directory to the archive by path `$moveToPath`
Add a directory **recursively** to the archive.
```php
$moveToPath = 'dir/subdir/';
$zipOutputFile->addDir($dirName, $boolResursive, $moveToPath);
$zipFile->addDirRecursive($dirName);
// with entry path
$localPath = "to/path/";
$zipFile->addDirRecursive($dirName, $localPath);
// with compression method for all files
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addDirRecursive($dirName, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add a directory to the archive with ignoring files.
Add a files from directory iterator.
```php
$ignoreFiles = ["file_ignore.txt", "dir_ignore/sub dir ignore/"];
$zipOutputFile->addDir($dirName, $boolResursive, $moveToPath, $ignoreFiles);
// $directoryIterator = new \DirectoryIterator($dir); // not recursive
// $directoryIterator = new \RecursiveDirectoryIterator($dir); // recursive
$zipFile->addFilesFromIterator($directoryIterator);
// with entry path
$localPath = "to/path/";
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
// or
$zipFile[$localPath] = $directoryIterator;
// with compression method for all files
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromIterator($directoryIterator, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add a directory and set compression method.
Example add a directory to the archive with ignoring files from directory iterator.
```php
$compressionMethod = ZipEntry::METHOD_DEFLATED;
$zipOutputFile->addDir($dirName, $boolRecursive, $moveToPath, $ignoreFiles, $compressionMethod);
$ignoreFiles = [
"file_ignore.txt",
"dir_ignore/sub dir ignore/"
];
// use \DirectoryIterator for not recursive
$directoryIterator = new \RecursiveDirectoryIterator($dir);
// use IgnoreFilesFilterIterator for not recursive
$ignoreIterator = new IgnoreFilesRecursiveFilterIterator(
$directoryIterator,
$ignoreFiles
);
$zipFile->addFilesFromIterator($ignoreIterator);
```
Add a files **recursively** from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive.
```php
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern);
$zipFile->addFilesFromGlobRecursive($dir, $globPattern);
// with entry path
$localPath = "to/path/";
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath);
// with compression method for all files
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath), ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath), ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath), ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add a files **not recursively** from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive.
```php
$recursive = false;
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern, $recursive);
```
Add a files from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive by path `$moveToPath`.
```php
$moveToPath = 'dir/dir2/dir3';
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern, $recursive = true, $moveToPath);
```
Add a files from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) to the archive and set compression method.
```php
$compressionMethod = ZipEntry::METHOD_DEFLATED;
$zipOutputFile->addFilesFromGlob($inputDir, $globPattern, $recursive, $moveToPath, $compressionMethod);
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files
$zipFile->addFilesFromGlob($dir, $globPattern);
// with entry path
$localPath = "to/path/";
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath);
// with compression method for all files
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath), ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath), ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromGlob($dir, $globPattern, $localPath), ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add a files **recursively** from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive.
```php
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern);
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern);
// with entry path
$localPath = "to/path/";
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath);
// with compression method for all files
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Add a files **not recursively** from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive.
```php
$recursive = false;
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern, $recursive);
```
Add a files from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive by path `$moveToPath`.
```php
$moveToPath = 'dir/dir2/dir3';
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern, $recursive = true, $moveToPath);
```
Add a files from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression) to the archive and set compression method.
```php
$compressionMethod = ZipEntry::METHOD_DEFLATED;
$zipOutputFile->addFilesFromRegex($inputDir, $regexPattern, $recursive, $moveToPath, $compressionMethod);
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files
$zipFile->addFilesFromRegex($dir, $regexPattern);
// with entry path
$localPath = "to/path/";
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath);
// with compression method for all files
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_DEFLATED); // Deflate compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_STORED); // No compression
$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, ZipFile::METHOD_BZIP2); // BZIP2 compression
```
Rename entry name.
```php
$zipOutputFile->rename($oldName, $newName);
$zipFile->rename($oldName, $newName);
```
Delete entry by name.
```php
$zipOutputFile->deleteFromName($entryName);
$zipFile->deleteFromName($entryName);
```
Delete entries from [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)).
```php
$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> delete all .jpg, .jpeg, .png and .gif files
$zipOutputFile->deleteFromGlob($globPattern);
$zipFile->deleteFromGlob($globPattern);
```
Delete entries from [RegEx (Regular Expression) pattern](https://en.wikipedia.org/wiki/Regular_expression).
```php
$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> delete all .jpg, .jpeg, .png and .gif files
$zipOutputFile->deleteFromRegex($regexPattern);
$zipFile->deleteFromRegex($regexPattern);
```
Delete all entries.
```php
$zipOutputFile->deleteAll();
```
Get num entries.
```php
$count = $zipOutputFile->count();
// or
$count = count($zipOutputFile);
```
Get list files.
```php
$listFiles = $zipOutputFile->getListFiles();
```
Get the compression level for entries.
```php
$compressionLevel = $zipOutputFile->getLevel();
$zipFile->deleteAll();
```
Sets the compression level for entries.
```php
// This property is only used if the effective compression method is DEFLATED or BZIP2.
// Legal values are ZipOutputFile::LEVEL_DEFAULT_COMPRESSION or range from
// ZipOutputFile::LEVEL_BEST_SPEED to ZipOutputFile::LEVEL_BEST_COMPRESSION.
$compressionMethod = ZipOutputFile::LEVEL_BEST_COMPRESSION;
$zipOutputFile->setLevel($compressionLevel);
```
Get comment archive.
```php
$commentArchive = $zipOutputFile->getComment();
// Legal values are ZipFile::LEVEL_DEFAULT_COMPRESSION or range from
// ZipFile::LEVEL_BEST_SPEED to ZipFile::LEVEL_BEST_COMPRESSION.
$compressionMethod = ZipFile::LEVEL_BEST_COMPRESSION;
$zipFile->setCompressionLevel($compressionLevel);
```
Set comment archive.
```php
$zipOutputFile->setComment($commentArchive);
```
Get comment zip entry.
```php
$commentEntry = $zipOutputFile->getEntryComment($entryName);
$zipFile->setArchiveComment($commentArchive);
```
Set comment zip entry.
```php
$zipOutputFile->setEntryComment($entryName, $entryComment);
$zipFile->setEntryComment($entryName, $entryComment);
```
Set compression method for zip entry.
Set a new password.
```php
$compressionMethod = ZipEntry::METHOD_DEFLATED;
$zipOutputMethod->setCompressionMethod($entryName, $compressionMethod);
// Support compression methods:
// ZipEntry::METHOD_STORED - no compression
// ZipEntry::METHOD_DEFLATED - deflate compression
// ZipEntry::METHOD_BZIP2 - bzip2 compression (need bz2 extension)
$zipFile->withNewPassword($password);
```
Set a password for all previously added entries.
Set a new password and encryption method.
```php
$zipOutputFile->setPassword($password);
```
Set a password and encryption method for all previously added entries.
```php
$encryptionMethod = ZipEntry::ENCRYPTION_METHOD_WINZIP_AES; // default value
$zipOutputFile->setPassword($password, $encryptionMethod);
$encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES; // default value
$zipFile->withNewPassword($password, $encryptionMethod);
// Support encryption methods:
// ZipEntry::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
// ZipEntry::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption
```
Set a password for a concrete entry.
```php
$zipOutputFile->setEntryPassword($entryName, $password);
```
Set a password and encryption method for a concrete entry.
```php
$zipOutputFile->setEntryPassword($entryName, $password, $encryptionMethod);
// Support encryption methods:
// ZipEntry::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
// ZipEntry::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption (default value)
// ZipFile::ENCRYPTION_METHOD_TRADITIONAL - Traditional PKWARE Encryption
// ZipFile::ENCRYPTION_METHOD_WINZIP_AES - WinZip AES Encryption
```
Remove password from all entries.
```php
$zipOutputFile->removePasswordAllEntries();
$zipFile->withoutPassword();
```
Remove password for concrete zip entry.
#### <a name="Documentation-ZipAlign-Usage"></a> ZipAlign Usage
Set archive alignment ([`zipalign`](https://developer.android.com/studio/command-line/zipalign.html)).
```php
$zipOutputFile->removePasswordFromEntry($entryName);
// before save or output
$zipFile->setAlign(4); // alternative command: zipalign -f -v 4 filename.zip
```
#### <a name="Documentation-Save-Or-Output-Entries"></a> Save Zip File or Output
Save archive to a file.
```php
$zipOutputFile->saveAsFile($filename);
$zipFile->saveAsFile($filename);
```
Save archive to a stream.
```php
$handle = fopen($filename, 'w+b');
$autoCloseResource = true;
$zipOutputFile->saveAsStream($handle, $autoCloseResource);
if(!$autoCloseResource){
fclose($handle);
}
// $fp = fopen($filename, 'w+b');
$zipFile->saveAsStream($fp);
```
Returns the zip archive as a string.
```php
$rawZipArchiveBytes = $zipOutputFile->outputAsString();
$rawZipArchiveBytes = $zipFile->outputAsString();
```
Output .ZIP archive as attachment and terminate.
```php
$zipOutputFile->outputAsAttachment($outputFilename);
$zipFile->outputAsAttachment($outputFilename);
// or set mime type
$zipOutputFile->outputAsAttachment($outputFilename = 'output.zip', $mimeType = 'application/zip');
$mimeType = 'application/zip'
$zipFile->outputAsAttachment($outputFilename, $mimeType);
```
Extract all files to directory.
Rewrite and reopen zip archive.
```php
$zipOutputFile->extractTo($directory);
```
Extract some files to directory.
```php
$extractOnlyFiles = ["filename1", "filename2", "dir/dir/dir/"];
$zipOutputFile->extractTo($directory, $extractOnlyFiles);
```
Get entry contents.
```php
$data = $zipOutputFile->getEntryContent($entryName);
```
Foreach zip entries.
```php
foreach($zipOutputFile as $entryName => $dataContent){
echo "Entry: $entryName" . PHP_EOL;
echo "Data: $dataContent" . PHP_EOL;
echo "-----------------------------" . PHP_EOL;
}
```
Iterator zip entries.
```php
$iterator = new \ArrayIterator($zipOutputFile);
while ($iterator->valid())
{
$entryName = $iterator->key();
$dataContent = $iterator->current();
echo "Entry: $entryName" . PHP_EOL;
echo "Data: $dataContent" . PHP_EOL;
echo "-----------------------------" . PHP_EOL;
$iterator->next();
}
```
Set zip alignment (alternate program `zipalign`).
```php
// before save or output
$zipOutputFile->setAlign(4); // alternative cmd: zipalign -f -v 4 filename.zip
$zipFile->rewrite();
```
#### <a name="Documentation-Close-Zip-Archive"></a> Close Zip Archive
Close zip archive.
```php
$zipOutputFile->close();
$zipFile->close();
```
Examples
--------
Create, open, extract and update archive.
```php
$outputFilename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'output.zip';
$outputDirExtract = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'extract';
if(!is_dir($outputDirExtract)){
mkdir($outputDirExtract, 0755, true);
}
$zipOutputFile = \PhpZip\ZipOutputFile::create(); // create archive
$zipOutputFile->addDir(__DIR__, true); // add this dir to archive
$zipOutputFile->saveAsFile($outputFilename); // save as file
$zipOutputFile->close(); // close output file, release all streams
$zipFile = \PhpZip\ZipFile::openFromFile($outputFilename); // open zip archive from file
$zipFile->extractTo($outputDirExtract); // extract files to dir
$zipOutputFile = $zipFile->edit(); // create zip output archive for update
$zipOutputFile->deleteFromRegex('~^\.~'); // delete all hidden (Unix) files
$zipOutputFile->addFromString('dir/file.txt', 'Test file'); // add files from string contents
$zipOutputFile->saveAsFile($outputFilename); // update zip file
$zipOutputFile->close(); // close output file, release all streams
$zipFile->close(); // close input file, release all streams
```
Other examples can be found in the `tests/` folder
Running Tests
-------------
### <a name="Running-Tests"></a> Running Tests
Installing development dependencies.
```bash
vendor/bin/phpunit -v --tap -c bootstrap.xml
```
composer install --dev
```
Run tests
```bash
vendor/bin/phpunit -v -c bootstrap.xml
```
### <a name="Upgrade"></a> Upgrade version 2 to version 3
Update to the New Major Version via Composer
```json
{
"require": {
"nelexa/zip": "^3.0"
}
}
```
Next, use Composer to download new versions of the libraries:
```bash
composer update nelexa/zip
```
Update your Code to Work with the New Version:
- Class `ZipOutputFile` merged to `ZipFile` and removed.
+ `new \PhpZip\ZipOutputFile()` to `new \PhpZip\ZipFile()`
- Static initialization methods are now not static.
+ `\PhpZip\ZipFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);`
+ `\PhpZip\ZipOutputFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);`
+ `\PhpZip\ZipFile::openFromString($contents);` to `(new \PhpZip\ZipFile())->openFromString($contents);`
+ `\PhpZip\ZipFile::openFromStream($stream);` to `(new \PhpZip\ZipFile())->openFromStream($stream);`
+ `\PhpZip\ZipOutputFile::create()` to `new \PhpZip\ZipFile()`
+ `\PhpZip\ZipOutputFile::openFromZipFile(\PhpZip\ZipFile $zipFile)` &gt; `(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`

View File

@ -4,13 +4,15 @@
"type": "library",
"keywords": [
"zip",
"unzip",
"archive",
"extract",
"winzip",
"zipalign"
],
"require-dev": {
"phpunit/phpunit": "4.8"
"phpunit/phpunit": "4.8",
"codeclimate/php-test-reporter": "^0.4.4"
},
"license": "MIT",
"authors": [
@ -22,17 +24,21 @@
],
"minimum-stability": "stable",
"require": {
"php-64bit": "^5.4 || ^7.0",
"ext-mbstring": "*"
"php-64bit": "^5.5 || ^7.0"
},
"autoload": {
"psr-4": {
"": "src/"
"PhpZip\\": "src/PhpZip"
}
},
"autoload-dev": {
"psr-4": {
"": "tests/"
"PhpZip\\": "tests/PhpZip"
}
},
"suggest": {
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
"ext-bz2": "Needed to support BZIP2 compression"
}
}

View 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);
}

View File

@ -2,6 +2,7 @@
namespace PhpZip\Crypto;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipCryptoException;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\CryptoUtil;
@ -12,7 +13,7 @@ use PhpZip\Util\CryptoUtil;
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class TraditionalPkwareEncryptionEngine
class TraditionalPkwareEncryptionEngine implements CryptoEngine
{
/**
* Encryption header size
@ -154,7 +155,7 @@ class TraditionalPkwareEncryptionEngine
if ($this->entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
// compare against the file type from extended local headers
$checkByte = ($this->entry->getRawTime() >> 8) & 0xff;
$checkByte = ($this->entry->getTime() >> 8) & 0xff;
} else {
// compare against the CRC otherwise
$checkByte = ($this->entry->getCrc() >> 24) & 0xff;
@ -187,11 +188,13 @@ class TraditionalPkwareEncryptionEngine
* Encryption data
*
* @param string $data
* @param int $crc
* @return string
*/
public function encrypt($data, $crc)
public function encrypt($data)
{
$crc = ($this->entry->isDataDescriptorRequired() ?
($this->entry->getTime() & 0x0000ffff) << 16 :
$this->entry->getCrc());
$headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE);
// Initialize again since the generated bytes were encrypted.
@ -206,11 +209,12 @@ class TraditionalPkwareEncryptionEngine
/**
* @param string $content
* @return string
* @throws ZipCryptoException
*/
private function encryptData($content)
{
if ($content === null) {
throw new \RuntimeException();
if (null === $content) {
throw new ZipCryptoException('content is null');
}
$buff = '';
foreach (unpack('C*', $content) as $val) {
@ -223,7 +227,7 @@ class TraditionalPkwareEncryptionEngine
* @param int $byte
* @return int
*/
protected function encryptByte($byte)
private function encryptByte($byte)
{
$tempVal = $byte ^ $this->decryptByte() & 0xff;
$this->updateKeys($byte);

View File

@ -1,6 +1,7 @@
<?php
namespace PhpZip\Crypto;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Exception\ZipCryptoException;
use PhpZip\Extra\WinZipAesEntryExtraField;
@ -13,7 +14,7 @@ use PhpZip\Util\CryptoUtil;
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class WinZipAesEngine
class WinZipAesEngine implements CryptoEngine
{
/**
* The block size of the Advanced Encryption Specification (AES) Algorithm
@ -42,12 +43,12 @@ class WinZipAesEngine
/**
* Decrypt from stream resource.
*
* @param resource $stream Input stream resource
* @param string $content Input stream buffer
* @return string
* @throws ZipAuthenticationException
* @throws ZipCryptoException
*/
public function decrypt($stream)
public function decrypt($content)
{
/**
* @var WinZipAesEntryExtraField $field
@ -57,20 +58,20 @@ class WinZipAesEngine
throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)");
}
$pos = ftell($stream);
// Get key strength.
$keyStrengthBits = $field->getKeyStrength();
$keyStrengthBytes = $keyStrengthBits / 8;
$salt = fread($stream, $keyStrengthBytes / 2);
$passwordVerifier = fread($stream, self::PWD_VERIFIER_BITS / 8);
$pos = $keyStrengthBytes / 2;
$salt = substr($content, 0, $pos);
$passwordVerifier = substr($content, $pos, self::PWD_VERIFIER_BITS / 8);
$pos += self::PWD_VERIFIER_BITS / 8;
$sha1Size = 20;
// Init start, end and size of encrypted data.
$endPos = $pos + $this->entry->getCompressedSize();
$start = ftell($stream);
$start = $pos;
$endPos = strlen($content);
$footerSize = $sha1Size / 2;
$end = $endPos - $footerSize;
$size = $end - $start;
@ -80,9 +81,8 @@ class WinZipAesEngine
}
// Load authentication code.
fseek($stream, $end, SEEK_SET);
$authenticationCode = fread($stream, $footerSize);
if (ftell($stream) !== $endPos) {
$authenticationCode = substr($content, $end, $footerSize);
if ($end + $footerSize !== $endPos) {
// This should never happen unless someone is writing to the
// end of the file concurrently!
throw new ZipCryptoException("Expected end of file after WinZip AES authentication code!");
@ -95,27 +95,33 @@ class WinZipAesEngine
// WinZip 99-character limit
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
$password = substr($password, 0, 99);
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(chr(0), $ctrIvSize);
do {
// Here comes the strange part about WinZip AES encryption:
// Its unorthodox use of the Password-Based Key Derivation
// Function 2 (PBKDF2) of PKCS #5 V2.0 alias RFC 2898.
// Yes, the password verifier is only a 16 bit value.
// So we must use the MAC for password verification, too.
$keyParam = hash_pbkdf2("sha1", $password, $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true);
$ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8;
$iv = str_repeat(chr(0), $ctrIvSize);
$keyParam = hash_pbkdf2(
"sha1",
$password,
$salt,
self::ITERATION_COUNT,
(2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8,
true
);
$key = substr($keyParam, 0, $keyStrengthBytes);
$sha1MacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes);
// Verify password.
} while (!$passwordVerifier === substr($keyParam, 2 * $keyStrengthBytes));
$content = stream_get_contents($stream, $size, $start);
$content = substr($content, $start, $size);
$mac = hash_hmac('sha1', $content, $sha1MacParam, true);
if ($authenticationCode !== substr($mac, 0, 10)) {
throw new ZipAuthenticationException($this->entry->getName() . " (authenticated WinZip AES entry content has been tampered with)");
if (substr($mac, 0, 10) !== $authenticationCode) {
throw new ZipAuthenticationException($this->entry->getName() .
" (authenticated WinZip AES entry content has been tampered with)");
}
return self::aesCtrSegmentIntegerCounter(false, $content, $key, $iv);
@ -137,7 +143,7 @@ class WinZipAesEngine
for ($i = 0; $i < $numOfBlocks; ++$i) {
for ($j = 0; $j < 16; ++$j) {
$n = ord($iv[$j]);
if (++$n === 0x100) {
if (0x100 === ++$n) {
// overflow, set this one to 0, increment next
$iv[$j] = chr(0);
} else {
@ -161,6 +167,7 @@ class WinZipAesEngine
* @param string $key Aes key
* @param string $iv Aes IV
* @return string Encrypted data
* @throws RuntimeException
*/
private static function encryptCtr($data, $key, $iv)
{
@ -170,7 +177,7 @@ class WinZipAesEngine
} elseif (extension_loaded("mcrypt")) {
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
} else {
throw new \RuntimeException('Extension openssl or mcrypt not loaded');
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
}
@ -181,6 +188,7 @@ class WinZipAesEngine
* @param string $key Aes key
* @param string $iv Aes IV
* @return string Raw data
* @throws RuntimeException
*/
private static function decryptCtr($data, $key, $iv)
{
@ -190,7 +198,7 @@ class WinZipAesEngine
} elseif (extension_loaded("mcrypt")) {
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv);
} else {
throw new \RuntimeException('Extension openssl or mcrypt not loaded');
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
}

View File

@ -8,7 +8,7 @@ namespace PhpZip\Exception;
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class IllegalArgumentException extends ZipException
class InvalidArgumentException extends ZipException
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace PhpZip\Exception;
/**
* Runtime exception.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class RuntimeException extends ZipException
{
}

View File

@ -45,7 +45,7 @@ abstract class ExtraField implements ExtraFieldHeader
if (isset(self::getRegistry()[$headerId])) {
$extraClassName = self::getRegistry()[$headerId];
$extraField = new $extraClassName;
if ($headerId !== $extraField::getHeaderId()) {
if ($extraField::getHeaderId() !== $headerId) {
throw new ZipException('Runtime error support headerId ' . $headerId);
}
} else {
@ -61,9 +61,9 @@ abstract class ExtraField implements ExtraFieldHeader
*/
private static function getRegistry()
{
if (self::$registry === null) {
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = '\PhpZip\Extra\WinZipAesEntryExtraField';
self::$registry[NtfsExtraField::getHeaderId()] = '\PhpZip\Extra\NtfsExtraField';
if (null === self::$registry) {
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
}
return self::$registry;
}
@ -80,7 +80,7 @@ abstract class ExtraField implements ExtraFieldHeader
if (0x0000 > $size || $size > 0xffff) {
throw new ZipException('size data block out of range.');
}
$fp = fopen('php://temp', 'r+b');
$fp = fopen('php://memory', 'r+b');
if (0 === $size) return $fp;
$this->writeTo($fp, 0);
rewind($fp);

View File

@ -1,7 +1,6 @@
<?php
namespace PhpZip\Extra;
use PhpZip\Exception\ZipException;
/**
@ -118,8 +117,17 @@ class ExtraFields
}
if (0 === $size) return '';
$fp = fopen('php://temp', 'r+b');
$this->writeTo($fp, 0);
$fp = fopen('php://memory', 'r+b');
$offset = 0;
/**
* @var ExtraField $ef
*/
foreach ($this->extra as $ef) {
fwrite($fp, pack('vv', $ef::getHeaderId(), $ef->getDataSize()));
$offset += 4;
fwrite($fp, $ef->writeTo($fp, $offset));
$offset += $ef->getDataSize();
}
rewind($fp);
$content = stream_get_contents($fp);
fclose($fp);
@ -148,27 +156,6 @@ class ExtraFields
return $length;
}
/**
* Serializes a list of Extra Fields of ExtraField::getExtraLength bytes to the
* stream resource $handle at the zero based offset $off.
*
* @param resource $handle
* @param int $off Offset
*/
private function writeTo($handle, $off)
{
fseek($handle, $off, SEEK_SET);
/**
* @var ExtraField $ef
*/
foreach ($this->extra as $ef) {
fwrite($handle, pack('vv', $ef::getHeaderId(), $ef->getDataSize()));
$off += 4;
fwrite($handle, $ef->writeTo($handle, $off));
$off += $ef->getDataSize();
}
}
/**
* Initializes this Extra Field by deserializing a Data Block of
* size bytes $size from the resource $handle at the zero based offset $off.
@ -187,7 +174,7 @@ class ExtraFields
if (null !== $handle && 0 < $size) {
$end = $off + $size;
while ($off < $end) {
fseek($handle, $off, SEEK_SET);
fseek($handle, $off);
$unpack = unpack('vheaderId/vdataSize', fread($handle, 4));
$off += 4;
$extraField = ExtraField::create($unpack['headerId']);

View File

@ -86,7 +86,7 @@ class NtfsExtraField extends ExtraField
fseek($handle, $off, SEEK_SET);
$unpack = unpack('vtag/vsizeAttr', fread($handle, 4));
if ($unpack['sizeAttr'] === 24) {
if (24 === $unpack['sizeAttr']) {
$tagData = fread($handle, $unpack['sizeAttr']);
$this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600;
@ -110,7 +110,7 @@ class NtfsExtraField extends ExtraField
*/
public function writeTo($handle, $off)
{
if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) {
if (null !== $this->mtime && null !== $this->atime && null !== $this->ctime) {
fseek($handle, $off, SEEK_SET);
fwrite($handle, pack('Vvv', 0, 1, 8 * 3 + strlen($this->rawData)));
$mtimeLong = ($this->mtime + 11644473600) * 10000000;

View File

@ -0,0 +1,470 @@
<?php
namespace PhpZip\Model;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipNotFoundEntry;
use PhpZip\Model\Entry\ZipNewStringEntry;
use PhpZip\Model\Entry\ZipReadEntry;
use PhpZip\ZipFile;
/**
* Read Central Directory
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class CentralDirectory
{
/** Central File Header signature. */
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
/**
* @var EndOfCentralDirectory End of Central Directory
*/
private $endOfCentralDirectory;
/**
* @var ZipEntry[] Maps entry names to zip entries.
*/
private $entries = [];
/**
* @var ZipEntry[] New and modified entries
*/
private $modifiedEntries = [];
/**
* @var int Default compression level for the methods DEFLATED and BZIP2.
*/
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
/**
* @var int|null ZipAlign setting
*/
private $zipAlign;
/**
* @var string New password
*/
private $password;
/**
* @var int
*/
private $encryptionMethod;
/**
* @var bool
*/
private $clearPassword;
public function __construct()
{
$this->endOfCentralDirectory = new EndOfCentralDirectory();
}
/**
* Reads the central directory from the given seekable byte channel
* and populates the internal tables with ZipEntry instances.
*
* The ZipEntry's will know all data that can be obtained from the
* central directory alone, but not the data that requires the local
* file header or additional data to be read.
*
* @param resource $inputStream
* @throws ZipException
*/
public function mountCentralDirectory($inputStream)
{
$this->modifiedEntries = [];
$this->checkZipFileSignature($inputStream);
$this->endOfCentralDirectory->findCentralDirectory($inputStream);
$numEntries = $this->endOfCentralDirectory->getCentralDirectoryEntriesSize();
$entries = [];
for (; $numEntries > 0; $numEntries--) {
$entry = new ZipReadEntry($inputStream);
$entry->setCentralDirectory($this);
// Re-load virtual offset after ZIP64 Extended Information
// Extra Field may have been parsed, map it to the real
// offset and conditionally update the preamble size from it.
$lfhOff = $this->endOfCentralDirectory->getMapper()->map($entry->getOffset());
if ($lfhOff < $this->endOfCentralDirectory->getPreamble()) {
$this->endOfCentralDirectory->setPreamble($lfhOff);
}
$entries[$entry->getName()] = $entry;
}
if (0 !== $numEntries % 0x10000) {
throw new ZipException("Expected " . abs($numEntries) .
($numEntries > 0 ? " more" : " less") .
" entries in the Central Directory!");
}
$this->entries = $entries;
if ($this->endOfCentralDirectory->getPreamble() + $this->endOfCentralDirectory->getPostamble() >= fstat($inputStream)['size']) {
assert(0 === $numEntries);
$this->checkZipFileSignature($inputStream);
}
}
/**
* Check zip file signature
*
* @param resource $inputStream
* @throws ZipException if this not .ZIP file.
*/
private function checkZipFileSignature($inputStream)
{
rewind($inputStream);
// Constraint: A ZIP file must start with a Local File Header
// or a (ZIP64) End Of Central Directory Record if it's empty.
$signatureBytes = fread($inputStream, 4);
if (strlen($signatureBytes) < 4) {
throw new ZipException("Invalid zip file.");
}
$signature = unpack('V', $signatureBytes)[1];
if (
ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature
&& EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
&& EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature
) {
throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature);
}
}
/**
* Set compression method for new or rewrites entries.
* @param int $compressionLevel
* @throws InvalidArgumentException
* @see ZipFile::LEVEL_DEFAULT_COMPRESSION
* @see ZipFile::LEVEL_BEST_SPEED
* @see ZipFile::LEVEL_BEST_COMPRESSION
*/
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
{
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
) {
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION);
}
$this->compressionLevel = $compressionLevel;
}
/**
* @return ZipEntry[]
*/
public function &getEntries()
{
return $this->entries;
}
/**
* @param string $entryName
* @return ZipEntry
* @throws ZipNotFoundEntry
*/
public function getEntry($entryName)
{
if (!isset($this->entries[$entryName])) {
throw new ZipNotFoundEntry('Zip entry ' . $entryName . ' not found');
}
return $this->entries[$entryName];
}
/**
* @return EndOfCentralDirectory
*/
public function getEndOfCentralDirectory()
{
return $this->endOfCentralDirectory;
}
public function getArchiveComment()
{
return null === $this->endOfCentralDirectory->getComment() ?
'' :
$this->endOfCentralDirectory->getComment();
}
/**
* Set entry comment
* @param string $entryName
* @param string|null $comment
* @throws ZipNotFoundEntry
*/
public function setEntryComment($entryName, $comment)
{
if (isset($this->modifiedEntries[$entryName])) {
$this->modifiedEntries[$entryName]->setComment($comment);
} elseif (isset($this->entries[$entryName])) {
$entry = clone $this->entries[$entryName];
$entry->setComment($comment);
$this->putInModified($entryName, $entry);
} else {
throw new ZipNotFoundEntry("Not found entry " . $entryName);
}
}
/**
* @param string|null $password
* @param int|null $encryptionMethod
*/
public function setNewPassword($password, $encryptionMethod = null)
{
$this->password = $password;
$this->encryptionMethod = $encryptionMethod;
$this->clearPassword = $password === null;
}
/**
* @return int|null
*/
public function getZipAlign()
{
return $this->zipAlign;
}
/**
* @param int|null $zipAlign
*/
public function setZipAlign($zipAlign = null)
{
if (null === $zipAlign) {
$this->zipAlign = null;
return;
}
$this->zipAlign = (int)$zipAlign;
}
/**
* Put modification or new entries.
*
* @param $entryName
* @param ZipEntry $entry
*/
public function putInModified($entryName, ZipEntry $entry)
{
$this->modifiedEntries[$entryName] = $entry;
}
/**
* @param string $entryName
* @throws ZipNotFoundEntry
*/
public function deleteEntry($entryName)
{
if (isset($this->entries[$entryName])) {
$this->modifiedEntries[$entryName] = null;
} elseif (isset($this->modifiedEntries[$entryName])) {
unset($this->modifiedEntries[$entryName]);
} else {
throw new ZipNotFoundEntry("Not found entry " . $entryName);
}
}
/**
* @param string $regexPattern
* @return bool
*/
public function deleteEntriesFromRegex($regexPattern)
{
$count = 0;
foreach ($this->modifiedEntries as $entryName => &$entry) {
if (preg_match($regexPattern, $entryName)) {
unset($entry);
$count++;
}
}
foreach ($this->entries as $entryName => $entry) {
if (preg_match($regexPattern, $entryName)) {
$this->modifiedEntries[$entryName] = null;
$count++;
}
}
return $count > 0;
}
/**
* @param string $oldName
* @param string $newName
* @throws InvalidArgumentException
* @throws ZipNotFoundEntry
*/
public function rename($oldName, $newName)
{
$oldName = (string)$oldName;
$newName = (string)$newName;
if (isset($this->entries[$newName]) || isset($this->modifiedEntries[$newName])) {
throw new InvalidArgumentException("New entry name " . $newName . ' is exists.');
}
if (isset($this->modifiedEntries[$oldName]) || isset($this->entries[$oldName])) {
$newEntry = clone (isset($this->modifiedEntries[$oldName]) ?
$this->modifiedEntries[$oldName] :
$this->entries[$oldName]);
$newEntry->setName($newName);
$this->modifiedEntries[$oldName] = null;
$this->modifiedEntries[$newName] = $newEntry;
return;
}
throw new ZipNotFoundEntry("Not found entry " . $oldName);
}
/**
* Delete all entries.
*/
public function deleteAll()
{
$this->modifiedEntries = [];
foreach ($this->entries as $entry) {
$this->modifiedEntries[$entry->getName()] = null;
}
}
/**
* @param resource $outputStream
*/
public function writeArchive($outputStream)
{
/**
* @var ZipEntry[] $memoryEntriesResult
*/
$memoryEntriesResult = [];
foreach ($this->entries as $entryName => $entry) {
if (isset($this->modifiedEntries[$entryName])) continue;
if (
(null !== $this->password || $this->clearPassword) &&
$entry->isEncrypted() &&
$entry->getPassword() !== null &&
(
$entry->getPassword() !== $this->password ||
$entry->getEncryptionMethod() !== $this->encryptionMethod
)
) {
$prototypeEntry = new ZipNewStringEntry($entry->getEntryContent());
$prototypeEntry->setName($entry->getName());
$prototypeEntry->setMethod($entry->getMethod());
$prototypeEntry->setTime($entry->getTime());
$prototypeEntry->setExternalAttributes($entry->getExternalAttributes());
$prototypeEntry->setExtra($entry->getExtra());
$prototypeEntry->setPassword($this->password, $this->encryptionMethod);
if ($this->clearPassword) {
$prototypeEntry->clearEncryption();
}
} else {
$prototypeEntry = clone $entry;
}
$memoryEntriesResult[$entryName] = $prototypeEntry;
}
foreach ($this->modifiedEntries as $entryName => $outputEntry) {
if (null === $outputEntry) { // remove marked entry
unset($memoryEntriesResult[$entryName]);
} else {
if (null !== $this->password) {
$outputEntry->setPassword($this->password, $this->encryptionMethod);
}
$memoryEntriesResult[$entryName] = $outputEntry;
}
}
foreach ($memoryEntriesResult as $key => $outputEntry) {
$outputEntry->setCentralDirectory($this);
$outputEntry->writeEntry($outputStream);
}
$centralDirectoryOffset = ftell($outputStream);
foreach ($memoryEntriesResult as $key => $outputEntry) {
if (!$this->writeCentralFileHeader($outputStream, $outputEntry)) {
unset($memoryEntriesResult[$key]);
}
}
$centralDirectoryEntries = sizeof($memoryEntriesResult);
$this->getEndOfCentralDirectory()->writeEndOfCentralDirectory(
$outputStream,
$centralDirectoryEntries,
$centralDirectoryOffset
);
}
/**
* Writes a Central File Header record.
*
* @param resource $outputStream
* @param ZipEntry $entry
* @return bool false if and only if the record has been skipped,
* i.e. not written for some other reason than an I/O error.
*/
private function writeCentralFileHeader($outputStream, ZipEntry $entry)
{
$compressedSize = $entry->getCompressedSize();
$size = $entry->getSize();
// This test MUST NOT include the CRC-32 because VV_AE_2 sets it to
// UNKNOWN!
if (ZipEntry::UNKNOWN === ($compressedSize | $size)) {
return false;
}
$extra = $entry->getExtra();
$extraSize = strlen($extra);
$commentLength = strlen($entry->getComment());
fwrite(
$outputStream,
pack(
'VvvvvVVVVvvvvvVV',
// central file header signature 4 bytes (0x02014b50)
self::CENTRAL_FILE_HEADER_SIG,
// version made by 2 bytes
($entry->getPlatform() << 8) | 63,
// version needed to extract 2 bytes
$entry->getVersionNeededToExtract(),
// general purpose bit flag 2 bytes
$entry->getGeneralPurposeBitFlags(),
// compression method 2 bytes
$entry->getMethod(),
// last mod file datetime 4 bytes
$entry->getTime(),
// crc-32 4 bytes
$entry->getCrc(),
// compressed size 4 bytes
$entry->getCompressedSize(),
// uncompressed size 4 bytes
$entry->getSize(),
// file name length 2 bytes
strlen($entry->getName()),
// extra field length 2 bytes
$extraSize,
// file comment length 2 bytes
$commentLength,
// disk number start 2 bytes
0,
// internal file attributes 2 bytes
0,
// external file attributes 4 bytes
$entry->getExternalAttributes(),
// relative offset of local header 4 bytes
$entry->getOffset()
)
);
// file name (variable size)
fwrite($outputStream, $entry->getName());
if (0 < $extraSize) {
// extra field (variable size)
fwrite($outputStream, $extra);
}
if (0 < $commentLength) {
// file comment (variable size)
fwrite($outputStream, $entry->getComment());
}
return true;
}
public function release()
{
unset($this->entries);
unset($this->modifiedEntries);
}
function __destruct()
{
$this->release();
}
}

View 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);
}
}
}

View File

@ -0,0 +1,922 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\DefaultExtraField;
use PhpZip\Extra\ExtraField;
use PhpZip\Extra\ExtraFields;
use PhpZip\Extra\WinZipAesEntryExtraField;
use PhpZip\Model\CentralDirectory;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\DateTimeConverter;
use PhpZip\Util\PackUtil;
use PhpZip\ZipFile;
/**
* Abstract ZIP entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
abstract class ZipAbstractEntry implements ZipEntry
{
/**
* @var CentralDirectory
*/
private $centralDirectory;
/**
* @var int Bit flags for init state.
*/
private $init;
/**
* @var string Entry name (filename in archive)
*/
private $name;
/**
* @var int Made by platform
*/
private $platform;
/**
* @var int
*/
private $versionNeededToExtract = 20;
/**
* @var int
*/
private $general;
/**
* @var int Compression method
*/
private $method;
/**
* @var int Dos time
*/
private $dosTime;
/**
* @var int Crc32
*/
private $crc;
/**
* @var int Compressed size
*/
private $compressedSize = self::UNKNOWN;
/**
* @var int Uncompressed size
*/
private $size = self::UNKNOWN;
/**
* @var int External attributes
*/
private $externalAttributes;
/**
* @var int Relative Offset Of Local File Header.
*/
private $offset = self::UNKNOWN;
/**
* The map of Extra Fields.
* Maps from Header ID [Integer] to Extra Field [ExtraField].
* Should be null or may be empty if no Extra Fields are used.
*
* @var ExtraFields
*/
private $fields;
/**
* @var string Comment field.
*/
private $comment;
/**
* @var string Entry password for read or write encryption data.
*/
private $password;
/**
* Encryption method.
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
* @var int
*/
private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL;
/**
* @var int
*/
private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION;
/**
* @param int $mask
* @return bool
*/
private function isInit($mask)
{
return 0 !== ($this->init & $mask);
}
/**
* @param int $mask
* @param bool $init
*/
private function setInit($mask, $init)
{
if ($init) {
$this->init |= $mask;
} else {
$this->init &= ~$mask;
}
}
/**
* @return CentralDirectory
*/
public function getCentralDirectory()
{
return $this->centralDirectory;
}
/**
* @param CentralDirectory $centralDirectory
* @return ZipEntry
*/
public function setCentralDirectory(CentralDirectory $centralDirectory)
{
$this->centralDirectory = $centralDirectory;
return $this;
}
/**
* Returns the ZIP entry name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set entry name.
*
* @param string $name New entry name
* @return ZipEntry
* @throws ZipException
*/
public function setName($name)
{
$length = strlen($name);
if (0x0000 > $length || $length > 0xffff) {
throw new ZipException('Illegal zip entry name parameter');
}
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
$this->name = $name;
return $this;
}
/**
* @return int Get platform
*/
public function getPlatform()
{
return $this->isInit(self::BIT_PLATFORM) ? $this->platform & 0xffff : self::UNKNOWN;
}
/**
* Set platform
*
* @param int $platform
* @return ZipEntry
* @throws ZipException
*/
public function setPlatform($platform)
{
$known = self::UNKNOWN !== $platform;
if ($known) {
if (0x00 > $platform || $platform > 0xff) {
throw new ZipException("Platform out of range");
}
$this->platform = $platform;
} else {
$this->platform = 0;
}
$this->setInit(self::BIT_PLATFORM, $known);
return $this;
}
/**
* Version needed to extract.
*
* @return int
*/
public function getVersionNeededToExtract()
{
return $this->versionNeededToExtract;
}
/**
* Set version needed to extract.
*
* @param int $version
* @return ZipEntry
*/
public function setVersionNeededToExtract($version)
{
$this->versionNeededToExtract = $version;
return $this;
}
/**
* @return bool
*/
public function isZip64ExtensionsRequired()
{
// Offset MUST be considered in decision about ZIP64 format - see
// description of Data Descriptor in ZIP File Format Specification!
return 0xffffffff <= $this->getCompressedSize()
|| 0xffffffff <= $this->getSize()
|| 0xffffffff <= $this->getOffset();
}
/**
* Returns the compressed size of this entry.
*
* @see int
*/
public function getCompressedSize()
{
return $this->compressedSize;
}
/**
* Sets the compressed size of this entry.
*
* @param int $compressedSize The Compressed Size.
* @return ZipEntry
* @throws ZipException
*/
public function setCompressedSize($compressedSize)
{
if (self::UNKNOWN != $compressedSize) {
if (0 > $compressedSize || $compressedSize > 0x7fffffffffffffff) {
throw new ZipException("Compressed size out of range - " . $this->name);
}
}
$this->compressedSize = $compressedSize;
return $this;
}
/**
* Returns the uncompressed size of this entry.
*
* @see ZipEntry::setCompressedSize
*/
public function getSize()
{
return $this->size;
}
/**
* Sets the uncompressed size of this entry.
*
* @param int $size The (Uncompressed) Size.
* @return ZipEntry
* @throws ZipException
*/
public function setSize($size)
{
if (self::UNKNOWN != $size) {
if (0 > $size || $size > 0x7fffffffffffffff) {
throw new ZipException("Uncompressed Size out of range - " . $this->name);
}
}
$this->size = $size;
return $this;
}
/**
* Return relative Offset Of Local File Header.
*
* @return int
*/
public function getOffset()
{
return $this->offset;
}
/**
* @param int $offset
* @return ZipEntry
* @throws ZipException
*/
public function setOffset($offset)
{
if (0 > $offset || $offset > 0x7fffffffffffffff) {
throw new ZipException("Offset out of range - " . $this->name);
}
$this->offset = $offset;
return $this;
}
/**
* Returns true if and only if this ZIP entry represents a directory entry
* (i.e. end with '/').
*
* @return bool
*/
public function isDirectory()
{
return $this->name[strlen($this->name) - 1] === '/';
}
/**
* Returns the General Purpose Bit Flags.
*
* @return bool
*/
public function getGeneralPurposeBitFlags()
{
return $this->general & 0xffff;
}
/**
* Sets the General Purpose Bit Flags.
*
* @var int general
* @return ZipEntry
* @throws ZipException
*/
public function setGeneralPurposeBitFlags($general)
{
if (0x0000 > $general || $general > 0xffff) {
throw new ZipException('general out of range');
}
$this->general = $general;
return $this;
}
/**
* Returns the indexed General Purpose Bit Flag.
*
* @param int $mask
* @return bool
*/
public function getGeneralPurposeBitFlag($mask)
{
return 0 !== ($this->general & $mask);
}
/**
* Sets the indexed General Purpose Bit Flag.
*
* @param int $mask
* @param bool $bit
* @return ZipEntry
*/
public function setGeneralPurposeBitFlag($mask, $bit)
{
if ($bit)
$this->general |= $mask;
else
$this->general &= ~$mask;
return $this;
}
/**
* Returns true if and only if this ZIP entry is encrypted.
*
* @return bool
*/
public function isEncrypted()
{
return $this->getGeneralPurposeBitFlag(self::GPBF_ENCRYPTED);
}
/**
* Sets the encryption property to false and removes any other
* encryption artifacts.
*
* @return ZipEntry
*/
public function clearEncryption()
{
$this->setEncrypted(false);
if (null !== $this->fields) {
$field = $this->fields->get(WinZipAesEntryExtraField::getHeaderId());
if (null !== $field) {
/**
* @var WinZipAesEntryExtraField $field
*/
$this->removeExtraField(WinZipAesEntryExtraField::getHeaderId());
}
if (self::METHOD_WINZIP_AES === $this->getMethod()) {
$this->setMethod(null === $field ? self::UNKNOWN : $field->getMethod());
}
}
$this->password = null;
return $this;
}
/**
* Sets the encryption flag for this ZIP entry.
*
* @param bool $encrypted
* @return ZipEntry
*/
public function setEncrypted($encrypted)
{
$this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted);
return $this;
}
/**
* Returns the compression method for this entry.
*
* @return int
*/
public function getMethod()
{
return $this->isInit(self::BIT_METHOD) ? $this->method & 0xffff : self::UNKNOWN;
}
/**
* Sets the compression method for this entry.
*
* @param int $method
* @return ZipEntry
* @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN.
*/
public function setMethod($method)
{
if (0x0000 > $method || $method > 0xffff) {
throw new ZipException('method out of range');
}
switch ($method) {
case self::METHOD_WINZIP_AES:
$this->method = $method;
$this->setInit(self::BIT_METHOD, true);
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
break;
case ZipFile::METHOD_STORED:
case ZipFile::METHOD_DEFLATED:
case ZipFile::METHOD_BZIP2:
$this->method = $method;
$this->setInit(self::BIT_METHOD, true);
break;
case self::UNKNOWN:
$this->method = ZipFile::METHOD_STORED;
$this->setInit(self::BIT_METHOD, false);
break;
default:
throw new ZipException($this->name . " (unsupported compression method $method)");
}
return $this;
}
/**
* Get Unix Timestamp
*
* @return int
*/
public function getTime()
{
if (!$this->isInit(self::BIT_DATE_TIME)) {
return self::UNKNOWN;
}
return DateTimeConverter::toUnixTimestamp($this->dosTime & 0xffffffff);
}
/**
* Set time from unix timestamp.
*
* @param int $unixTimestamp
* @return ZipEntry
*/
public function setTime($unixTimestamp)
{
$known = self::UNKNOWN != $unixTimestamp;
if ($known) {
$this->dosTime = DateTimeConverter::toDosTime($unixTimestamp);
} else {
$this->dosTime = 0;
}
$this->setInit(self::BIT_DATE_TIME, $known);
return $this;
}
/**
* Returns the external file attributes.
*
* @return int The external file attributes.
*/
public function getExternalAttributes()
{
if (!$this->isInit(self::BIT_EXTERNAL_ATTR)) {
return $this->isDirectory() ? 0x10 : 0;
}
return $this->externalAttributes & 0xffffffff;
}
/**
* Sets the external file attributes.
*
* @param int $externalAttributes the external file attributes.
* @return ZipEntry
* @throws ZipException
*/
public function setExternalAttributes($externalAttributes)
{
$known = self::UNKNOWN != $externalAttributes;
if ($known) {
if (0x00000000 > $externalAttributes || $externalAttributes > 0xffffffff) {
throw new ZipException("external file attributes out of range - " . $this->name);
}
$this->externalAttributes = $externalAttributes;
} else {
$this->externalAttributes = 0;
}
$this->setInit(self::BIT_EXTERNAL_ATTR, $known);
return $this;
}
/**
* Return extra field from header id.
*
* @param int $headerId
* @return ExtraField|null
*/
public function getExtraField($headerId)
{
return $this->fields === null ? null : $this->fields->get($headerId);
}
/**
* Add extra field.
*
* @param ExtraField $field
* @return ExtraField
* @throws ZipException
*/
public function addExtraField($field)
{
if (null === $field) {
throw new ZipException("extra field null");
}
if (null === $this->fields) {
$this->fields = new ExtraFields();
}
return $this->fields->add($field);
}
/**
* Return exists extra field from header id.
*
* @param int $headerId
* @return bool
*/
public function hasExtraField($headerId)
{
return $this->fields === null ? false : $this->fields->has($headerId);
}
/**
* Remove extra field from header id.
*
* @param int $headerId
* @return ExtraField|null
*/
public function removeExtraField($headerId)
{
return null !== $this->fields ? $this->fields->remove($headerId) : null;
}
/**
* Returns a protective copy of the serialized Extra Fields.
*
* @return string A new byte array holding the serialized Extra Fields.
* null is never returned.
*/
public function getExtra()
{
return $this->getExtraFields(false);
}
/**
* @param bool $zip64
* @return string
* @throws ZipException
*/
private function getExtraFields($zip64)
{
if ($zip64) {
$field = $this->composeZip64ExtraField();
if (null !== $field) {
if (null === $this->fields) {
$this->fields = new ExtraFields();
}
$this->fields->add($field);
}
} else {
assert(null === $this->fields || null === $this->fields->get(ExtraField::ZIP64_HEADER_ID));
}
return null === $this->fields ? null : $this->fields->getExtra();
}
/**
* Composes a ZIP64 Extended Information Extra Field from the properties
* of this entry.
* If no ZIP64 Extended Information Extra Field is required it is removed
* from the collection of Extra Fields.
*
* @return ExtraField|null
*/
private function composeZip64ExtraField()
{
$handle = fopen('php://memory', 'r+b');
// Write out Uncompressed Size.
$size = $this->getSize();
if (0xffffffff <= $size) {
fwrite($handle, PackUtil::packLongLE($size));
}
// Write out Compressed Size.
$compressedSize = $this->getCompressedSize();
if (0xffffffff <= $compressedSize) {
fwrite($handle, PackUtil::packLongLE($compressedSize));
}
// Write out Relative Header Offset.
$offset = $this->getOffset();
if (0xffffffff <= $offset) {
fwrite($handle, PackUtil::packLongLE($offset));
}
// Create ZIP64 Extended Information Extra Field from serialized data.
$field = null;
if (ftell($handle) > 0) {
$field = new DefaultExtraField(ExtraField::ZIP64_HEADER_ID);
$field->readFrom($handle, 0, ftell($handle));
} else {
$field = null;
}
return $field;
}
/**
* Sets the serialized Extra Fields by making a protective copy.
* Note that this method parses the serialized Extra Fields according to
* the ZIP File Format Specification and limits its size to 64 KB.
* Therefore, this property cannot not be used to hold arbitrary
* (application) data.
* Consider storing such data in a separate entry instead.
*
* @param string $data The byte array holding the serialized Extra Fields.
* @throws ZipException if the serialized Extra Fields exceed 64 KB
* @return ZipEntry
* or do not conform to the ZIP File Format Specification
*/
public function setExtra($data)
{
if (null !== $data) {
$length = strlen($data);
if (0x0000 > $length || $length > 0xffff) {
throw new ZipException("Extra Fields too large");
}
}
if (null === $data || strlen($data) <= 0) {
$this->fields = null;
} else {
$this->setExtraFields($data, false);
}
return $this;
}
/**
* @param string $data
* @param bool $zip64
*/
private function setExtraFields($data, $zip64)
{
if (null === $this->fields) {
$this->fields = new ExtraFields();
}
$handle = fopen('php://memory', 'r+b');
fwrite($handle, $data);
rewind($handle);
$this->fields->readFrom($handle, 0, strlen($data));
$result = false;
if ($zip64) {
$result = $this->parseZip64ExtraField();
}
if ($result) {
$this->fields->remove(ExtraField::ZIP64_HEADER_ID);
if ($this->fields->size() <= 0) {
if (0 !== $this->fields->size()) {
$this->fields = null;
}
}
}
fclose($handle);
}
/**
* Parses the properties of this entry from the ZIP64 Extended Information
* Extra Field, if present.
* The ZIP64 Extended Information Extra Field is not removed.
*
* @return bool
* @throws ZipException
*/
private function parseZip64ExtraField()
{
if (null === $this->fields) {
return false;
}
$ef = $this->fields->get(ExtraField::ZIP64_HEADER_ID);
if (null === $ef) {
return false;
}
$dataBlockHandle = $ef->getDataBlock();
$off = 0;
// Read in Uncompressed Size.
$size = $this->getSize();
if (0xffffffff <= $size) {
assert(0xffffffff === $size);
fseek($dataBlockHandle, $off);
$this->setSize(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
$off += 8;
}
// Read in Compressed Size.
$compressedSize = $this->getCompressedSize();
if (0xffffffff <= $compressedSize) {
assert(0xffffffff === $compressedSize);
fseek($dataBlockHandle, $off);
$this->setCompressedSize(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
$off += 8;
}
// Read in Relative Header Offset.
$offset = $this->getOffset();
if (0xffffffff <= $offset) {
assert(0xffffffff, $offset);
fseek($dataBlockHandle, $off);
$this->setOffset(PackUtil::unpackLongLE(fread($dataBlockHandle, 8)));
//$off += 8;
}
fclose($dataBlockHandle);
return true;
}
/**
* Returns comment entry
*
* @return string
*/
public function getComment()
{
return null != $this->comment ? $this->comment : "";
}
/**
* Set entry comment.
*
* @param $comment
* @return ZipEntry
* @throws ZipException
*/
public function setComment($comment)
{
if (null !== $comment) {
$commentLength = strlen($comment);
if (0x0000 > $commentLength || $commentLength > 0xffff) {
throw new ZipException("Comment too long");
}
}
$this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true);
$this->comment = $comment;
return $this;
}
/**
* @return bool
*/
public function isDataDescriptorRequired()
{
return self::UNKNOWN == ($this->getCrc() | $this->getCompressedSize() | $this->getSize());
}
/**
* Return crc32 content or 0 for WinZip AES v2
*
* @return int
*/
public function getCrc()
{
return $this->crc & 0xffffffff;
}
/**
* Set crc32 content.
*
* @param int $crc
* @return ZipEntry
* @throws ZipException
*/
public function setCrc($crc)
{
if (0x00000000 > $crc || $crc > 0xffffffff) {
throw new ZipException("CRC-32 out of range - " . $this->name);
}
$this->crc = $crc;
$this->setInit(self::BIT_CRC, true);
return $this;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set password and encryption method from entry
*
* @param string $password
* @param null|int $encryptionMethod
* @return ZipEntry
*/
public function setPassword($password, $encryptionMethod = null)
{
$this->password = $password;
if (null !== $encryptionMethod) {
$this->setEncryptionMethod($encryptionMethod);
}
$this->setEncrypted(!empty($this->password));
return $this;
}
/**
* @return int
*/
public function getEncryptionMethod()
{
return $this->encryptionMethod;
}
/**
* @return int
*/
public function getCompressionLevel()
{
return $this->compressionLevel;
}
/**
* @param int $compressionLevel
* @return ZipEntry
* @throws InvalidArgumentException
*/
public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION)
{
if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION ||
$compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION
) {
throw new InvalidArgumentException('Invalid compression level. Minimum level ' .
ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION);
}
$this->compressionLevel = $compressionLevel;
return $this;
}
/**
* Set encryption method
*
* @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL
* @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES
*
* @param int $encryptionMethod
* @return ZipEntry
* @throws ZipException
*/
public function setEncryptionMethod($encryptionMethod)
{
if (
ZipFile::ENCRYPTION_METHOD_TRADITIONAL !== $encryptionMethod &&
ZipFile::ENCRYPTION_METHOD_WINZIP_AES !== $encryptionMethod
) {
throw new ZipException('Invalid encryption method');
}
$this->encryptionMethod = $encryptionMethod;
$this->setEncrypted(true);
return $this;
}
/**
* Clone extra fields
*/
function __clone()
{
$this->fields = $this->fields !== null ? clone $this->fields : null;
}
}

View 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;
}
}

View File

@ -0,0 +1,268 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
use PhpZip\Crypto\WinZipAesEngine;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\WinZipAesEntryExtraField;
use PhpZip\Model\ZipEntry;
use PhpZip\Util\PackUtil;
use PhpZip\ZipFile;
/**
* Abstract class for new zip entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
abstract class ZipNewEntry extends ZipAbstractEntry
{
/**
* Default compression level for bzip2
*/
const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4;
/**
* Version needed to extract.
*
* @return int
*/
public function getVersionNeededToExtract()
{
$method = $this->getMethod();
return self::METHOD_WINZIP_AES === $method ? 51 :
(ZipFile::METHOD_BZIP2 === $method ? 46 :
($this->isZip64ExtensionsRequired() ? 45 :
(ZipFile::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
)
);
}
/**
* Write local file header, encryption header, file data and data descriptor to output stream.
*
* @param resource $outputStream
* @throws ZipException
*/
public function writeEntry($outputStream)
{
$nameLength = strlen($this->getName());
$size = $nameLength + strlen($this->getExtra()) + strlen($this->getComment());
if (0xffff < $size) {
throw new ZipException($this->getName()
. " (the total size of "
. $size
. " bytes for the name, extra fields and comment exceeds the maximum size of "
. 0xffff . " bytes)");
}
if (self::UNKNOWN === $this->getPlatform()) {
$this->setPlatform(self::PLATFORM_UNIX);
}
if (self::UNKNOWN === $this->getTime()) {
$this->setTime(time());
}
$method = $this->getMethod();
if (self::UNKNOWN === $method) {
$this->setMethod($method = ZipFile::METHOD_DEFLATED);
}
$skipCrc = false;
$encrypted = $this->isEncrypted();
$dd = $this->isDataDescriptorRequired();
// Compose General Purpose Bit Flag.
// See appendix D of PKWARE's ZIP File Format Specification.
$utf8 = true;
$general = ($encrypted ? self::GPBF_ENCRYPTED : 0)
| ($dd ? self::GPBF_DATA_DESCRIPTOR : 0)
| ($utf8 ? self::GPBF_UTF8 : 0);
$entryContent = $this->getEntryContent();
$this->setSize(strlen($entryContent));
$this->setCrc(crc32($entryContent));
if ($encrypted && null === $this->getPassword()) {
throw new ZipException("Can not password from entry " . $this->getName());
}
if (
$encrypted &&
(
self::METHOD_WINZIP_AES === $method ||
$this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_WINZIP_AES
)
) {
$field = null;
$method = $this->getMethod();
$keyStrength = 256; // bits
$compressedSize = $this->getCompressedSize();
if (self::METHOD_WINZIP_AES === $method) {
/**
* @var WinZipAesEntryExtraField $field
*/
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
if (null !== $field) {
$method = $field->getMethod();
if (self::UNKNOWN !== $compressedSize) {
$compressedSize -= $field->getKeyStrength() / 2 // salt value
+ 2 // password verification value
+ 10; // authentication code
}
$this->setMethod($method);
}
}
if (null === $field) {
$field = new WinZipAesEntryExtraField();
}
$field->setKeyStrength($keyStrength);
$field->setMethod($method);
$size = $this->getSize();
if (20 <= $size && ZipFile::METHOD_BZIP2 !== $method) {
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
} else {
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
$skipCrc = true;
}
$this->addExtraField($field);
if (self::UNKNOWN !== $compressedSize) {
$compressedSize += $field->getKeyStrength() / 2 // salt value
+ 2 // password verification value
+ 10; // authentication code
$this->setCompressedSize($compressedSize);
}
if ($skipCrc) {
$this->setCrc(0);
}
}
switch ($method) {
case ZipFile::METHOD_STORED:
break;
case ZipFile::METHOD_DEFLATED:
$entryContent = gzdeflate($entryContent, $this->getCompressionLevel());
break;
case ZipFile::METHOD_BZIP2:
$compressionLevel = $this->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ?
self::LEVEL_DEFAULT_BZIP2_COMPRESSION :
$this->getCompressionLevel();
$entryContent = bzcompress($entryContent, $compressionLevel);
if (is_int($entryContent)) {
throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent);
}
break;
default:
throw new ZipException($this->getName() . " (unsupported compression method " . $method . ")");
}
if ($encrypted) {
if ($this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_WINZIP_AES) {
if ($skipCrc) {
$this->setCrc(0);
}
$this->setMethod(self::METHOD_WINZIP_AES);
/**
* @var WinZipAesEntryExtraField $field
*/
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
$winZipAesEngine = new WinZipAesEngine($this, $field);
$entryContent = $winZipAesEngine->encrypt($entryContent);
} elseif ($this->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_TRADITIONAL) {
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($this);
$entryContent = $zipCryptoEngine->encrypt($entryContent);
}
}
$compressedSize = strlen($entryContent);
$this->setCompressedSize($compressedSize);
$offset = ftell($outputStream);
// Commit changes.
$this->setGeneralPurposeBitFlags($general);
$this->setOffset($offset);
$extra = $this->getExtra();
// zip align
$padding = 0;
$zipAlign = $this->getCentralDirectory()->getZipAlign();
$extraLength = strlen($extra);
if ($zipAlign !== null && !$this->isEncrypted() && $this->getMethod() === ZipFile::METHOD_STORED) {
$padding =
(
$zipAlign -
(
$offset +
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
$nameLength + $extraLength
) % $zipAlign
) % $zipAlign;
}
fwrite(
$outputStream,
pack(
'VvvvVVVVvv',
// local file header signature 4 bytes (0x04034b50)
self::LOCAL_FILE_HEADER_SIG,
// version needed to extract 2 bytes
$this->getVersionNeededToExtract(),
// general purpose bit flag 2 bytes
$general,
// compression method 2 bytes
$this->getMethod(),
// last mod file time 2 bytes
// last mod file date 2 bytes
$this->getTime(),
// crc-32 4 bytes
$dd ? 0 : $this->getCrc(),
// compressed size 4 bytes
$dd ? 0 : $this->getCompressedSize(),
// uncompressed size 4 bytes
$dd ? 0 : $this->getSize(),
// file name length 2 bytes
$nameLength,
// extra field length 2 bytes
$extraLength + $padding
)
);
fwrite($outputStream, $this->getName());
if ($extraLength > 0) {
fwrite($outputStream, $extra);
}
if ($padding > 0) {
fwrite($outputStream, str_repeat(chr(0), $padding));
}
if (null !== $entryContent) {
fwrite($outputStream, $entryContent);
}
assert(self::UNKNOWN !== $this->getCrc());
assert(self::UNKNOWN !== $this->getSize());
if ($this->getGeneralPurposeBitFlag(self::GPBF_DATA_DESCRIPTOR)) {
// data descriptor signature 4 bytes (0x08074b50)
// crc-32 4 bytes
fwrite($outputStream, pack('VV', self::DATA_DESCRIPTOR_SIG, $this->getCrc()));
// compressed size 4 or 8 bytes
// uncompressed size 4 or 8 bytes
if ($this->isZip64ExtensionsRequired()) {
fwrite($outputStream, PackUtil::packLongLE($compressedSize));
fwrite($outputStream, PackUtil::packLongLE($this->getSize()));
} else {
fwrite($outputStream, pack('VV', $this->getCompressedSize(), $this->getSize()));
}
} elseif ($this->getCompressedSize() !== $compressedSize) {
throw new ZipException($this->getName()
. " (expected compressed entry size of "
. $this->getCompressedSize() . " bytes, but is actually " . $compressedSize . " bytes)");
}
}
}

View 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;
}
}
}

View 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;
}
}

View File

@ -0,0 +1,327 @@
<?php
namespace PhpZip\Model\Entry;
use PhpZip\Crypto\TraditionalPkwareEncryptionEngine;
use PhpZip\Crypto\WinZipAesEngine;
use PhpZip\Exception\Crc32Exception;
use PhpZip\Exception\InvalidArgumentException;
use PhpZip\Exception\ZipCryptoException;
use PhpZip\Exception\ZipException;
use PhpZip\Exception\ZipUnsupportMethod;
use PhpZip\Extra\WinZipAesEntryExtraField;
use PhpZip\Model\CentralDirectory;
use PhpZip\Model\ZipEntry;
use PhpZip\ZipFile;
/**
* This class is used to represent a ZIP file entry.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipReadEntry extends ZipAbstractEntry
{
/**
* Max size cached content in memory.
*/
const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 3145728; // 3 mb
/**
* @var resource
*/
private $inputStream;
/**
* @var string
*/
private $charset;
/**
* @var string|resource Cached entry content.
*/
private $entryContent;
/**
* ZipFileEntry constructor.
* @param $inputStream
*/
public function __construct($inputStream)
{
$this->inputStream = $inputStream;
$this->readZipEntry($inputStream);
}
/**
* @param resource $inputStream
* @throws InvalidArgumentException
*/
private function readZipEntry($inputStream)
{
// central file header signature 4 bytes (0x02014b50)
$fileHeaderSig = unpack('V', fread($inputStream, 4))[1];
if (CentralDirectory::CENTRAL_FILE_HEADER_SIG !== $fileHeaderSig) {
throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry.");
}
// version made by 2 bytes
// version needed to extract 2 bytes
// general purpose bit flag 2 bytes
// compression method 2 bytes
// last mod file time 2 bytes
// last mod file date 2 bytes
// crc-32 4 bytes
// compressed size 4 bytes
// uncompressed size 4 bytes
// file name length 2 bytes
// extra field length 2 bytes
// file comment length 2 bytes
// disk number start 2 bytes
// internal file attributes 2 bytes
// external file attributes 4 bytes
// relative offset of local header 4 bytes
$data = unpack(
'vversionMadeBy/vversionNeededToExtract/vgpbf/vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' .
'VrawSize/vfileLength/vextraLength/vcommentLength/VrawInternalAttributes/VrawExternalAttributes/VlfhOff',
fread($inputStream, 42)
);
$utf8 = 0 !== ($data['gpbf'] & self::GPBF_UTF8);
if ($utf8) {
$this->charset = "UTF-8";
}
// See appendix D of PKWARE's ZIP File Format Specification.
$name = fread($inputStream, $data['fileLength']);
$this->setName($name);
$this->setVersionNeededToExtract($data['versionNeededToExtract']);
$this->setPlatform($data['versionMadeBy'] >> 8);
$this->setGeneralPurposeBitFlags($data['gpbf']);
$this->setMethod($data['rawMethod']);
$this->setTime($data['rawTime']);
$this->setCrc($data['rawCrc']);
$this->setCompressedSize($data['rawCompressedSize']);
$this->setSize($data['rawSize']);
$this->setExternalAttributes($data['rawExternalAttributes']);
$this->setOffset($data['lfhOff']); // must be unmapped!
if (0 < $data['extraLength']) {
$this->setExtra(fread($inputStream, $data['extraLength']));
}
if (0 < $data['commentLength']) {
$this->setComment(fread($inputStream, $data['commentLength']));
}
}
/**
* Returns an string content of the given entry.
*
* @return string
* @throws ZipException
*/
public function getEntryContent()
{
if (null === $this->entryContent) {
if ($this->isDirectory()) {
$this->entryContent = null;
return $this->entryContent;
}
$isEncrypted = $this->isEncrypted();
$password = $this->getPassword();
if ($isEncrypted && empty($password)) {
throw new ZipException("Not set password");
}
$pos = $this->getOffset();
assert(self::UNKNOWN !== $pos);
$startPos = $pos = $this->getCentralDirectory()->getEndOfCentralDirectory()->getMapper()->map($pos);
fseek($this->inputStream, $startPos);
// local file header signature 4 bytes (0x04034b50)
if (self::LOCAL_FILE_HEADER_SIG !== unpack('V', fread($this->inputStream, 4))[1]) {
throw new ZipException($this->getName() . " (expected Local File Header)");
}
fseek($this->inputStream, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS);
// file name length 2 bytes
// extra field length 2 bytes
$data = unpack('vfileLength/vextraLength', fread($this->inputStream, 4));
$pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength'];
assert(self::UNKNOWN !== $this->getCrc());
$method = $this->getMethod();
fseek($this->inputStream, $pos);
// Get raw entry content
$content = fread($this->inputStream, $this->getCompressedSize());
// Strong Encryption Specification - WinZip AES
if ($this->isEncrypted()) {
if (self::METHOD_WINZIP_AES === $method) {
$winZipAesEngine = new WinZipAesEngine($this);
$content = $winZipAesEngine->decrypt($content);
// Disable redundant CRC-32 check.
$isEncrypted = false;
/**
* @var WinZipAesEntryExtraField $field
*/
$field = $this->getExtraField(WinZipAesEntryExtraField::getHeaderId());
$method = $field->getMethod();
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_WINZIP_AES);
} else {
// Traditional PKWARE Decryption
$zipCryptoEngine = new TraditionalPkwareEncryptionEngine($this);
$content = $zipCryptoEngine->decrypt($content);
$this->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_TRADITIONAL);
}
}
if ($isEncrypted) {
// Check CRC32 in the Local File Header or Data Descriptor.
$localCrc = null;
if ($this->getGeneralPurposeBitFlag(self::GPBF_DATA_DESCRIPTOR)) {
// The CRC32 is in the Data Descriptor after the compressed size.
// Note the Data Descriptor's Signature is optional:
// All newer apps should write it (and so does TrueVFS),
// but older apps might not.
fseek($this->inputStream, $pos + $this->getCompressedSize());
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
if (self::DATA_DESCRIPTOR_SIG === $localCrc) {
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
}
} else {
fseek($this->inputStream, $startPos + 14);
// The CRC32 in the Local File Header.
$localCrc = unpack('V', fread($this->inputStream, 4))[1];
}
if ($this->getCrc() !== $localCrc) {
throw new Crc32Exception($this->getName(), $this->getCrc(), $localCrc);
}
}
switch ($method) {
case ZipFile::METHOD_STORED:
break;
case ZipFile::METHOD_DEFLATED:
$content = gzinflate($content);
break;
case ZipFile::METHOD_BZIP2:
if (!extension_loaded('bz2')) {
throw new ZipException('Extension bzip2 not install');
}
$content = bzdecompress($content);
break;
default:
throw new ZipUnsupportMethod($this->getName()
. " (compression method "
. $method
. " is not supported)");
}
if ($isEncrypted) {
$localCrc = crc32($content);
if ($this->getCrc() !== $localCrc) {
if ($this->isEncrypted()) {
throw new ZipCryptoException("Wrong password");
}
throw new Crc32Exception($this->getName(), $this->getCrc(), $localCrc);
}
}
if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) {
$this->entryContent = $content;
} else {
$this->entryContent = fopen('php://temp', 'rb');
fwrite($this->entryContent, $content);
}
return $content;
}
if (is_resource($this->entryContent)) {
return stream_get_contents($this->entryContent, -1, 0);
}
return $this->entryContent;
}
/**
* Write local file header, encryption header, file data and data descriptor to output stream.
*
* @param resource $outputStream
*/
public function writeEntry($outputStream)
{
$pos = $this->getOffset();
assert(ZipEntry::UNKNOWN !== $pos);
$pos = $this->getCentralDirectory()->getEndOfCentralDirectory()->getMapper()->map($pos);
$pos += ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS;
$this->setOffset(ftell($outputStream));
// zip align
$padding = 0;
$zipAlign = $this->getCentralDirectory()->getZipAlign();
$extra = $this->getExtra();
$extraLength = strlen($extra);
$nameLength = strlen($this->getName());
if ($zipAlign !== null && !$this->isEncrypted() && $this->getMethod() === ZipFile::METHOD_STORED) {
$padding =
(
$zipAlign -
($this->getOffset() + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength)
% $zipAlign
) % $zipAlign;
}
$dd = $this->isDataDescriptorRequired();
fwrite(
$outputStream,
pack(
'VvvvVVVVvv',
// local file header signature 4 bytes (0x04034b50)
self::LOCAL_FILE_HEADER_SIG,
// version needed to extract 2 bytes
$this->getVersionNeededToExtract(),
// general purpose bit flag 2 bytes
$this->getGeneralPurposeBitFlags(),
// compression method 2 bytes
$this->getMethod(),
// last mod file time 2 bytes
// last mod file date 2 bytes
$this->getTime(),
// crc-32 4 bytes
$dd ? 0 : $this->getCrc(),
// compressed size 4 bytes
$dd ? 0 : $this->getCompressedSize(),
// uncompressed size 4 bytes
$dd ? 0 : $this->getSize(),
$nameLength,
// extra field length 2 bytes
$extraLength + $padding
)
);
fwrite($outputStream, $this->getName());
if ($extraLength > 0) {
fwrite($outputStream, $extra);
}
if ($padding > 0) {
fwrite($outputStream, str_repeat(chr(0), $padding));
}
fseek($this->inputStream, $pos);
$data = unpack('vfileLength/vextraLength', fread($this->inputStream, 4));
fseek($this->inputStream, $data['fileLength'] + $data['extraLength'], SEEK_CUR);
$length = $this->getCompressedSize();
if ($this->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
$length += 12;
if ($this->isZip64ExtensionsRequired()) {
$length += 8;
}
}
stream_copy_to_stream($this->inputStream, $outputStream, $length);
}
function __destruct()
{
if (null !== $this->entryContent && is_resource($this->entryContent)) {
fclose($this->entryContent);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ namespace PhpZip\Model;
use PhpZip\Extra\NtfsExtraField;
use PhpZip\Extra\WinZipAesEntryExtraField;
use PhpZip\Util\FilesUtil;
use PhpZip\ZipFile;
/**
* Zip info
@ -85,7 +86,7 @@ class ZipInfo
];
private static $valuesCompressionMethod = [
ZipEntry::METHOD_STORED => 'no compression',
ZipFile::METHOD_STORED => 'no compression',
1 => 'shrink',
2 => 'reduce level 1',
3 => 'reduce level 2',
@ -93,7 +94,7 @@ class ZipInfo
5 => 'reduce level 4',
6 => 'implode',
7 => 'reserved for Tokenizing compression algorithm',
ZipEntry::METHOD_DEFLATED => 'deflate',
ZipFile::METHOD_DEFLATED => 'deflate',
9 => 'deflate64',
10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
11 => 'reserved by PKWARE',
@ -107,7 +108,7 @@ class ZipInfo
19 => 'IBM LZ77 z Architecture (PFS)',
97 => 'WavPack',
98 => 'PPMd version I, Rev 1',
ZipEntry::WINZIP_AES => 'WinZip AES',
ZipEntry::METHOD_WINZIP_AES => 'WinZip AES',
];
/**
@ -192,7 +193,7 @@ class ZipInfo
$ctime = null;
$field = $entry->getExtraField(NtfsExtraField::getHeaderId());
if ($field !== null && $field instanceof NtfsExtraField) {
if (null !== $field && $field instanceof NtfsExtraField) {
/**
* @var NtfsExtraField $field
*/
@ -214,34 +215,36 @@ class ZipInfo
$this->platform = self::getPlatformName($entry);
$this->version = $entry->getVersionNeededToExtract();
$attribs = str_repeat(" ", 12);
$xattr = (($entry->getRawExternalAttributes() >> 16) & 0xFFFF);
$attributes = str_repeat(" ", 12);
$externalAttributes = $entry->getExternalAttributes();
$xattr = (($externalAttributes >> 16) & 0xFFFF);
switch ($entry->getPlatform()) {
case self::MADE_BY_MS_DOS:
/** @noinspection PhpMissingBreakStatementInspection */
case self::MADE_BY_WINDOWS_NTFS:
if ($entry->getPlatform() != self::MADE_BY_MS_DOS ||
($xattr & 0700) !=
(0400 |
(!($entry->getRawExternalAttributes() & 1) << 7) |
(($entry->getRawExternalAttributes() & 0x10) << 2))
(!($externalAttributes & 1) << 7) |
(($externalAttributes & 0x10) << 2))
) {
$xattr = $entry->getRawExternalAttributes() & 0xFF;
$attribs = ".r.-... ";
$attribs[2] = ($xattr & 0x01) ? '-' : 'w';
$attribs[5] = ($xattr & 0x02) ? 'h' : '-';
$attribs[6] = ($xattr & 0x04) ? 's' : '-';
$attribs[4] = ($xattr & 0x20) ? 'a' : '-';
$xattr = $externalAttributes & 0xFF;
$attributes = ".r.-... ";
$attributes[2] = ($xattr & 0x01) ? '-' : 'w';
$attributes[5] = ($xattr & 0x02) ? 'h' : '-';
$attributes[6] = ($xattr & 0x04) ? 's' : '-';
$attributes[4] = ($xattr & 0x20) ? 'a' : '-';
if ($xattr & 0x10) {
$attribs[0] = 'd';
$attribs[3] = 'x';
$attributes[0] = 'd';
$attributes[3] = 'x';
} else
$attribs[0] = '-';
$attributes[0] = '-';
if ($xattr & 0x08)
$attribs[0] = 'V';
$attributes[0] = 'V';
else {
$ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION));
if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) {
$attribs[3] = 'x';
$attributes[3] = 'x';
}
}
break;
@ -250,51 +253,51 @@ class ZipInfo
default: /* assume Unix-like */
switch ($xattr & self::UNX_IFMT) {
case self::UNX_IFDIR:
$attribs[0] = 'd';
$attributes[0] = 'd';
break;
case self::UNX_IFREG:
$attribs[0] = '-';
$attributes[0] = '-';
break;
case self::UNX_IFLNK:
$attribs[0] = 'l';
$attributes[0] = 'l';
break;
case self::UNX_IFBLK:
$attribs[0] = 'b';
$attributes[0] = 'b';
break;
case self::UNX_IFCHR:
$attribs[0] = 'c';
$attributes[0] = 'c';
break;
case self::UNX_IFIFO:
$attribs[0] = 'p';
$attributes[0] = 'p';
break;
case self::UNX_IFSOCK:
$attribs[0] = 's';
$attributes[0] = 's';
break;
default:
$attribs[0] = '?';
$attributes[0] = '?';
break;
}
$attribs[1] = ($xattr & self::UNX_IRUSR) ? 'r' : '-';
$attribs[4] = ($xattr & self::UNX_IRGRP) ? 'r' : '-';
$attribs[7] = ($xattr & self::UNX_IROTH) ? 'r' : '-';
$attribs[2] = ($xattr & self::UNX_IWUSR) ? 'w' : '-';
$attribs[5] = ($xattr & self::UNX_IWGRP) ? 'w' : '-';
$attribs[8] = ($xattr & self::UNX_IWOTH) ? 'w' : '-';
$attributes[1] = ($xattr & self::UNX_IRUSR) ? 'r' : '-';
$attributes[4] = ($xattr & self::UNX_IRGRP) ? 'r' : '-';
$attributes[7] = ($xattr & self::UNX_IROTH) ? 'r' : '-';
$attributes[2] = ($xattr & self::UNX_IWUSR) ? 'w' : '-';
$attributes[5] = ($xattr & self::UNX_IWGRP) ? 'w' : '-';
$attributes[8] = ($xattr & self::UNX_IWOTH) ? 'w' : '-';
if ($xattr & self::UNX_IXUSR)
$attribs[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
$attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x';
else
$attribs[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; /* S==undefined */
$attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; /* S==undefined */
if ($xattr & self::UNX_IXGRP)
$attribs[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; /* == UNX_ENFMT */
$attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; /* == UNX_ENFMT */
else
$attribs[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; /* SunOS 4.1.x */
$attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; /* SunOS 4.1.x */
if ($xattr & self::UNX_IXOTH)
$attribs[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; /* "sticky bit" */
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; /* "sticky bit" */
else
$attribs[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; /* T==undefined */
$attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; /* T==undefined */
}
$this->attributes = trim($attribs);
$this->attributes = trim($attributes);
}
/**
@ -305,10 +308,10 @@ class ZipInfo
{
$return = '';
if ($entry->isEncrypted()) {
if ($entry->getMethod() === ZipEntry::WINZIP_AES) {
if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) {
$field = $entry->getExtraField(WinZipAesEntryExtraField::getHeaderId());
$return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]);
if ($field !== null) {
if (null !== $field) {
/**
* @var WinZipAesEntryExtraField $field
*/

View File

@ -1,22 +0,0 @@
<?php
namespace PhpZip\Output;
/**
* Zip output entry for empty dir.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipOutputEmptyDirEntry extends ZipOutputEntry
{
/**
* Returns entry data.
*
* @return string
*/
public function getEntryContent()
{
return '';
}
}

View File

@ -1,46 +0,0 @@
<?php
namespace PhpZip\Output;
use PhpZip\Model\ZipEntry;
/**
* Zip output Entry
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
abstract class ZipOutputEntry
{
/**
* @var ZipEntry
*/
private $entry;
/**
* @param ZipEntry $entry
*/
public function __construct(ZipEntry $entry)
{
if ($entry === null) {
throw new \RuntimeException('entry is null');
}
$this->entry = $entry;
}
/**
* Returns zip entry
*
* @return ZipEntry
*/
public function getEntry()
{
return $this->entry;
}
/**
* Returns entry data.
*
* @return string
*/
abstract public function getEntryContent();
}

View File

@ -1,54 +0,0 @@
<?php
namespace PhpZip\Output;
use PhpZip\Model\ZipEntry;
use RuntimeException;
/**
* Zip output entry for stream resource.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipOutputStreamEntry extends ZipOutputEntry
{
/**
* @var resource
*/
private $stream;
/**
* @param resource $stream
* @param ZipEntry $entry
*/
public function __construct($stream, ZipEntry $entry)
{
parent::__construct($entry);
if (!is_resource($stream)) {
throw new RuntimeException('stream is not resource');
}
$this->stream = $stream;
}
/**
* Returns entry data.
*
* @return string
*/
public function getEntryContent()
{
rewind($this->stream);
return stream_get_contents($this->stream);
}
/**
* Release stream resource.
*/
function __destruct()
{
if ($this->stream !== null) {
fclose($this->stream);
$this->stream = null;
}
}
}

View File

@ -1,46 +0,0 @@
<?php
namespace PhpZip\Output;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipEntry;
/**
* Zip output entry for string data.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipOutputStringEntry extends ZipOutputEntry
{
/**
* Data content.
*
* @var string
*/
private $data;
/**
* @param string $data
* @param ZipEntry $entry
* @throws ZipException If data empty.
*/
public function __construct($data, ZipEntry $entry)
{
parent::__construct($entry);
$data = (string)$data;
if ($data === null) {
throw new ZipException("data is null");
}
$this->data = $data;
}
/**
* Returns entry data.
*
* @return string
*/
public function getEntryContent()
{
return $this->data;
}
}

View File

@ -1,56 +0,0 @@
<?php
namespace PhpZip\Output;
use PhpZip\Exception\ZipException;
use PhpZip\Model\ZipEntry;
use PhpZip\ZipFile;
/**
* Zip output entry for input zip file.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class ZipOutputZipFileEntry extends ZipOutputEntry
{
/**
* Input zip file.
*
* @var ZipFile
*/
private $inputZipFile;
/**
* Input entry name.
*
* @var string
*/
private $inputEntryName;
/**
* ZipOutputZipFileEntry constructor.
* @param ZipFile $zipFile
* @param ZipEntry $zipEntry
* @throws ZipException If input zip file is null.
*/
public function __construct(ZipFile $zipFile, ZipEntry $zipEntry)
{
if ($zipFile === null) {
throw new ZipException('ZipFile is null');
}
parent::__construct(clone $zipEntry);
$this->inputZipFile = $zipFile;
$this->inputEntryName = $zipEntry->getName();
}
/**
* Returns entry data.
*
* @return string
*/
public function getEntryContent()
{
return $this->inputZipFile->getEntryContent($this->inputEntryName);
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace PhpZip\Util;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipException;
/**
@ -14,7 +15,7 @@ class CryptoUtil
*
* @param int $length
* @return string
* @throws ZipException
* @throws RuntimeException
*/
public static final function randomBytes($length)
{
@ -26,7 +27,7 @@ class CryptoUtil
} elseif (function_exists('mcrypt_create_iv')) {
return mcrypt_create_iv($length);
} else {
throw new ZipException('Extension openssl or mcrypt not loaded');
throw new RuntimeException('Extension openssl or mcrypt not loaded');
}
}
}

View File

@ -1,115 +0,0 @@
<?php
namespace PhpZip;
/**
* Constants for ZIP files.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
interface ZipConstants
{
/** Local File Header signature. */
const LOCAL_FILE_HEADER_SIG = 0x04034B50;
/** Data Descriptor signature. */
const DATA_DESCRIPTOR_SIG = 0x08074B50;
/** Central File Header signature. */
const CENTRAL_FILE_HEADER_SIG = 0x02014B50;
/** Zip64 End Of Central Directory Record. */
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
/** Zip64 End Of Central Directory Locator. */
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
/** End Of Central Directory Record signature. */
const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
/**
* The minimum length of the Local File Header record.
*
* local file header signature 4
* version needed to extract 2
* general purpose bit flag 2
* compression method 2
* last mod file time 2
* last mod file date 2
* crc-32 4
* compressed size 4
* uncompressed size 4
* file name length 2
* extra field length 2
*/
const LOCAL_FILE_HEADER_MIN_LEN = 30;
/**
* The minimum length of the End Of Central Directory Record.
*
* end of central dir signature 4
* number of this disk 2
* number of the disk with the
* start of the central directory 2
* total number of entries in the
* central directory on this disk 2
* total number of entries in
* the central directory 2
* size of the central directory 4
* offset of start of central *
* directory with respect to *
* the starting disk number 4
* zipfile comment length 2
*/
const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
/**
* The length of the Zip64 End Of Central Directory Locator.
* zip64 end of central dir locator
* signature 4
* number of the disk with the
* start of the zip64 end of
* central directory 4
* relative offset of the zip64
* end of central directory record 8
* total number of disks 4
*/
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
/**
* The minimum length of the Zip64 End Of Central Directory Record.
*
* zip64 end of central dir
* signature 4
* size of zip64 end of central
* directory record 8
* version made by 2
* version needed to extract 2
* number of this disk 4
* number of the disk with the
* start of the central directory 4
* total number of entries in the
* central directory on this disk 8
* total number of entries in
* the central directory 8
* size of the central directory 8
* offset of start of central
* directory with respect to
* the starting disk number 8
*/
const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
/**
* Local File Header signature 4
* Version Needed To Extract 2
* General Purpose Bit Flags 2
* Compression Method 2
* Last Mod File Time 2
* Last Mod File Date 2
* CRC-32 4
* Compressed Size 4
* Uncompressed Size 4
*/
const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,359 @@
<?php
namespace PhpZip;
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
/**
* Test add directory to zip archive.
*/
class ZipFileAddDirTest extends ZipTestCase
{
private static $files = [
'.hidden' => 'Hidden file',
'text file.txt' => 'Text file',
'Текстовый документ.txt' => 'Текстовый документ',
'empty dir/' => null,
'empty dir2/ещё пустой каталог/' => null,
'catalog/New File' => 'New Catalog File',
'catalog/New File 2' => 'New Catalog File 2',
'catalog/Empty Dir/' => null,
'category/list.txt' => 'Category list',
'category/Pictures/128x160/Car/01.jpg' => 'File 01.jpg',
'category/Pictures/128x160/Car/02.jpg' => 'File 02.jpg',
'category/Pictures/240x320/Car/01.jpg' => 'File 01.jpg',
'category/Pictures/240x320/Car/02.jpg' => 'File 02.jpg',
];
/**
* Before test
*/
protected function setUp()
{
parent::setUp();
$this->fillDirectory();
}
protected function fillDirectory()
{
foreach (self::$files as $name => $content) {
$fullName = $this->outputDirname . '/' . $name;
if ($content === null) {
if (!is_dir($fullName)) {
mkdir($fullName, 0755, true);
}
} else {
$dirname = dirname($fullName);
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
file_put_contents($fullName, $content);
}
}
}
protected static function assertFilesResult(ZipFile $zipFile, array $actualResultFiles = [], $localPath = '/')
{
$localPath = rtrim($localPath, '/');
$localPath = empty($localPath) ? "" : $localPath . '/';
self::assertEquals(sizeof($zipFile), sizeof($actualResultFiles));
$actualResultFiles = array_flip($actualResultFiles);
foreach (self::$files as $file => $content) {
$zipEntryName = $localPath . $file;
if (isset($actualResultFiles[$file])) {
self::assertTrue(isset($zipFile[$zipEntryName]));
self::assertEquals($zipFile[$zipEntryName], $content);
unset($actualResultFiles[$file]);
} else {
self::assertFalse(isset($zipFile[$zipEntryName]));
}
}
self::assertEmpty($actualResultFiles);
}
public function testAddDirWithLocalPath()
{
$localPath = 'to/path';
$zipFile = new ZipFile();
$zipFile->addDir($this->outputDirname, $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
], $localPath);
$zipFile->close();
}
public function testAddDirWithoutLocalPath()
{
$zipFile = new ZipFile();
$zipFile->addDir($this->outputDirname);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
]);
$zipFile->close();
}
public function testAddFilesFromIterator()
{
$localPath = 'to/project';
$directoryIterator = new \DirectoryIterator($this->outputDirname);
$zipFile = new ZipFile();
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
], $localPath);
$zipFile->close();
}
public function testAddFilesFromRecursiveIterator()
{
$localPath = 'to/project';
$directoryIterator = new \RecursiveDirectoryIterator($this->outputDirname);
$zipFile = new ZipFile();
$zipFile->addFilesFromIterator($directoryIterator, $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
public function testAddRecursiveDirWithLocalPath()
{
$localPath = 'to/path';
$zipFile = new ZipFile();
$zipFile->addDirRecursive($this->outputDirname, $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
public function testAddRecursiveDirWithoutLocalPath()
{
$zipFile = new ZipFile();
$zipFile->addDirRecursive($this->outputDirname);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files));
$zipFile->close();
}
public function testAddFilesFromIteratorWithIgnoreFiles(){
$localPath = 'to/project';
$ignoreFiles = [
'Текстовый документ.txt',
'empty dir/'
];
$directoryIterator = new \DirectoryIterator($this->outputDirname);
$ignoreIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
$zipFile = new ZipFile();
$zipFile->addFilesFromIterator($ignoreIterator, $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'.hidden',
'text file.txt',
], $localPath);
$zipFile->close();
}
public function testAddFilesFromRecursiveIteratorWithIgnoreFiles(){
$localPath = 'to/project';
$ignoreFiles = [
'.hidden',
'empty dir2/ещё пустой каталог/',
'list.txt',
'category/Pictures/240x320',
];
$directoryIterator = new \RecursiveDirectoryIterator($this->outputDirname);
$ignoreIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
$zipFile = new ZipFile();
$zipFile->addFilesFromIterator($ignoreIterator, $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
'empty dir/',
'catalog/New File',
'catalog/New File 2',
'catalog/Empty Dir/',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
], $localPath);
$zipFile->close();
}
/**
* Create archive and add files from glob pattern
*/
public function testAddFilesFromGlob()
{
$localPath = '/';
$zipFile = new ZipFile();
$zipFile->addFilesFromGlob($this->outputDirname, '**.{txt,jpg}', $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
], $localPath);
$zipFile->close();
}
/**
* Create archive and add recursively files from glob pattern
*/
public function testAddFilesFromGlobRecursive()
{
$localPath = '/';
$zipFile = new ZipFile();
$zipFile->addFilesFromGlobRecursive($this->outputDirname, '**.{txt,jpg}', $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
'category/list.txt',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
'category/Pictures/240x320/Car/01.jpg',
'category/Pictures/240x320/Car/02.jpg',
], $localPath);
$zipFile->close();
}
/**
* Create archive and add files from regex pattern
*/
public function testAddFilesFromRegex()
{
$localPath = 'path';
$zipFile = new ZipFile();
$zipFile->addFilesFromRegex($this->outputDirname, '~\.(txt|jpe?g)$~i', $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
], $localPath);
$zipFile->close();
}
/**
* Create archive and add files recursively from regex pattern
*/
public function testAddFilesFromRegexRecursive()
{
$localPath = '/';
$zipFile = new ZipFile();
$zipFile->addFilesFromRegexRecursive($this->outputDirname, '~\.(txt|jpe?g)$~i', $localPath);
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, [
'text file.txt',
'Текстовый документ.txt',
'category/list.txt',
'category/Pictures/128x160/Car/01.jpg',
'category/Pictures/128x160/Car/02.jpg',
'category/Pictures/240x320/Car/01.jpg',
'category/Pictures/240x320/Car/02.jpg',
], $localPath);
$zipFile->close();
}
public function testArrayAccessAddDir()
{
$localPath = 'path/to';
$iterator = new \RecursiveDirectoryIterator($this->outputDirname);
$zipFile = new ZipFile();
$zipFile[$localPath] = $iterator;
$zipFile->saveAsFile($this->outputFilename);
$zipFile->close();
self::assertCorrectZipArchive($this->outputFilename);
$zipFile->openFile($this->outputFilename);
self::assertFilesResult($zipFile, array_keys(self::$files), $localPath);
$zipFile->close();
}
}

1850
tests/PhpZip/ZipFileTest.php Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,51 @@
<?php
namespace PhpZip;
use PhpZip\Model\EndOfCentralDirectory;
use PhpZip\Util\FilesUtil;
/**
* PHPUnit test case and helper methods.
*/
class ZipTestCase extends \PHPUnit_Framework_TestCase
{
/**
* @var string
*/
protected $outputFilename;
/**
* @var string
*/
protected $outputDirname;
/**
* Before test
*/
protected function setUp()
{
parent::setUp();
$id = uniqid('phpzip');
$this->outputFilename = sys_get_temp_dir() . '/' . $id . '.zip';
$this->outputDirname = sys_get_temp_dir() . '/' . $id;
}
/**
* After test
*/
protected function tearDown()
{
parent::tearDown();
if ($this->outputFilename !== null && file_exists($this->outputFilename)) {
unlink($this->outputFilename);
}
if ($this->outputDirname !== null && is_dir($this->outputDirname)) {
FilesUtil::removeDir($this->outputDirname);
}
}
/**
* Assert correct zip archive.
*
@ -25,12 +65,12 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
$output = implode(PHP_EOL, $output);
if ($password !== null && $returnCode === 81) {
if(`which 7z`){
if (`which 7z`) {
// WinZip 99-character limit
// @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
$password = substr($password, 0, 99);
$command = "7z t -p" . escapeshellarg($password). " " . escapeshellarg($filename);
$command = "7z t -p" . escapeshellarg($password) . " " . escapeshellarg($filename);
exec($command, $output, $returnCode);
$output = implode(PHP_EOL, $output);
@ -38,14 +78,12 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
self::assertEquals($returnCode, 0);
self::assertNotContains(' Errors', $output);
self::assertContains(' Ok', $output);
} else {
fwrite(STDERR, 'Program unzip cannot support this function.' . PHP_EOL);
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . PHP_EOL);
}
else{
fwrite(STDERR, 'Program unzip cannot support this function.'.PHP_EOL);
fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full'.PHP_EOL);
}
}
else {
self::assertEquals($returnCode, 0);
} else {
self::assertEquals($returnCode, 0, $output);
self::assertNotContains('incorrect password', $output);
self::assertContains(' OK', $output);
self::assertContains('No errors', $output);
@ -67,18 +105,20 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
self::assertContains('Empty zipfile', $output);
}
$actualEmptyZipData = pack('VVVVVv', ZipConstants::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
$actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0);
self::assertEquals(file_get_contents($filename), $actualEmptyZipData);
}
/**
* @param string $filename
* @param bool $showErrors
* @return bool|null If null - can not install zipalign
*/
public static function doZipAlignVerify($filename)
public static function doZipAlignVerify($filename, $showErrors = false)
{
if (DIRECTORY_SEPARATOR !== '\\' && `which zipalign`) {
exec("zipalign -c -v 4 " . escapeshellarg($filename), $output, $returnCode);
if ($showErrors && $returnCode !== 0) fwrite(STDERR, implode(PHP_EOL, $output));
return $returnCode === 0;
} else {
fwrite(STDERR, 'Can not find program "zipalign" for test');