Переглянути джерело

Merge tag '3.1.2' into develop

Tagging version 3.1.2 3.1.2
Ne-Lexa 8 роки тому
батько
коміт
fb1a9ced88

+ 11 - 0
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`.

+ 4 - 4
README.RU.md

@@ -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`, когда это возможно.
   + Установка пароля для чтения архива глобально или для некоторых записей.
   + Изменение пароля архива, в том числе и для отдельных записей.
   + Удаление пароля архива глобально или для отдельных записей.

+ 4 - 4
README.md

@@ -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.

+ 73 - 0
src/PhpZip/Extra/ExtraFieldsFactory.php

@@ -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 - 0
src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php

@@ -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 - 0
src/PhpZip/Extra/Fields/JarMarkerExtraField.php

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

+ 2 - 35
src/PhpZip/Model/Entry/ZipAbstractEntry.php

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

+ 2 - 2
src/PhpZip/Model/Entry/ZipNewEntry.php

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

+ 0 - 1
src/PhpZip/Model/ZipEntry.php

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

+ 72 - 19
src/PhpZip/Stream/ZipInputStream.php

@@ -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];
+
+        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);
 
-        $length = ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $extraLength + $nameLength;
+        if (
+            $this->zipModel->isZipAlign() &&
+            !$entry->isEncrypted() &&
+            $entry->getMethod() === ZipFileInterface::METHOD_STORED
+        ) {
+            if (StringUtil::endsWith($entry->getName(), '.so')) {
+                $dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
+            }
 
-        $padding = 0;
-        if ($this->zipModel->isZipAlign() && !$entry->isEncrypted() && $entry->getMethod() === ZipFileInterface::METHOD_STORED) {
+            $dataMinStartOffset =
+                ftell($out->getStream()) +
+                ZipEntry::LOCAL_FILE_HEADER_MIN_LEN +
+                $destExtraLength +
+                $nameLength +
+                ApkAlignmentExtraField::ALIGNMENT_ZIP_EXTRA_MIN_SIZE_BYTES;
             $padding =
-                (
-                    $this->zipModel->getZipAlign() -
-                    (ftell($out->getStream()) + $length) % $this->zipModel->getZipAlign()
-                ) % $this->zipModel->getZipAlign();
-        }
+                ($dataAlignmentMultiple - ($dataMinStartOffset % $dataAlignmentMultiple))
+                % $dataAlignmentMultiple;
 
-        fseek($this->in, $pos, SEEK_SET);
-        if ($padding > 0) {
+            $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);
-            fwrite($out->getStream(), pack('v', $extraLength + $padding));
+            // 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);
-            stream_copy_to_stream($this->in, $out->getStream(), $nameLength + $extraLength);
-            fwrite($out->getStream(), str_repeat(chr(0), $padding));
+            // 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 {
-            stream_copy_to_stream($this->in, $out->getStream(), $length);
+            $copyInToOutLength += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $sourceExtraLength + $nameLength;
+            ;
         }
-        stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize());
         if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) {
-            $length = 12;
+//            crc-32                          4 bytes
+//            compressed size                 4 bytes
+//            uncompressed size               4 bytes
+            $copyInToOutLength += 12;
             if ($entry->isZip64ExtensionsRequired()) {
-                $length += 8;
+//              compressed size                 +4 bytes
+//              uncompressed size               +4 bytes
+                $copyInToOutLength += 8;
             }
-            stream_copy_to_stream($this->in, $out->getStream(), $length);
         }
+        // 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());
     }
 

+ 3 - 0
src/PhpZip/Stream/ZipInputStreamInterface.php

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

+ 39 - 19
src/PhpZip/Stream/ZipOutputStream.php

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

+ 8 - 18
tests/PhpZip/ZipAlignTest.php

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