mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-03-13 19:19:42 +01:00
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.
This commit is contained in:
parent
d67fc4db7d
commit
a3083b821c
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
||||
# 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)
|
||||
- Added class `ZipModel` for all changes.
|
||||
- All manipulations with incoming and outgoing streams are in separate files: `ZipInputStream` and `ZipOutputStream`.
|
||||
|
@ -51,10 +51,10 @@
|
||||
- Поддержка `ZIP64` (размер файла более 4 GB или количество записей в архиве более 65535).
|
||||
- Встроенная поддержка выравнивания архива для оптимизации Android пакетов (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html).
|
||||
- Работа с паролями для PHP 5.5
|
||||
> **Внимание!**
|
||||
>
|
||||
> Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`.
|
||||
> Используйте метод шифрования `WinZIP AES Encryption`, когда это возможно.
|
||||
> **Внимание!**
|
||||
>
|
||||
> Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`.
|
||||
> Используйте метод шифрования `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).
|
||||
- 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
|
||||
> **Attention!**
|
||||
>
|
||||
> For 32-bit systems, the `Traditional PKWARE Encryption (ZipCrypto)` encryption method is not currently supported.
|
||||
> Use the encryption method `WinZIP AES Encryption`, whenever possible.
|
||||
> **Attention!**
|
||||
>
|
||||
> For 32-bit systems, the `Traditional PKWARE Encryption (ZipCrypto)` encryption method is not currently supported.
|
||||
> Use the encryption method `WinZIP AES Encryption`, whenever possible.
|
||||
+ Set the password to read the archive for all entries or only for some.
|
||||
+ Change the password for the archive, including for individual entries.
|
||||
+ Delete the archive password for all or individual entries.
|
||||
|
@ -3,11 +3,14 @@
|
||||
namespace PhpZip\Extra;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Extra\Fields\DefaultExtraField;
|
||||
use PhpZip\Extra\Fields\JarMarkerExtraField;
|
||||
use PhpZip\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\StringUtil;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* given Header ID.
|
||||
@ -69,6 +122,8 @@ class ExtraFieldsFactory
|
||||
self::$registry[WinZipAesEntryExtraField::getHeaderId()] = WinZipAesEntryExtraField::class;
|
||||
self::$registry[NtfsExtraField::getHeaderId()] = NtfsExtraField::class;
|
||||
self::$registry[Zip64ExtraField::getHeaderId()] = Zip64ExtraField::class;
|
||||
self::$registry[ApkAlignmentExtraField::getHeaderId()] = ApkAlignmentExtraField::class;
|
||||
self::$registry[JarMarkerExtraField::getHeaderId()] = JarMarkerExtraField::class;
|
||||
}
|
||||
return self::$registry;
|
||||
}
|
||||
@ -97,4 +152,22 @@ class ExtraFieldsFactory
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
109
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
Normal file
109
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?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()
|
||||
{
|
||||
$args = array_merge(
|
||||
['vc*', $this->multiple],
|
||||
array_fill(2, $this->padding, 0)
|
||||
);
|
||||
return call_user_func_array('pack', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Util\DateTimeConverter;
|
||||
use PhpZip\Util\StringUtil;
|
||||
@ -585,18 +584,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
$extraData = '';
|
||||
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;
|
||||
return ExtraFieldsFactory::createSerializedData($this->extraFieldsCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -612,28 +600,7 @@ abstract class ZipAbstractEntry implements ZipEntry
|
||||
*/
|
||||
public function setExtra($data)
|
||||
{
|
||||
$this->extraFieldsCollection = new ExtraFieldsCollection();
|
||||
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;
|
||||
}
|
||||
}
|
||||
$this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,9 +62,9 @@ class ZipNewEntry extends ZipAbstractEntry
|
||||
$method = $this->getMethod();
|
||||
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)
|
||||
)
|
||||
);
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace PhpZip\Model;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
|
@ -10,6 +10,9 @@ use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipCryptoException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Exception\ZipUnsupportMethod;
|
||||
use PhpZip\Extra\ExtraFieldsCollection;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Mapper\OffsetPositionMapper;
|
||||
use PhpZip\Mapper\PositionMapper;
|
||||
@ -18,6 +21,7 @@ use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\Util\StringUtil;
|
||||
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 ZipOutputStreamInterface $out
|
||||
*/
|
||||
@ -484,37 +491,82 @@ class ZipInputStream implements ZipInputStreamInterface
|
||||
$nameLength = strlen($entry->getName());
|
||||
|
||||
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;
|
||||
|
||||
$padding = 0;
|
||||
if ($this->zipModel->isZipAlign() && !$entry->isEncrypted() && $entry->getMethod() === ZipFileInterface::METHOD_STORED) {
|
||||
$padding =
|
||||
(
|
||||
$this->zipModel->getZipAlign() -
|
||||
(ftell($out->getStream()) + $length) % $this->zipModel->getZipAlign()
|
||||
) % $this->zipModel->getZipAlign();
|
||||
if ($sourceExtraLength > 0) {
|
||||
// read Local File Header extra fields
|
||||
fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, SEEK_SET);
|
||||
$extra = fread($this->in, $sourceExtraLength);
|
||||
$extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($extra, $entry);
|
||||
if (isset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]) && $this->zipModel->isZipAlign()) {
|
||||
unset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]);
|
||||
$destExtraLength = strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection));
|
||||
}
|
||||
} else {
|
||||
$extraFieldsCollection = new ExtraFieldsCollection();
|
||||
}
|
||||
|
||||
$dataAlignmentMultiple = $this->zipModel->getZipAlign();
|
||||
$copyInToOutLength = $entry->getCompressedSize();
|
||||
|
||||
fseek($this->in, $pos, SEEK_SET);
|
||||
if ($padding > 0) {
|
||||
stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2);
|
||||
fwrite($out->getStream(), pack('v', $extraLength + $padding));
|
||||
fseek($this->in, 2, SEEK_CUR);
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $nameLength + $extraLength);
|
||||
fwrite($out->getStream(), str_repeat(chr(0), $padding));
|
||||
} else {
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $length);
|
||||
}
|
||||
stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
|
||||
if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
|
||||
$length = 12;
|
||||
if ($entry->isZip64ExtensionsRequired()) {
|
||||
$length += 8;
|
||||
|
||||
if (
|
||||
$this->zipModel->isZipAlign() &&
|
||||
!$entry->isEncrypted() &&
|
||||
$entry->getMethod() === ZipFileInterface::METHOD_STORED
|
||||
) {
|
||||
if (StringUtil::endsWith($entry->getName(), '.so')) {
|
||||
$dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
|
||||
}
|
||||
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];
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,9 @@ interface ZipInputStreamInterface
|
||||
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 ZipOutputStreamInterface $out
|
||||
*/
|
||||
|
@ -8,6 +8,7 @@ use PhpZip\Exception\InvalidArgumentException;
|
||||
use PhpZip\Exception\RuntimeException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Extra\ExtraFieldsFactory;
|
||||
use PhpZip\Extra\Fields\ApkAlignmentExtraField;
|
||||
use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
|
||||
use PhpZip\Extra\Fields\Zip64ExtraField;
|
||||
use PhpZip\Model\EndOfCentralDirectory;
|
||||
@ -17,6 +18,7 @@ use PhpZip\Model\Entry\ZipSourceEntry;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\Model\ZipModel;
|
||||
use PhpZip\Util\PackUtil;
|
||||
use PhpZip\Util\StringUtil;
|
||||
use PhpZip\ZipFileInterface;
|
||||
|
||||
/**
|
||||
@ -87,6 +89,39 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
|
||||
$nameLength = strlen($entry->getName());
|
||||
$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;
|
||||
if (0xffff < $size) {
|
||||
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();
|
||||
|
||||
fwrite(
|
||||
$this->out,
|
||||
pack(
|
||||
@ -134,18 +156,16 @@ class ZipOutputStream implements ZipOutputStreamInterface
|
||||
// file name length 2 bytes
|
||||
$nameLength,
|
||||
// extra field length 2 bytes
|
||||
$extraLength + $padding
|
||||
$extraLength
|
||||
)
|
||||
);
|
||||
fwrite($this->out, $entry->getName());
|
||||
if ($nameLength > 0) {
|
||||
fwrite($this->out, $entry->getName());
|
||||
}
|
||||
if ($extraLength > 0) {
|
||||
fwrite($this->out, $extra);
|
||||
}
|
||||
|
||||
if ($padding > 0) {
|
||||
fwrite($this->out, str_repeat(chr(0), $padding));
|
||||
}
|
||||
|
||||
if ($entry instanceof ZipChangesEntry && !$entry->isChangedContent()) {
|
||||
$entry->getSourceEntry()->getInputStream()->copyEntryData($entry->getSourceEntry(), $this);
|
||||
} elseif (null !== $entryContent) {
|
||||
|
@ -9,28 +9,15 @@ use PhpZip\Util\CryptoUtil;
|
||||
*/
|
||||
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()
|
||||
{
|
||||
$filename = __DIR__ . '/resources/test.apk';
|
||||
|
||||
self::assertCorrectZipArchive($filename);
|
||||
self::doZipAlignVerify($this->outputFilename);
|
||||
$result = self::doZipAlignVerify($filename);
|
||||
if (null !== $result) {
|
||||
self::assertTrue($result);
|
||||
}
|
||||
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile->openFile($filename);
|
||||
@ -39,7 +26,10 @@ class ZipAlignTest extends ZipTestCase
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
self::doZipAlignVerify($this->outputFilename);
|
||||
$result = self::doZipAlignVerify($this->outputFilename, true);
|
||||
if (null !== $result) {
|
||||
self::assertTrue($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user