    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.archivers.zip;
    020
    021import java.util.zip.ZipException;
    022
    023/**
    024 * 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.
    025 *
    026 * <p>
    027 * The padding content of the padding is ignored and not retained when reading a padding field.
    028 * </p>
    029 *
    030 * <p>
    031 * This enables Commons Compress to create "aligned" archives similar to Android's {@code zipalign} command line tool.
    032 * </p>
    033 *
    034 * @since 1.14
    035 * @see "https://developer.android.com/studio/command-line/zipalign.html"
    036 * @see ZipArchiveEntry#setAlignment
    037 */
    038public class ResourceAlignmentExtraField implements ZipExtraField {
    039
    040    private static final int MAX_ALIGNMENT = Short.MAX_VALUE;
    041
    042    /**
    043     * Extra field id used for storing alignment and padding.
    044     */
    045    public static final ZipShort ID = new ZipShort(0xa11e);
    046
    047    /**
    048     * Base field size.
    049     */
    050    public static final int BASE_SIZE = 2;
    051
    052    private static final int ALLOW_METHOD_MESSAGE_CHANGE_FLAG = 0x8000;
    053
    054    private short alignment;
    055
    056    private boolean allowMethodChange;
    057
    058    private int padding;
    059
    060    /**
    061     * Constructs a new instance.
    062     */
    063    public ResourceAlignmentExtraField() {
    064    }
    065
    066    /**
    067     * Constructs a new instance.
    068     *
    069     * @param alignment A positive alignment less than {@link Short#MAX_VALUE}.
    070     */
    071    public ResourceAlignmentExtraField(final int alignment) {
    072        this(alignment, false);
    073    }
    074
    075    /**
    076     * Constructs a new instance.
    077     *
    078     * @param alignment A positive alignment less than {@link Short#MAX_VALUE}.
    079     * @param allowMethodChange whether a method change is allowed when re-compressing the ZIP file.
    080     */
    081    public ResourceAlignmentExtraField(final int alignment, final boolean allowMethodChange) {
    082        this(alignment, allowMethodChange, 0);
    083    }
    084
    085    /**
    086     * Constructs a new instance.
    087     *
    088     * @param alignment A positive alignment less than {@link Short#MAX_VALUE}.
    089     * @param allowMethodChange whether a method change is allowed when re-compressing the ZIP file.
    090     * @param padding padding.
    091     */
    092    public ResourceAlignmentExtraField(final int alignment, final boolean allowMethodChange, final int padding) {
    093        if (alignment < 0 || alignment > MAX_ALIGNMENT) {
    094            throw new IllegalArgumentException("Alignment must be between 0 and 0x7fff, was: " + alignment);
    095        }
    096        if (padding < 0) {
    097            throw new IllegalArgumentException("Padding must not be negative, was: " + padding);
    098        }
    099        this.alignment = (short) alignment;
    100        this.allowMethodChange = allowMethodChange;
    101        this.padding = padding;
    102    }
    103
    104    /**
    105     * Indicates whether a method change is allowed when re-compressing the ZIP file.
    106     *
    107     * @return true if a method change is allowed, false otherwise.
    108     */
    109    public boolean allowMethodChange() {
    110        return allowMethodChange;
    111    }
    112
    113    /**
    114     * Gets requested alignment.
    115     *
    116     * @return requested alignment.
    117     */
    118    public short getAlignment() {
    119        return alignment;
    120    }
    121
    122    @Override
    123    public byte[] getCentralDirectoryData() {
    124        return ZipShort.getBytes(alignment | (allowMethodChange ? ALLOW_METHOD_MESSAGE_CHANGE_FLAG : 0));
    125    }
    126
    127    @Override
    128    public ZipShort getCentralDirectoryLength() {
    129        return new ZipShort(BASE_SIZE);
    130    }
    131
    132    @Override
    133    public ZipShort getHeaderId() {
    134        return ID;
    135    }
    136
    137    @Override
    138    public byte[] getLocalFileDataData() {
    139        final byte[] content = new byte[BASE_SIZE + padding];
    140        ZipShort.putShort(alignment | (allowMethodChange ? ALLOW_METHOD_MESSAGE_CHANGE_FLAG : 0), content, 0);
    141        return content;
    142    }
    143
    144    @Override
    145    public ZipShort getLocalFileDataLength() {
    146        return new ZipShort(BASE_SIZE + padding);
    147    }
    148
    149    @Override
    150    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
    151        if (length < BASE_SIZE) {
    152            throw new ZipException("Too short content for ResourceAlignmentExtraField (0xa11e): " + length);
    153        }
    154        final int alignmentValue = ZipShort.getValue(buffer, offset);
    155        this.alignment = (short) (alignmentValue & ALLOW_METHOD_MESSAGE_CHANGE_FLAG - 1);
    156        this.allowMethodChange = (alignmentValue & ALLOW_METHOD_MESSAGE_CHANGE_FLAG) != 0;
    157    }
    158
    159    @Override
    160    public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) throws ZipException {
    161        parseFromCentralDirectoryData(buffer, offset, length);
    162        this.padding = length - BASE_SIZE;
    163    }
    164}