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