• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package java.util.zip;
19 
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.nio.ByteOrder;
23 import java.nio.charset.Charset;
24 import java.nio.charset.StandardCharsets;
25 import java.util.Arrays;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.GregorianCalendar;
29 import libcore.io.BufferIterator;
30 import libcore.io.HeapBufferIterator;
31 import libcore.io.Streams;
32 
33 /**
34  * An entry within a zip file.
35  * An entry has attributes such as its name (which is actually a path) and the uncompressed size
36  * of the corresponding data. An entry does not contain the data itself, but can be used as a key
37  * with {@link ZipFile#getInputStream}. The class documentation for {@link ZipInputStream} and
38  * {@link ZipOutputStream} shows how {@code ZipEntry} is used in conjunction with those two classes.
39  */
40 public class ZipEntry implements ZipConstants, Cloneable {
41     String name;
42     String comment;
43 
44     long crc = -1; // Needs to be a long to distinguish -1 ("not set") from the 0xffffffff CRC32.
45 
46     long compressedSize = -1;
47     long size = -1;
48 
49     int compressionMethod = -1;
50     int time = -1;
51     int modDate = -1;
52 
53     byte[] extra;
54 
55     int nameLength = -1;
56     long localHeaderRelOffset = -1;
57 
58     long dataOffset = -1;
59 
60     /**
61      * Zip entry state: Deflated.
62      */
63     public static final int DEFLATED = 8;
64 
65     /**
66      * Zip entry state: Stored.
67      */
68     public static final int STORED = 0;
69 
ZipEntry(String name, String comment, long crc, long compressedSize, long size, int compressionMethod, int time, int modDate, byte[] extra, int nameLength, long localHeaderRelOffset, long dataOffset)70     ZipEntry(String name, String comment, long crc, long compressedSize,
71             long size, int compressionMethod, int time, int modDate, byte[] extra,
72             int nameLength, long localHeaderRelOffset, long dataOffset) {
73         this.name = name;
74         this.comment = comment;
75         this.crc = crc;
76         this.compressedSize = compressedSize;
77         this.size = size;
78         this.compressionMethod = compressionMethod;
79         this.time = time;
80         this.modDate = modDate;
81         this.extra = extra;
82         this.nameLength = nameLength;
83         this.localHeaderRelOffset = localHeaderRelOffset;
84         this.dataOffset = dataOffset;
85     }
86 
87     /**
88      * Constructs a new {@code ZipEntry} with the specified name. The name is actually a path,
89      * and may contain {@code /} characters.
90      *
91      * @throws IllegalArgumentException
92      *             if the name length is outside the range (> 0xFFFF).
93      */
ZipEntry(String name)94     public ZipEntry(String name) {
95         if (name == null) {
96             throw new NullPointerException("name == null");
97         }
98         validateStringLength("Name", name);
99         this.name = name;
100     }
101 
102     /**
103      * Returns the comment for this {@code ZipEntry}, or {@code null} if there is no comment.
104      * If we're reading a zip file using {@code ZipInputStream}, the comment is not available.
105      */
getComment()106     public String getComment() {
107         return comment;
108     }
109 
110     /**
111      * Gets the compressed size of this {@code ZipEntry}.
112      *
113      * @return the compressed size, or -1 if the compressed size has not been
114      *         set.
115      */
getCompressedSize()116     public long getCompressedSize() {
117         return compressedSize;
118     }
119 
120     /**
121      * Gets the checksum for this {@code ZipEntry}.
122      *
123      * @return the checksum, or -1 if the checksum has not been set.
124      */
getCrc()125     public long getCrc() {
126         return crc;
127     }
128 
129     /**
130      * Gets the extra information for this {@code ZipEntry}.
131      *
132      * @return a byte array containing the extra information, or {@code null} if
133      *         there is none.
134      */
getExtra()135     public byte[] getExtra() {
136         return extra;
137     }
138 
139     /**
140      * Gets the compression method for this {@code ZipEntry}.
141      *
142      * @return the compression method, either {@code DEFLATED}, {@code STORED}
143      *         or -1 if the compression method has not been set.
144      */
getMethod()145     public int getMethod() {
146         return compressionMethod;
147     }
148 
149     /**
150      * Gets the name of this {@code ZipEntry}.
151      *
152      * @return the entry name.
153      */
getName()154     public String getName() {
155         return name;
156     }
157 
158     /**
159      * Gets the uncompressed size of this {@code ZipEntry}.
160      *
161      * @return the uncompressed size, or {@code -1} if the size has not been
162      *         set.
163      */
getSize()164     public long getSize() {
165         return size;
166     }
167 
168     /**
169      * Gets the last modification time of this {@code ZipEntry}.
170      *
171      * @return the last modification time as the number of milliseconds since
172      *         Jan. 1, 1970.
173      */
getTime()174     public long getTime() {
175         if (time != -1) {
176             GregorianCalendar cal = new GregorianCalendar();
177             cal.set(Calendar.MILLISECOND, 0);
178             cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1,
179                     modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f,
180                     (time & 0x1f) << 1);
181             return cal.getTime().getTime();
182         }
183         return -1;
184     }
185 
186     /**
187      * Determine whether or not this {@code ZipEntry} is a directory.
188      *
189      * @return {@code true} when this {@code ZipEntry} is a directory, {@code
190      *         false} otherwise.
191      */
isDirectory()192     public boolean isDirectory() {
193         return name.charAt(name.length() - 1) == '/';
194     }
195 
196     /**
197      * Sets the comment for this {@code ZipEntry}.
198      * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes.
199      */
setComment(String comment)200     public void setComment(String comment) {
201         if (comment == null) {
202             this.comment = null;
203             return;
204         }
205         validateStringLength("Comment", comment);
206 
207         this.comment = comment;
208     }
209 
210     /**
211      * Sets the compressed size for this {@code ZipEntry}.
212      *
213      * @param value
214      *            the compressed size (in bytes).
215      */
setCompressedSize(long value)216     public void setCompressedSize(long value) {
217         compressedSize = value;
218     }
219 
220     /**
221      * Sets the checksum for this {@code ZipEntry}.
222      *
223      * @param value
224      *            the checksum for this entry.
225      * @throws IllegalArgumentException
226      *             if {@code value} is < 0 or > 0xFFFFFFFFL.
227      */
setCrc(long value)228     public void setCrc(long value) {
229         if (value >= 0 && value <= 0xFFFFFFFFL) {
230             crc = value;
231         } else {
232             throw new IllegalArgumentException("Bad CRC32: " + value);
233         }
234     }
235 
236     /**
237      * Sets the extra information for this {@code ZipEntry}.
238      *
239      * @throws IllegalArgumentException if the data length >= 64 KiB.
240      */
setExtra(byte[] data)241     public void setExtra(byte[] data) {
242         if (data != null && data.length > 0xffff) {
243             throw new IllegalArgumentException("Extra data too long: " + data.length);
244         }
245         extra = data;
246     }
247 
248     /**
249      * Sets the compression method for this entry to either {@code DEFLATED} or {@code STORED}.
250      * The default is {@code DEFLATED}, which will cause the size, compressed size, and CRC to be
251      * set automatically, and the entry's data to be compressed. If you switch to {@code STORED}
252      * note that you'll have to set the size (or compressed size; they must be the same, but it's
253      * okay to only set one) and CRC yourself because they must appear <i>before</i> the user data
254      * in the resulting zip file. See {@link #setSize} and {@link #setCrc}.
255      * @throws IllegalArgumentException
256      *             when value is not {@code DEFLATED} or {@code STORED}.
257      */
setMethod(int value)258     public void setMethod(int value) {
259         if (value != STORED && value != DEFLATED) {
260             throw new IllegalArgumentException("Bad method: " + value);
261         }
262         compressionMethod = value;
263     }
264 
265     /**
266      * Sets the uncompressed size of this {@code ZipEntry}.
267      *
268      * @param value
269      *            the uncompressed size for this entry.
270      * @throws IllegalArgumentException
271      *             if {@code value} < 0 or {@code value} > 0xFFFFFFFFL.
272      */
setSize(long value)273     public void setSize(long value) {
274         if (value >= 0 && value <= 0xFFFFFFFFL) {
275             size = value;
276         } else {
277             throw new IllegalArgumentException("Bad size: " + value);
278         }
279     }
280 
281     /**
282      * Sets the modification time of this {@code ZipEntry}.
283      *
284      * @param value
285      *            the modification time as the number of milliseconds since Jan.
286      *            1, 1970.
287      */
setTime(long value)288     public void setTime(long value) {
289         GregorianCalendar cal = new GregorianCalendar();
290         cal.setTime(new Date(value));
291         int year = cal.get(Calendar.YEAR);
292         if (year < 1980) {
293             modDate = 0x21;
294             time = 0;
295         } else {
296             modDate = cal.get(Calendar.DATE);
297             modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
298             modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
299             time = cal.get(Calendar.SECOND) >> 1;
300             time = (cal.get(Calendar.MINUTE) << 5) | time;
301             time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
302         }
303     }
304 
305 
306     /** @hide */
setDataOffset(long value)307     public void setDataOffset(long value) {
308         dataOffset = value;
309     }
310 
311     /** @hide */
getDataOffset()312     public long getDataOffset() {
313         return dataOffset;
314     }
315 
316     /**
317      * Returns the string representation of this {@code ZipEntry}.
318      *
319      * @return the string representation of this {@code ZipEntry}.
320      */
321     @Override
toString()322     public String toString() {
323         return name;
324     }
325 
326     /**
327      * Constructs a new {@code ZipEntry} using the values obtained from {@code
328      * ze}.
329      *
330      * @param ze
331      *            the {@code ZipEntry} from which to obtain values.
332      */
ZipEntry(ZipEntry ze)333     public ZipEntry(ZipEntry ze) {
334         name = ze.name;
335         comment = ze.comment;
336         time = ze.time;
337         size = ze.size;
338         compressedSize = ze.compressedSize;
339         crc = ze.crc;
340         compressionMethod = ze.compressionMethod;
341         modDate = ze.modDate;
342         extra = ze.extra;
343         nameLength = ze.nameLength;
344         localHeaderRelOffset = ze.localHeaderRelOffset;
345         dataOffset = ze.dataOffset;
346     }
347 
348     /**
349      * Returns a deep copy of this zip entry.
350      */
clone()351     @Override public Object clone() {
352         try {
353             ZipEntry result = (ZipEntry) super.clone();
354             result.extra = extra != null ? extra.clone() : null;
355             return result;
356         } catch (CloneNotSupportedException e) {
357             throw new AssertionError(e);
358         }
359     }
360 
361     /**
362      * Returns the hash code for this {@code ZipEntry}.
363      *
364      * @return the hash code of the entry.
365      */
366     @Override
hashCode()367     public int hashCode() {
368         return name.hashCode();
369     }
370 
371     /*
372      * Internal constructor.  Creates a new ZipEntry by reading the
373      * Central Directory Entry (CDE) from "in", which must be positioned
374      * at the CDE signature. If the GPBF_UTF8_FLAG is set in the CDE then
375      * UTF-8 is used to decode the string information, otherwise the
376      * defaultCharset is used.
377      *
378      * On exit, "in" will be positioned at the start of the next entry
379      * in the Central Directory.
380      */
ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset)381     ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset) throws IOException {
382         Streams.readFully(cdStream, cdeHdrBuf, 0, cdeHdrBuf.length);
383 
384         BufferIterator it = HeapBufferIterator.iterator(cdeHdrBuf, 0, cdeHdrBuf.length,
385                 ByteOrder.LITTLE_ENDIAN);
386 
387         int sig = it.readInt();
388         if (sig != CENSIG) {
389             ZipFile.throwZipException("Central Directory Entry", sig);
390         }
391 
392         it.seek(8);
393         int gpbf = it.readShort() & 0xffff;
394 
395         if ((gpbf & ZipFile.GPBF_UNSUPPORTED_MASK) != 0) {
396             throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
397         }
398 
399         // If the GPBF_UTF8_FLAG is set then the character encoding is UTF-8 whatever the default
400         // provided.
401         Charset charset = defaultCharset;
402         if ((gpbf & ZipFile.GPBF_UTF8_FLAG) != 0) {
403             charset = StandardCharsets.UTF_8;
404         }
405 
406         compressionMethod = it.readShort() & 0xffff;
407         time = it.readShort() & 0xffff;
408         modDate = it.readShort() & 0xffff;
409 
410         // These are 32-bit values in the file, but 64-bit fields in this object.
411         crc = ((long) it.readInt()) & 0xffffffffL;
412         compressedSize = ((long) it.readInt()) & 0xffffffffL;
413         size = ((long) it.readInt()) & 0xffffffffL;
414 
415         nameLength = it.readShort() & 0xffff;
416         int extraLength = it.readShort() & 0xffff;
417         int commentByteCount = it.readShort() & 0xffff;
418 
419         // This is a 32-bit value in the file, but a 64-bit field in this object.
420         it.seek(42);
421         localHeaderRelOffset = ((long) it.readInt()) & 0xffffffffL;
422 
423         byte[] nameBytes = new byte[nameLength];
424         Streams.readFully(cdStream, nameBytes, 0, nameBytes.length);
425         if (containsNulByte(nameBytes)) {
426             throw new ZipException("Filename contains NUL byte: " + Arrays.toString(nameBytes));
427         }
428         name = new String(nameBytes, 0, nameBytes.length, charset);
429 
430         if (extraLength > 0) {
431             extra = new byte[extraLength];
432             Streams.readFully(cdStream, extra, 0, extraLength);
433         }
434 
435         if (commentByteCount > 0) {
436             byte[] commentBytes = new byte[commentByteCount];
437             Streams.readFully(cdStream, commentBytes, 0, commentByteCount);
438             comment = new String(commentBytes, 0, commentBytes.length, charset);
439         }
440     }
441 
containsNulByte(byte[] bytes)442     private static boolean containsNulByte(byte[] bytes) {
443         for (byte b : bytes) {
444             if (b == 0) {
445                 return true;
446             }
447         }
448         return false;
449     }
450 
validateStringLength(String argument, String string)451     private static void validateStringLength(String argument, String string) {
452         // This check is not perfect: the character encoding is determined when the entry is
453         // written out. UTF-8 is probably a worst-case: most alternatives should be single byte per
454         // character.
455         byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
456         if (bytes.length > 0xffff) {
457             throw new IllegalArgumentException(argument + " too long: " + bytes.length);
458         }
459     }
460 }
461