1 /*
2 * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.util.zip;
27
28 import static java.util.zip.ZipUtils.*;
29 import java.nio.file.attribute.FileTime;
30 import java.util.Objects;
31 import java.util.concurrent.TimeUnit;
32 import java.time.LocalDateTime;
33 import java.time.ZonedDateTime;
34 import java.time.ZoneId;
35
36 import static java.util.zip.ZipConstants64.*;
37
38 /**
39 * This class is used to represent a ZIP file entry.
40 *
41 * @author David Connelly
42 * @since 1.1
43 */
44 public
45 class ZipEntry implements ZipConstants, Cloneable {
46
47 String name; // entry name
48 long xdostime = -1; // last modification time (in extended DOS time,
49 // where milliseconds lost in conversion might
50 // be encoded into the upper half)
51 FileTime mtime; // last modification time, from extra field data
52 FileTime atime; // last access time, from extra field data
53 FileTime ctime; // creation time, from extra field data
54 long crc = -1; // crc-32 of entry data
55 long size = -1; // uncompressed size of entry data
56 long csize = -1; // compressed size of entry data
57 int method = -1; // compression method
58 int flag = 0; // general purpose flag
59 byte[] extra; // optional extra field data for entry
60 String comment; // optional comment string for entry
61
62 /**
63 * Compression method for uncompressed entries.
64 */
65 public static final int STORED = 0;
66
67 /**
68 * Compression method for compressed (deflated) entries.
69 */
70 public static final int DEFLATED = 8;
71
72 /**
73 * DOS time constant for representing timestamps before 1980.
74 */
75 static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16);
76
77 /**
78 * Approximately 128 years, in milliseconds (ignoring leap years etc).
79 *
80 * This establish an approximate high-bound value for DOS times in
81 * milliseconds since epoch, used to enable an efficient but
82 * sufficient bounds check to avoid generating extended last modified
83 * time entries.
84 *
85 * Calculating the exact number is locale dependent, would require loading
86 * TimeZone data eagerly, and would make little practical sense. Since DOS
87 * times theoretically go to 2107 - with compatibility not guaranteed
88 * after 2099 - setting this to a time that is before but near 2099
89 * should be sufficient.
90 */
91 private static final long UPPER_DOSTIME_BOUND =
92 128L * 365 * 24 * 60 * 60 * 1000;
93
94 /**
95 * Creates a new zip entry with the specified name.
96 *
97 * @param name
98 * The entry name
99 *
100 * @throws NullPointerException if the entry name is null
101 * @throws IllegalArgumentException if the entry name is longer than
102 * 0xFFFF bytes
103 */
104 public ZipEntry(String name) {
105 Objects.requireNonNull(name, "name");
106 if (name.length() > 0xFFFF) {
107 throw new IllegalArgumentException("entry name too long");
108 }
109 this.name = name;
110 }
111
112 /**
113 * Creates a new zip entry with fields taken from the specified
114 * zip entry.
115 *
116 * @param e
117 * A zip Entry object
118 *
119 * @throws NullPointerException if the entry object is null
120 */
121 public ZipEntry(ZipEntry e) {
122 Objects.requireNonNull(e, "entry");
123 name = e.name;
124 xdostime = e.xdostime;
125 mtime = e.mtime;
126 atime = e.atime;
127 ctime = e.ctime;
128 crc = e.crc;
129 size = e.size;
130 csize = e.csize;
131 method = e.method;
132 flag = e.flag;
133 extra = e.extra;
134 comment = e.comment;
135 }
136
137 /**
138 * Creates a new un-initialized zip entry
139 */
140 ZipEntry() {}
141
142 /**
143 * Returns the name of the entry.
144 * @return the name of the entry
145 */
146 public String getName() {
147 return name;
148 }
149
150 /**
151 * Sets the last modification time of the entry.
152 *
153 * <p> If the entry is output to a ZIP file or ZIP file formatted
154 * output stream the last modification time set by this method will
155 * be stored into the {@code date and time fields} of the zip file
156 * entry and encoded in standard {@code MS-DOS date and time format}.
157 * The {@link java.util.TimeZone#getDefault() default TimeZone} is
158 * used to convert the epoch time to the MS-DOS data and time.
159 *
160 * @param time
161 * The last modification time of the entry in milliseconds
162 * since the epoch
163 *
164 * @see #getTime()
165 * @see #getLastModifiedTime()
166 */
167 public void setTime(long time) {
168 this.xdostime = javaToExtendedDosTime(time);
169 // Avoid setting the mtime field if time is in the valid
170 // range for a DOS time
171 if (xdostime != DOSTIME_BEFORE_1980 && time <= UPPER_DOSTIME_BOUND) {
172 this.mtime = null;
173 } else {
174 this.mtime = FileTime.from(time, TimeUnit.MILLISECONDS);
175 }
176 }
177
178 /**
179 * Returns the last modification time of the entry.
180 *
181 * <p> If the entry is read from a ZIP file or ZIP file formatted
182 * input stream, this is the last modification time from the {@code
183 * date and time fields} of the zip file entry. The
184 * {@link java.util.TimeZone#getDefault() default TimeZone} is used
185 * to convert the standard MS-DOS formatted date and time to the
186 * epoch time.
187 *
188 * @return The last modification time of the entry in milliseconds
189 * since the epoch, or -1 if not specified
190 *
191 * @see #setTime(long)
192 * @see #setLastModifiedTime(FileTime)
193 */
194 public long getTime() {
195 if (mtime != null) {
196 return mtime.toMillis();
197 }
198 return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1;
199 }
200
201 /**
202 * Sets the last modification time of the entry in local date-time.
203 *
204 * <p> If the entry is output to a ZIP file or ZIP file formatted
205 * output stream the last modification time set by this method will
206 * be stored into the {@code date and time fields} of the zip file
207 * entry and encoded in standard {@code MS-DOS date and time format}.
208 * If the date-time set is out of the range of the standard {@code
209 * MS-DOS date and time format}, the time will also be stored into
210 * zip file entry's extended timestamp fields in {@code optional
211 * extra data} in UTC time. The {@link java.time.ZoneId#systemDefault()
212 * system default TimeZone} is used to convert the local date-time
213 * to UTC time.
214 *
215 * <p> {@code LocalDateTime} uses a precision of nanoseconds, whereas
216 * this class uses a precision of milliseconds. The conversion will
217 * truncate any excess precision information as though the amount in
218 * nanoseconds was subject to integer division by one million.
219 *
220 * @param time
221 * The last modification time of the entry in local date-time
222 *
223 * @see #getTimeLocal()
224 * @since 9
225 */
226 public void setTimeLocal(LocalDateTime time) {
227 int year = time.getYear() - 1980;
228 if (year < 0) {
229 this.xdostime = DOSTIME_BEFORE_1980;
230 } else {
231 this.xdostime = ((year << 25 |
232 time.getMonthValue() << 21 |
233 time.getDayOfMonth() << 16 |
234 time.getHour() << 11 |
235 time.getMinute() << 5 |
236 time.getSecond() >> 1) & 0xffffffffL)
237 + ((long)(((time.getSecond() & 0x1) * 1000) +
238 time.getNano() / 1000_000) << 32);
239 }
240 if (xdostime != DOSTIME_BEFORE_1980 && year <= 0x7f) {
241 this.mtime = null;
242 } else {
243 this.mtime = FileTime.from(
244 ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant());
245 }
246 }
247
248 /**
249 * Returns the last modification time of the entry in local date-time.
250 *
251 * <p> If the entry is read from a ZIP file or ZIP file formatted
252 * input stream, this is the last modification time from the zip
253 * file entry's {@code optional extra data} if the extended timestamp
254 * fields are present. Otherwise, the last modification time is read
255 * from entry's standard MS-DOS formatted {@code date and time fields}.
256 *
257 * <p> The {@link java.time.ZoneId#systemDefault() system default TimeZone}
258 * is used to convert the UTC time to local date-time.
259 *
260 * @return The last modification time of the entry in local date-time
261 *
262 * @see #setTimeLocal(LocalDateTime)
263 * @since 9
264 */
265 public LocalDateTime getTimeLocal() {
266 if (mtime != null) {
267 return LocalDateTime.ofInstant(mtime.toInstant(), ZoneId.systemDefault());
268 }
269 int ms = (int)(xdostime >> 32);
270 return LocalDateTime.of((int)(((xdostime >> 25) & 0x7f) + 1980),
271 (int)((xdostime >> 21) & 0x0f),
272 (int)((xdostime >> 16) & 0x1f),
273 (int)((xdostime >> 11) & 0x1f),
274 (int)((xdostime >> 5) & 0x3f),
275 (int)((xdostime << 1) & 0x3e) + ms / 1000,
276 (ms % 1000) * 1000_000);
277 }
278
279
280 /**
281 * Sets the last modification time of the entry.
282 *
283 * <p> When output to a ZIP file or ZIP file formatted output stream
284 * the last modification time set by this method will be stored into
285 * zip file entry's {@code date and time fields} in {@code standard
286 * MS-DOS date and time format}), and the extended timestamp fields
287 * in {@code optional extra data} in UTC time.
288 *
289 * @param time
290 * The last modification time of the entry
291 * @return This zip entry
292 *
293 * @throws NullPointerException if the {@code time} is null
294 *
295 * @see #getLastModifiedTime()
296 * @since 1.8
297 */
298 public ZipEntry setLastModifiedTime(FileTime time) {
299 this.mtime = Objects.requireNonNull(time, "lastModifiedTime");
300 this.xdostime = javaToExtendedDosTime(time.to(TimeUnit.MILLISECONDS));
301 return this;
302 }
303
304 /**
305 * Returns the last modification time of the entry.
306 *
307 * <p> If the entry is read from a ZIP file or ZIP file formatted
308 * input stream, this is the last modification time from the zip
309 * file entry's {@code optional extra data} if the extended timestamp
310 * fields are present. Otherwise the last modification time is read
311 * from the entry's {@code date and time fields}, the {@link
312 * java.util.TimeZone#getDefault() default TimeZone} is used to convert
313 * the standard MS-DOS formatted date and time to the epoch time.
314 *
315 * @return The last modification time of the entry, null if not specified
316 *
317 * @see #setLastModifiedTime(FileTime)
318 * @since 1.8
319 */
320 public FileTime getLastModifiedTime() {
321 if (mtime != null)
322 return mtime;
323 if (xdostime == -1)
324 return null;
325 return FileTime.from(getTime(), TimeUnit.MILLISECONDS);
326 }
327
328 /**
329 * Sets the last access time of the entry.
330 *
331 * <p> If set, the last access time will be stored into the extended
332 * timestamp fields of entry's {@code optional extra data}, when output
333 * to a ZIP file or ZIP file formatted stream.
334 *
335 * @param time
336 * The last access time of the entry
337 * @return This zip entry
338 *
339 * @throws NullPointerException if the {@code time} is null
340 *
341 * @see #getLastAccessTime()
342 * @since 1.8
343 */
344 public ZipEntry setLastAccessTime(FileTime time) {
345 this.atime = Objects.requireNonNull(time, "lastAccessTime");
346 return this;
347 }
348
349 /**
350 * Returns the last access time of the entry.
351 *
352 * <p> The last access time is from the extended timestamp fields
353 * of entry's {@code optional extra data} when read from a ZIP file
354 * or ZIP file formatted stream.
355 *
356 * @return The last access time of the entry, null if not specified
357 * @see #setLastAccessTime(FileTime)
358 * @since 1.8
359 */
360 public FileTime getLastAccessTime() {
361 return atime;
362 }
363
364 /**
365 * Sets the creation time of the entry.
366 *
367 * <p> If set, the creation time will be stored into the extended
368 * timestamp fields of entry's {@code optional extra data}, when
369 * output to a ZIP file or ZIP file formatted stream.
370 *
371 * @param time
372 * The creation time of the entry
373 * @return This zip entry
374 *
375 * @throws NullPointerException if the {@code time} is null
376 *
377 * @see #getCreationTime()
378 * @since 1.8
379 */
380 public ZipEntry setCreationTime(FileTime time) {
381 this.ctime = Objects.requireNonNull(time, "creationTime");
382 return this;
383 }
384
385 /**
386 * Returns the creation time of the entry.
387 *
388 * <p> The creation time is from the extended timestamp fields of
389 * entry's {@code optional extra data} when read from a ZIP file
390 * or ZIP file formatted stream.
391 *
392 * @return the creation time of the entry, null if not specified
393 * @see #setCreationTime(FileTime)
394 * @since 1.8
395 */
396 public FileTime getCreationTime() {
397 return ctime;
398 }
399
400 /**
401 * Sets the uncompressed size of the entry data.
402 *
403 * @param size the uncompressed size in bytes
404 *
405 * @throws IllegalArgumentException if the specified size is less
406 * than 0, is greater than 0xFFFFFFFF when
407 * <a href="package-summary.html#zip64">ZIP64 format</a> is not supported,
408 * or is less than 0 when ZIP64 is supported
409 * @see #getSize()
410 */
411 public void setSize(long size) {
412 if (size < 0) {
413 throw new IllegalArgumentException("invalid entry size");
414 }
415 this.size = size;
416 }
417
418 /**
419 * Returns the uncompressed size of the entry data.
420 *
421 * @return the uncompressed size of the entry data, or -1 if not known
422 * @see #setSize(long)
423 */
424 public long getSize() {
425 return size;
426 }
427
428 /**
429 * Returns the size of the compressed entry data.
430 *
431 * <p> In the case of a stored entry, the compressed size will be the same
432 * as the uncompressed size of the entry.
433 *
434 * @return the size of the compressed entry data, or -1 if not known
435 * @see #setCompressedSize(long)
436 */
437 public long getCompressedSize() {
438 return csize;
439 }
440
441 /**
442 * Sets the size of the compressed entry data.
443 *
444 * @param csize the compressed size to set
445 *
446 * @see #getCompressedSize()
447 */
448 public void setCompressedSize(long csize) {
449 this.csize = csize;
450 }
451
452 /**
453 * Sets the CRC-32 checksum of the uncompressed entry data.
454 *
455 * @param crc the CRC-32 value
456 *
457 * @throws IllegalArgumentException if the specified CRC-32 value is
458 * less than 0 or greater than 0xFFFFFFFF
459 * @see #getCrc()
460 */
461 public void setCrc(long crc) {
462 if (crc < 0 || crc > 0xFFFFFFFFL) {
463 throw new IllegalArgumentException("invalid entry crc-32");
464 }
465 this.crc = crc;
466 }
467
468 /**
469 * Returns the CRC-32 checksum of the uncompressed entry data.
470 *
471 * @return the CRC-32 checksum of the uncompressed entry data, or -1 if
472 * not known
473 *
474 * @see #setCrc(long)
475 */
476 public long getCrc() {
477 return crc;
478 }
479
480 /**
481 * Sets the compression method for the entry.
482 *
483 * @param method the compression method, either STORED or DEFLATED
484 *
485 * @throws IllegalArgumentException if the specified compression
486 * method is invalid
487 * @see #getMethod()
488 */
489 public void setMethod(int method) {
490 if (method != STORED && method != DEFLATED) {
491 throw new IllegalArgumentException("invalid compression method");
492 }
493 this.method = method;
494 }
495
496 /**
497 * Returns the compression method of the entry.
498 *
499 * @return the compression method of the entry, or -1 if not specified
500 * @see #setMethod(int)
501 */
502 public int getMethod() {
503 return method;
504 }
505
506 /**
507 * Sets the optional extra field data for the entry.
508 *
509 * <p> Invoking this method may change this entry's last modification
510 * time, last access time and creation time, if the {@code extra} field
511 * data includes the extensible timestamp fields, such as {@code NTFS tag
512 * 0x0001} or {@code Info-ZIP Extended Timestamp}, as specified in
513 * <a href="http://www.info-zip.org/doc/appnote-19970311-iz.zip">Info-ZIP
514 * Application Note 970311</a>.
515 *
516 * @param extra
517 * The extra field data bytes
518 *
519 * @throws IllegalArgumentException if the length of the specified
520 * extra field data is greater than 0xFFFF bytes
521 *
522 * @see #getExtra()
523 */
524 public void setExtra(byte[] extra) {
525 setExtra0(extra, false, true);
526 }
527
528 /**
529 * Sets the optional extra field data for the entry.
530 *
531 * @param extra
532 * the extra field data bytes
533 * @param doZIP64
534 * if true, set size and csize from ZIP64 fields if present
535 * @param isLOC
536 * true if setting the extra field for a LOC, false if for
537 * a CEN
538 */
539 void setExtra0(byte[] extra, boolean doZIP64, boolean isLOC) {
540 if (extra != null) {
541 if (extra.length > 0xFFFF) {
542 throw new IllegalArgumentException("invalid extra field length");
543 }
544 // extra fields are in "HeaderID(2)DataSize(2)Data... format
545 int off = 0;
546 int len = extra.length;
547 while (off + 4 < len) {
548 int tag = get16(extra, off);
549 int sz = get16(extra, off + 2);
550 off += 4;
551 if (off + sz > len) // invalid data
552 break;
553 switch (tag) {
554 case EXTID_ZIP64:
555 if (doZIP64) {
556 if (isLOC) {
557 // LOC extra zip64 entry MUST include BOTH original
558 // and compressed file size fields.
559 // If invalid zip64 extra fields, simply skip. Even
560 // it's rare, it's possible the entry size happens to
561 // be the magic value and it "accidently" has some
562 // bytes in extra match the id.
563 if (sz >= 16) {
564 size = get64(extra, off);
565 csize = get64(extra, off + 8);
566 }
567 } else {
568 // CEN extra zip64
569 if (size == ZIP64_MAGICVAL) {
570 if (off + 8 > len) // invalid zip64 extra
571 break; // fields, just skip
572 size = get64(extra, off);
573 }
574 if (csize == ZIP64_MAGICVAL) {
575 if (off + 16 > len) // invalid zip64 extra
576 break; // fields, just skip
577 csize = get64(extra, off + 8);
578 }
579 }
580 }
581 break;
582 case EXTID_NTFS:
583 if (sz < 32) // reserved 4 bytes + tag 2 bytes + size 2 bytes
584 break; // m[a|c]time 24 bytes
585 int pos = off + 4; // reserved 4 bytes
586 if (get16(extra, pos) != 0x0001 || get16(extra, pos + 2) != 24)
587 break;
588 long wtime = get64(extra, pos + 4);
589 if (wtime != WINDOWS_TIME_NOT_AVAILABLE) {
590 mtime = winTimeToFileTime(wtime);
591 }
592 wtime = get64(extra, pos + 12);
593 if (wtime != WINDOWS_TIME_NOT_AVAILABLE) {
594 atime = winTimeToFileTime(wtime);
595 }
596 wtime = get64(extra, pos + 20);
597 if (wtime != WINDOWS_TIME_NOT_AVAILABLE) {
598 ctime = winTimeToFileTime(wtime);
599 }
600 break;
601 case EXTID_EXTT:
602 int flag = Byte.toUnsignedInt(extra[off]);
603 int sz0 = 1;
604 // The CEN-header extra field contains the modification
605 // time only, or no timestamp at all. 'sz' is used to
606 // flag its presence or absence. But if mtime is present
607 // in LOC it must be present in CEN as well.
608 if ((flag & 0x1) != 0 && (sz0 + 4) <= sz) {
609 mtime = unixTimeToFileTime(get32S(extra, off + sz0));
610 sz0 += 4;
611 }
612 if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) {
613 atime = unixTimeToFileTime(get32S(extra, off + sz0));
614 sz0 += 4;
615 }
616 if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) {
617 ctime = unixTimeToFileTime(get32S(extra, off + sz0));
618 sz0 += 4;
619 }
620 break;
621 default:
622 }
623 off += sz;
624 }
625 }
626 this.extra = extra;
627 }
628
629 /**
630 * Returns the extra field data for the entry.
631 *
632 * @return the extra field data for the entry, or null if none
633 *
634 * @see #setExtra(byte[])
635 */
636 public byte[] getExtra() {
637 return extra;
638 }
639
640 /**
641 * Sets the optional comment string for the entry.
642 *
643 * <p>ZIP entry comments have maximum length of 0xffff. If the length of the
644 * specified comment string is greater than 0xFFFF bytes after encoding, only
645 * the first 0xFFFF bytes are output to the ZIP file entry.
646 *
647 * @param comment the comment string
648 *
649 * @see #getComment()
650 */
651 public void setComment(String comment) {
652 this.comment = comment;
653 }
654
655 /**
656 * Returns the comment string for the entry.
657 *
658 * @return the comment string for the entry, or null if none
659 *
660 * @see #setComment(String)
661 */
662 public String getComment() {
663 return comment;
664 }
665
666 /**
667 * Returns true if this is a directory entry. A directory entry is
668 * defined to be one whose name ends with a '/'.
669 * @return true if this is a directory entry
670 */
671 public boolean isDirectory() {
672 return name.endsWith("/");
673 }
674
675 /**
676 * Returns a string representation of the ZIP entry.
677 */
678 public String toString() {
679 return getName();
680 }
681
682 /**
683 * Returns the hash code value for this entry.
684 */
685 public int hashCode() {
686 return name.hashCode();
687 }
688
689 /**
690 * Returns a copy of this entry.
691 */
692 public Object clone() {
693 try {
694 ZipEntry e = (ZipEntry)super.clone();
695 e.extra = (extra == null) ? null : extra.clone();
696 return e;
697 } catch (CloneNotSupportedException e) {
698 // This should never happen, since we are Cloneable
699 throw new InternalError(e);
700 }
701 }
702 }
703