ResourceAlignmentExtraField.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  * http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.compress.archivers.zip;

  20. import java.util.zip.ZipException;

  21. /**
  22.  * 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.
  23.  *
  24.  * <p>
  25.  * The padding content of the padding is ignored and not retained when reading a padding field.
  26.  * </p>
  27.  *
  28.  * <p>
  29.  * This enables Commons Compress to create "aligned" archives similar to Android's {@code zipalign} command line tool.
  30.  * </p>
  31.  *
  32.  * @since 1.14
  33.  * @see "https://developer.android.com/studio/command-line/zipalign.html"
  34.  * @see ZipArchiveEntry#setAlignment
  35.  */
  36. public class ResourceAlignmentExtraField implements ZipExtraField {

  37.     /**
  38.      * Extra field id used for storing alignment and padding.
  39.      */
  40.     public static final ZipShort ID = new ZipShort(0xa11e);

  41.     public static final int BASE_SIZE = 2;

  42.     private static final int ALLOW_METHOD_MESSAGE_CHANGE_FLAG = 0x8000;

  43.     private short alignment;

  44.     private boolean allowMethodChange;

  45.     private int padding;

  46.     public ResourceAlignmentExtraField() {
  47.     }

  48.     public ResourceAlignmentExtraField(final int alignment) {
  49.         this(alignment, false);
  50.     }

  51.     public ResourceAlignmentExtraField(final int alignment, final boolean allowMethodChange) {
  52.         this(alignment, allowMethodChange, 0);
  53.     }

  54.     public ResourceAlignmentExtraField(final int alignment, final boolean allowMethodChange, final int padding) {
  55.         if (alignment < 0 || alignment > 0x7fff) {
  56.             throw new IllegalArgumentException("Alignment must be between 0 and 0x7fff, was: " + alignment);
  57.         }
  58.         if (padding < 0) {
  59.             throw new IllegalArgumentException("Padding must not be negative, was: " + padding);
  60.         }
  61.         this.alignment = (short) alignment;
  62.         this.allowMethodChange = allowMethodChange;
  63.         this.padding = padding;
  64.     }

  65.     /**
  66.      * Indicates whether method change is allowed when re-compressing the ZIP file.
  67.      *
  68.      * @return true if method change is allowed, false otherwise.
  69.      */
  70.     public boolean allowMethodChange() {
  71.         return allowMethodChange;
  72.     }

  73.     /**
  74.      * Gets requested alignment.
  75.      *
  76.      * @return requested alignment.
  77.      */
  78.     public short getAlignment() {
  79.         return alignment;
  80.     }

  81.     @Override
  82.     public byte[] getCentralDirectoryData() {
  83.         return ZipShort.getBytes(alignment | (allowMethodChange ? ALLOW_METHOD_MESSAGE_CHANGE_FLAG : 0));
  84.     }

  85.     @Override
  86.     public ZipShort getCentralDirectoryLength() {
  87.         return new ZipShort(BASE_SIZE);
  88.     }

  89.     @Override
  90.     public ZipShort getHeaderId() {
  91.         return ID;
  92.     }

  93.     @Override
  94.     public byte[] getLocalFileDataData() {
  95.         final byte[] content = new byte[BASE_SIZE + padding];
  96.         ZipShort.putShort(alignment | (allowMethodChange ? ALLOW_METHOD_MESSAGE_CHANGE_FLAG : 0), content, 0);
  97.         return content;
  98.     }

  99.     @Override
  100.     public ZipShort getLocalFileDataLength() {
  101.         return new ZipShort(BASE_SIZE + padding);
  102.     }

  103.     @Override
  104.     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
  105.         if (length < BASE_SIZE) {
  106.             throw new ZipException("Too short content for ResourceAlignmentExtraField (0xa11e): " + length);
  107.         }
  108.         final int alignmentValue = ZipShort.getValue(buffer, offset);
  109.         this.alignment = (short) (alignmentValue & ALLOW_METHOD_MESSAGE_CHANGE_FLAG - 1);
  110.         this.allowMethodChange = (alignmentValue & ALLOW_METHOD_MESSAGE_CHANGE_FLAG) != 0;
  111.     }

  112.     @Override
  113.     public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) throws ZipException {
  114.         parseFromCentralDirectoryData(buffer, offset, length);
  115.         this.padding = length - BASE_SIZE;
  116.     }
  117. }