Repository: commons-compress
Updated Branches:
  refs/heads/master ca7ea939e -> fef18a23d


COMPRESS-391: Persist alignment request


Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/f2af9f0c
Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/f2af9f0c
Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/f2af9f0c

Branch: refs/heads/master
Commit: f2af9f0c583ce5d5e294b60ada9e14516f5e92ac
Parents: c312e43
Author: Zbynek Vyskovsky <kvr...@gmail.com>
Authored: Wed May 10 20:06:08 2017 -0700
Committer: Stefan Bodewig <bode...@apache.org>
Committed: Thu May 11 20:04:34 2017 +0200

----------------------------------------------------------------------
 .../compress/archivers/zip/ExtraFieldUtils.java |   2 +-
 .../archivers/zip/PaddingExtraField.java        |  87 ------------
 .../zip/ResourceAlignmentExtraField.java        | 133 +++++++++++++++++++
 .../archivers/zip/ZipArchiveOutputStream.java   |  34 +++--
 .../compress/archivers/zip/ZipFileTest.java     |  42 +++++-
 5 files changed, 195 insertions(+), 103 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f2af9f0c/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java 
b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
index c510efa..14691c4 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
@@ -52,7 +52,7 @@ public class ExtraFieldUtils {
         register(X0016_CertificateIdForCentralDirectory.class);
         register(X0017_StrongEncryptionHeader.class);
         register(X0019_EncryptionRecipientCertificateList.class);
-        register(PaddingExtraField.class);
+        register(ResourceAlignmentExtraField.class);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f2af9f0c/src/main/java/org/apache/commons/compress/archivers/zip/PaddingExtraField.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/PaddingExtraField.java
 
b/src/main/java/org/apache/commons/compress/archivers/zip/PaddingExtraField.java
deleted file mode 100644
index fc57b36..0000000
--- 
a/src/main/java/org/apache/commons/compress/archivers/zip/PaddingExtraField.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.commons.compress.archivers.zip;
-
-import java.util.zip.ZipException;
-
-/**
- * An extra field who's sole purpose is to pad the local file header
- * so that the entry's data starts at a certain position.
- *
- * <p>The actual content of the padding is ignored and not retained
- * when reading a padding field.</p>
- *
- * <p>This enables Commons Compress to create "aligned" archives
- * similar to Android's zipalign command line tool.</p>
- *
- * @since 1.14
- * @see "https://developer.android.com/studio/command-line/zipalign.html";
- * @see ZipArchiveEntry#setAlignment
- */
-public class PaddingExtraField implements ZipExtraField {
-
-    /**
-     * Extra field id used for padding (there is no special value documented,
-     * therefore USHORT_MAX seems to be good choice).
-     */
-    public static final ZipShort ID = new ZipShort(0xffff);
-
-    private int len = 0;
-
-    public PaddingExtraField() {
-    }
-
-    public PaddingExtraField(int len) {
-        this.len = len;
-    }
-
-    @Override
-    public ZipShort getHeaderId() {
-        return ID;
-    }
-
-    @Override
-    public ZipShort getLocalFileDataLength() {
-        return new ZipShort(len);
-    }
-
-    @Override
-    public ZipShort getCentralDirectoryLength() {
-        return ZipShort.ZERO;
-    }
-
-    @Override
-    public byte[] getLocalFileDataData() {
-        return new byte[len];
-    }
-
-    @Override
-    public byte[] getCentralDirectoryData() {
-        return new byte[0];
-    }
-
-    @Override
-    public void parseFromLocalFileData(byte[] buffer, int offset, int length) {
-        len = length;
-    }
-
-    @Override
-    public void parseFromCentralDirectoryData(byte[] buffer, int offset, int 
length) {
-    }
-}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f2af9f0c/src/main/java/org/apache/commons/compress/archivers/zip/ResourceAlignmentExtraField.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/ResourceAlignmentExtraField.java
 
b/src/main/java/org/apache/commons/compress/archivers/zip/ResourceAlignmentExtraField.java
new file mode 100644
index 0000000..a9f8748
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/compress/archivers/zip/ResourceAlignmentExtraField.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.zip;
+
+
+import java.util.zip.ZipException;
+
+/**
+ * An extra field who's sole purpose is to align and pad the local file header
+ * so that the entry's data starts at a certain position.
+ *
+ * <p>The padding content of the padding is ignored and not retained
+ * when reading a padding field.</p>
+ *
+ * <p>This enables Commons Compress to create "aligned" archives
+ * similar to Android's zipalign command line tool.</p>
+ *
+ * @since 1.14
+ * @see "https://developer.android.com/studio/command-line/zipalign.html";
+ * @see ZipArchiveEntry#setAlignment
+ */
+public class ResourceAlignmentExtraField implements ZipExtraField {
+
+    /**
+     * Extra field id used for storing alignment and padding.
+     */
+    public static final ZipShort ID = new ZipShort(0xa11e);
+
+    public static final int BASE_SIZE = 2;
+
+    private short alignment;
+
+    private boolean allowMethodChange;
+
+    private int padding = 0;
+
+    public ResourceAlignmentExtraField() {
+    }
+
+    public ResourceAlignmentExtraField(int alignment) {
+        this(alignment, false);
+    }
+
+    public ResourceAlignmentExtraField(int alignment, boolean 
allowMethodChange) {
+        this(alignment, allowMethodChange, 0);
+    }
+
+    public ResourceAlignmentExtraField(int alignment, boolean 
allowMethodChange, int padding) {
+        if (alignment < 0 || alignment > 0x7fff)
+            throw new IllegalArgumentException("Alignment must be between 0 
and 0x7fff, was: " + alignment);
+        this.alignment = (short) alignment;
+        this.allowMethodChange = allowMethodChange;
+        this.padding = padding;
+    }
+
+    /**
+     * Gets requested alignment.
+     *
+     * @return
+     *      requested alignment.
+     */
+    public short getAlignment() {
+        return alignment;
+    }
+
+    /**
+     * Indicates whether method change is allowed when re-compressing the zip 
file.
+     *
+     * @return
+     *      true if method change is allowed, false otherwise.
+     */
+    public boolean allowMethodChange() {
+        return allowMethodChange;
+    }
+
+    @Override
+    public ZipShort getHeaderId() {
+        return ID;
+    }
+
+    @Override
+    public ZipShort getLocalFileDataLength() {
+        return new ZipShort(BASE_SIZE + padding);
+    }
+
+    @Override
+    public ZipShort getCentralDirectoryLength() {
+        return new ZipShort(BASE_SIZE);
+    }
+
+    @Override
+    public byte[] getLocalFileDataData() {
+        byte[] content = new byte[2+padding];
+        ZipShort.putShort(alignment | (allowMethodChange ? 0x8000 : 0), 
content, 0);
+        return content;
+    }
+
+    @Override
+    public byte[] getCentralDirectoryData() {
+        return ZipShort.getBytes(alignment | (allowMethodChange ? 0x8000 : 0));
+    }
+
+    @Override
+    public void parseFromLocalFileData(byte[] buffer, int offset, int length) 
throws ZipException {
+        parseFromCentralDirectoryData(buffer, offset, length);
+        this.padding = length - BASE_SIZE;
+    }
+
+    @Override
+    public void parseFromCentralDirectoryData(byte[] buffer, int offset, int 
length) throws ZipException {
+        if (length < 2)
+            throw new ZipException("Too short content for 
ResourceAlignmentExtraField (0xa11e): " + length);
+        int alignmentValue = ZipShort.getValue(buffer, offset);
+        this.alignment = (short) (alignmentValue&0x7fff);
+        this.allowMethodChange = (alignmentValue&0x8000) != 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f2af9f0c/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
 
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
index 359f92b..5649af3 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java
@@ -1042,21 +1042,31 @@ public class ZipArchiveOutputStream extends 
ArchiveOutputStream {
 
     private byte[] createLocalFileHeader(final ZipArchiveEntry ze, final 
ByteBuffer name, final boolean encodable,
                                          final boolean phased, long 
archiveOffset) throws IOException {
+        ResourceAlignmentExtraField oldAlignmentEx =
+                        (ResourceAlignmentExtraField) 
ze.getExtraField(ResourceAlignmentExtraField.ID);
+        if (oldAlignmentEx != null)
+            ze.removeExtraField(ResourceAlignmentExtraField.ID);
+
+        int alignment = ze.getAlignment();
+        if (alignment <= 0 && oldAlignmentEx != null) {
+            alignment = oldAlignmentEx.getAlignment();
+        }
+
+        if (alignment > 1 || (oldAlignmentEx != null && 
!oldAlignmentEx.allowMethodChange())) {
+            int oldLength = LFH_FILENAME_OFFSET +
+                            name.limit() - name.position() +
+                            ze.getLocalFileDataExtra().length;
+
+            int padding = (int) ((-archiveOffset - oldLength - 
EXTRAFIELD_HEADER_SIZE
+                            - ResourceAlignmentExtraField.BASE_SIZE) &
+                            (alignment - 1));
+            ze.addExtraField(new ResourceAlignmentExtraField(alignment,
+                            oldAlignmentEx != null ? 
oldAlignmentEx.allowMethodChange() : false, padding));
+        }
+
         byte[] extra = ze.getLocalFileDataExtra();
         final int nameLen = name.limit() - name.position();
         int len = LFH_FILENAME_OFFSET + nameLen + extra.length;
-        int alignment = ze.getAlignment();
-        if (alignment > 1 && ((archiveOffset + len) & (alignment - 1)) != 0) {
-            int padding = (int) ((-archiveOffset - len - 
EXTRAFIELD_HEADER_SIZE) & (alignment - 1));
-            ZipExtraField pex = (PaddingExtraField) 
ze.getExtraField(PaddingExtraField.ID);
-            if (pex != null) {
-                padding += pex.getLocalFileDataLength().getValue() + 
EXTRAFIELD_HEADER_SIZE;
-            }
-            // will overwrite an existing PaddingExtraField
-            ze.addExtraField(new PaddingExtraField(padding));
-            extra = ze.getLocalFileDataExtra();
-            len = LFH_FILENAME_OFFSET + nameLen + extra.length;
-        }
         final byte[] buf = new byte[len];
 
         System.arraycopy(LFH_SIG,  0, buf, LFH_SIG_OFFSET, WORD);

http://git-wip-us.apache.org/repos/asf/commons-compress/blob/f2af9f0c/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java 
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
index 98d6dcc..6efaba4 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
@@ -485,9 +485,16 @@ public class ZipFileTest {
             ZipArchiveEntry storedEntry2 = new ZipArchiveEntry("stored2.txt");
             storedEntry2.setMethod(ZipEntry.STORED);
             storedEntry2.setAlignment(1024);
-            storedEntry2.addExtraField(new PaddingExtraField(123));
+            storedEntry2.addExtraField(new ResourceAlignmentExtraField(1));
             zipOutput.putArchiveEntry(storedEntry2);
-            zipOutput.write("Hello pre-aligned 
Stored\n".getBytes(Charset.forName("UTF-8")));
+            zipOutput.write("Hello overload-alignment 
Stored\n".getBytes(Charset.forName("UTF-8")));
+            zipOutput.closeArchiveEntry();
+
+            ZipArchiveEntry storedEntry3 = new ZipArchiveEntry("stored3.txt");
+            storedEntry3.setMethod(ZipEntry.STORED);
+            storedEntry3.addExtraField(new ResourceAlignmentExtraField(1024));
+            zipOutput.putArchiveEntry(storedEntry3);
+            zipOutput.write("Hello copy-alignment 
Stored\n".getBytes(Charset.forName("UTF-8")));
             zipOutput.closeArchiveEntry();
 
         }
@@ -496,28 +503,57 @@ public class ZipFileTest {
                         Arrays.copyOfRange(zipContent.array(), 0, 
(int)zipContent.size())
         ))) {
             ZipArchiveEntry inflatedEntry = zf.getEntry("inflated.txt");
+            ResourceAlignmentExtraField inflatedAlignmentEx =
+                            
(ResourceAlignmentExtraField)inflatedEntry.getExtraField(ResourceAlignmentExtraField.ID);
             assertNotEquals(-1L, inflatedEntry.getCompressedSize());
             assertNotEquals(-1L, inflatedEntry.getSize());
             assertEquals(0L, inflatedEntry.getDataOffset()%1024);
+            assertNotNull(inflatedAlignmentEx);
+            assertEquals(1024, inflatedAlignmentEx.getAlignment());
+            assertFalse(inflatedAlignmentEx.allowMethodChange());
             try (InputStream stream = zf.getInputStream(inflatedEntry)) {
                 Assert.assertEquals("Hello Deflated\n",
                                 new String(IOUtils.toByteArray(stream), 
Charset.forName("UTF-8")));
             }
             ZipArchiveEntry storedEntry = zf.getEntry("stored.txt");
+            ResourceAlignmentExtraField storedAlignmentEx =
+                            
(ResourceAlignmentExtraField)storedEntry.getExtraField(ResourceAlignmentExtraField.ID);
             assertNotEquals(-1L, storedEntry.getCompressedSize());
             assertNotEquals(-1L, storedEntry.getSize());
             assertEquals(0L, storedEntry.getDataOffset()%1024);
+            assertNotNull(storedAlignmentEx);
+            assertEquals(1024, storedAlignmentEx.getAlignment());
+            assertFalse(storedAlignmentEx.allowMethodChange());
             try (InputStream stream = zf.getInputStream(storedEntry)) {
                 Assert.assertEquals("Hello Stored\n",
                                 new String(IOUtils.toByteArray(stream), 
Charset.forName("UTF-8")));
             }
 
             ZipArchiveEntry storedEntry2 = zf.getEntry("stored2.txt");
+            ResourceAlignmentExtraField stored2AlignmentEx =
+                            
(ResourceAlignmentExtraField)storedEntry2.getExtraField(ResourceAlignmentExtraField.ID);
             assertNotEquals(-1L, storedEntry2.getCompressedSize());
             assertNotEquals(-1L, storedEntry2.getSize());
             assertEquals(0L, storedEntry2.getDataOffset()%1024);
+            assertNotNull(stored2AlignmentEx);
+            assertEquals(1024, stored2AlignmentEx.getAlignment());
+            assertFalse(stored2AlignmentEx.allowMethodChange());
             try (InputStream stream = zf.getInputStream(storedEntry2)) {
-                Assert.assertEquals("Hello pre-aligned Stored\n",
+                Assert.assertEquals("Hello overload-alignment Stored\n",
+                                new String(IOUtils.toByteArray(stream), 
Charset.forName("UTF-8")));
+            }
+
+            ZipArchiveEntry storedEntry3 = zf.getEntry("stored3.txt");
+            ResourceAlignmentExtraField stored3AlignmentEx =
+                            
(ResourceAlignmentExtraField)storedEntry3.getExtraField(ResourceAlignmentExtraField.ID);
+            assertNotEquals(-1L, storedEntry3.getCompressedSize());
+            assertNotEquals(-1L, storedEntry3.getSize());
+            assertEquals(0L, storedEntry3.getDataOffset()%1024);
+            assertNotNull(stored3AlignmentEx);
+            assertEquals(1024, stored3AlignmentEx.getAlignment());
+            assertFalse(stored3AlignmentEx.allowMethodChange());
+            try (InputStream stream = zf.getInputStream(storedEntry3)) {
+                Assert.assertEquals("Hello copy-alignment Stored\n",
                                 new String(IOUtils.toByteArray(stream), 
Charset.forName("UTF-8")));
             }
         }

Reply via email to