1 // Copyright 2016 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.archivepatcher.generator; 16 17 import java.io.UnsupportedEncodingException; 18 import java.util.Arrays; 19 20 /** 21 * A class that contains <em>just enough data</em> to generate a patch. 22 */ 23 public class MinimalZipEntry { 24 /** 25 * The compression method that was used, typically 8 (for deflate) or 0 (for stored). 26 */ 27 private final int compressionMethod; 28 29 /** 30 * The CRC32 of the <em>uncompressed</em> data. 31 */ 32 private final long crc32OfUncompressedData; 33 34 /** 35 * The size of the data as it exists in the archive. For compressed entries, this is the size of 36 * the compressed data; for uncompressed entries, this is the same as {@link #uncompressedSize}. 37 */ 38 private final long compressedSize; 39 40 /** 41 * The size of the <em>uncompressed</em data. 42 */ 43 private final long uncompressedSize; 44 45 /** 46 * The file name for the entry. By convention, names ending with '/' denote directories. The 47 * encoding is controlled by the general purpose flags, bit 11. See {@link #getFileName()} for 48 * more information. 49 */ 50 private final byte[] fileNameBytes; 51 52 /** 53 * The value of the 11th bit of the general purpose flag, which controls the encoding of file 54 * names and comments. See {@link #getFileName()} for more information. 55 */ 56 private final boolean generalPurposeFlagBit11; 57 58 /** 59 * The file offset at which the first byte of the local entry header begins. 60 */ 61 private final long fileOffsetOfLocalEntry; 62 63 /** 64 * The file offset at which the first byte of the data for the entry begins. For compressed data, 65 * this is the first byte of the deflated data; for uncompressed data, this is the first byte of 66 * the uncompressed data. 67 */ 68 private long fileOffsetOfCompressedData = -1; 69 70 /** 71 * Create a new Central Directory entry with the corresponding data. 72 * @param compressionMethod the method used to compress the data 73 * @param crc32OfUncompressedData the CRC32 of the uncompressed data 74 * @param compressedSize the size of the data in its compressed form 75 * @param uncompressedSize the size of the data in its uncompressed form 76 * @param fileNameBytes the name of the file, as a byte array; see {@link #getFileName()} for 77 * information on encoding 78 * @param generalPurposeFlagBit11 the value of the 11th bit of the general purpose flag, which 79 * nominally controls the default character encoding for file names and comments; see 80 * {@link #getFileName()} for more information on encoding 81 * @param fileOffsetOfLocalEntry the file offset at which the local entry begins 82 */ MinimalZipEntry( int compressionMethod, long crc32OfUncompressedData, long compressedSize, long uncompressedSize, byte[] fileNameBytes, boolean generalPurposeFlagBit11, long fileOffsetOfLocalEntry)83 public MinimalZipEntry( 84 int compressionMethod, 85 long crc32OfUncompressedData, 86 long compressedSize, 87 long uncompressedSize, 88 byte[] fileNameBytes, 89 boolean generalPurposeFlagBit11, 90 long fileOffsetOfLocalEntry) { 91 this.compressionMethod = compressionMethod; 92 this.crc32OfUncompressedData = crc32OfUncompressedData; 93 this.compressedSize = compressedSize; 94 this.uncompressedSize = uncompressedSize; 95 this.fileNameBytes = fileNameBytes == null ? null : fileNameBytes.clone(); 96 this.generalPurposeFlagBit11 = generalPurposeFlagBit11; 97 this.fileOffsetOfLocalEntry = fileOffsetOfLocalEntry; 98 } 99 100 /** 101 * Sets the file offset at which the data for this entry begins. 102 * @param offset the offset 103 */ setFileOffsetOfCompressedData(long offset)104 public void setFileOffsetOfCompressedData(long offset) { 105 fileOffsetOfCompressedData = offset; 106 } 107 108 /** 109 * Returns the compression method that was used, typically 8 (for deflate) or 0 (for stored). 110 * @return as described 111 */ getCompressionMethod()112 public int getCompressionMethod() { 113 return compressionMethod; 114 } 115 116 /** 117 * Returns the CRC32 of the uncompressed data. 118 * @return as described 119 */ getCrc32OfUncompressedData()120 public long getCrc32OfUncompressedData() { 121 return crc32OfUncompressedData; 122 } 123 124 /** 125 * Returns the size of the data as it exists in the archive. For compressed entries, this is the 126 * size of the compressed data; for uncompressed entries, this is the same as 127 * {@link #getUncompressedSize()}. 128 * @return as described 129 */ getCompressedSize()130 public long getCompressedSize() { 131 return compressedSize; 132 } 133 134 /** 135 * Returns the size of the uncompressed data. 136 * @return as described 137 */ getUncompressedSize()138 public long getUncompressedSize() { 139 return uncompressedSize; 140 } 141 142 /** 143 * Returns a copy of the bytes of the file name, exactly the same as they were in the archive 144 * file. See {@link #getFileName()} for an explanation of why this is useful. 145 * @return as described 146 */ getFileNameBytes()147 public byte[] getFileNameBytes() { 148 return fileNameBytes == null ? null : fileNameBytes.clone(); 149 } 150 151 /** 152 * Returns a best-effort conversion of the file name into a string, based on strict adherence to 153 * the PKWARE APPNOTE that defines this behavior. If the value of the 11th bit of the general 154 * purpose flag was set to 1, these bytes should be encoded with the UTF8 character set; otherwise 155 * the character set should be Cp437. Adherence to this standard varies significantly, and some 156 * systems use the default character set for the environment instead of Cp437 when writing these 157 * bytes. For such instances, callers can obtain the raw bytes by using 158 * {@link #getFileNameBytes()} instead and checking the value of the 11th bit of the general 159 * purpose bit flag for a hint using {@link #getGeneralPurposeFlagBit11()}. There is also 160 * something called EFS ("0x0008 extra field storage") that specifies additional behavior for 161 * character encoding, but this tool doesn't support it as the use is not standardized. 162 * @return as described 163 */ 164 // TODO(andrewhayden): Support EFS getFileName()165 public String getFileName() { 166 String charsetName = generalPurposeFlagBit11 ? "UTF8" : "Cp437"; 167 try { 168 return new String(fileNameBytes, charsetName); 169 } catch (UnsupportedEncodingException e) { 170 // Cp437 has been supported at least since JDK 1.6.0, so this should rarely occur in practice. 171 // Older versions of the JDK also support Cp437, but as part of charsets.jar, which didn't 172 // ship in every distribution; it is conceivable that those systems might have problems here. 173 throw new RuntimeException("System doesn't support " + charsetName, e); 174 } 175 } 176 177 /** 178 * Returns the value of the 11th bit of the general purpose flag; true for 1, false for 0. See 179 * {@link #getFileName()} for more information on the usefulness of this flag. 180 * @return as described 181 */ getGeneralPurposeFlagBit11()182 public boolean getGeneralPurposeFlagBit11() { 183 return generalPurposeFlagBit11; 184 } 185 186 /** 187 * Returns the file offset at which the first byte of the local entry header begins. 188 * @return as described 189 */ getFileOffsetOfLocalEntry()190 public long getFileOffsetOfLocalEntry() { 191 return fileOffsetOfLocalEntry; 192 } 193 194 /** 195 * Returns the file offset at which the first byte of the data for the entry begins. For 196 * compressed data, this is the first byte of the deflated data; for uncompressed data, this is 197 * the first byte of the uncompressed data. 198 * @return as described 199 */ getFileOffsetOfCompressedData()200 public long getFileOffsetOfCompressedData() { 201 return fileOffsetOfCompressedData; 202 } 203 204 /** 205 * Convenience methods that returns true if and only if the entry is compressed with deflate. 206 * @return as described 207 */ isDeflateCompressed()208 public boolean isDeflateCompressed() { 209 // 8 is deflate according to the zip spec. 210 if (getCompressionMethod() != 8) { 211 return false; 212 } 213 // Some tools may list compression method deflate but set level to zero (store), so they will 214 // have a compressed size equal to the uncompresesd size. Don't consider such things to be 215 // compressed, even if they are "deflated". 216 return getCompressedSize() != getUncompressedSize(); 217 } 218 219 @Override hashCode()220 public int hashCode() { 221 final int prime = 31; 222 int result = 1; 223 result = prime * result + (int) (compressedSize ^ (compressedSize >>> 32)); 224 result = prime * result + compressionMethod; 225 result = prime * result + (int) (crc32OfUncompressedData ^ (crc32OfUncompressedData >>> 32)); 226 result = prime * result + Arrays.hashCode(fileNameBytes); 227 result = 228 prime * result + (int) (fileOffsetOfCompressedData ^ (fileOffsetOfCompressedData >>> 32)); 229 result = prime * result + (int) (fileOffsetOfLocalEntry ^ (fileOffsetOfLocalEntry >>> 32)); 230 result = prime * result + (generalPurposeFlagBit11 ? 1231 : 1237); 231 result = prime * result + (int) (uncompressedSize ^ (uncompressedSize >>> 32)); 232 return result; 233 } 234 235 @Override equals(Object obj)236 public boolean equals(Object obj) { 237 if (this == obj) { 238 return true; 239 } 240 if (obj == null) { 241 return false; 242 } 243 if (getClass() != obj.getClass()) { 244 return false; 245 } 246 MinimalZipEntry other = (MinimalZipEntry) obj; 247 if (compressedSize != other.compressedSize) { 248 return false; 249 } 250 if (compressionMethod != other.compressionMethod) { 251 return false; 252 } 253 if (crc32OfUncompressedData != other.crc32OfUncompressedData) { 254 return false; 255 } 256 if (!Arrays.equals(fileNameBytes, other.fileNameBytes)) { 257 return false; 258 } 259 if (fileOffsetOfCompressedData != other.fileOffsetOfCompressedData) { 260 return false; 261 } 262 if (fileOffsetOfLocalEntry != other.fileOffsetOfLocalEntry) { 263 return false; 264 } 265 if (generalPurposeFlagBit11 != other.generalPurposeFlagBit11) { 266 return false; 267 } 268 if (uncompressedSize != other.uncompressedSize) { 269 return false; 270 } 271 return true; 272 } 273 } 274