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}