mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-03-21 06:59:40 +01:00
Merge branch 'hotfix/3.1.2'
This commit is contained in:
commit
7d73ac417f
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;
|
||||
}
|
||||
}
|
||||
|
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\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