Roll android_tools support library to 25.1.0
[android_tools.git] / sdk / sources / android-23 / java / util / zip / ZipEntry.java
bloba06f1b6642a0938a022a935fb7cc5a2be9847254
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
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;
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.
40 public class ZipEntry implements ZipConstants, Cloneable {
41 String name;
42 String comment;
44 long crc = -1; // Needs to be a long to distinguish -1 ("not set") from the 0xffffffff CRC32.
46 long compressedSize = -1;
47 long size = -1;
49 int compressionMethod = -1;
50 int time = -1;
51 int modDate = -1;
53 byte[] extra;
55 long localHeaderRelOffset = -1;
57 long dataOffset = -1;
59 /**
60 * Zip entry state: Deflated.
62 public static final int DEFLATED = 8;
64 /**
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) {
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.localHeaderRelOffset = localHeaderRelOffset;
83 this.dataOffset = dataOffset;
86 /**
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) {
94 if (name == null) {
95 throw new NullPointerException("name == null");
97 validateStringLength("Name", name);
98 this.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() {
106 return comment;
110 * Gets the compressed size of this {@code ZipEntry}.
112 * @return the compressed size, or -1 if the compressed size has not been
113 * set.
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() {
125 return crc;
129 * Gets the extra information for this {@code ZipEntry}.
131 * @return a byte array containing the extra information, or {@code null} if
132 * there is none.
134 public byte[] getExtra() {
135 return extra;
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() {
159 return name;
163 * Gets the uncompressed size of this {@code ZipEntry}.
165 * @return the uncompressed size, or {@code -1} if the size has not been
166 * set.
168 public long getSize() {
169 return size;
173 * Gets the last modification time of this {@code ZipEntry}.
175 * @return the last modification time as the number of milliseconds since
176 * Jan. 1, 1970.
178 public long getTime() {
179 if (time != -1) {
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,
184 (time & 0x1f) << 1);
185 return cal.getTime().getTime();
187 return -1;
191 * Determine whether or not this {@code ZipEntry} is a directory.
193 * @return {@code true} when this {@code ZipEntry} is a directory, {@code
194 * false} otherwise.
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) {
206 this.comment = null;
207 return;
209 validateStringLength("Comment", comment);
211 this.comment = comment;
215 * Sets the compressed size for this {@code ZipEntry}.
217 * @param value
218 * the compressed size (in bytes).
220 public void setCompressedSize(long value) {
221 compressedSize = value;
225 * Sets the checksum for this {@code ZipEntry}.
227 * @param value
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) {
234 crc = value;
235 } else {
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);
249 extra = data;
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) {
276 if (value < 0) {
277 throw new IllegalArgumentException("Bad size: " + value);
280 size = value;
284 * Sets the modification time of this {@code ZipEntry}.
286 * @param value
287 * the modification time as the number of milliseconds since Jan.
288 * 1, 1970.
290 public void setTime(long value) {
291 GregorianCalendar cal = new GregorianCalendar();
292 cal.setTime(new Date(value));
293 int year = cal.get(Calendar.YEAR);
294 if (year < 1980) {
295 modDate = 0x21;
296 time = 0;
297 } else {
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;
308 /** @hide */
309 public void setDataOffset(long value) {
310 dataOffset = value;
313 /** @hide */
314 public long getDataOffset() {
315 return dataOffset;
319 * Returns the string representation of this {@code ZipEntry}.
321 * @return the string representation of this {@code ZipEntry}.
323 @Override
324 public String toString() {
325 return name;
329 * Constructs a new {@code ZipEntry} using the values obtained from {@code
330 * ze}.
332 * @param ze
333 * the {@code ZipEntry} from which to obtain values.
335 public ZipEntry(ZipEntry ze) {
336 name = ze.name;
337 comment = ze.comment;
338 time = ze.time;
339 size = ze.size;
340 compressedSize = ze.compressedSize;
341 crc = ze.crc;
342 compressionMethod = ze.compressionMethod;
343 modDate = ze.modDate;
344 extra = ze.extra;
345 localHeaderRelOffset = ze.localHeaderRelOffset;
346 dataOffset = ze.dataOffset;
350 * Returns a deep copy of this zip entry.
352 @Override public Object clone() {
353 try {
354 ZipEntry result = (ZipEntry) super.clone();
355 result.extra = extra != null ? extra.clone() : null;
356 return result;
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.
367 @Override
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();
389 if (sig != CENSIG) {
390 ZipFile.throwZipException("Central Directory Entry", sig);
393 it.seek(8);
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
401 // provided.
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.
421 it.seek(42);
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);
442 if (isZip64) {
443 Zip64.parseZip64ExtendedInfo(this, true /* from central directory */);
447 private static boolean containsNulByte(byte[] bytes) {
448 for (byte b : bytes) {
449 if (b == 0) {
450 return true;
453 return false;
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
459 // character.
460 byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
461 if (bytes.length > 0xffff) {
462 throw new IllegalArgumentException(argument + " too long: " + bytes.length);