mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-08-30 02:09:50 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
251ce11bdc | ||
|
f969e59319 | ||
|
6808e4ffdc | ||
|
91f08b9f55 | ||
|
d0cf7f7d1d | ||
|
9f0d151f5e | ||
|
171d4a8e4c | ||
|
aa09b24d02 | ||
|
c34f90ac18 |
@@ -1,45 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "nelexa/zip",
|
"name": "nelexa/zip",
|
||||||
"description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
|
"type": "library",
|
||||||
"type": "library",
|
"description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"zip",
|
"zip",
|
||||||
"unzip",
|
"unzip",
|
||||||
"archive",
|
"archive",
|
||||||
"extract",
|
"extract",
|
||||||
"winzip",
|
"winzip",
|
||||||
"zipalign",
|
"zipalign",
|
||||||
"ziparchive"
|
"ziparchive"
|
||||||
],
|
],
|
||||||
"require-dev": {
|
"homepage": "https://github.com/Ne-Lexa/php-zip",
|
||||||
"phpunit/phpunit": "4.8"
|
"license": "MIT",
|
||||||
},
|
"authors": [
|
||||||
"license": "MIT",
|
{
|
||||||
"authors": [
|
"name": "Ne-Lexa",
|
||||||
{
|
"email": "alexey@nelexa.ru",
|
||||||
"name": "Ne-Lexa",
|
"role": "Developer"
|
||||||
"email": "alexey@nelexa.ru",
|
}
|
||||||
"role": "Developer"
|
],
|
||||||
}
|
"require": {
|
||||||
],
|
"php": "^5.5 || ^7.0",
|
||||||
"minimum-stability": "stable",
|
"psr/http-message": "^1.0"
|
||||||
"require": {
|
},
|
||||||
"php": "^5.5 || ^7.0",
|
"require-dev": {
|
||||||
"psr/http-message": "^1.0"
|
"phpunit/phpunit": "~4.8|~5.7",
|
||||||
},
|
"zendframework/zend-diactoros": "^1.4"
|
||||||
"autoload": {
|
},
|
||||||
"psr-4": {
|
"autoload": {
|
||||||
"PhpZip\\": "src/PhpZip"
|
"psr-4": {
|
||||||
}
|
"PhpZip\\": "src/PhpZip"
|
||||||
},
|
}
|
||||||
"autoload-dev": {
|
},
|
||||||
"psr-4": {
|
"autoload-dev": {
|
||||||
"PhpZip\\": "tests/PhpZip"
|
"psr-4": {
|
||||||
}
|
"PhpZip\\": "tests/PhpZip"
|
||||||
},
|
}
|
||||||
"suggest": {
|
},
|
||||||
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
|
"suggest": {
|
||||||
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
|
"ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt",
|
||||||
"ext-bz2": "Needed to support BZIP2 compression"
|
"ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
|
||||||
}
|
"ext-bz2": "Needed to support BZIP2 compression"
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable"
|
||||||
}
|
}
|
||||||
|
@@ -255,11 +255,8 @@ abstract class ZipAbstractEntry implements ZipEntry
|
|||||||
*/
|
*/
|
||||||
public function isZip64ExtensionsRequired()
|
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()
|
return 0xffffffff <= $this->getCompressedSize()
|
||||||
|| 0xffffffff <= $this->getSize()
|
|| 0xffffffff <= $this->getSize();
|
||||||
|| 0xffffffff <= sprintf('%u', $this->getOffset());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -432,7 +429,10 @@ abstract class ZipAbstractEntry implements ZipEntry
|
|||||||
*/
|
*/
|
||||||
public function getMethod()
|
public function getMethod()
|
||||||
{
|
{
|
||||||
return $this->isInit(self::BIT_METHOD) ? $this->method & 0xffff : self::UNKNOWN;
|
$isInit = $this->isInit(self::BIT_METHOD);
|
||||||
|
return $isInit ?
|
||||||
|
$this->method & 0xffff :
|
||||||
|
self::UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -446,17 +446,14 @@ abstract class ZipAbstractEntry implements ZipEntry
|
|||||||
{
|
{
|
||||||
if (self::UNKNOWN === $method) {
|
if (self::UNKNOWN === $method) {
|
||||||
$this->method = $method;
|
$this->method = $method;
|
||||||
|
$this->setInit(self::BIT_METHOD, false);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
if (0x0000 > $method || $method > 0xffff) {
|
if (0x0000 > $method || $method > 0xffff) {
|
||||||
throw new ZipException('method out of range');
|
throw new ZipException('method out of range: ' . $method);
|
||||||
}
|
}
|
||||||
switch ($method) {
|
switch ($method) {
|
||||||
case self::METHOD_WINZIP_AES:
|
case self::METHOD_WINZIP_AES:
|
||||||
$this->method = $method;
|
|
||||||
$this->setInit(self::BIT_METHOD, true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ZipFileInterface::METHOD_STORED:
|
case ZipFileInterface::METHOD_STORED:
|
||||||
case ZipFileInterface::METHOD_DEFLATED:
|
case ZipFileInterface::METHOD_DEFLATED:
|
||||||
case ZipFileInterface::METHOD_BZIP2:
|
case ZipFileInterface::METHOD_BZIP2:
|
||||||
|
@@ -18,7 +18,7 @@ interface ZipEntry
|
|||||||
// Bit masks for initialized fields.
|
// Bit masks for initialized fields.
|
||||||
const BIT_PLATFORM = 1,
|
const BIT_PLATFORM = 1,
|
||||||
BIT_METHOD = 2 /* 1 << 1 */,
|
BIT_METHOD = 2 /* 1 << 1 */,
|
||||||
BIT_CRC = 2 /* 1 << 2 */,
|
BIT_CRC = 4 /* 1 << 2 */,
|
||||||
BIT_DATE_TIME = 64 /* 1 << 6 */,
|
BIT_DATE_TIME = 64 /* 1 << 6 */,
|
||||||
BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/
|
BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/
|
||||||
;
|
;
|
||||||
|
@@ -224,7 +224,7 @@ class ZipModel implements \Countable
|
|||||||
if (isset($this->outEntries[$entryName])) {
|
if (isset($this->outEntries[$entryName])) {
|
||||||
return $this->outEntries[$entryName];
|
return $this->outEntries[$entryName];
|
||||||
}
|
}
|
||||||
throw new ZipNotFoundEntry('Zip entry ' . $entryName . ' not found');
|
throw new ZipNotFoundEntry('Zip entry "' . $entryName . '" not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -223,7 +223,6 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
| ($entry->isDataDescriptorRequired() ? ZipEntry::GPBF_DATA_DESCRIPTOR : 0)
|
| ($entry->isDataDescriptorRequired() ? ZipEntry::GPBF_DATA_DESCRIPTOR : 0)
|
||||||
| ($utf8 ? ZipEntry::GPBF_UTF8 : 0);
|
| ($utf8 ? ZipEntry::GPBF_UTF8 : 0);
|
||||||
|
|
||||||
$skipCrc = false;
|
|
||||||
$entryContent = null;
|
$entryContent = null;
|
||||||
$extraFieldsCollection = $entry->getExtraFieldsCollection();
|
$extraFieldsCollection = $entry->getExtraFieldsCollection();
|
||||||
if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) {
|
if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) {
|
||||||
@@ -233,57 +232,13 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
$entry->setSize(strlen($entryContent));
|
$entry->setSize(strlen($entryContent));
|
||||||
$entry->setCrc(crc32($entryContent));
|
$entry->setCrc(crc32($entryContent));
|
||||||
|
|
||||||
if (
|
if ($encrypted && ZipEntry::METHOD_WINZIP_AES === $method) {
|
||||||
$encrypted &&
|
/**
|
||||||
(
|
* @var WinZipAesEntryExtraField $field
|
||||||
ZipEntry::METHOD_WINZIP_AES === $method ||
|
*/
|
||||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 ||
|
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
|
||||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 ||
|
if (null !== $field) {
|
||||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
$method = $field->getMethod();
|
||||||
)
|
|
||||||
) {
|
|
||||||
$field = null;
|
|
||||||
$method = $entry->getMethod();
|
|
||||||
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits
|
|
||||||
|
|
||||||
$compressedSize = $entry->getCompressedSize();
|
|
||||||
|
|
||||||
if (ZipEntry::METHOD_WINZIP_AES === $method) {
|
|
||||||
/**
|
|
||||||
* @var WinZipAesEntryExtraField $field
|
|
||||||
*/
|
|
||||||
$field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId());
|
|
||||||
if (null !== $field) {
|
|
||||||
$method = $field->getMethod();
|
|
||||||
if (ZipEntry::UNKNOWN !== $compressedSize) {
|
|
||||||
$compressedSize -= $field->getKeyStrength() / 2 // salt value
|
|
||||||
+ 2 // password verification value
|
|
||||||
+ 10; // authentication code
|
|
||||||
}
|
|
||||||
$entry->setMethod($method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (null === $field) {
|
|
||||||
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
|
|
||||||
}
|
|
||||||
$field->setKeyStrength($keyStrength);
|
|
||||||
$field->setMethod($method);
|
|
||||||
$size = $entry->getSize();
|
|
||||||
if (20 <= $size && ZipFileInterface::METHOD_BZIP2 !== $method) {
|
|
||||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
|
||||||
} else {
|
|
||||||
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
|
||||||
$skipCrc = true;
|
|
||||||
}
|
|
||||||
$extraFieldsCollection->add($field);
|
|
||||||
if (ZipEntry::UNKNOWN !== $compressedSize) {
|
|
||||||
$compressedSize += $field->getKeyStrength() / 2 // salt value
|
|
||||||
+ 2 // password verification value
|
|
||||||
+ 10; // authentication code
|
|
||||||
$entry->setCompressedSize($compressedSize);
|
|
||||||
}
|
|
||||||
if ($skipCrc) {
|
|
||||||
$entry->setCrc(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,14 +292,23 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($encrypted) {
|
if ($encrypted) {
|
||||||
if (
|
if (in_array($entry->getEncryptionMethod(), [
|
||||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 ||
|
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128,
|
||||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 ||
|
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192,
|
||||||
$entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256
|
ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256,
|
||||||
) {
|
], true)) {
|
||||||
if ($skipCrc) {
|
$keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits
|
||||||
|
$field = ExtraFieldsFactory::createWinZipAesEntryExtra();
|
||||||
|
$field->setKeyStrength($keyStrength);
|
||||||
|
$field->setMethod($method);
|
||||||
|
$size = $entry->getSize();
|
||||||
|
if (20 <= $size && ZipFileInterface::METHOD_BZIP2 !== $method) {
|
||||||
|
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1);
|
||||||
|
} else {
|
||||||
|
$field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2);
|
||||||
$entry->setCrc(0);
|
$entry->setCrc(0);
|
||||||
}
|
}
|
||||||
|
$extraFieldsCollection->add($field);
|
||||||
$entry->setMethod(ZipEntry::METHOD_WINZIP_AES);
|
$entry->setMethod(ZipEntry::METHOD_WINZIP_AES);
|
||||||
|
|
||||||
$winZipAesEngine = new WinZipAesEngine($entry);
|
$winZipAesEngine = new WinZipAesEngine($entry);
|
||||||
@@ -375,6 +339,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
* @param ZipEntry $entry
|
* @param ZipEntry $entry
|
||||||
* @param string $content
|
* @param string $content
|
||||||
* @return string
|
* @return string
|
||||||
|
* @throws ZipException
|
||||||
*/
|
*/
|
||||||
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
|
protected function determineBestCompressionMethod(ZipEntry $entry, $content)
|
||||||
{
|
{
|
||||||
|
@@ -1299,11 +1299,11 @@ class ZipFile implements ZipFileInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$stream = new ResponseStream($handle);
|
$stream = new ResponseStream($handle);
|
||||||
$response->withHeader('Content-Type', $mimeType);
|
return $response
|
||||||
$response->withHeader('Content-Disposition', $contentDispositionValue);
|
->withHeader('Content-Type', $mimeType)
|
||||||
$response->withHeader('Content-Length', $stream->getSize());
|
->withHeader('Content-Disposition', $contentDispositionValue)
|
||||||
$response->withBody($stream);
|
->withHeader('Content-Length', $stream->getSize())
|
||||||
return $response;
|
->withBody($stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -75,7 +75,6 @@ class PhpZipExtResourceTest extends ZipTestCase
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[__DIR__ . '/php-zip-ext-test-resources/bug40228.zip'],
|
[__DIR__ . '/php-zip-ext-test-resources/bug40228.zip'],
|
||||||
[__DIR__ . '/php-zip-ext-test-resources/bug40228私はガラスを食べられます.zip'],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ use PhpZip\Model\ZipInfo;
|
|||||||
use PhpZip\Util\CryptoUtil;
|
use PhpZip\Util\CryptoUtil;
|
||||||
use PhpZip\Util\FilesUtil;
|
use PhpZip\Util\FilesUtil;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Zend\Diactoros\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ZipFile test
|
* ZipFile test
|
||||||
@@ -1672,7 +1673,7 @@ class ZipFileTest extends ZipTestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \PhpZip\Exception\ZipNotFoundEntry
|
* @expectedException \PhpZip\Exception\ZipNotFoundEntry
|
||||||
* @expectedExceptionMessage Zip entry bad entry name not found
|
* @expectedExceptionMessage Zip entry "bad entry name" not found
|
||||||
*/
|
*/
|
||||||
public function testNotFoundEntry()
|
public function testNotFoundEntry()
|
||||||
{
|
{
|
||||||
@@ -1786,9 +1787,10 @@ class ZipFileTest extends ZipTestCase
|
|||||||
$zipFile[$i] = $i;
|
$zipFile[$i] = $i;
|
||||||
}
|
}
|
||||||
$filename = 'file.jar';
|
$filename = 'file.jar';
|
||||||
$response = $this->getMock(ResponseInterface::class);
|
$response = $zipFile->outputAsResponse(new Response(), $filename);
|
||||||
$response = $zipFile->outputAsResponse($response, $filename);
|
|
||||||
$this->assertInstanceOf(ResponseInterface::class, $response);
|
$this->assertInstanceOf(ResponseInterface::class, $response);
|
||||||
|
$this->assertEquals('application/java-archive', $response->getHeaderLine('content-type'));
|
||||||
|
$this->assertEquals('attachment; filename="file.jar"', $response->getHeaderLine('content-disposition'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCompressionLevel()
|
public function testCompressionLevel()
|
||||||
|
@@ -346,4 +346,54 @@ class ZipPasswordTest extends ZipFileAddDirTest
|
|||||||
|
|
||||||
$zipFile->close();
|
$zipFile->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/Ne-Lexa/php-zip/issues/9
|
||||||
|
*/
|
||||||
|
public function testIssues9()
|
||||||
|
{
|
||||||
|
$contents = str_pad('', 1000, 'test;test2;test3' . PHP_EOL, STR_PAD_RIGHT);
|
||||||
|
$password = base64_encode(CryptoUtil::randomBytes(20));
|
||||||
|
|
||||||
|
$encryptMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256;
|
||||||
|
$zipFile = new ZipFile();
|
||||||
|
$zipFile
|
||||||
|
->addFromString('codes.csv', $contents)
|
||||||
|
->setPassword($password, $encryptMethod)
|
||||||
|
->saveAsFile($this->outputFilename)
|
||||||
|
->close();
|
||||||
|
|
||||||
|
$this->assertCorrectZipArchive($this->outputFilename, $password);
|
||||||
|
|
||||||
|
$zipFile->openFile($this->outputFilename);
|
||||||
|
$zipFile->setReadPassword($password);
|
||||||
|
$this->assertEquals($zipFile['codes.csv'], $contents);
|
||||||
|
$zipFile->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadAesEncryptedAndRewriteArchive()
|
||||||
|
{
|
||||||
|
$file = __DIR__ . '/resources/aes_password_archive.zip';
|
||||||
|
$password = '1234567890';
|
||||||
|
|
||||||
|
$zipFile = new ZipFile();
|
||||||
|
$zipFile->openFile($file);
|
||||||
|
$zipFile->setReadPassword($password);
|
||||||
|
$zipFile->setEntryComment('contents.txt', 'comment'); // change entry, but not changed contents
|
||||||
|
$zipFile->saveAsFile($this->outputFilename);
|
||||||
|
|
||||||
|
$zipFile2 = new ZipFile();
|
||||||
|
$zipFile2->openFile($this->outputFilename);
|
||||||
|
$zipFile2->setReadPassword($password);
|
||||||
|
$this->assertEquals($zipFile2->getListFiles(), $zipFile->getListFiles());
|
||||||
|
foreach ($zipFile as $name => $contents) {
|
||||||
|
$this->assertNotEmpty($name);
|
||||||
|
$this->assertNotEmpty($contents);
|
||||||
|
$this->assertContains('test contents', $contents);
|
||||||
|
$this->assertEquals($zipFile2[$name], $contents);
|
||||||
|
}
|
||||||
|
$zipFile2->close();
|
||||||
|
|
||||||
|
$zipFile->close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
BIN
tests/PhpZip/resources/aes_password_archive.zip
Normal file
BIN
tests/PhpZip/resources/aes_password_archive.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user