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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package java
.util
.zip
;
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
;
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.
40 public class ZipEntry
implements ZipConstants
, Cloneable
{
44 long crc
= -1; // Needs to be a long to distinguish -1 ("not set") from the 0xffffffff CRC32.
46 long compressedSize
= -1;
49 int compressionMethod
= -1;
55 long localHeaderRelOffset
= -1;
60 * Zip entry state: Deflated.
62 public static final int DEFLATED
= 8;
65 * Zip entry state: Stored.
67 public static final int STORED
= 0;
69 /** @hide - for testing only */
70 public ZipEntry(String name
, String comment
, long crc
, long compressedSize
,
71 long size
, int compressionMethod
, int time
, int modDate
, byte[] extra
,
72 long localHeaderRelOffset
, long dataOffset
) {
74 this.comment
= comment
;
76 this.compressedSize
= compressedSize
;
78 this.compressionMethod
= compressionMethod
;
80 this.modDate
= modDate
;
82 this.localHeaderRelOffset
= localHeaderRelOffset
;
83 this.dataOffset
= dataOffset
;
87 * Constructs a new {@code ZipEntry} with the specified name. The name is actually a path,
88 * and may contain {@code /} characters.
90 * @throws IllegalArgumentException
91 * if the name length is outside the range (> 0xFFFF).
93 public ZipEntry(String name
) {
95 throw new NullPointerException("name == null");
97 validateStringLength("Name", name
);
102 * Returns the comment for this {@code ZipEntry}, or {@code null} if there is no comment.
103 * If we're reading a zip file using {@code ZipInputStream}, the comment is not available.
105 public String
getComment() {
110 * Gets the compressed size of this {@code ZipEntry}.
112 * @return the compressed size, or -1 if the compressed size has not been
115 public long getCompressedSize() {
116 return compressedSize
;
120 * Gets the checksum for this {@code ZipEntry}.
122 * @return the checksum, or -1 if the checksum has not been set.
124 public long getCrc() {
129 * Gets the extra information for this {@code ZipEntry}.
131 * @return a byte array containing the extra information, or {@code null} if
134 public byte[] getExtra() {
139 * Gets the compression method for this {@code ZipEntry}.
141 * @return the compression method, either {@code DEFLATED}, {@code STORED}
142 * or -1 if the compression method has not been set.
144 public int getMethod() {
145 return compressionMethod
;
149 * Gets the name of this {@code ZipEntry}.
151 * <p><em>Security note:</em> Entry names can represent relative paths. {@code foo/../bar} or
152 * {@code ../bar/baz}, for example. If the entry name is being used to construct a filename
153 * or as a path component, it must be validated or sanitized to ensure that files are not
154 * written outside of the intended destination directory.
156 * @return the entry name.
158 public String
getName() {
163 * Gets the uncompressed size of this {@code ZipEntry}.
165 * @return the uncompressed size, or {@code -1} if the size has not been
168 public long getSize() {
173 * Gets the last modification time of this {@code ZipEntry}.
175 * @return the last modification time as the number of milliseconds since
178 public long getTime() {
180 GregorianCalendar cal
= new GregorianCalendar();
181 cal
.set(Calendar
.MILLISECOND
, 0);
182 cal
.set(1980 + ((modDate
>> 9) & 0x7f), ((modDate
>> 5) & 0xf) - 1,
183 modDate
& 0x1f, (time
>> 11) & 0x1f, (time
>> 5) & 0x3f,
185 return cal
.getTime().getTime();
191 * Determine whether or not this {@code ZipEntry} is a directory.
193 * @return {@code true} when this {@code ZipEntry} is a directory, {@code
196 public boolean isDirectory() {
197 return name
.charAt(name
.length() - 1) == '/';
201 * Sets the comment for this {@code ZipEntry}.
202 * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes.
204 public void setComment(String comment
) {
205 if (comment
== null) {
209 validateStringLength("Comment", comment
);
211 this.comment
= comment
;
215 * Sets the compressed size for this {@code ZipEntry}.
218 * the compressed size (in bytes).
220 public void setCompressedSize(long value
) {
221 compressedSize
= value
;
225 * Sets the checksum for this {@code ZipEntry}.
228 * the checksum for this entry.
229 * @throws IllegalArgumentException
230 * if {@code value} is < 0 or > 0xFFFFFFFFL.
232 public void setCrc(long value
) {
233 if (value
>= 0 && value
<= 0xFFFFFFFFL
) {
236 throw new IllegalArgumentException("Bad CRC32: " + value
);
241 * Sets the extra information for this {@code ZipEntry}.
243 * @throws IllegalArgumentException if the data length >= 64 KiB.
245 public void setExtra(byte[] data
) {
246 if (data
!= null && data
.length
> 0xffff) {
247 throw new IllegalArgumentException("Extra data too long: " + data
.length
);
253 * Sets the compression method for this entry to either {@code DEFLATED} or {@code STORED}.
254 * The default is {@code DEFLATED}, which will cause the size, compressed size, and CRC to be
255 * set automatically, and the entry's data to be compressed. If you switch to {@code STORED}
256 * note that you'll have to set the size (or compressed size; they must be the same, but it's
257 * okay to only set one) and CRC yourself because they must appear <i>before</i> the user data
258 * in the resulting zip file. See {@link #setSize} and {@link #setCrc}.
259 * @throws IllegalArgumentException
260 * when value is not {@code DEFLATED} or {@code STORED}.
262 public void setMethod(int value
) {
263 if (value
!= STORED
&& value
!= DEFLATED
) {
264 throw new IllegalArgumentException("Bad method: " + value
);
266 compressionMethod
= value
;
270 * Sets the uncompressed size of this {@code ZipEntry}.
272 * @param value the uncompressed size for this entry.
273 * @throws IllegalArgumentException if {@code value < 0}.
275 public void setSize(long value
) {
277 throw new IllegalArgumentException("Bad size: " + value
);
284 * Sets the modification time of this {@code ZipEntry}.
287 * the modification time as the number of milliseconds since Jan.
290 public void setTime(long value
) {
291 GregorianCalendar cal
= new GregorianCalendar();
292 cal
.setTime(new Date(value
));
293 int year
= cal
.get(Calendar
.YEAR
);
298 modDate
= cal
.get(Calendar
.DATE
);
299 modDate
= (cal
.get(Calendar
.MONTH
) + 1 << 5) | modDate
;
300 modDate
= ((cal
.get(Calendar
.YEAR
) - 1980) << 9) | modDate
;
301 time
= cal
.get(Calendar
.SECOND
) >> 1;
302 time
= (cal
.get(Calendar
.MINUTE
) << 5) | time
;
303 time
= (cal
.get(Calendar
.HOUR_OF_DAY
) << 11) | time
;
309 public void setDataOffset(long value
) {
314 public long getDataOffset() {
319 * Returns the string representation of this {@code ZipEntry}.
321 * @return the string representation of this {@code ZipEntry}.
324 public String
toString() {
329 * Constructs a new {@code ZipEntry} using the values obtained from {@code
333 * the {@code ZipEntry} from which to obtain values.
335 public ZipEntry(ZipEntry ze
) {
337 comment
= ze
.comment
;
340 compressedSize
= ze
.compressedSize
;
342 compressionMethod
= ze
.compressionMethod
;
343 modDate
= ze
.modDate
;
345 localHeaderRelOffset
= ze
.localHeaderRelOffset
;
346 dataOffset
= ze
.dataOffset
;
350 * Returns a deep copy of this zip entry.
352 @Override public Object
clone() {
354 ZipEntry result
= (ZipEntry
) super.clone();
355 result
.extra
= extra
!= null ? extra
.clone() : null;
357 } catch (CloneNotSupportedException e
) {
358 throw new AssertionError(e
);
363 * Returns the hash code for this {@code ZipEntry}.
365 * @return the hash code of the entry.
368 public int hashCode() {
369 return name
.hashCode();
373 * Internal constructor. Creates a new ZipEntry by reading the
374 * Central Directory Entry (CDE) from "in", which must be positioned
375 * at the CDE signature. If the GPBF_UTF8_FLAG is set in the CDE then
376 * UTF-8 is used to decode the string information, otherwise the
377 * defaultCharset is used.
379 * On exit, "in" will be positioned at the start of the next entry
380 * in the Central Directory.
382 ZipEntry(byte[] cdeHdrBuf
, InputStream cdStream
, Charset defaultCharset
, boolean isZip64
) throws IOException
{
383 Streams
.readFully(cdStream
, cdeHdrBuf
, 0, cdeHdrBuf
.length
);
385 BufferIterator it
= HeapBufferIterator
.iterator(cdeHdrBuf
, 0, cdeHdrBuf
.length
,
386 ByteOrder
.LITTLE_ENDIAN
);
388 int sig
= it
.readInt();
390 ZipFile
.throwZipException("Central Directory Entry", sig
);
394 int gpbf
= it
.readShort() & 0xffff;
396 if ((gpbf
& ZipFile
.GPBF_UNSUPPORTED_MASK
) != 0) {
397 throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf
);
400 // If the GPBF_UTF8_FLAG is set then the character encoding is UTF-8 whatever the default
402 Charset charset
= defaultCharset
;
403 if ((gpbf
& ZipFile
.GPBF_UTF8_FLAG
) != 0) {
404 charset
= StandardCharsets
.UTF_8
;
407 compressionMethod
= it
.readShort() & 0xffff;
408 time
= it
.readShort() & 0xffff;
409 modDate
= it
.readShort() & 0xffff;
411 // These are 32-bit values in the file, but 64-bit fields in this object.
412 crc
= ((long) it
.readInt()) & 0xffffffffL
;
413 compressedSize
= ((long) it
.readInt()) & 0xffffffffL
;
414 size
= ((long) it
.readInt()) & 0xffffffffL
;
416 int nameLength
= it
.readShort() & 0xffff;
417 int extraLength
= it
.readShort() & 0xffff;
418 int commentByteCount
= it
.readShort() & 0xffff;
420 // This is a 32-bit value in the file, but a 64-bit field in this object.
422 localHeaderRelOffset
= ((long) it
.readInt()) & 0xffffffffL
;
424 byte[] nameBytes
= new byte[nameLength
];
425 Streams
.readFully(cdStream
, nameBytes
, 0, nameBytes
.length
);
426 if (containsNulByte(nameBytes
)) {
427 throw new ZipException("Filename contains NUL byte: " + Arrays
.toString(nameBytes
));
429 name
= new String(nameBytes
, 0, nameBytes
.length
, charset
);
431 if (extraLength
> 0) {
432 extra
= new byte[extraLength
];
433 Streams
.readFully(cdStream
, extra
, 0, extraLength
);
436 if (commentByteCount
> 0) {
437 byte[] commentBytes
= new byte[commentByteCount
];
438 Streams
.readFully(cdStream
, commentBytes
, 0, commentByteCount
);
439 comment
= new String(commentBytes
, 0, commentBytes
.length
, charset
);
443 Zip64
.parseZip64ExtendedInfo(this, true /* from central directory */);
447 private static boolean containsNulByte(byte[] bytes
) {
448 for (byte b
: bytes
) {
456 private static void validateStringLength(String argument
, String string
) {
457 // This check is not perfect: the character encoding is determined when the entry is
458 // written out. UTF-8 is probably a worst-case: most alternatives should be single byte per
460 byte[] bytes
= string
.getBytes(StandardCharsets
.UTF_8
);
461 if (bytes
.length
> 0xffff) {
462 throw new IllegalArgumentException(argument
+ " too long: " + bytes
.length
);