1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.build.apkzlib.zip; 18 19 import com.android.tools.build.apkzlib.zip.utils.MsDosDateTimeUtils; 20 import com.google.common.base.Verify; 21 import java.io.IOException; 22 import java.util.Arrays; 23 import java.util.concurrent.ExecutionException; 24 import java.util.concurrent.Future; 25 import javax.annotation.Nonnull; 26 27 /** 28 * The Central Directory Header contains information about files stored in the zip. Instances of 29 * this class contain information for files that already are in the zip and, for which the data was 30 * read from the Central Directory. But some instances of this class are used for new files. 31 * Because instances of this class can refer to files not yet on the zip, some of the fields may 32 * not be filled in, or may be filled in with default values. 33 * <p> 34 * Because compression decision is done lazily, some data is stored with futures. 35 */ 36 public class CentralDirectoryHeader implements Cloneable { 37 38 /** 39 * Default "version made by" field: upper byte needs to be 0 to set to MS-DOS compatibility. 40 * Lower byte can be anything, really. We use 18 because aapt uses 17 :) 41 */ 42 private static final int DEFAULT_VERSION_MADE_BY = 0x0018; 43 44 /** 45 * Name of the file. 46 */ 47 @Nonnull 48 private String name; 49 50 /** 51 * CRC32 of the data. 0 if not yet computed. 52 */ 53 private long crc32; 54 55 /** 56 * Size of the file uncompressed. 0 if the file has no data. 57 */ 58 private long uncompressedSize; 59 60 /** 61 * Code of the program that made the zip. We actually don't care about this. 62 */ 63 private long madeBy; 64 65 /** 66 * General-purpose bit flag. 67 */ 68 @Nonnull 69 private GPFlags gpBit; 70 71 /** 72 * Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packTime(long)}). 73 */ 74 private long lastModTime; 75 76 /** 77 * Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packDate(long)}). 78 */ 79 private long lastModDate; 80 81 /** 82 * Extra data field contents. This field follows a specific structure according to the 83 * specification. 84 */ 85 @Nonnull 86 private ExtraField extraField; 87 88 /** 89 * File comment. 90 */ 91 @Nonnull 92 private byte[] comment; 93 94 /** 95 * File internal attributes. 96 */ 97 private long internalAttributes; 98 99 /** 100 * File external attributes. 101 */ 102 private long externalAttributes; 103 104 /** 105 * Offset in the file where the data is located. This will be -1 if the header corresponds to 106 * a new file that is not yet written in the zip and, therefore, has no written data. 107 */ 108 private long offset; 109 110 /** 111 * Encoded file name. 112 */ 113 private byte[] encodedFileName; 114 115 /** 116 * Compress information that may not have been computed yet due to lazy compression. 117 */ 118 @Nonnull 119 private Future<CentralDirectoryHeaderCompressInfo> compressInfo; 120 121 /** 122 * The file this header belongs to. 123 */ 124 @Nonnull 125 private final ZFile file; 126 127 /** 128 * Creates data for a file. 129 * 130 * @param name the file name 131 * @param encodedFileName the encoded file name, this array will be owned by the header 132 * @param uncompressedSize the uncompressed file size 133 * @param compressInfo computation that defines the compression information 134 * @param flags flags used in the entry 135 * @param zFile the file this header belongs to 136 */ CentralDirectoryHeader( @onnull String name, @Nonnull byte[] encodedFileName, long uncompressedSize, @Nonnull Future<CentralDirectoryHeaderCompressInfo> compressInfo, @Nonnull GPFlags flags, @Nonnull ZFile zFile)137 CentralDirectoryHeader( 138 @Nonnull String name, 139 @Nonnull byte[] encodedFileName, 140 long uncompressedSize, 141 @Nonnull Future<CentralDirectoryHeaderCompressInfo> compressInfo, 142 @Nonnull GPFlags flags, 143 @Nonnull ZFile zFile) { 144 this.name = name; 145 this.uncompressedSize = uncompressedSize; 146 crc32 = 0; 147 148 /* 149 * Set sensible defaults for the rest. 150 */ 151 madeBy = DEFAULT_VERSION_MADE_BY; 152 153 gpBit = flags; 154 lastModTime = MsDosDateTimeUtils.packCurrentTime(); 155 lastModDate = MsDosDateTimeUtils.packCurrentDate(); 156 extraField = new ExtraField(); 157 comment = new byte[0]; 158 internalAttributes = 0; 159 externalAttributes = 0; 160 offset = -1; 161 this.encodedFileName = encodedFileName; 162 this.compressInfo = compressInfo; 163 file = zFile; 164 } 165 166 /** 167 * Obtains the name of the file. 168 * 169 * @return the name 170 */ 171 @Nonnull getName()172 public String getName() { 173 return name; 174 } 175 176 /** 177 * Obtains the size of the uncompressed file. 178 * 179 * @return the size of the file 180 */ getUncompressedSize()181 public long getUncompressedSize() { 182 return uncompressedSize; 183 } 184 185 /** 186 * Obtains the CRC32 of the data. 187 * 188 * @return the CRC32, 0 if not yet computed 189 */ getCrc32()190 public long getCrc32() { 191 return crc32; 192 } 193 194 /** 195 * Sets the CRC32 of the data. 196 * 197 * @param crc32 the CRC 32 198 */ setCrc32(long crc32)199 void setCrc32(long crc32) { 200 this.crc32 = crc32; 201 } 202 203 /** 204 * Obtains the code of the program that made the zip. 205 * 206 * @return the code 207 */ getMadeBy()208 public long getMadeBy() { 209 return madeBy; 210 } 211 212 /** 213 * Sets the code of the progtram that made the zip. 214 * 215 * @param madeBy the code 216 */ setMadeBy(long madeBy)217 void setMadeBy(long madeBy) { 218 this.madeBy = madeBy; 219 } 220 221 /** 222 * Obtains the general-purpose bit flag. 223 * 224 * @return the bit flag 225 */ 226 @Nonnull getGpBit()227 public GPFlags getGpBit() { 228 return gpBit; 229 } 230 231 /** 232 * Obtains the last modification time of the entry. 233 * 234 * @return the last modification time in MS-DOS format (see 235 * {@link MsDosDateTimeUtils#packTime(long)}) 236 */ getLastModTime()237 public long getLastModTime() { 238 return lastModTime; 239 } 240 241 /** 242 * Sets the last modification time of the entry. 243 * 244 * @param lastModTime the last modification time in MS-DOS format (see 245 * {@link MsDosDateTimeUtils#packTime(long)}) 246 */ setLastModTime(long lastModTime)247 void setLastModTime(long lastModTime) { 248 this.lastModTime = lastModTime; 249 } 250 251 /** 252 * Obtains the last modification date of the entry. 253 * 254 * @return the last modification date in MS-DOS format (see 255 * {@link MsDosDateTimeUtils#packDate(long)}) 256 */ getLastModDate()257 public long getLastModDate() { 258 return lastModDate; 259 } 260 261 /** 262 * Sets the last modification date of the entry. 263 * 264 * @param lastModDate the last modification date in MS-DOS format (see 265 * {@link MsDosDateTimeUtils#packDate(long)}) 266 */ setLastModDate(long lastModDate)267 void setLastModDate(long lastModDate) { 268 this.lastModDate = lastModDate; 269 } 270 271 /** 272 * Obtains the data in the extra field. 273 * 274 * @return the data (returns an empty array if there is none) 275 */ 276 @Nonnull getExtraField()277 public ExtraField getExtraField() { 278 return extraField; 279 } 280 281 /** 282 * Sets the data in the extra field. 283 * 284 * @param extraField the data to set 285 */ setExtraField(@onnull ExtraField extraField)286 public void setExtraField(@Nonnull ExtraField extraField) { 287 setExtraFieldNoNotify(extraField); 288 file.centralDirectoryChanged(); 289 } 290 291 /** 292 * Sets the data in the extra field, but does not notify {@link ZFile}. This method is invoked 293 * when the {@link ZFile} knows the extra field is being set. 294 * 295 * @param extraField the data to set 296 */ setExtraFieldNoNotify(@onnull ExtraField extraField)297 void setExtraFieldNoNotify(@Nonnull ExtraField extraField) { 298 this.extraField = extraField; 299 } 300 301 /** 302 * Obtains the entry's comment. 303 * 304 * @return the comment (returns an empty array if there is no comment) 305 */ 306 @Nonnull getComment()307 public byte[] getComment() { 308 return comment; 309 } 310 311 /** 312 * Sets the entry's comment. 313 * 314 * @param comment the comment 315 */ setComment(@onnull byte[] comment)316 void setComment(@Nonnull byte[] comment) { 317 this.comment = comment; 318 } 319 320 /** 321 * Obtains the entry's internal attributes. 322 * 323 * @return the entry's internal attributes 324 */ getInternalAttributes()325 public long getInternalAttributes() { 326 return internalAttributes; 327 } 328 329 /** 330 * Sets the entry's internal attributes. 331 * 332 * @param internalAttributes the entry's internal attributes 333 */ setInternalAttributes(long internalAttributes)334 void setInternalAttributes(long internalAttributes) { 335 this.internalAttributes = internalAttributes; 336 } 337 338 /** 339 * Obtains the entry's external attributes. 340 * 341 * @return the entry's external attributes 342 */ getExternalAttributes()343 public long getExternalAttributes() { 344 return externalAttributes; 345 } 346 347 /** 348 * Sets the entry's external attributes. 349 * 350 * @param externalAttributes the entry's external attributes 351 */ setExternalAttributes(long externalAttributes)352 void setExternalAttributes(long externalAttributes) { 353 this.externalAttributes = externalAttributes; 354 } 355 356 /** 357 * Obtains the offset in the zip file where this entry's data is. 358 * 359 * @return the offset or {@code -1} if the file has no data in the zip and, therefore, data 360 * is stored in memory 361 */ getOffset()362 public long getOffset() { 363 return offset; 364 } 365 366 /** 367 * Sets the offset in the zip file where this entry's data is. 368 * 369 * @param offset the offset or {@code -1} if the file is new and has no data in the zip yet 370 */ setOffset(long offset)371 void setOffset(long offset) { 372 this.offset = offset; 373 } 374 375 /** 376 * Obtains the encoded file name. 377 * 378 * @return the encoded file name 379 */ getEncodedFileName()380 public byte[] getEncodedFileName() { 381 return encodedFileName; 382 } 383 384 /** 385 * Resets the deferred CRC flag in the GP flags. 386 */ resetDeferredCrc()387 void resetDeferredCrc() { 388 /* 389 * We actually create a new set of flags. Since the only information we care about is the 390 * UTF-8 encoding, we'll just create a brand new object. 391 */ 392 gpBit = GPFlags.make(gpBit.isUtf8FileName()); 393 } 394 395 @Override clone()396 protected CentralDirectoryHeader clone() throws CloneNotSupportedException { 397 CentralDirectoryHeader cdr = (CentralDirectoryHeader) super.clone(); 398 cdr.extraField = extraField; 399 cdr.comment = Arrays.copyOf(comment, comment.length); 400 cdr.encodedFileName = Arrays.copyOf(encodedFileName, encodedFileName.length); 401 return cdr; 402 } 403 404 /** 405 * Obtains the future with the compression information. 406 * 407 * @return the information 408 */ 409 @Nonnull getCompressionInfo()410 public Future<CentralDirectoryHeaderCompressInfo> getCompressionInfo() { 411 return compressInfo; 412 } 413 414 /** 415 * Equivalent to {@code getCompressionInfo().get()} but masking the possible exceptions and 416 * guaranteeing non-{@code null} return. 417 * 418 * @return the result of the future 419 * @throws IOException failed to get the information 420 */ 421 @Nonnull getCompressionInfoWithWait()422 public CentralDirectoryHeaderCompressInfo getCompressionInfoWithWait() 423 throws IOException { 424 try { 425 CentralDirectoryHeaderCompressInfo info = getCompressionInfo().get(); 426 Verify.verifyNotNull(info, "info == null"); 427 return info; 428 } catch (InterruptedException e) { 429 throw new IOException("Interrupted while waiting for compression information.", e); 430 } catch (ExecutionException e) { 431 throw new IOException("Execution of compression failed.", e); 432 } 433 } 434 } 435