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