64 public XEntry(ZipEntry entry, long offset) {
65 this.entry = entry;
66 this.offset = offset;
67 }
68 }
69
70 private XEntry current;
71 private Vector<XEntry> xentries = new Vector<>();
72 private HashSet<String> names = new HashSet<>();
73 private CRC32 crc = new CRC32();
74 private long written = 0;
75 private long locoff = 0;
76 private byte[] comment;
77 private int method = DEFLATED;
78 private boolean finished;
79
80 private boolean closed = false;
81
82 private final ZipCoder zc;
83
84 private static int version(ZipEntry e) throws ZipException {
85 switch (e.method) {
86 case DEFLATED: return 20;
87 case STORED: return 10;
88 default: throw new ZipException("unsupported compression method");
89 }
90 }
91
92 /**
93 * Checks to make sure that this stream has not been closed.
94 */
95 private void ensureOpen() throws IOException {
96 if (closed) {
97 throw new IOException("Stream closed");
98 }
99 }
100 /**
101 * Compression method for uncompressed (STORED) entries.
102 */
103 public static final int STORED = ZipEntry.STORED;
104
105 /**
106 * Compression method for compressed (DEFLATED) entries.
107 */
108 public static final int DEFLATED = ZipEntry.DEFLATED;
109
110 /**
111 * Creates a new ZIP output stream.
112 *
113 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
114 * to encode the entry names and comments.
115 *
116 * @param out the actual output stream
117 */
118 public ZipOutputStream(OutputStream out) {
119 this(out, StandardCharsets.UTF_8);
120 }
121
122 /**
123 * Creates a new ZIP output stream.
124 *
125 * @param out the actual output stream
126 *
127 * @param charset the {@linkplain java.nio.charset.Charset charset}
128 * to be used to encode the entry names and comments
129 *
130 * @since 1.7
131 */
132 public ZipOutputStream(OutputStream out, Charset charset) {
133 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
134 if (charset == null)
135 throw new NullPointerException("charset is null");
136 this.zc = ZipCoder.get(charset);
137 usesDefaultDeflater = true;
138 }
139
140 /**
141 * Sets the ZIP file comment.
142 * @param comment the comment string
143 * @exception IllegalArgumentException if the length of the specified
144 * ZIP file comment is greater than 0xFFFF bytes
145 */
146 public void setComment(String comment) {
147 if (comment != null) {
148 this.comment = zc.getBytes(comment);
149 if (this.comment.length > 0xffff)
150 throw new IllegalArgumentException("ZIP file comment too long.");
151 }
152 }
153
154 /**
155 * Sets the default compression method for subsequent entries. This
156 * default will be used whenever the compression method is not specified
157 * for an individual ZIP file entry, and is initially set to DEFLATED.
174 */
175 public void setLevel(int level) {
176 def.setLevel(level);
177 }
178
179 /**
180 * Begins writing a new ZIP file entry and positions the stream to the
181 * start of the entry data. Closes the current entry if still active.
182 * The default compression method will be used if no compression method
183 * was specified for the entry, and the current time will be used if
184 * the entry has no set modification time.
185 * @param e the ZIP entry to be written
186 * @exception ZipException if a ZIP format error has occurred
187 * @exception IOException if an I/O error has occurred
188 */
189 public void putNextEntry(ZipEntry e) throws IOException {
190 ensureOpen();
191 if (current != null) {
192 closeEntry(); // close previous entry
193 }
194 if (e.xdostime == -1) {
195 // by default, do NOT use extended timestamps in extra
196 // data, for now.
197 e.setTime(System.currentTimeMillis());
198 }
199 if (e.method == -1) {
200 e.method = method; // use default method
201 }
202 // store size, compressed size, and crc-32 in LOC header
203 e.flag = 0;
204 switch (e.method) {
205 case DEFLATED:
206 // store size, compressed size, and crc-32 in data descriptor
207 // immediately following the compressed entry data
208 if (e.size == -1 || e.csize == -1 || e.crc == -1)
209 e.flag = 8;
210
211 break;
212 case STORED:
213 // compressed size, uncompressed size, and crc-32 must all be
214 // set for entries using STORED compression method
215 if (e.size == -1) {
216 e.size = e.csize;
217 } else if (e.csize == -1) {
218 e.csize = e.size;
219 } else if (e.size != e.csize) {
220 throw new ZipException(
221 "STORED entry where compressed != uncompressed size");
222 }
223 if (e.size == -1 || e.crc == -1) {
224 throw new ZipException(
225 "STORED entry missing size, compressed size, or crc-32");
226 }
227 break;
228 default:
229 throw new ZipException("unsupported compression method");
230 }
231 if (! names.add(e.name)) {
232 throw new ZipException("duplicate entry: " + e.name);
233 }
234 if (zc.isUTF8())
235 e.flag |= EFS;
236 current = new XEntry(e, written);
237 xentries.add(current);
238 writeLOC(current);
239 }
240
241 /**
242 * Closes the current ZIP entry and positions the stream for writing
243 * the next entry.
244 * @exception ZipException if a ZIP format error has occurred
245 * @exception IOException if an I/O error has occurred
246 */
247 public void closeEntry() throws IOException {
248 ensureOpen();
249 if (current != null) {
250 ZipEntry e = current.entry;
251 switch (e.method) {
252 case DEFLATED:
253 def.finish();
254 while (!def.finished()) {
255 deflate();
256 }
257 if ((e.flag & 8) == 0) {
258 // verify size, compressed size, and crc-32 settings
263 }
264 if (e.csize != def.getBytesWritten()) {
265 throw new ZipException(
266 "invalid entry compressed size (expected " +
267 e.csize + " but got " + def.getBytesWritten() + " bytes)");
268 }
269 if (e.crc != crc.getValue()) {
270 throw new ZipException(
271 "invalid entry CRC-32 (expected 0x" +
272 Long.toHexString(e.crc) + " but got 0x" +
273 Long.toHexString(crc.getValue()) + ")");
274 }
275 } else {
276 e.size = def.getBytesRead();
277 e.csize = def.getBytesWritten();
278 e.crc = crc.getValue();
279 writeEXT(e);
280 }
281 def.reset();
282 written += e.csize;
283 break;
284 case STORED:
285 // we already know that both e.size and e.csize are the same
286 if (e.size != written - locoff) {
287 throw new ZipException(
288 "invalid entry size (expected " + e.size +
289 " but got " + (written - locoff) + " bytes)");
290 }
291 if (e.crc != crc.getValue()) {
292 throw new ZipException(
293 "invalid entry crc-32 (expected 0x" +
294 Long.toHexString(e.crc) + " but got 0x" +
295 Long.toHexString(crc.getValue()) + ")");
296 }
297 break;
298 default:
299 throw new ZipException("invalid compression method");
300 }
301 crc.reset();
302 current = null;
312 * @exception ZipException if a ZIP file error has occurred
313 * @exception IOException if an I/O error has occurred
314 */
315 public synchronized void write(byte[] b, int off, int len)
316 throws IOException
317 {
318 ensureOpen();
319 if (off < 0 || len < 0 || off > b.length - len) {
320 throw new IndexOutOfBoundsException();
321 } else if (len == 0) {
322 return;
323 }
324
325 if (current == null) {
326 throw new ZipException("no current ZIP entry");
327 }
328 ZipEntry entry = current.entry;
329 switch (entry.method) {
330 case DEFLATED:
331 super.write(b, off, len);
332 break;
333 case STORED:
334 written += len;
335 if (written - locoff > entry.size) {
336 throw new ZipException(
337 "attempt to write past end of STORED entry");
338 }
339 out.write(b, off, len);
340 break;
341 default:
342 throw new ZipException("invalid compression method");
343 }
344 crc.update(b, off, len);
345 }
346
347 /**
348 * Finishes writing the contents of the ZIP output stream without closing
349 * the underlying stream. Use this method when applying multiple filters
350 * in succession to the same output stream.
351 * @exception ZipException if a ZIP file error has occurred
352 * @exception IOException if an I/O exception has occurred
353 */
354 public void finish() throws IOException {
355 ensureOpen();
356 if (finished) {
357 return;
358 }
359 if (current != null) {
360 closeEntry();
361 }
362 // write central directory
363 long off = written;
364 for (XEntry xentry : xentries)
450 writeShort(EXTID_EXTT);
451 writeShort(elenEXTT + 1); // flag + data
452 writeByte(flagEXTT);
453 if (e.mtime != null)
454 writeInt(fileTimeToUnixTime(e.mtime));
455 if (e.atime != null)
456 writeInt(fileTimeToUnixTime(e.atime));
457 if (e.ctime != null)
458 writeInt(fileTimeToUnixTime(e.ctime));
459 }
460 writeExtra(e.extra);
461 locoff = written;
462 }
463
464 /*
465 * Writes extra data descriptor (EXT) for specified entry.
466 */
467 private void writeEXT(ZipEntry e) throws IOException {
468 writeInt(EXTSIG); // EXT header signature
469 writeInt(e.crc); // crc-32
470 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
471 writeLong(e.csize);
472 writeLong(e.size);
473 } else {
474 writeInt(e.csize); // compressed size
475 writeInt(e.size); // uncompressed size
476 }
477 }
478
479 /*
480 * Write central directory (CEN) header for specified entry.
481 * REMIND: add support for file attributes
482 */
483 private void writeCEN(XEntry xentry) throws IOException {
484 ZipEntry e = xentry.entry;
485 int flag = e.flag;
486 int version = version(e);
487 long csize = e.csize;
488 long size = e.size;
489 long offset = xentry.offset;
|
64 public XEntry(ZipEntry entry, long offset) {
65 this.entry = entry;
66 this.offset = offset;
67 }
68 }
69
70 private XEntry current;
71 private Vector<XEntry> xentries = new Vector<>();
72 private HashSet<String> names = new HashSet<>();
73 private CRC32 crc = new CRC32();
74 private long written = 0;
75 private long locoff = 0;
76 private byte[] comment;
77 private int method = DEFLATED;
78 private boolean finished;
79
80 private boolean closed = false;
81
82 private final ZipCoder zc;
83
84 private ZipCryption zipCryption;
85
86 private int version(ZipEntry e) throws ZipException {
87 int result;
88
89 switch (e.method) {
90 case DEFLATED:
91 result = 20;
92 break;
93
94 case STORED:
95 result = 10;
96 break;
97
98 default:
99 throw new ZipException("unsupported compression method");
100 }
101
102 /*
103 * Zip Crypto is defined version 2.0 or later.
104 * 4.4.3.2 Current minimum feature versions
105 * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
106 */
107 if (zipCryption != null) {
108 result = 20;
109 }
110
111 return result;
112 }
113
114 /**
115 * Checks to make sure that this stream has not been closed.
116 */
117 private void ensureOpen() throws IOException {
118 if (closed) {
119 throw new IOException("Stream closed");
120 }
121 }
122 /**
123 * Compression method for uncompressed (STORED) entries.
124 */
125 public static final int STORED = ZipEntry.STORED;
126
127 /**
128 * Compression method for compressed (DEFLATED) entries.
129 */
130 public static final int DEFLATED = ZipEntry.DEFLATED;
131
132 /**
133 * Creates a new ZIP output stream.
134 *
135 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
136 * to encode the entry names and comments.
137 *
138 * @param out the actual output stream
139 */
140 public ZipOutputStream(OutputStream out) {
141 this(out, StandardCharsets.UTF_8, null);
142 }
143
144 /**
145 * Creates a new ZIP output stream.
146 *
147 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
148 * to encode the entry names and comments.
149 *
150 * @param out the actual output stream
151 * @param zipCryption ZIP encrypt/decrypt engine
152 */
153 public ZipOutputStream(OutputStream out, ZipCryption zipCryption) {
154 this(out, StandardCharsets.UTF_8, zipCryption);
155 }
156
157 /**
158 * Creates a new ZIP output stream.
159 *
160 * @param out the actual output stream
161 *
162 * @param charset the {@linkplain java.nio.charset.Charset charset}
163 * to be used to encode the entry names and comments
164 *
165 * @since 1.7
166 */
167 public ZipOutputStream(OutputStream out, Charset charset) {
168 this(out, charset, null);
169 }
170
171 /**
172 * Creates a new ZIP output stream.
173 *
174 * @param out the actual output stream
175 *
176 * @param charset the {@linkplain java.nio.charset.Charset charset}
177 * to be used to encode the entry names and comments
178 *
179 * @param zipCryption ZIP encrypt/decrypt engine
180 */
181 public ZipOutputStream(OutputStream out,
182 Charset charset, ZipCryption zipCryption) {
183 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
184 if (charset == null)
185 throw new NullPointerException("charset is null");
186 this.zc = ZipCoder.get(charset);
187 usesDefaultDeflater = true;
188 this.zipCryption = zipCryption;
189 super.setZipCryption(zipCryption);
190 }
191
192 /**
193 * Sets the ZIP file comment.
194 * @param comment the comment string
195 * @exception IllegalArgumentException if the length of the specified
196 * ZIP file comment is greater than 0xFFFF bytes
197 */
198 public void setComment(String comment) {
199 if (comment != null) {
200 this.comment = zc.getBytes(comment);
201 if (this.comment.length > 0xffff)
202 throw new IllegalArgumentException("ZIP file comment too long.");
203 }
204 }
205
206 /**
207 * Sets the default compression method for subsequent entries. This
208 * default will be used whenever the compression method is not specified
209 * for an individual ZIP file entry, and is initially set to DEFLATED.
226 */
227 public void setLevel(int level) {
228 def.setLevel(level);
229 }
230
231 /**
232 * Begins writing a new ZIP file entry and positions the stream to the
233 * start of the entry data. Closes the current entry if still active.
234 * The default compression method will be used if no compression method
235 * was specified for the entry, and the current time will be used if
236 * the entry has no set modification time.
237 * @param e the ZIP entry to be written
238 * @exception ZipException if a ZIP format error has occurred
239 * @exception IOException if an I/O error has occurred
240 */
241 public void putNextEntry(ZipEntry e) throws IOException {
242 ensureOpen();
243 if (current != null) {
244 closeEntry(); // close previous entry
245 }
246 if (zipCryption != null) {
247 zipCryption.reset();
248 }
249 if (e.xdostime == -1) {
250 // by default, do NOT use extended timestamps in extra
251 // data, for now.
252 e.setTime(System.currentTimeMillis());
253 }
254 if (e.method == -1) {
255 e.method = method; // use default method
256 }
257 // store size, compressed size, and crc-32 in LOC header
258 e.flag = 0;
259 switch (e.method) {
260 case DEFLATED:
261 // store size, compressed size, and crc-32 in data descriptor
262 // immediately following the compressed entry data
263 if (e.size == -1 || e.csize == -1 || e.crc == -1)
264 e.flag = 8;
265
266 break;
267 case STORED:
268 // compressed size, uncompressed size, and crc-32 must all be
269 // set for entries using STORED compression method
270 if (e.size == -1) {
271 e.size = e.csize;
272 } else if (e.csize == -1) {
273 e.csize = e.size;
274 } else if (e.size != e.csize) {
275 throw new ZipException(
276 "STORED entry where compressed != uncompressed size");
277 }
278 if (e.size == -1 || e.crc == -1) {
279 throw new ZipException(
280 "STORED entry missing size, compressed size, or crc-32");
281 }
282 if (zipCryption != null) {
283 e.csize += zipCryption.getEncryptionHeaderSize();
284 }
285 break;
286 default:
287 throw new ZipException("unsupported compression method");
288 }
289 if (! names.add(e.name)) {
290 throw new ZipException("duplicate entry: " + e.name);
291 }
292 if (zc.isUTF8())
293 e.flag |= EFS;
294 if (zipCryption != null)
295 e.flag |= 1; // Bit 0: If set, indicates that the file is encrypted.
296 current = new XEntry(e, written);
297 xentries.add(current);
298 writeLOC(current);
299
300 if (zipCryption != null) {
301 byte[] encryptionHeader = zipCryption.getEncryptionHeader(e);
302 writeBytes(encryptionHeader, 0, encryptionHeader.length);
303 locoff += encryptionHeader.length;
304 }
305 }
306
307 /**
308 * Closes the current ZIP entry and positions the stream for writing
309 * the next entry.
310 * @exception ZipException if a ZIP format error has occurred
311 * @exception IOException if an I/O error has occurred
312 */
313 public void closeEntry() throws IOException {
314 ensureOpen();
315 if (current != null) {
316 ZipEntry e = current.entry;
317 switch (e.method) {
318 case DEFLATED:
319 def.finish();
320 while (!def.finished()) {
321 deflate();
322 }
323 if ((e.flag & 8) == 0) {
324 // verify size, compressed size, and crc-32 settings
329 }
330 if (e.csize != def.getBytesWritten()) {
331 throw new ZipException(
332 "invalid entry compressed size (expected " +
333 e.csize + " but got " + def.getBytesWritten() + " bytes)");
334 }
335 if (e.crc != crc.getValue()) {
336 throw new ZipException(
337 "invalid entry CRC-32 (expected 0x" +
338 Long.toHexString(e.crc) + " but got 0x" +
339 Long.toHexString(crc.getValue()) + ")");
340 }
341 } else {
342 e.size = def.getBytesRead();
343 e.csize = def.getBytesWritten();
344 e.crc = crc.getValue();
345 writeEXT(e);
346 }
347 def.reset();
348 written += e.csize;
349
350 if (zipCryption != null) {
351 /* Substruct sizeof encryption header.
352 * This value adds in writeBytes() when encryption header
353 * is written.
354 */
355 written -= zipCryption.getEncryptionHeaderSize();
356 }
357
358 break;
359 case STORED:
360 // we already know that both e.size and e.csize are the same
361 if (e.size != written - locoff) {
362 throw new ZipException(
363 "invalid entry size (expected " + e.size +
364 " but got " + (written - locoff) + " bytes)");
365 }
366 if (e.crc != crc.getValue()) {
367 throw new ZipException(
368 "invalid entry crc-32 (expected 0x" +
369 Long.toHexString(e.crc) + " but got 0x" +
370 Long.toHexString(crc.getValue()) + ")");
371 }
372 break;
373 default:
374 throw new ZipException("invalid compression method");
375 }
376 crc.reset();
377 current = null;
387 * @exception ZipException if a ZIP file error has occurred
388 * @exception IOException if an I/O error has occurred
389 */
390 public synchronized void write(byte[] b, int off, int len)
391 throws IOException
392 {
393 ensureOpen();
394 if (off < 0 || len < 0 || off > b.length - len) {
395 throw new IndexOutOfBoundsException();
396 } else if (len == 0) {
397 return;
398 }
399
400 if (current == null) {
401 throw new ZipException("no current ZIP entry");
402 }
403 ZipEntry entry = current.entry;
404 switch (entry.method) {
405 case DEFLATED:
406 super.write(b, off, len);
407 crc.update(b, off, len);
408 break;
409 case STORED:
410 written += len;
411 if (written - locoff > entry.size) {
412 throw new ZipException(
413 "attempt to write past end of STORED entry");
414 }
415
416 crc.update(b, off, len);
417
418 if (zipCryption != null) {
419 zipCryption.encryptBytes(b, off, len);
420 }
421
422 out.write(b, off, len);
423 break;
424 default:
425 throw new ZipException("invalid compression method");
426 }
427 }
428
429 /**
430 * Finishes writing the contents of the ZIP output stream without closing
431 * the underlying stream. Use this method when applying multiple filters
432 * in succession to the same output stream.
433 * @exception ZipException if a ZIP file error has occurred
434 * @exception IOException if an I/O exception has occurred
435 */
436 public void finish() throws IOException {
437 ensureOpen();
438 if (finished) {
439 return;
440 }
441 if (current != null) {
442 closeEntry();
443 }
444 // write central directory
445 long off = written;
446 for (XEntry xentry : xentries)
532 writeShort(EXTID_EXTT);
533 writeShort(elenEXTT + 1); // flag + data
534 writeByte(flagEXTT);
535 if (e.mtime != null)
536 writeInt(fileTimeToUnixTime(e.mtime));
537 if (e.atime != null)
538 writeInt(fileTimeToUnixTime(e.atime));
539 if (e.ctime != null)
540 writeInt(fileTimeToUnixTime(e.ctime));
541 }
542 writeExtra(e.extra);
543 locoff = written;
544 }
545
546 /*
547 * Writes extra data descriptor (EXT) for specified entry.
548 */
549 private void writeEXT(ZipEntry e) throws IOException {
550 writeInt(EXTSIG); // EXT header signature
551 writeInt(e.crc); // crc-32
552
553 if (zipCryption != null) {
554 e.csize += zipCryption.getEncryptionHeaderSize();
555 }
556
557 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
558 writeLong(e.csize);
559 writeLong(e.size);
560 } else {
561 writeInt(e.csize); // compressed size
562 writeInt(e.size); // uncompressed size
563 }
564 }
565
566 /*
567 * Write central directory (CEN) header for specified entry.
568 * REMIND: add support for file attributes
569 */
570 private void writeCEN(XEntry xentry) throws IOException {
571 ZipEntry e = xentry.entry;
572 int flag = e.flag;
573 int version = version(e);
574 long csize = e.csize;
575 long size = e.size;
576 long offset = xentry.offset;
|