|
1 /* |
|
2 * Copyright (c) 1996, 2013, 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 java.io.OutputStream; |
|
29 import java.io.IOException; |
|
30 import java.nio.charset.Charset; |
|
31 import java.nio.charset.StandardCharsets; |
|
32 import java.util.Vector; |
|
33 import java.util.HashSet; |
|
34 import static java.util.zip.ZipConstants64.*; |
|
35 import static java.util.zip.ZipUtils.*; |
|
36 |
|
37 /** |
|
38 * This class implements an output stream filter for writing files in the |
|
39 * ZIP file format. Includes support for both compressed and uncompressed |
|
40 * entries. |
|
41 * |
|
42 * @author David Connelly |
|
43 */ |
|
44 public |
|
45 class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { |
|
46 |
|
47 /** |
|
48 * Whether to use ZIP64 for zip files with more than 64k entries. |
|
49 * Until ZIP64 support in zip implementations is ubiquitous, this |
|
50 * system property allows the creation of zip files which can be |
|
51 * read by legacy zip implementations which tolerate "incorrect" |
|
52 * total entry count fields, such as the ones in jdk6, and even |
|
53 * some in jdk7. |
|
54 */ |
|
55 private static final boolean inhibitZip64 = |
|
56 Boolean.parseBoolean( |
|
57 java.security.AccessController.doPrivileged( |
|
58 new sun.security.action.GetPropertyAction( |
|
59 "jdk.util.zip.inhibitZip64", "false"))); |
|
60 |
|
61 private static class XEntry { |
|
62 final ZipEntry entry; |
|
63 final long offset; |
|
64 long dostime; // last modification time in msdos format |
|
65 public XEntry(ZipEntry entry, long offset) { |
|
66 this.entry = entry; |
|
67 this.offset = offset; |
|
68 } |
|
69 } |
|
70 |
|
71 private XEntry current; |
|
72 private Vector<XEntry> xentries = new Vector<>(); |
|
73 private HashSet<String> names = new HashSet<>(); |
|
74 private CRC32 crc = new CRC32(); |
|
75 private long written = 0; |
|
76 private long locoff = 0; |
|
77 private byte[] comment; |
|
78 private int method = DEFLATED; |
|
79 private boolean finished; |
|
80 |
|
81 private boolean closed = false; |
|
82 |
|
83 private final ZipCoder zc; |
|
84 |
|
85 private static int version(ZipEntry e) throws ZipException { |
|
86 switch (e.method) { |
|
87 case DEFLATED: return 20; |
|
88 case STORED: return 10; |
|
89 default: throw new ZipException("unsupported compression method"); |
|
90 } |
|
91 } |
|
92 |
|
93 /** |
|
94 * Checks to make sure that this stream has not been closed. |
|
95 */ |
|
96 private void ensureOpen() throws IOException { |
|
97 if (closed) { |
|
98 throw new IOException("Stream closed"); |
|
99 } |
|
100 } |
|
101 /** |
|
102 * Compression method for uncompressed (STORED) entries. |
|
103 */ |
|
104 public static final int STORED = ZipEntry.STORED; |
|
105 |
|
106 /** |
|
107 * Compression method for compressed (DEFLATED) entries. |
|
108 */ |
|
109 public static final int DEFLATED = ZipEntry.DEFLATED; |
|
110 |
|
111 /** |
|
112 * Creates a new ZIP output stream. |
|
113 * |
|
114 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used |
|
115 * to encode the entry names and comments. |
|
116 * |
|
117 * @param out the actual output stream |
|
118 */ |
|
119 public ZipOutputStream(OutputStream out) { |
|
120 this(out, StandardCharsets.UTF_8); |
|
121 } |
|
122 |
|
123 /** |
|
124 * Creates a new ZIP output stream. |
|
125 * |
|
126 * @param out the actual output stream |
|
127 * |
|
128 * @param charset the {@linkplain java.nio.charset.Charset charset} |
|
129 * to be used to encode the entry names and comments |
|
130 * |
|
131 * @since 1.7 |
|
132 */ |
|
133 public ZipOutputStream(OutputStream out, Charset charset) { |
|
134 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); |
|
135 if (charset == null) |
|
136 throw new NullPointerException("charset is null"); |
|
137 this.zc = ZipCoder.get(charset); |
|
138 usesDefaultDeflater = true; |
|
139 } |
|
140 |
|
141 /** |
|
142 * Sets the ZIP file comment. |
|
143 * @param comment the comment string |
|
144 * @exception IllegalArgumentException if the length of the specified |
|
145 * ZIP file comment is greater than 0xFFFF bytes |
|
146 */ |
|
147 public void setComment(String comment) { |
|
148 if (comment != null) { |
|
149 this.comment = zc.getBytes(comment); |
|
150 if (this.comment.length > 0xffff) |
|
151 throw new IllegalArgumentException("ZIP file comment too long."); |
|
152 } |
|
153 } |
|
154 |
|
155 /** |
|
156 * Sets the default compression method for subsequent entries. This |
|
157 * default will be used whenever the compression method is not specified |
|
158 * for an individual ZIP file entry, and is initially set to DEFLATED. |
|
159 * @param method the default compression method |
|
160 * @exception IllegalArgumentException if the specified compression method |
|
161 * is invalid |
|
162 */ |
|
163 public void setMethod(int method) { |
|
164 if (method != DEFLATED && method != STORED) { |
|
165 throw new IllegalArgumentException("invalid compression method"); |
|
166 } |
|
167 this.method = method; |
|
168 } |
|
169 |
|
170 /** |
|
171 * Sets the compression level for subsequent entries which are DEFLATED. |
|
172 * The default setting is DEFAULT_COMPRESSION. |
|
173 * @param level the compression level (0-9) |
|
174 * @exception IllegalArgumentException if the compression level is invalid |
|
175 */ |
|
176 public void setLevel(int level) { |
|
177 def.setLevel(level); |
|
178 } |
|
179 |
|
180 /** |
|
181 * Begins writing a new ZIP file entry and positions the stream to the |
|
182 * start of the entry data. Closes the current entry if still active. |
|
183 * The default compression method will be used if no compression method |
|
184 * was specified for the entry, and the current time will be used if |
|
185 * the entry has no set modification time. |
|
186 * @param e the ZIP entry to be written |
|
187 * @exception ZipException if a ZIP format error has occurred |
|
188 * @exception IOException if an I/O error has occurred |
|
189 */ |
|
190 public void putNextEntry(ZipEntry e) throws IOException { |
|
191 ensureOpen(); |
|
192 if (current != null) { |
|
193 closeEntry(); // close previous entry |
|
194 } |
|
195 if (e.time == -1) { |
|
196 // by default, do NOT use extended timestamps in extra |
|
197 // data, for now. |
|
198 e.setTime(System.currentTimeMillis()); |
|
199 } |
|
200 if (e.method == -1) { |
|
201 e.method = method; // use default method |
|
202 } |
|
203 // store size, compressed size, and crc-32 in LOC header |
|
204 e.flag = 0; |
|
205 switch (e.method) { |
|
206 case DEFLATED: |
|
207 // store size, compressed size, and crc-32 in data descriptor |
|
208 // immediately following the compressed entry data |
|
209 if (e.size == -1 || e.csize == -1 || e.crc == -1) |
|
210 e.flag = 8; |
|
211 |
|
212 break; |
|
213 case STORED: |
|
214 // compressed size, uncompressed size, and crc-32 must all be |
|
215 // set for entries using STORED compression method |
|
216 if (e.size == -1) { |
|
217 e.size = e.csize; |
|
218 } else if (e.csize == -1) { |
|
219 e.csize = e.size; |
|
220 } else if (e.size != e.csize) { |
|
221 throw new ZipException( |
|
222 "STORED entry where compressed != uncompressed size"); |
|
223 } |
|
224 if (e.size == -1 || e.crc == -1) { |
|
225 throw new ZipException( |
|
226 "STORED entry missing size, compressed size, or crc-32"); |
|
227 } |
|
228 break; |
|
229 default: |
|
230 throw new ZipException("unsupported compression method"); |
|
231 } |
|
232 if (! names.add(e.name)) { |
|
233 throw new ZipException("duplicate entry: " + e.name); |
|
234 } |
|
235 if (zc.isUTF8()) |
|
236 e.flag |= EFS; |
|
237 current = new XEntry(e, written); |
|
238 xentries.add(current); |
|
239 writeLOC(current); |
|
240 } |
|
241 |
|
242 /** |
|
243 * Closes the current ZIP entry and positions the stream for writing |
|
244 * the next entry. |
|
245 * @exception ZipException if a ZIP format error has occurred |
|
246 * @exception IOException if an I/O error has occurred |
|
247 */ |
|
248 public void closeEntry() throws IOException { |
|
249 ensureOpen(); |
|
250 if (current != null) { |
|
251 ZipEntry e = current.entry; |
|
252 switch (e.method) { |
|
253 case DEFLATED: |
|
254 def.finish(); |
|
255 while (!def.finished()) { |
|
256 deflate(); |
|
257 } |
|
258 if ((e.flag & 8) == 0) { |
|
259 // verify size, compressed size, and crc-32 settings |
|
260 if (e.size != def.getBytesRead()) { |
|
261 throw new ZipException( |
|
262 "invalid entry size (expected " + e.size + |
|
263 " but got " + def.getBytesRead() + " bytes)"); |
|
264 } |
|
265 if (e.csize != def.getBytesWritten()) { |
|
266 throw new ZipException( |
|
267 "invalid entry compressed size (expected " + |
|
268 e.csize + " but got " + def.getBytesWritten() + " bytes)"); |
|
269 } |
|
270 if (e.crc != crc.getValue()) { |
|
271 throw new ZipException( |
|
272 "invalid entry CRC-32 (expected 0x" + |
|
273 Long.toHexString(e.crc) + " but got 0x" + |
|
274 Long.toHexString(crc.getValue()) + ")"); |
|
275 } |
|
276 } else { |
|
277 e.size = def.getBytesRead(); |
|
278 e.csize = def.getBytesWritten(); |
|
279 e.crc = crc.getValue(); |
|
280 writeEXT(e); |
|
281 } |
|
282 def.reset(); |
|
283 written += e.csize; |
|
284 break; |
|
285 case STORED: |
|
286 // we already know that both e.size and e.csize are the same |
|
287 if (e.size != written - locoff) { |
|
288 throw new ZipException( |
|
289 "invalid entry size (expected " + e.size + |
|
290 " but got " + (written - locoff) + " bytes)"); |
|
291 } |
|
292 if (e.crc != crc.getValue()) { |
|
293 throw new ZipException( |
|
294 "invalid entry crc-32 (expected 0x" + |
|
295 Long.toHexString(e.crc) + " but got 0x" + |
|
296 Long.toHexString(crc.getValue()) + ")"); |
|
297 } |
|
298 break; |
|
299 default: |
|
300 throw new ZipException("invalid compression method"); |
|
301 } |
|
302 crc.reset(); |
|
303 current = null; |
|
304 } |
|
305 } |
|
306 |
|
307 /** |
|
308 * Writes an array of bytes to the current ZIP entry data. This method |
|
309 * will block until all the bytes are written. |
|
310 * @param b the data to be written |
|
311 * @param off the start offset in the data |
|
312 * @param len the number of bytes that are written |
|
313 * @exception ZipException if a ZIP file error has occurred |
|
314 * @exception IOException if an I/O error has occurred |
|
315 */ |
|
316 public synchronized void write(byte[] b, int off, int len) |
|
317 throws IOException |
|
318 { |
|
319 ensureOpen(); |
|
320 if (off < 0 || len < 0 || off > b.length - len) { |
|
321 throw new IndexOutOfBoundsException(); |
|
322 } else if (len == 0) { |
|
323 return; |
|
324 } |
|
325 |
|
326 if (current == null) { |
|
327 throw new ZipException("no current ZIP entry"); |
|
328 } |
|
329 ZipEntry entry = current.entry; |
|
330 switch (entry.method) { |
|
331 case DEFLATED: |
|
332 super.write(b, off, len); |
|
333 break; |
|
334 case STORED: |
|
335 written += len; |
|
336 if (written - locoff > entry.size) { |
|
337 throw new ZipException( |
|
338 "attempt to write past end of STORED entry"); |
|
339 } |
|
340 out.write(b, off, len); |
|
341 break; |
|
342 default: |
|
343 throw new ZipException("invalid compression method"); |
|
344 } |
|
345 crc.update(b, off, len); |
|
346 } |
|
347 |
|
348 /** |
|
349 * Finishes writing the contents of the ZIP output stream without closing |
|
350 * the underlying stream. Use this method when applying multiple filters |
|
351 * in succession to the same output stream. |
|
352 * @exception ZipException if a ZIP file error has occurred |
|
353 * @exception IOException if an I/O exception has occurred |
|
354 */ |
|
355 public void finish() throws IOException { |
|
356 ensureOpen(); |
|
357 if (finished) { |
|
358 return; |
|
359 } |
|
360 if (current != null) { |
|
361 closeEntry(); |
|
362 } |
|
363 // write central directory |
|
364 long off = written; |
|
365 for (XEntry xentry : xentries) |
|
366 writeCEN(xentry); |
|
367 writeEND(off, written - off); |
|
368 finished = true; |
|
369 } |
|
370 |
|
371 /** |
|
372 * Closes the ZIP output stream as well as the stream being filtered. |
|
373 * @exception ZipException if a ZIP file error has occurred |
|
374 * @exception IOException if an I/O error has occurred |
|
375 */ |
|
376 public void close() throws IOException { |
|
377 if (!closed) { |
|
378 super.close(); |
|
379 closed = true; |
|
380 } |
|
381 } |
|
382 |
|
383 /* |
|
384 * Writes local file (LOC) header for specified entry. |
|
385 */ |
|
386 private void writeLOC(XEntry xentry) throws IOException { |
|
387 ZipEntry e = xentry.entry; |
|
388 int flag = e.flag; |
|
389 boolean hasZip64 = false; |
|
390 int elen = getExtraLen(e.extra); |
|
391 |
|
392 // keep a copy of dostime for writeCEN(), otherwise the tz |
|
393 // sensitive local time entries in loc and cen might be |
|
394 // different if the default tz get changed during writeLOC() |
|
395 // and writeCEN() |
|
396 xentry.dostime = javaToDosTime(e.time); |
|
397 |
|
398 writeInt(LOCSIG); // LOC header signature |
|
399 if ((flag & 8) == 8) { |
|
400 writeShort(version(e)); // version needed to extract |
|
401 writeShort(flag); // general purpose bit flag |
|
402 writeShort(e.method); // compression method |
|
403 writeInt(xentry.dostime); // last modification time |
|
404 // store size, uncompressed size, and crc-32 in data descriptor |
|
405 // immediately following compressed entry data |
|
406 writeInt(0); |
|
407 writeInt(0); |
|
408 writeInt(0); |
|
409 } else { |
|
410 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { |
|
411 hasZip64 = true; |
|
412 writeShort(45); // ver 4.5 for zip64 |
|
413 } else { |
|
414 writeShort(version(e)); // version needed to extract |
|
415 } |
|
416 writeShort(flag); // general purpose bit flag |
|
417 writeShort(e.method); // compression method |
|
418 writeInt(xentry.dostime); // last modification time |
|
419 writeInt(e.crc); // crc-32 |
|
420 if (hasZip64) { |
|
421 writeInt(ZIP64_MAGICVAL); |
|
422 writeInt(ZIP64_MAGICVAL); |
|
423 elen += 20; //headid(2) + size(2) + size(8) + csize(8) |
|
424 } else { |
|
425 writeInt(e.csize); // compressed size |
|
426 writeInt(e.size); // uncompressed size |
|
427 } |
|
428 } |
|
429 byte[] nameBytes = zc.getBytes(e.name); |
|
430 writeShort(nameBytes.length); |
|
431 |
|
432 int elenEXTT = 0; // info-zip extended timestamp |
|
433 int flagEXTT = 0; |
|
434 if (e.mtime != null) { |
|
435 elenEXTT += 4; |
|
436 flagEXTT |= EXTT_FLAG_LMT; |
|
437 } |
|
438 if (e.atime != null) { |
|
439 elenEXTT += 4; |
|
440 flagEXTT |= EXTT_FLAG_LAT; |
|
441 } |
|
442 if (e.ctime != null) { |
|
443 elenEXTT += 4; |
|
444 flagEXTT |= EXTT_FLAT_CT; |
|
445 } |
|
446 if (flagEXTT != 0) |
|
447 elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data |
|
448 writeShort(elen); |
|
449 writeBytes(nameBytes, 0, nameBytes.length); |
|
450 if (hasZip64) { |
|
451 writeShort(ZIP64_EXTID); |
|
452 writeShort(16); |
|
453 writeLong(e.size); |
|
454 writeLong(e.csize); |
|
455 } |
|
456 if (flagEXTT != 0) { |
|
457 writeShort(EXTID_EXTT); |
|
458 writeShort(elenEXTT + 1); // flag + data |
|
459 writeByte(flagEXTT); |
|
460 if (e.mtime != null) |
|
461 writeInt(fileTimeToUnixTime(e.mtime)); |
|
462 if (e.atime != null) |
|
463 writeInt(fileTimeToUnixTime(e.atime)); |
|
464 if (e.ctime != null) |
|
465 writeInt(fileTimeToUnixTime(e.ctime)); |
|
466 } |
|
467 writeExtra(e.extra); |
|
468 locoff = written; |
|
469 } |
|
470 |
|
471 /* |
|
472 * Writes extra data descriptor (EXT) for specified entry. |
|
473 */ |
|
474 private void writeEXT(ZipEntry e) throws IOException { |
|
475 writeInt(EXTSIG); // EXT header signature |
|
476 writeInt(e.crc); // crc-32 |
|
477 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) { |
|
478 writeLong(e.csize); |
|
479 writeLong(e.size); |
|
480 } else { |
|
481 writeInt(e.csize); // compressed size |
|
482 writeInt(e.size); // uncompressed size |
|
483 } |
|
484 } |
|
485 |
|
486 /* |
|
487 * Write central directory (CEN) header for specified entry. |
|
488 * REMIND: add support for file attributes |
|
489 */ |
|
490 private void writeCEN(XEntry xentry) throws IOException { |
|
491 ZipEntry e = xentry.entry; |
|
492 int flag = e.flag; |
|
493 int version = version(e); |
|
494 long csize = e.csize; |
|
495 long size = e.size; |
|
496 long offset = xentry.offset; |
|
497 int elenZIP64 = 0; |
|
498 boolean hasZip64 = false; |
|
499 |
|
500 if (e.csize >= ZIP64_MAGICVAL) { |
|
501 csize = ZIP64_MAGICVAL; |
|
502 elenZIP64 += 8; // csize(8) |
|
503 hasZip64 = true; |
|
504 } |
|
505 if (e.size >= ZIP64_MAGICVAL) { |
|
506 size = ZIP64_MAGICVAL; // size(8) |
|
507 elenZIP64 += 8; |
|
508 hasZip64 = true; |
|
509 } |
|
510 if (xentry.offset >= ZIP64_MAGICVAL) { |
|
511 offset = ZIP64_MAGICVAL; |
|
512 elenZIP64 += 8; // offset(8) |
|
513 hasZip64 = true; |
|
514 } |
|
515 writeInt(CENSIG); // CEN header signature |
|
516 if (hasZip64) { |
|
517 writeShort(45); // ver 4.5 for zip64 |
|
518 writeShort(45); |
|
519 } else { |
|
520 writeShort(version); // version made by |
|
521 writeShort(version); // version needed to extract |
|
522 } |
|
523 writeShort(flag); // general purpose bit flag |
|
524 writeShort(e.method); // compression method |
|
525 // use the copy in xentry, which has been converted |
|
526 // from e.time in writeLOC() |
|
527 writeInt(xentry.dostime); // last modification time |
|
528 writeInt(e.crc); // crc-32 |
|
529 writeInt(csize); // compressed size |
|
530 writeInt(size); // uncompressed size |
|
531 byte[] nameBytes = zc.getBytes(e.name); |
|
532 writeShort(nameBytes.length); |
|
533 |
|
534 int elen = getExtraLen(e.extra); |
|
535 if (hasZip64) { |
|
536 elen += (elenZIP64 + 4);// + headid(2) + datasize(2) |
|
537 } |
|
538 // cen info-zip extended timestamp only outputs mtime |
|
539 // but set the flag for a/ctime, if present in loc |
|
540 int flagEXTT = 0; |
|
541 if (e.mtime != null) { |
|
542 elen += 4; // + mtime(4) |
|
543 flagEXTT |= EXTT_FLAG_LMT; |
|
544 } |
|
545 if (e.atime != null) { |
|
546 flagEXTT |= EXTT_FLAG_LAT; |
|
547 } |
|
548 if (e.ctime != null) { |
|
549 flagEXTT |= EXTT_FLAT_CT; |
|
550 } |
|
551 if (flagEXTT != 0) { |
|
552 elen += 5; // headid + sz + flag |
|
553 } |
|
554 writeShort(elen); |
|
555 byte[] commentBytes; |
|
556 if (e.comment != null) { |
|
557 commentBytes = zc.getBytes(e.comment); |
|
558 writeShort(Math.min(commentBytes.length, 0xffff)); |
|
559 } else { |
|
560 commentBytes = null; |
|
561 writeShort(0); |
|
562 } |
|
563 writeShort(0); // starting disk number |
|
564 writeShort(0); // internal file attributes (unused) |
|
565 writeInt(0); // external file attributes (unused) |
|
566 writeInt(offset); // relative offset of local header |
|
567 writeBytes(nameBytes, 0, nameBytes.length); |
|
568 |
|
569 // take care of EXTID_ZIP64 and EXTID_EXTT |
|
570 if (hasZip64) { |
|
571 writeShort(ZIP64_EXTID);// Zip64 extra |
|
572 writeShort(elenZIP64); |
|
573 if (size == ZIP64_MAGICVAL) |
|
574 writeLong(e.size); |
|
575 if (csize == ZIP64_MAGICVAL) |
|
576 writeLong(e.csize); |
|
577 if (offset == ZIP64_MAGICVAL) |
|
578 writeLong(xentry.offset); |
|
579 } |
|
580 if (flagEXTT != 0) { |
|
581 writeShort(EXTID_EXTT); |
|
582 if (e.mtime != null) { |
|
583 writeShort(5); // flag + mtime |
|
584 writeByte(flagEXTT); |
|
585 writeInt(fileTimeToUnixTime(e.mtime)); |
|
586 } else { |
|
587 writeShort(1); // flag only |
|
588 writeByte(flagEXTT); |
|
589 } |
|
590 } |
|
591 writeExtra(e.extra); |
|
592 if (commentBytes != null) { |
|
593 writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); |
|
594 } |
|
595 } |
|
596 |
|
597 /* |
|
598 * Writes end of central directory (END) header. |
|
599 */ |
|
600 private void writeEND(long off, long len) throws IOException { |
|
601 boolean hasZip64 = false; |
|
602 long xlen = len; |
|
603 long xoff = off; |
|
604 if (xlen >= ZIP64_MAGICVAL) { |
|
605 xlen = ZIP64_MAGICVAL; |
|
606 hasZip64 = true; |
|
607 } |
|
608 if (xoff >= ZIP64_MAGICVAL) { |
|
609 xoff = ZIP64_MAGICVAL; |
|
610 hasZip64 = true; |
|
611 } |
|
612 int count = xentries.size(); |
|
613 if (count >= ZIP64_MAGICCOUNT) { |
|
614 hasZip64 |= !inhibitZip64; |
|
615 if (hasZip64) { |
|
616 count = ZIP64_MAGICCOUNT; |
|
617 } |
|
618 } |
|
619 if (hasZip64) { |
|
620 long off64 = written; |
|
621 //zip64 end of central directory record |
|
622 writeInt(ZIP64_ENDSIG); // zip64 END record signature |
|
623 writeLong(ZIP64_ENDHDR - 12); // size of zip64 end |
|
624 writeShort(45); // version made by |
|
625 writeShort(45); // version needed to extract |
|
626 writeInt(0); // number of this disk |
|
627 writeInt(0); // central directory start disk |
|
628 writeLong(xentries.size()); // number of directory entires on disk |
|
629 writeLong(xentries.size()); // number of directory entires |
|
630 writeLong(len); // length of central directory |
|
631 writeLong(off); // offset of central directory |
|
632 |
|
633 //zip64 end of central directory locator |
|
634 writeInt(ZIP64_LOCSIG); // zip64 END locator signature |
|
635 writeInt(0); // zip64 END start disk |
|
636 writeLong(off64); // offset of zip64 END |
|
637 writeInt(1); // total number of disks (?) |
|
638 } |
|
639 writeInt(ENDSIG); // END record signature |
|
640 writeShort(0); // number of this disk |
|
641 writeShort(0); // central directory start disk |
|
642 writeShort(count); // number of directory entries on disk |
|
643 writeShort(count); // total number of directory entries |
|
644 writeInt(xlen); // length of central directory |
|
645 writeInt(xoff); // offset of central directory |
|
646 if (comment != null) { // zip file comment |
|
647 writeShort(comment.length); |
|
648 writeBytes(comment, 0, comment.length); |
|
649 } else { |
|
650 writeShort(0); |
|
651 } |
|
652 } |
|
653 |
|
654 /* |
|
655 * Returns the length of extra data without EXTT and ZIP64. |
|
656 */ |
|
657 private int getExtraLen(byte[] extra) { |
|
658 if (extra == null) |
|
659 return 0; |
|
660 int skipped = 0; |
|
661 int len = extra.length; |
|
662 int off = 0; |
|
663 while (off + 4 <= len) { |
|
664 int tag = get16(extra, off); |
|
665 int sz = get16(extra, off + 2); |
|
666 if (sz < 0 || (off + 4 + sz) > len) { |
|
667 break; |
|
668 } |
|
669 if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { |
|
670 skipped += (sz + 4); |
|
671 } |
|
672 off += (sz + 4); |
|
673 } |
|
674 return len - skipped; |
|
675 } |
|
676 |
|
677 /* |
|
678 * Writes extra data without EXTT and ZIP64. |
|
679 * |
|
680 * Extra timestamp and ZIP64 data is handled/output separately |
|
681 * in writeLOC and writeCEN. |
|
682 */ |
|
683 private void writeExtra(byte[] extra) throws IOException { |
|
684 if (extra != null) { |
|
685 int len = extra.length; |
|
686 int off = 0; |
|
687 while (off + 4 <= len) { |
|
688 int tag = get16(extra, off); |
|
689 int sz = get16(extra, off + 2); |
|
690 if (sz < 0 || (off + 4 + sz) > len) { |
|
691 writeBytes(extra, off, len - off); |
|
692 return; |
|
693 } |
|
694 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { |
|
695 writeBytes(extra, off, sz + 4); |
|
696 } |
|
697 off += (sz + 4); |
|
698 } |
|
699 if (off < len) { |
|
700 writeBytes(extra, off, len - off); |
|
701 } |
|
702 } |
|
703 } |
|
704 |
|
705 /* |
|
706 * Writes a 8-bit byte to the output stream. |
|
707 */ |
|
708 private void writeByte(int v) throws IOException { |
|
709 OutputStream out = this.out; |
|
710 out.write(v & 0xff); |
|
711 written += 1; |
|
712 } |
|
713 |
|
714 /* |
|
715 * Writes a 16-bit short to the output stream in little-endian byte order. |
|
716 */ |
|
717 private void writeShort(int v) throws IOException { |
|
718 OutputStream out = this.out; |
|
719 out.write((v >>> 0) & 0xff); |
|
720 out.write((v >>> 8) & 0xff); |
|
721 written += 2; |
|
722 } |
|
723 |
|
724 /* |
|
725 * Writes a 32-bit int to the output stream in little-endian byte order. |
|
726 */ |
|
727 private void writeInt(long v) throws IOException { |
|
728 OutputStream out = this.out; |
|
729 out.write((int)((v >>> 0) & 0xff)); |
|
730 out.write((int)((v >>> 8) & 0xff)); |
|
731 out.write((int)((v >>> 16) & 0xff)); |
|
732 out.write((int)((v >>> 24) & 0xff)); |
|
733 written += 4; |
|
734 } |
|
735 |
|
736 /* |
|
737 * Writes a 64-bit int to the output stream in little-endian byte order. |
|
738 */ |
|
739 private void writeLong(long v) throws IOException { |
|
740 OutputStream out = this.out; |
|
741 out.write((int)((v >>> 0) & 0xff)); |
|
742 out.write((int)((v >>> 8) & 0xff)); |
|
743 out.write((int)((v >>> 16) & 0xff)); |
|
744 out.write((int)((v >>> 24) & 0xff)); |
|
745 out.write((int)((v >>> 32) & 0xff)); |
|
746 out.write((int)((v >>> 40) & 0xff)); |
|
747 out.write((int)((v >>> 48) & 0xff)); |
|
748 out.write((int)((v >>> 56) & 0xff)); |
|
749 written += 8; |
|
750 } |
|
751 |
|
752 /* |
|
753 * Writes an array of bytes to the output stream. |
|
754 */ |
|
755 private void writeBytes(byte[] b, int off, int len) throws IOException { |
|
756 super.out.write(b, off, len); |
|
757 written += len; |
|
758 } |
|
759 } |