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. zip encryption will not
152 * work if this value set to null.
153 * @since 1.9
154 */
155 public ZipOutputStream(OutputStream out, ZipCryption zipCryption) {
156 this(out, StandardCharsets.UTF_8, zipCryption);
157 }
158
159 /**
160 * Creates a new ZIP output stream.
161 *
162 * @param out the actual output stream
163 *
164 * @param charset the {@linkplain java.nio.charset.Charset charset}
165 * to be used to encode the entry names and comments
166 *
167 * @since 1.7
168 */
169 public ZipOutputStream(OutputStream out, Charset charset) {
170 this(out, charset, null);
171 }
172
173 /**
174 * Creates a new ZIP output stream.
175 *
176 * @param out the actual output stream
177 *
178 * @param charset the {@linkplain java.nio.charset.Charset charset}
179 * to be used to encode the entry names and comments
180 *
181 * @param zipCryption ZIP encrypt/decrypt engine. zip encryption will not
182 * work if this value set to null.
183 * @since 1.9
184 */
185 public ZipOutputStream(OutputStream out,
186 Charset charset, ZipCryption zipCryption) {
187 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
188 if (charset == null)
189 throw new NullPointerException("charset is null");
190 this.zc = ZipCoder.get(charset);
191 usesDefaultDeflater = true;
192 this.zipCryption = zipCryption;
193 super.setZipCryption(zipCryption);
194 }
195
196 /**
197 * Sets the ZIP file comment.
198 * @param comment the comment string
199 * @exception IllegalArgumentException if the length of the specified
200 * ZIP file comment is greater than 0xFFFF bytes
201 */
202 public void setComment(String comment) {
203 if (comment != null) {
204 this.comment = zc.getBytes(comment);
205 if (this.comment.length > 0xffff)
206 throw new IllegalArgumentException("ZIP file comment too long.");
207 }
208 }
209
210 /**
211 * Sets the default compression method for subsequent entries. This
212 * default will be used whenever the compression method is not specified
213 * for an individual ZIP file entry, and is initially set to DEFLATED.
230 */
231 public void setLevel(int level) {
232 def.setLevel(level);
233 }
234
235 /**
236 * Begins writing a new ZIP file entry and positions the stream to the
237 * start of the entry data. Closes the current entry if still active.
238 * The default compression method will be used if no compression method
239 * was specified for the entry, and the current time will be used if
240 * the entry has no set modification time.
241 * @param e the ZIP entry to be written
242 * @exception ZipException if a ZIP format error has occurred
243 * @exception IOException if an I/O error has occurred
244 */
245 public void putNextEntry(ZipEntry e) throws IOException {
246 ensureOpen();
247 if (current != null) {
248 closeEntry(); // close previous entry
249 }
250 if (zipCryption != null) {
251 zipCryption.reset();
252 }
253 if (e.xdostime == -1) {
254 // by default, do NOT use extended timestamps in extra
255 // data, for now.
256 e.setTime(System.currentTimeMillis());
257 }
258 if (e.method == -1) {
259 e.method = method; // use default method
260 }
261 // store size, compressed size, and crc-32 in LOC header
262 e.flag = 0;
263 switch (e.method) {
264 case DEFLATED:
265 // store size, compressed size, and crc-32 in data descriptor
266 // immediately following the compressed entry data
267 if (e.size == -1 || e.csize == -1 || e.crc == -1)
268 e.flag = 8;
269
270 break;
271 case STORED:
272 // compressed size, uncompressed size, and crc-32 must all be
273 // set for entries using STORED compression method
274 if (e.size == -1) {
275 e.size = e.csize;
276 } else if (e.csize == -1) {
277 e.csize = e.size;
278 } else if (e.size != e.csize) {
279 throw new ZipException(
280 "STORED entry where compressed != uncompressed size");
281 }
282 if (e.size == -1 || e.crc == -1) {
283 throw new ZipException(
284 "STORED entry missing size, compressed size, or crc-32");
285 }
286 if (zipCryption != null) {
287 e.csize += zipCryption.getEncryptionHeaderSize();
288 }
289 break;
290 default:
291 throw new ZipException("unsupported compression method");
292 }
293 if (! names.add(e.name)) {
294 throw new ZipException("duplicate entry: " + e.name);
295 }
296 if (zc.isUTF8())
297 e.flag |= EFS;
298 if (zipCryption != null)
299 e.flag |= 1; // Bit 0: If set, indicates that the file is encrypted.
300 current = new XEntry(e, written);
301 xentries.add(current);
302 writeLOC(current);
303
304 if (zipCryption != null) {
305 byte[] encryptionHeader = zipCryption.getEncryptionHeader(e);
306 writeBytes(encryptionHeader, 0, encryptionHeader.length);
307 locoff += encryptionHeader.length;
308 }
309 }
310
311 /**
312 * Closes the current ZIP entry and positions the stream for writing
313 * the next entry.
314 * @exception ZipException if a ZIP format error has occurred
315 * @exception IOException if an I/O error has occurred
316 */
317 public void closeEntry() throws IOException {
318 ensureOpen();
319 if (current != null) {
320 ZipEntry e = current.entry;
321 switch (e.method) {
322 case DEFLATED:
323 def.finish();
324 while (!def.finished()) {
325 deflate();
326 }
327 if ((e.flag & 8) == 0) {
328 // verify size, compressed size, and crc-32 settings
333 }
334 if (e.csize != def.getBytesWritten()) {
335 throw new ZipException(
336 "invalid entry compressed size (expected " +
337 e.csize + " but got " + def.getBytesWritten() + " bytes)");
338 }
339 if (e.crc != crc.getValue()) {
340 throw new ZipException(
341 "invalid entry CRC-32 (expected 0x" +
342 Long.toHexString(e.crc) + " but got 0x" +
343 Long.toHexString(crc.getValue()) + ")");
344 }
345 } else {
346 e.size = def.getBytesRead();
347 e.csize = def.getBytesWritten();
348 e.crc = crc.getValue();
349 writeEXT(e);
350 }
351 def.reset();
352 written += e.csize;
353
354 if (zipCryption != null) {
355 /* Substruct sizeof encryption header.
356 * This value adds in writeBytes() when encryption header
357 * is written.
358 */
359 written -= zipCryption.getEncryptionHeaderSize();
360 }
361
362 break;
363 case STORED:
364 // we already know that both e.size and e.csize are the same
365 if (e.size != written - locoff) {
366 throw new ZipException(
367 "invalid entry size (expected " + e.size +
368 " but got " + (written - locoff) + " bytes)");
369 }
370 if (e.crc != crc.getValue()) {
371 throw new ZipException(
372 "invalid entry crc-32 (expected 0x" +
373 Long.toHexString(e.crc) + " but got 0x" +
374 Long.toHexString(crc.getValue()) + ")");
375 }
376 break;
377 default:
378 throw new ZipException("invalid compression method");
379 }
380 crc.reset();
381 current = null;
391 * @exception ZipException if a ZIP file error has occurred
392 * @exception IOException if an I/O error has occurred
393 */
394 public synchronized void write(byte[] b, int off, int len)
395 throws IOException
396 {
397 ensureOpen();
398 if (off < 0 || len < 0 || off > b.length - len) {
399 throw new IndexOutOfBoundsException();
400 } else if (len == 0) {
401 return;
402 }
403
404 if (current == null) {
405 throw new ZipException("no current ZIP entry");
406 }
407 ZipEntry entry = current.entry;
408 switch (entry.method) {
409 case DEFLATED:
410 super.write(b, off, len);
411 crc.update(b, off, len);
412 break;
413 case STORED:
414 written += len;
415 if (written - locoff > entry.size) {
416 throw new ZipException(
417 "attempt to write past end of STORED entry");
418 }
419
420 crc.update(b, off, len);
421
422 if (zipCryption != null) {
423 zipCryption.encryptBytes(b, off, len);
424 }
425
426 out.write(b, off, len);
427 break;
428 default:
429 throw new ZipException("invalid compression method");
430 }
431 }
432
433 /**
434 * Finishes writing the contents of the ZIP output stream without closing
435 * the underlying stream. Use this method when applying multiple filters
436 * in succession to the same output stream.
437 * @exception ZipException if a ZIP file error has occurred
438 * @exception IOException if an I/O exception has occurred
439 */
440 public void finish() throws IOException {
441 ensureOpen();
442 if (finished) {
443 return;
444 }
445 if (current != null) {
446 closeEntry();
447 }
448 // write central directory
449 long off = written;
450 for (XEntry xentry : xentries)
536 writeShort(EXTID_EXTT);
537 writeShort(elenEXTT + 1); // flag + data
538 writeByte(flagEXTT);
539 if (e.mtime != null)
540 writeInt(fileTimeToUnixTime(e.mtime));
541 if (e.atime != null)
542 writeInt(fileTimeToUnixTime(e.atime));
543 if (e.ctime != null)
544 writeInt(fileTimeToUnixTime(e.ctime));
545 }
546 writeExtra(e.extra);
547 locoff = written;
548 }
549
550 /*
551 * Writes extra data descriptor (EXT) for specified entry.
552 */
553 private void writeEXT(ZipEntry e) throws IOException {
554 writeInt(EXTSIG); // EXT header signature
555 writeInt(e.crc); // crc-32
556
557 if (zipCryption != null) {
558 e.csize += zipCryption.getEncryptionHeaderSize();
559 }
560
561 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
562 writeLong(e.csize);
563 writeLong(e.size);
564 } else {
565 writeInt(e.csize); // compressed size
566 writeInt(e.size); // uncompressed size
567 }
568 }
569
570 /*
571 * Write central directory (CEN) header for specified entry.
572 * REMIND: add support for file attributes
573 */
574 private void writeCEN(XEntry xentry) throws IOException {
575 ZipEntry e = xentry.entry;
576 int flag = e.flag;
577 int version = version(e);
578 long csize = e.csize;
579 long size = e.size;
580 long offset = xentry.offset;
|