mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-08-15 03:34:49 +02:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
464050ff85 | ||
|
015166d165 | ||
|
47d308605e | ||
|
951433d0b7 | ||
|
9370f353c6 | ||
|
ac20d6fbf3 | ||
|
2729d8cbec | ||
|
4c6f27c269 | ||
|
f0d90da75c | ||
|
cf7f98b7c9 |
@@ -2,7 +2,7 @@
|
||||
================
|
||||
`PhpZip` - is to create, update, opening and unpacking ZIP archives in pure PHP.
|
||||
|
||||
The library supports `ZIP64`, `Traditional PKWARE Encryption` and `WinZIP AES Encryption`.
|
||||
The library supports `ZIP64`, `zipalign`, `Traditional PKWARE Encryption` and `WinZIP AES Encryption`.
|
||||
|
||||
The library does not require extension `php-xml` and class `ZipArchive`.
|
||||
|
||||
@@ -362,7 +362,7 @@ $zipOutputFile->saveAsFile($filename);
|
||||
```
|
||||
Save archive to a stream.
|
||||
```php
|
||||
$handle = fopen($filename, 'w+b);
|
||||
$handle = fopen($filename, 'w+b');
|
||||
$autoCloseResource = true;
|
||||
$zipOutputFile->saveAsStream($handle, $autoCloseResource);
|
||||
if(!$autoCloseResource){
|
||||
@@ -415,6 +415,11 @@ while ($iterator->valid())
|
||||
$iterator->next();
|
||||
}
|
||||
```
|
||||
Set zip alignment (alternate program `zipalign`).
|
||||
```php
|
||||
// before save or output
|
||||
$zipOutputFile->setAlign(4); // alternative cmd: zipalign -f -v 4 filename.zip
|
||||
```
|
||||
Close zip archive.
|
||||
```php
|
||||
$zipOutputFile->close();
|
||||
|
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "nelexa/zip",
|
||||
"description": "Zip files CRUD. Open, create, update, extract and get info tool. Support read and write encrypted archives. Support ZIP64 ext. Alternative ZipArchive. It does not require php-zip extension.",
|
||||
"description": "Zip files CRUD. Open, create, update, extract and get info tool. Support read and write encrypted archives. Support ZIP64 ext and zip align. Alternative ZipArchive. It does not require php-zip extension.",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"zip",
|
||||
"archive",
|
||||
"extract",
|
||||
"winzip"
|
||||
"winzip",
|
||||
"zipalign"
|
||||
],
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.8"
|
||||
|
@@ -18,7 +18,9 @@ class PackUtil
|
||||
*/
|
||||
public static function packLongLE($longValue)
|
||||
{
|
||||
// TODO test if (version_compare(PHP_VERSION, '5.6.3') >= 0) {return pack("P", $longValue);}
|
||||
if (version_compare(PHP_VERSION, '5.6.3') >= 0) {
|
||||
return pack("P", $longValue);
|
||||
}
|
||||
|
||||
$left = 0xffffffff00000000;
|
||||
$right = 0x00000000ffffffff;
|
||||
@@ -36,7 +38,9 @@ class PackUtil
|
||||
*/
|
||||
public static function unpackLongLE($value)
|
||||
{
|
||||
// TODO test if (version_compare(PHP_VERSION, '5.6.3') >= 0){ return current(unpack('P', $value)); }
|
||||
if (version_compare(PHP_VERSION, '5.6.3') >= 0) {
|
||||
return current(unpack('P', $value));
|
||||
}
|
||||
$unpack = unpack('Va/Vb', $value);
|
||||
return $unpack['a'] + ($unpack['b'] << 32);
|
||||
}
|
||||
|
@@ -401,7 +401,7 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
*/
|
||||
public static function openFromString($data)
|
||||
{
|
||||
if (empty($data)) {
|
||||
if (null === $data || strlen($data) === 0) {
|
||||
throw new IllegalArgumentException("Data not available");
|
||||
}
|
||||
if (!($handle = fopen('php://temp', 'r+b'))) {
|
||||
|
@@ -103,6 +103,13 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
*/
|
||||
private $level = self::LEVEL_DEFAULT_COMPRESSION;
|
||||
|
||||
/**
|
||||
* ZipAlign setting
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $align;
|
||||
|
||||
/**
|
||||
* ZipOutputFile constructor.
|
||||
* @param ZipFile|null $zipFile
|
||||
@@ -262,7 +269,7 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
if (null !== $comment && !empty($comment)) {
|
||||
if (null !== $comment && strlen($comment) !== 0) {
|
||||
$comment = (string)$comment;
|
||||
$length = strlen($comment);
|
||||
if (0x0000 > $length || $length > 0xffff) {
|
||||
@@ -285,10 +292,10 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
public function addFromString($entryName, $data, $compressionMethod = ZipEntry::METHOD_DEFLATED)
|
||||
{
|
||||
$entryName = (string)$entryName;
|
||||
if ($data === null) {
|
||||
throw new IllegalArgumentException("data is null");
|
||||
if ($data === null || strlen($data) === 0) {
|
||||
throw new IllegalArgumentException("Data is empty");
|
||||
}
|
||||
if (empty($entryName)) {
|
||||
if ($entryName === null || strlen($entryName) === 0) {
|
||||
throw new IllegalArgumentException("Incorrect entry name " . $entryName);
|
||||
}
|
||||
$this->validateCompressionMethod($compressionMethod);
|
||||
@@ -336,7 +343,7 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
)
|
||||
{
|
||||
$inputDir = (string)$inputDir;
|
||||
if (empty($inputDir)) {
|
||||
if ($inputDir === null || strlen($inputDir) === 0) {
|
||||
throw new IllegalArgumentException('Input dir empty');
|
||||
}
|
||||
if (!is_dir($inputDir)) {
|
||||
@@ -360,8 +367,12 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
foreach ($files as $file) {
|
||||
$filename = str_replace($inputDir, $moveToPath, $file);
|
||||
$filename = ltrim($filename, '/');
|
||||
is_dir($file) && FilesUtil::isEmptyDir($file) && $this->addEmptyDir($filename);
|
||||
is_file($file) && $this->addFromFile($file, $filename, $compressionMethod);
|
||||
if(is_dir($file)){
|
||||
FilesUtil::isEmptyDir($file) && $this->addEmptyDir($filename);
|
||||
}
|
||||
elseif(is_file($file)){
|
||||
$this->addFromFile($file, $filename, $compressionMethod);
|
||||
}
|
||||
}
|
||||
return $this->count() > $count;
|
||||
}
|
||||
@@ -385,7 +396,7 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
public function addEmptyDir($dirName)
|
||||
{
|
||||
$dirName = (string)$dirName;
|
||||
if (empty($dirName)) {
|
||||
if (strlen($dirName) === 0) {
|
||||
throw new IllegalArgumentException("dirName null or not string");
|
||||
}
|
||||
$dirName = rtrim($dirName, '/') . '/';
|
||||
@@ -440,7 +451,7 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
throw new IllegalArgumentException("stream is not resource");
|
||||
}
|
||||
$entryName = (string)$entryName;
|
||||
if (empty($entryName)) {
|
||||
if (strlen($entryName) === 0) {
|
||||
throw new IllegalArgumentException("Incorrect entry name " . $entryName);
|
||||
}
|
||||
$this->validateCompressionMethod($compressionMethod);
|
||||
@@ -479,7 +490,7 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
if (!is_dir($inputDir)) {
|
||||
throw new IllegalArgumentException('Directory ' . $inputDir . ' can\'t exists');
|
||||
}
|
||||
if (null === $globPattern || !is_string($globPattern)) {
|
||||
if (null === $globPattern || strlen($globPattern) === 0) {
|
||||
throw new IllegalArgumentException("globPattern null");
|
||||
}
|
||||
if (empty($globPattern)) {
|
||||
@@ -507,8 +518,12 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
foreach ($filesFound as $file) {
|
||||
$filename = str_replace($inputDir, $moveToPath, $file);
|
||||
$filename = ltrim($filename, '/');
|
||||
is_dir($file) && FilesUtil::isEmptyDir($file) && $this->addEmptyDir($filename);
|
||||
is_file($file) && $this->addFromFile($file, $filename, $compressionMethod);
|
||||
if(is_dir($file)){
|
||||
FilesUtil::isEmptyDir($file) && $this->addEmptyDir($filename);
|
||||
}
|
||||
elseif(is_file($file)){
|
||||
$this->addFromFile($file, $filename, $compressionMethod);
|
||||
}
|
||||
}
|
||||
return $this->count() > $count;
|
||||
}
|
||||
@@ -564,8 +579,12 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
foreach ($files as $file) {
|
||||
$filename = str_replace($inputDir, $moveToPath, $file);
|
||||
$filename = ltrim($filename, '/');
|
||||
is_dir($file) && FilesUtil::isEmptyDir($file) && $this->addEmptyDir($filename);
|
||||
is_file($file) && $this->addFromFile($file, $filename, $compressionMethod);
|
||||
if(is_dir($file)){
|
||||
FilesUtil::isEmptyDir($file) && $this->addEmptyDir($filename);
|
||||
}
|
||||
elseif(is_file($file)){
|
||||
$this->addFromFile($file, $filename, $compressionMethod);
|
||||
}
|
||||
}
|
||||
return $this->count() > $count;
|
||||
}
|
||||
@@ -805,6 +824,18 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $align
|
||||
*/
|
||||
public function setZipAlign($align = 4)
|
||||
{
|
||||
if ($align === null) {
|
||||
$this->align = null;
|
||||
return;
|
||||
}
|
||||
$this->align = (int)$align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save as file
|
||||
*
|
||||
@@ -999,6 +1030,10 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
|
||||
$offset = ftell($outputHandle);
|
||||
|
||||
// Commit changes.
|
||||
$entry->setGeneralPurposeBitFlags($general);
|
||||
$entry->setRawOffset($offset);
|
||||
|
||||
// Start changes.
|
||||
// local file header signature 4 bytes (0x04034b50)
|
||||
// version needed to extract 2 bytes
|
||||
@@ -1012,6 +1047,22 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
// file name length 2 bytes
|
||||
// extra field length 2 bytes
|
||||
$extra = $entry->getRawExtraFields();
|
||||
|
||||
// zip align
|
||||
$padding = 0;
|
||||
if ($this->align !== null && !$entry->isEncrypted() && $entry->getMethod() === ZipEntry::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$this->align -
|
||||
(
|
||||
$offset +
|
||||
self::LOCAL_FILE_HEADER_MIN_LEN +
|
||||
strlen($entry->getName()) +
|
||||
strlen($extra)
|
||||
) % $this->align
|
||||
) % $this->align;
|
||||
}
|
||||
|
||||
fwrite($outputHandle, pack('VvvvVVVVvv',
|
||||
ZipConstants::LOCAL_FILE_HEADER_SIG,
|
||||
$entry->getVersionNeededToExtract(),
|
||||
@@ -1022,15 +1073,16 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
$dd ? 0 : (int)$entry->getRawCompressedSize(),
|
||||
$dd ? 0 : (int)$entry->getRawSize(),
|
||||
strlen($entry->getName()),
|
||||
strlen($extra)
|
||||
strlen($extra) + $padding
|
||||
));
|
||||
// file name (variable size)
|
||||
fwrite($outputHandle, $entry->getName());
|
||||
// extra field (variable size)
|
||||
fwrite($outputHandle, $extra);
|
||||
// Commit changes.
|
||||
$entry->setGeneralPurposeBitFlags($general);
|
||||
$entry->setRawOffset($offset);
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($outputHandle, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
fwrite($outputHandle, $entryContent);
|
||||
|
||||
@@ -1059,7 +1111,6 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
. " (expected compressed entry size of "
|
||||
. $entry->getCompressedSize() . " bytes, but is actually " . $compressedSize . " bytes)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1229,7 +1280,7 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
public function outputAsAttachment($outputFilename, $mimeType = null)
|
||||
{
|
||||
$outputFilename = (string)$outputFilename;
|
||||
if (empty($outputFilename)) {
|
||||
if (strlen($outputFilename) === 0) {
|
||||
throw new IllegalArgumentException("Output filename is empty.");
|
||||
}
|
||||
if (empty($mimeType) || !is_string($mimeType)) {
|
||||
@@ -1321,13 +1372,13 @@ class ZipOutputFile implements \Countable, \ArrayAccess, \Iterator, ZipConstants
|
||||
*/
|
||||
public function offsetSet($entryName, $uncompressedDataContent)
|
||||
{
|
||||
if(empty($entryName)){
|
||||
$entryName = (string)$entryName;
|
||||
if (strlen($entryName) === 0) {
|
||||
throw new IllegalArgumentException('Entry name empty');
|
||||
}
|
||||
if($entryName[strlen($entryName)-1] === '/'){
|
||||
if ($entryName[strlen($entryName) - 1] === '/') {
|
||||
$this->addEmptyDir($entryName);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
$this->addFromString($entryName, $uncompressedDataContent);
|
||||
}
|
||||
}
|
||||
|
@@ -848,7 +848,7 @@ class ZipTest extends ZipTestCase
|
||||
'data' => CryptoUtil::randomBytes(255),
|
||||
'password' => CryptoUtil::randomBytes(255),
|
||||
'encryption_method' => ZipEntry::ENCRYPTION_METHOD_WINZIP_AES,
|
||||
'compression_method' => ZipEntry::METHOD_BZIP2,
|
||||
'compression_method' => extension_loaded("bz2") ? ZipEntry::METHOD_BZIP2 : ZipEntry::METHOD_STORED,
|
||||
],
|
||||
'Not password.dat' => [
|
||||
'data' => CryptoUtil::randomBytes(255),
|
||||
@@ -1022,8 +1022,50 @@ class ZipTest extends ZipTestCase
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test zip alignment.
|
||||
*/
|
||||
public function testZipAlign()
|
||||
{
|
||||
$zipOutputFile = ZipOutputFile::create();
|
||||
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$zipOutputFile->addFromString(
|
||||
'entry' . $i . '.txt',
|
||||
CryptoUtil::randomBytes(mt_rand(100, 4096)),
|
||||
ZipEntry::METHOD_STORED
|
||||
);
|
||||
}
|
||||
$zipOutputFile->saveAsFile($this->outputFilename);
|
||||
$zipOutputFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = self::doZipAlignVerify($this->outputFilename);
|
||||
if($result === null) return; // zip align not installed
|
||||
|
||||
// check not zip align
|
||||
self::assertFalse($result);
|
||||
|
||||
$zipFile = ZipFile::openFromFile($this->outputFilename);
|
||||
$zipOutputFile = ZipOutputFile::openFromZipFile($zipFile);
|
||||
$zipOutputFile->setZipAlign(4);
|
||||
$zipOutputFile->saveAsFile($this->outputFilename);
|
||||
$zipOutputFile->close();
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$result = self::doZipAlignVerify($this->outputFilename);
|
||||
self::assertNotNull($result);
|
||||
|
||||
// check zip align
|
||||
self::assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test support ZIP64 ext (slow test - normal).
|
||||
* Create > 65535 files in archive and open and extract to /dev/null.
|
||||
*/
|
||||
public function testCreateAndOpenZip64Ext()
|
||||
{
|
||||
@@ -1040,8 +1082,7 @@ class ZipTest extends ZipTestCase
|
||||
|
||||
$zipFile = ZipFile::openFromFile($this->outputFilename);
|
||||
self::assertEquals($zipFile->count(), $countFiles);
|
||||
foreach ($zipFile->getListFiles() as $entry) {
|
||||
$zipFile->getEntryContent($entry);
|
||||
foreach ($zipFile as $entry => $content) {
|
||||
}
|
||||
$zipFile->close();
|
||||
}
|
||||
|
@@ -42,4 +42,20 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase
|
||||
self::assertEquals(file_get_contents($filename), $actualEmptyZipData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool|null If null - can not install zipalign
|
||||
*/
|
||||
public static function doZipAlignVerify($filename)
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR !== '\\' && `which zipalign`) {
|
||||
exec("zipalign -c -v 4 " . escapeshellarg($filename), $output, $returnCode);
|
||||
return $returnCode === 0;
|
||||
} else {
|
||||
echo 'Can not find program "zipalign" for test' . PHP_EOL;
|
||||
fwrite(STDERR, 'Can not find program "zipalign" for test');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user