1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-08-29 17:59:55 +02:00

Merge tag '3.1.2' into develop

Tagging version 3.1.2 3.1.2
This commit is contained in:
Ne-Lexa
2017-11-17 11:13:15 +03:00
13 changed files with 387 additions and 108 deletions

View File

@@ -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;
}
}

View 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;
}
}

View 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");
}
}
}

View File

@@ -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);
}
/**

View File

@@ -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)
)
);

View File

@@ -3,7 +3,6 @@
namespace PhpZip\Model;
use PhpZip\Exception\ZipException;
use PhpZip\Extra\ExtraFieldsCollection;
use PhpZip\ZipFileInterface;

View File

@@ -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());
}

View File

@@ -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
*/

View File

@@ -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) {