mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-07-25 17:51:13 +02:00
Merge tag '3.1.2' into develop
Tagging version 3.1.2 3.1.2
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 3.1.2 (2017-11-17)
|
||||||
|
- Changed the algorithm for adding paddings to zipalign.
|
||||||
|
Now we will use the special field ExtraField c ID 0xD935,
|
||||||
|
which was implemented by Google in the apksigner library.
|
||||||
|
Now this field corresponds to the ZIP standard for storing
|
||||||
|
ExtraField records, and not just filling with zero bytes,
|
||||||
|
as in the zipalign console utility.
|
||||||
|
|
||||||
|
## 3.1.1 (2017-11-15)
|
||||||
|
- Fix resave zip aligned archive
|
||||||
|
|
||||||
## 3.1.0 (2017-11-14)
|
## 3.1.0 (2017-11-14)
|
||||||
- Added class `ZipModel` for all changes.
|
- Added class `ZipModel` for all changes.
|
||||||
- All manipulations with incoming and outgoing streams are in separate files: `ZipInputStream` and `ZipOutputStream`.
|
- All manipulations with incoming and outgoing streams are in separate files: `ZipInputStream` and `ZipOutputStream`.
|
||||||
|
@@ -51,10 +51,10 @@
|
|||||||
- Поддержка `ZIP64` (размер файла более 4 GB или количество записей в архиве более 65535).
|
- Поддержка `ZIP64` (размер файла более 4 GB или количество записей в архиве более 65535).
|
||||||
- Встроенная поддержка выравнивания архива для оптимизации Android пакетов (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
- Встроенная поддержка выравнивания архива для оптимизации Android пакетов (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
||||||
- Работа с паролями для PHP 5.5
|
- Работа с паролями для PHP 5.5
|
||||||
> **Внимание!**
|
> **Внимание!**
|
||||||
>
|
>
|
||||||
> Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`.
|
> Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`.
|
||||||
> Используйте метод шифрования `WinZIP AES Encryption`, когда это возможно.
|
> Используйте метод шифрования `WinZIP AES Encryption`, когда это возможно.
|
||||||
+ Установка пароля для чтения архива глобально или для некоторых записей.
|
+ Установка пароля для чтения архива глобально или для некоторых записей.
|
||||||
+ Изменение пароля архива, в том числе и для отдельных записей.
|
+ Изменение пароля архива, в том числе и для отдельных записей.
|
||||||
+ Удаление пароля архива глобально или для отдельных записей.
|
+ Удаление пароля архива глобально или для отдельных записей.
|
||||||
|
@@ -51,10 +51,10 @@ Table of contents
|
|||||||
- Support for `ZIP64` (file size is more than 4 GB or the number of entries in the archive is more than 65535).
|
- Support for `ZIP64` (file size is more than 4 GB or the number of entries in the archive is more than 65535).
|
||||||
- Built-in support for aligning the archive to optimize Android packages (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
- Built-in support for aligning the archive to optimize Android packages (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
||||||
- Working with passwords for PHP 5.5
|
- Working with passwords for PHP 5.5
|
||||||
> **Attention!**
|
> **Attention!**
|
||||||
>
|
>
|
||||||
> For 32-bit systems, the `Traditional PKWARE Encryption (ZipCrypto)` encryption method is not currently supported.
|
> For 32-bit systems, the `Traditional PKWARE Encryption (ZipCrypto)` encryption method is not currently supported.
|
||||||
> Use the encryption method `WinZIP AES Encryption`, whenever possible.
|
> Use the encryption method `WinZIP AES Encryption`, whenever possible.
|
||||||
+ Set the password to read the archive for all entries or only for some.
|
+ Set the password to read the archive for all entries or only for some.
|
||||||
+ Change the password for the archive, including for individual entries.
|
+ Change the password for the archive, including for individual entries.
|
||||||
+ Delete the archive password for all or individual entries.
|
+ Delete the archive password for all or individual entries.
|
||||||
|
@@ -3,11 +3,14 @@
|
|||||||
namespace PhpZip\Extra;
|
namespace PhpZip\Extra;
|
||||||
|
|
||||||
use PhpZip\Exception\ZipException;
|
use PhpZip\Exception\ZipException;
|
||||||
|
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||||
use PhpZip\Extra\Fields\DefaultExtraField;
|
use PhpZip\Extra\Fields\DefaultExtraField;
|
||||||
|
use PhpZip\Extra\Fields\JarMarkerExtraField;
|
||||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||||
use PhpZip\Model\ZipEntry;
|
use PhpZip\Model\ZipEntry;
|
||||||
|
use PhpZip\Util\StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extra Fields Factory
|
* Extra Fields Factory
|
||||||
@@ -26,6 +29,56 @@ class ExtraFieldsFactory
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $extra
|
||||||
|
* @param ZipEntry|null $entry
|
||||||
|
* @return ExtraFieldsCollection
|
||||||
|
* @throws ZipException
|
||||||
|
*/
|
||||||
|
public static function createExtraFieldCollections($extra, ZipEntry $entry = null)
|
||||||
|
{
|
||||||
|
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||||
|
if (null !== $extra) {
|
||||||
|
$extraLength = strlen($extra);
|
||||||
|
if ($extraLength > 0xffff) {
|
||||||
|
throw new ZipException("Extra Fields too large: " . $extraLength);
|
||||||
|
}
|
||||||
|
$pos = 0;
|
||||||
|
$endPos = $extraLength;
|
||||||
|
|
||||||
|
while ($endPos - $pos >= 4) {
|
||||||
|
$unpack = unpack('vheaderId/vdataSize', substr($extra, $pos, 4));
|
||||||
|
$pos += 4;
|
||||||
|
$headerId = (int)$unpack['headerId'];
|
||||||
|
$dataSize = (int)$unpack['dataSize'];
|
||||||
|
$extraField = ExtraFieldsFactory::create($headerId);
|
||||||
|
if ($extraField instanceof Zip64ExtraField && $entry !== null) {
|
||||||
|
$extraField->setEntry($entry);
|
||||||
|
}
|
||||||
|
$extraField->deserialize(substr($extra, $pos, $dataSize));
|
||||||
|
$pos += $dataSize;
|
||||||
|
$extraFieldsCollection[$headerId] = $extraField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $extraFieldsCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createSerializedData(ExtraFieldsCollection $extraFieldsCollection)
|
||||||
|
{
|
||||||
|
$extraData = '';
|
||||||
|
foreach ($extraFieldsCollection as $extraField) {
|
||||||
|
$data = $extraField->serialize();
|
||||||
|
$extraData .= pack('vv', $extraField::getHeaderId(), strlen($data));
|
||||||
|
$extraData .= $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = strlen($extraData);
|
||||||
|
if (0x0000 > $size || $size > 0xffff) {
|
||||||
|
throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData);
|
||||||
|
}
|
||||||
|
return $extraData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A static factory method which creates a new Extra Field based on the
|
* A static factory method which creates a new Extra Field based on the
|
||||||
* given Header ID.
|
* given Header ID.
|
||||||
@@ -69,6 +122,8 @@ class ExtraFieldsFactory
|
|||||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
||||||
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
||||||
self::$registry[Zip64ExtraField::getHeaderId()] = Zip64ExtraField::class;
|
self::$registry[Zip64ExtraField::getHeaderId()] = Zip64ExtraField::class;
|
||||||
|
self::$registry[ApkAlignmentExtraField::getHeaderId()] = ApkAlignmentExtraField::class;
|
||||||
|
self::$registry[JarMarkerExtraField::getHeaderId()] = JarMarkerExtraField::class;
|
||||||
}
|
}
|
||||||
return self::$registry;
|
return self::$registry;
|
||||||
}
|
}
|
||||||
@@ -97,4 +152,22 @@ class ExtraFieldsFactory
|
|||||||
{
|
{
|
||||||
return new Zip64ExtraField($entry);
|
return new Zip64ExtraField($entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ZipEntry $entry
|
||||||
|
* @param int $padding
|
||||||
|
* @return ApkAlignmentExtraField
|
||||||
|
*/
|
||||||
|
public static function createApkAlignExtra(ZipEntry $entry, $padding)
|
||||||
|
{
|
||||||
|
$padding = (int)$padding;
|
||||||
|
$multiple = 4;
|
||||||
|
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||||
|
$multiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||||
|
}
|
||||||
|
$extraField = new ApkAlignmentExtraField();
|
||||||
|
$extraField->setMultiple($multiple);
|
||||||
|
$extraField->setPadding($padding);
|
||||||
|
return $extraField;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
112
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
Normal file
112
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpZip\Extra\Fields;
|
||||||
|
|
||||||
|
use PhpZip\Exception\InvalidArgumentException;
|
||||||
|
use PhpZip\Extra\ExtraField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apk Alignment Extra Field
|
||||||
|
*
|
||||||
|
* @author Ne-Lexa alexey@nelexa.ru
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
class ApkAlignmentExtraField implements ExtraField
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Minimum size (in bytes) of the extensible data block/field used
|
||||||
|
* for alignment of uncompressed entries.
|
||||||
|
*/
|
||||||
|
const ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES = 6;
|
||||||
|
|
||||||
|
const ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $multiple;
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $padding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Header ID (type) of this Extra Field.
|
||||||
|
* The Header ID is an unsigned short integer (two bytes)
|
||||||
|
* which must be constant during the life cycle of this object.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function getHeaderId()
|
||||||
|
{
|
||||||
|
return 0xD935;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a Data Block.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function serialize()
|
||||||
|
{
|
||||||
|
if ($this->padding > 0) {
|
||||||
|
$args = array_merge(
|
||||||
|
['vc*', $this->multiple],
|
||||||
|
array_fill(2, $this->padding, 0)
|
||||||
|
);
|
||||||
|
return call_user_func_array('pack', $args);
|
||||||
|
}
|
||||||
|
return pack('v', $this->multiple);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this Extra Field by deserializing a Data Block.
|
||||||
|
* @param string $data
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function deserialize($data)
|
||||||
|
{
|
||||||
|
$length = strlen($data);
|
||||||
|
if ($length < 2) {
|
||||||
|
// This is APK alignment field.
|
||||||
|
// FORMAT:
|
||||||
|
// * uint16 alignment multiple (in bytes)
|
||||||
|
// * remaining bytes -- padding to achieve alignment of data which starts after
|
||||||
|
// the extra field
|
||||||
|
throw new InvalidArgumentException("Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.");
|
||||||
|
}
|
||||||
|
$this->multiple = unpack('v', $data)[1];
|
||||||
|
$this->padding = $length - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getMultiple()
|
||||||
|
{
|
||||||
|
return $this->multiple;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPadding()
|
||||||
|
{
|
||||||
|
return $this->padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $multiple
|
||||||
|
*/
|
||||||
|
public function setMultiple($multiple)
|
||||||
|
{
|
||||||
|
$this->multiple = $multiple;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $padding
|
||||||
|
*/
|
||||||
|
public function setPadding($padding)
|
||||||
|
{
|
||||||
|
$this->padding = $padding;
|
||||||
|
}
|
||||||
|
}
|
51
src/PhpZip/Extra/Fields/JarMarkerExtraField.php
Normal file
51
src/PhpZip/Extra/Fields/JarMarkerExtraField.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpZip\Extra\Fields;
|
||||||
|
|
||||||
|
use PhpZip\Exception\ZipException;
|
||||||
|
use PhpZip\Extra\ExtraField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jar Marker Extra Field
|
||||||
|
* An executable Java program can be packaged in a JAR file with all the libraries it uses.
|
||||||
|
* Executable JAR files can easily be distinguished from the files packed in the JAR file
|
||||||
|
* by the extra field in the first file, which is hexadecimal in the 0xCAFE bytes series.
|
||||||
|
*
|
||||||
|
* @author Ne-Lexa alexey@nelexa.ru
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
class JarMarkerExtraField implements ExtraField
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the Header ID (type) of this Extra Field.
|
||||||
|
* The Header ID is an unsigned short integer (two bytes)
|
||||||
|
* which must be constant during the life cycle of this object.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function getHeaderId()
|
||||||
|
{
|
||||||
|
return 0xCAFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a Data Block.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function serialize()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this Extra Field by deserializing a Data Block.
|
||||||
|
* @param string $data
|
||||||
|
* @throws ZipException
|
||||||
|
*/
|
||||||
|
public function deserialize($data)
|
||||||
|
{
|
||||||
|
if (0 !== strlen($data)) {
|
||||||
|
throw new ZipException("JarMarker doesn't expect any data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,7 +7,6 @@ use PhpZip\Exception\ZipException;
|
|||||||
use PhpZip\Extra\ExtraFieldsCollection;
|
use PhpZip\Extra\ExtraFieldsCollection;
|
||||||
use PhpZip\Extra\ExtraFieldsFactory;
|
use PhpZip\Extra\ExtraFieldsFactory;
|
||||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
|
||||||
use PhpZip\Model\ZipEntry;
|
use PhpZip\Model\ZipEntry;
|
||||||
use PhpZip\Util\DateTimeConverter;
|
use PhpZip\Util\DateTimeConverter;
|
||||||
use PhpZip\Util\StringUtil;
|
use PhpZip\Util\StringUtil;
|
||||||
@@ -585,18 +584,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
|||||||
*/
|
*/
|
||||||
public function getExtra()
|
public function getExtra()
|
||||||
{
|
{
|
||||||
$extraData = '';
|
return ExtraFieldsFactory::createSerializedData($this->extraFieldsCollection);
|
||||||
foreach ($this->getExtraFieldsCollection() as $extraField) {
|
|
||||||
$data = $extraField->serialize();
|
|
||||||
$extraData .= pack('vv', $extraField::getHeaderId(), strlen($data));
|
|
||||||
$extraData .= $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = strlen($extraData);
|
|
||||||
if (0x0000 > $size || $size > 0xffff) {
|
|
||||||
throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData);
|
|
||||||
}
|
|
||||||
return $extraData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -612,28 +600,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
|||||||
*/
|
*/
|
||||||
public function setExtra($data)
|
public function setExtra($data)
|
||||||
{
|
{
|
||||||
$this->extraFieldsCollection = new ExtraFieldsCollection();
|
$this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);
|
||||||
if (null !== $data) {
|
|
||||||
$extraLength = strlen($data);
|
|
||||||
if (0x0000 > $extraLength || $extraLength > 0xffff) {
|
|
||||||
throw new ZipException("Extra Fields too large: " . $extraLength);
|
|
||||||
}
|
|
||||||
$pos = 0;
|
|
||||||
$endPos = $extraLength;
|
|
||||||
while ($pos < $endPos) {
|
|
||||||
$unpack = unpack('vheaderId/vdataSize', substr($data, $pos, 4));
|
|
||||||
$pos += 4;
|
|
||||||
$headerId = (int)$unpack['headerId'];
|
|
||||||
$dataSize = (int)$unpack['dataSize'];
|
|
||||||
$extraField = ExtraFieldsFactory::create($headerId);
|
|
||||||
if ($extraField instanceof Zip64ExtraField) {
|
|
||||||
$extraField->setEntry($this);
|
|
||||||
}
|
|
||||||
$extraField->deserialize(substr($data, $pos, $dataSize));
|
|
||||||
$pos += $dataSize;
|
|
||||||
$this->extraFieldsCollection[$headerId] = $extraField;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -62,9 +62,9 @@ class ZipNewEntry extends ZipAbstractEntry
|
|||||||
$method = $this->getMethod();
|
$method = $this->getMethod();
|
||||||
return self::METHOD_WINZIP_AES === $method ? 51 :
|
return self::METHOD_WINZIP_AES === $method ? 51 :
|
||||||
(
|
(
|
||||||
ZipFileInterface::METHOD_BZIP2 === $method ? 46 :
|
ZipFileInterface::METHOD_BZIP2 === $method ? 46 :
|
||||||
(
|
(
|
||||||
$this->isZip64ExtensionsRequired() ? 45 :
|
$this->isZip64ExtensionsRequired() ? 45 :
|
||||||
(ZipFileInterface::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
|
(ZipFileInterface::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
namespace PhpZip\Model;
|
namespace PhpZip\Model;
|
||||||
|
|
||||||
use PhpZip\Exception\ZipException;
|
use PhpZip\Exception\ZipException;
|
||||||
|
|
||||||
use PhpZip\Extra\ExtraFieldsCollection;
|
use PhpZip\Extra\ExtraFieldsCollection;
|
||||||
use PhpZip\ZipFileInterface;
|
use PhpZip\ZipFileInterface;
|
||||||
|
|
||||||
|
@@ -10,6 +10,9 @@ use PhpZip\Exception\RuntimeException;
|
|||||||
use PhpZip\Exception\ZipCryptoException;
|
use PhpZip\Exception\ZipCryptoException;
|
||||||
use PhpZip\Exception\ZipException;
|
use PhpZip\Exception\ZipException;
|
||||||
use PhpZip\Exception\ZipUnsupportMethod;
|
use PhpZip\Exception\ZipUnsupportMethod;
|
||||||
|
use PhpZip\Extra\ExtraFieldsCollection;
|
||||||
|
use PhpZip\Extra\ExtraFieldsFactory;
|
||||||
|
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||||
use PhpZip\Mapper\OffsetPositionMapper;
|
use PhpZip\Mapper\OffsetPositionMapper;
|
||||||
use PhpZip\Mapper\PositionMapper;
|
use PhpZip\Mapper\PositionMapper;
|
||||||
@@ -18,6 +21,7 @@ use PhpZip\Model\Entry\ZipSourceEntry;
|
|||||||
use PhpZip\Model\ZipEntry;
|
use PhpZip\Model\ZipEntry;
|
||||||
use PhpZip\Model\ZipModel;
|
use PhpZip\Model\ZipModel;
|
||||||
use PhpZip\Util\PackUtil;
|
use PhpZip\Util\PackUtil;
|
||||||
|
use PhpZip\Util\StringUtil;
|
||||||
use PhpZip\ZipFileInterface;
|
use PhpZip\ZipFileInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -471,6 +475,9 @@ class ZipInputStream implements ZipInputStreamInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Copy the input stream of the LOC entry zip and the data into
|
||||||
|
* the output stream and zip the alignment if necessary.
|
||||||
|
*
|
||||||
* @param ZipEntry $entry
|
* @param ZipEntry $entry
|
||||||
* @param ZipOutputStreamInterface $out
|
* @param ZipOutputStreamInterface $out
|
||||||
*/
|
*/
|
||||||
@@ -484,37 +491,82 @@ class ZipInputStream implements ZipInputStreamInterface
|
|||||||
$nameLength = strlen($entry->getName());
|
$nameLength = strlen($entry->getName());
|
||||||
|
|
||||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
|
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
|
||||||
$extraLength = unpack('v', fread($this->in, 2))[1];
|
$sourceExtraLength = $destExtraLength = unpack('v', fread($this->in, 2))[1];
|
||||||
|
|
||||||
$length = ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $extraLength + $nameLength;
|
if ($sourceExtraLength > 0) {
|
||||||
|
// read Local File Header extra fields
|
||||||
$padding = 0;
|
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, SEEK_SET);
|
||||||
if ($this->zipModel->isZipAlign() && !$entry->isEncrypted() && $entry->getMethod() === ZipFileInterface::METHOD_STORED) {
|
$extra = fread($this->in, $sourceExtraLength);
|
||||||
$padding =
|
$extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($extra, $entry);
|
||||||
(
|
if (isset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]) && $this->zipModel->isZipAlign()) {
|
||||||
$this->zipModel->getZipAlign() -
|
unset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]);
|
||||||
(ftell($out->getStream()) + $length) % $this->zipModel->getZipAlign()
|
$destExtraLength = strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection));
|
||||||
) % $this->zipModel->getZipAlign();
|
}
|
||||||
|
} else {
|
||||||
|
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||||
|
$copyInToOutLength = $entry->getCompressedSize();
|
||||||
|
|
||||||
fseek($this->in, $pos, SEEK_SET);
|
fseek($this->in, $pos, SEEK_SET);
|
||||||
if ($padding > 0) {
|
|
||||||
stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2);
|
if (
|
||||||
fwrite($out->getStream(), pack('v', $extraLength + $padding));
|
$this->zipModel->isZipAlign() &&
|
||||||
fseek($this->in, 2, SEEK_CUR);
|
!$entry->isEncrypted() &&
|
||||||
stream_copy_to_stream($this->in, $out->getStream(), $nameLength + $extraLength);
|
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||||
fwrite($out->getStream(), str_repeat(chr(0), $padding));
|
) {
|
||||||
} else {
|
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||||
stream_copy_to_stream($this->in, $out->getStream(), $length);
|
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||||
}
|
|
||||||
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
|
|
||||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
|
||||||
$length = 12;
|
|
||||||
if ($entry->isZip64ExtensionsRequired()) {
|
|
||||||
$length += 8;
|
|
||||||
}
|
}
|
||||||
stream_copy_to_stream($this->in, $out->getStream(), $length);
|
|
||||||
|
$dataMinStartOffset =
|
||||||
|
ftell($out->getStream()) +
|
||||||
|
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||||
|
$destExtraLength +
|
||||||
|
$nameLength +
|
||||||
|
ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES;
|
||||||
|
$padding =
|
||||||
|
($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple))
|
||||||
|
% $dataAlignmentMultiple;
|
||||||
|
|
||||||
|
$alignExtra = new ApkAlignmentExtraField();
|
||||||
|
$alignExtra->setMultiple($dataAlignmentMultiple);
|
||||||
|
$alignExtra->setPadding($padding);
|
||||||
|
$extraFieldsCollection->add($alignExtra);
|
||||||
|
|
||||||
|
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
|
||||||
|
|
||||||
|
// copy Local File Header without extra field length
|
||||||
|
// from input stream to output stream
|
||||||
|
stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2);
|
||||||
|
// write new extra field length (2 bytes) to output stream
|
||||||
|
fwrite($out->getStream(), pack('v', strlen($extra)));
|
||||||
|
// skip 2 bytes to input stream
|
||||||
|
fseek($this->in, 2, SEEK_CUR);
|
||||||
|
// copy name from input stream to output stream
|
||||||
|
stream_copy_to_stream($this->in, $out->getStream(), $nameLength);
|
||||||
|
// write extra field to output stream
|
||||||
|
fwrite($out->getStream(), $extra);
|
||||||
|
// skip source extraLength from input stream
|
||||||
|
fseek($this->in, $sourceExtraLength, SEEK_CUR);
|
||||||
|
} else {
|
||||||
|
$copyInToOutLength += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $sourceExtraLength + $nameLength;
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||||
|
// crc-32 4 bytes
|
||||||
|
// compressed size 4 bytes
|
||||||
|
// uncompressed size 4 bytes
|
||||||
|
$copyInToOutLength += 12;
|
||||||
|
if ($entry->isZip64ExtensionsRequired()) {
|
||||||
|
// compressed size +4 bytes
|
||||||
|
// uncompressed size +4 bytes
|
||||||
|
$copyInToOutLength += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// copy loc, data, data descriptor from input to output stream
|
||||||
|
stream_copy_to_stream($this->in, $out->getStream(), $copyInToOutLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -532,6 +584,7 @@ class ZipInputStream implements ZipInputStreamInterface
|
|||||||
$extraLength = unpack('v', fread($this->in, 2))[1];
|
$extraLength = unpack('v', fread($this->in, 2))[1];
|
||||||
|
|
||||||
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, SEEK_SET);
|
fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, SEEK_SET);
|
||||||
|
// copy raw data from input stream to output stream
|
||||||
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
|
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,6 +35,9 @@ interface ZipInputStreamInterface
|
|||||||
public function getStream();
|
public function getStream();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Copy the input stream of the LOC entry zip and the data into
|
||||||
|
* the output stream and zip the alignment if necessary.
|
||||||
|
*
|
||||||
* @param ZipEntry $entry
|
* @param ZipEntry $entry
|
||||||
* @param ZipOutputStreamInterface $out
|
* @param ZipOutputStreamInterface $out
|
||||||
*/
|
*/
|
||||||
|
@@ -8,6 +8,7 @@ use PhpZip\Exception\InvalidArgumentException;
|
|||||||
use PhpZip\Exception\RuntimeException;
|
use PhpZip\Exception\RuntimeException;
|
||||||
use PhpZip\Exception\ZipException;
|
use PhpZip\Exception\ZipException;
|
||||||
use PhpZip\Extra\ExtraFieldsFactory;
|
use PhpZip\Extra\ExtraFieldsFactory;
|
||||||
|
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||||
use PhpZip\Model\EndOfCentralDirectory;
|
use PhpZip\Model\EndOfCentralDirectory;
|
||||||
@@ -17,6 +18,7 @@ use PhpZip\Model\Entry\ZipSourceEntry;
|
|||||||
use PhpZip\Model\ZipEntry;
|
use PhpZip\Model\ZipEntry;
|
||||||
use PhpZip\Model\ZipModel;
|
use PhpZip\Model\ZipModel;
|
||||||
use PhpZip\Util\PackUtil;
|
use PhpZip\Util\PackUtil;
|
||||||
|
use PhpZip\Util\StringUtil;
|
||||||
use PhpZip\ZipFileInterface;
|
use PhpZip\ZipFileInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,6 +89,39 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
|
|
||||||
$nameLength = strlen($entry->getName());
|
$nameLength = strlen($entry->getName());
|
||||||
$extraLength = strlen($extra);
|
$extraLength = strlen($extra);
|
||||||
|
|
||||||
|
// zip align
|
||||||
|
if (
|
||||||
|
$this->zipModel->isZipAlign() &&
|
||||||
|
!$entry->isEncrypted() &&
|
||||||
|
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||||
|
) {
|
||||||
|
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||||
|
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||||
|
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||||
|
}
|
||||||
|
$dataMinStartOffset =
|
||||||
|
$offset +
|
||||||
|
ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
|
||||||
|
$extraLength +
|
||||||
|
$nameLength +
|
||||||
|
ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES;
|
||||||
|
|
||||||
|
$padding =
|
||||||
|
($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple))
|
||||||
|
% $dataAlignmentMultiple;
|
||||||
|
|
||||||
|
$alignExtra = new ApkAlignmentExtraField();
|
||||||
|
$alignExtra->setMultiple($dataAlignmentMultiple);
|
||||||
|
$alignExtra->setPadding($padding);
|
||||||
|
|
||||||
|
$extraFieldsCollection = clone $entry->getExtraFieldsCollection();
|
||||||
|
$extraFieldsCollection->add($alignExtra);
|
||||||
|
|
||||||
|
$extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection);
|
||||||
|
$extraLength = strlen($extra);
|
||||||
|
}
|
||||||
|
|
||||||
$size = $nameLength + $extraLength;
|
$size = $nameLength + $extraLength;
|
||||||
if (0xffff < $size) {
|
if (0xffff < $size) {
|
||||||
throw new ZipException(
|
throw new ZipException(
|
||||||
@@ -96,20 +131,7 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// zip align
|
|
||||||
$padding = 0;
|
|
||||||
if ($this->zipModel->isZipAlign() && !$entry->isEncrypted() && $entry->getMethod() === ZipFileInterface::METHOD_STORED) {
|
|
||||||
$padding =
|
|
||||||
(
|
|
||||||
$this->zipModel->getZipAlign() -
|
|
||||||
(
|
|
||||||
$offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength
|
|
||||||
) % $this->zipModel->getZipAlign()
|
|
||||||
) % $this->zipModel->getZipAlign();
|
|
||||||
}
|
|
||||||
|
|
||||||
$dd = $entry->isDataDescriptorRequired();
|
$dd = $entry->isDataDescriptorRequired();
|
||||||
|
|
||||||
fwrite(
|
fwrite(
|
||||||
$this->out,
|
$this->out,
|
||||||
pack(
|
pack(
|
||||||
@@ -134,18 +156,16 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
|||||||
// file name length 2 bytes
|
// file name length 2 bytes
|
||||||
$nameLength,
|
$nameLength,
|
||||||
// extra field length 2 bytes
|
// extra field length 2 bytes
|
||||||
$extraLength + $padding
|
$extraLength
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
fwrite($this->out, $entry->getName());
|
if ($nameLength > 0) {
|
||||||
|
fwrite($this->out, $entry->getName());
|
||||||
|
}
|
||||||
if ($extraLength > 0) {
|
if ($extraLength > 0) {
|
||||||
fwrite($this->out, $extra);
|
fwrite($this->out, $extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($padding > 0) {
|
|
||||||
fwrite($this->out, str_repeat(chr(0), $padding));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($entry instanceof ZipChangesEntry && !$entry->isChangedContent()) {
|
if ($entry instanceof ZipChangesEntry && !$entry->isChangedContent()) {
|
||||||
$entry->getSourceEntry()->getInputStream()->copyEntryData($entry->getSourceEntry(), $this);
|
$entry->getSourceEntry()->getInputStream()->copyEntryData($entry->getSourceEntry(), $this);
|
||||||
} elseif (null !== $entryContent) {
|
} elseif (null !== $entryContent) {
|
||||||
|
@@ -9,28 +9,15 @@ use PhpZip\Util\CryptoUtil;
|
|||||||
*/
|
*/
|
||||||
class ZipAlignTest extends ZipTestCase
|
class ZipAlignTest extends ZipTestCase
|
||||||
{
|
{
|
||||||
public function testApkAlignedAndReSave()
|
|
||||||
{
|
|
||||||
$filename = __DIR__ . '/resources/test.apk';
|
|
||||||
|
|
||||||
self::assertCorrectZipArchive($filename);
|
|
||||||
self::doZipAlignVerify($this->outputFilename);
|
|
||||||
|
|
||||||
$zipFile = new ZipFile();
|
|
||||||
$zipFile->openFile($filename);
|
|
||||||
$zipFile->saveAsFile($this->outputFilename);
|
|
||||||
$zipFile->close();
|
|
||||||
|
|
||||||
self::assertCorrectZipArchive($this->outputFilename);
|
|
||||||
self::doZipAlignVerify($this->outputFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testApkAlignedAndSetZipAlignAndReSave()
|
public function testApkAlignedAndSetZipAlignAndReSave()
|
||||||
{
|
{
|
||||||
$filename = __DIR__ . '/resources/test.apk';
|
$filename = __DIR__ . '/resources/test.apk';
|
||||||
|
|
||||||
self::assertCorrectZipArchive($filename);
|
self::assertCorrectZipArchive($filename);
|
||||||
self::doZipAlignVerify($this->outputFilename);
|
$result = self::doZipAlignVerify($filename);
|
||||||
|
if (null !== $result) {
|
||||||
|
self::assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
$zipFile = new ZipFile();
|
$zipFile = new ZipFile();
|
||||||
$zipFile->openFile($filename);
|
$zipFile->openFile($filename);
|
||||||
@@ -39,7 +26,10 @@ class ZipAlignTest extends ZipTestCase
|
|||||||
$zipFile->close();
|
$zipFile->close();
|
||||||
|
|
||||||
self::assertCorrectZipArchive($this->outputFilename);
|
self::assertCorrectZipArchive($this->outputFilename);
|
||||||
self::doZipAlignVerify($this->outputFilename);
|
$result = self::doZipAlignVerify($this->outputFilename, true);
|
||||||
|
if (null !== $result) {
|
||||||
|
self::assertTrue($result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user