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
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.
158 * @param method the default compression method
159 * @exception IllegalArgumentException if the specified compression method
160 * is invalid
161 */
162 public void setMethod(int method) {
163 if (method != DEFLATED && method != STORED) {
164 throw new IllegalArgumentException("invalid compression method");
165 }
166 this.method = method;
167 }
168
169 /**
170 * Sets the compression level for subsequent entries which are DEFLATED.
171 * The default setting is DEFAULT_COMPRESSION.
172 * @param level the compression level (0-9)
173 * @exception IllegalArgumentException if the compression level is invalid
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
172 throw new IllegalArgumentException("ZIP file comment too long.");
173 }
174 }
175
176 /**
177 * Sets the default compression method for subsequent entries. This
178 * default will be used whenever the compression method is not specified
179 * for an individual ZIP file entry, and is initially set to DEFLATED.
180 * @param method the default compression method
181 * @exception IllegalArgumentException if the specified compression method
182 * is invalid
183 */
184 public void setMethod(int method) {
185 if (method != DEFLATED && method != STORED) {
186 throw new IllegalArgumentException("invalid compression method");
187 }
188 this.method = method;
189 }
190
191 /**
192 * The default setting is DEFAULT_COMPRESSION.
193 * Sets the compression level for subsequent entries which are DEFLATED.
194 * @param level the compression level (0-9)
195 * @exception IllegalArgumentException if the compression level is invalid
196 */
197 public void setLevel(int level) {
198 def.setLevel(level);
199 }
200
201 /**
202 * Begins writing a new ZIP file entry and positions the stream to the
203 * start of the entry data. Closes the current entry if still active.
204 * The default compression method will be used if no compression method
205 * was specified for the entry, and the current time will be used if
206 * the entry has no set modification time.
207 * @param e the ZIP entry to be written
208 * @exception ZipException if a ZIP format error has occurred
209 * @exception IOException if an I/O error has occurred
210 */
211 public void putNextEntry(ZipEntry e) throws IOException {
212 ensureOpen();
213 if (current != null) {
214 closeEntry(); // close previous entry
215 }
216 zipCryption = e.zipCryption;
217 super.setZipCryption(zipCryption);
218 if (zipCryption != null) {
219 zipCryption.reset();
220 }
221 if (e.xdostime == -1) {
222 // by default, do NOT use extended timestamps in extra
223 // data, for now.
224 e.setTime(System.currentTimeMillis());
225 }
226 if (e.method == -1) {
227 e.method = method; // use default method
228 }
229 // store size, compressed size, and crc-32 in LOC header
230 e.flag = 0;
231 switch (e.method) {
232 case DEFLATED:
233 // store size, compressed size, and crc-32 in data descriptor
234 // immediately following the compressed entry data
235 if (e.size == -1 || e.csize == -1 || e.crc == -1)
236 e.flag = 8;
237
238 break;
239 case STORED:
240 // compressed size, uncompressed size, and crc-32 must all be
241 // set for entries using STORED compression method
242 if (e.size == -1) {
243 e.size = e.csize;
244 } else if (e.csize == -1) {
245 e.csize = e.size;
246 } else if (e.size != e.csize) {
247 throw new ZipException(
248 "STORED entry where compressed != uncompressed size");
249 }
250 if (e.size == -1 || e.crc == -1) {
251 throw new ZipException(
252 "STORED entry missing size, compressed size, or crc-32");
253 }
254 if (zipCryption != null) {
255 e.csize += zipCryption.getEncryptionHeaderSize();
256 }
257 break;
258 default:
259 throw new ZipException("unsupported compression method");
260 }
261 if (! names.add(e.name)) {
262 throw new ZipException("duplicate entry: " + e.name);
263 }
264 if (zc.isUTF8())
265 e.flag |= EFS;
266 if (zipCryption != null)
267 e.flag |= 1; // Bit 0: If set, indicates that the file is encrypted.
268 current = new XEntry(e, written);
269 xentries.add(current);
270 writeLOC(current);
271
272 if (zipCryption != null) {
273 byte[] encryptionHeader = zipCryption.getEncryptionHeader(e);
274 writeBytes(encryptionHeader, 0, encryptionHeader.length);
275 locoff += encryptionHeader.length;
276 }
277 }
278
279 /**
280 * Closes the current ZIP entry and positions the stream for writing
281 * the next entry.
282 * @exception ZipException if a ZIP format error has occurred
283 * @exception IOException if an I/O error has occurred
284 */
285 public void closeEntry() throws IOException {
286 ensureOpen();
287 if (current != null) {
288 ZipEntry e = current.entry;
289 switch (e.method) {
290 case DEFLATED:
291 def.finish();
292 while (!def.finished()) {
293 deflate();
294 }
295 if ((e.flag & 8) == 0) {
296 // verify size, compressed size, and crc-32 settings
301 }
302 if (e.csize != def.getBytesWritten()) {
303 throw new ZipException(
304 "invalid entry compressed size (expected " +
305 e.csize + " but got " + def.getBytesWritten() + " bytes)");
306 }
307 if (e.crc != crc.getValue()) {
308 throw new ZipException(
309 "invalid entry CRC-32 (expected 0x" +
310 Long.toHexString(e.crc) + " but got 0x" +
311 Long.toHexString(crc.getValue()) + ")");
312 }
313 } else {
314 e.size = def.getBytesRead();
315 e.csize = def.getBytesWritten();
316 e.crc = crc.getValue();
317 writeEXT(e);
318 }
319 def.reset();
320 written += e.csize;
321
322 if (zipCryption != null) {
323 /* Substruct sizeof encryption header.
324 * This value adds in writeBytes() when encryption header
325 * is written.
326 */
327 written -= zipCryption.getEncryptionHeaderSize();
328 }
329
330 break;
331 case STORED:
332 // we already know that both e.size and e.csize are the same
333 if (e.size != written - locoff) {
334 throw new ZipException(
335 "invalid entry size (expected " + e.size +
336 " but got " + (written - locoff) + " bytes)");
337 }
338 if (e.crc != crc.getValue()) {
339 throw new ZipException(
340 "invalid entry crc-32 (expected 0x" +
341 Long.toHexString(e.crc) + " but got 0x" +
342 Long.toHexString(crc.getValue()) + ")");
343 }
344 break;
345 default:
346 throw new ZipException("invalid compression method");
347 }
348 crc.reset();
349 current = null;
359 * @exception ZipException if a ZIP file error has occurred
360 * @exception IOException if an I/O error has occurred
361 */
362 public synchronized void write(byte[] b, int off, int len)
363 throws IOException
364 {
365 ensureOpen();
366 if (off < 0 || len < 0 || off > b.length - len) {
367 throw new IndexOutOfBoundsException();
368 } else if (len == 0) {
369 return;
370 }
371
372 if (current == null) {
373 throw new ZipException("no current ZIP entry");
374 }
375 ZipEntry entry = current.entry;
376 switch (entry.method) {
377 case DEFLATED:
378 super.write(b, off, len);
379 crc.update(b, off, len);
380 break;
381 case STORED:
382 written += len;
383 if (written - locoff > entry.size) {
384 throw new ZipException(
385 "attempt to write past end of STORED entry");
386 }
387
388 crc.update(b, off, len);
389
390 if (zipCryption != null) {
391 zipCryption.encryptBytes(b, off, len);
392 }
393
394 out.write(b, off, len);
395 break;
396 default:
397 throw new ZipException("invalid compression method");
398 }
399 }
400
401 /**
402 * Finishes writing the contents of the ZIP output stream without closing
403 * the underlying stream. Use this method when applying multiple filters
404 * in succession to the same output stream.
405 * @exception ZipException if a ZIP file error has occurred
406 * @exception IOException if an I/O exception has occurred
407 */
408 public void finish() throws IOException {
409 ensureOpen();
410 if (finished) {
411 return;
412 }
413 if (current != null) {
414 closeEntry();
415 }
416 // write central directory
417 long off = written;
418 for (XEntry xentry : xentries)
504 writeShort(EXTID_EXTT);
505 writeShort(elenEXTT + 1); // flag + data
506 writeByte(flagEXTT);
507 if (e.mtime != null)
508 writeInt(fileTimeToUnixTime(e.mtime));
509 if (e.atime != null)
510 writeInt(fileTimeToUnixTime(e.atime));
511 if (e.ctime != null)
512 writeInt(fileTimeToUnixTime(e.ctime));
513 }
514 writeExtra(e.extra);
515 locoff = written;
516 }
517
518 /*
519 * Writes extra data descriptor (EXT) for specified entry.
520 */
521 private void writeEXT(ZipEntry e) throws IOException {
522 writeInt(EXTSIG); // EXT header signature
523 writeInt(e.crc); // crc-32
524
525 if (zipCryption != null) {
526 e.csize += zipCryption.getEncryptionHeaderSize();
527 }
528
529 if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
530 writeLong(e.csize);
531 writeLong(e.size);
532 } else {
533 writeInt(e.csize); // compressed size
534 writeInt(e.size); // uncompressed size
535 }
536 }
537
538 /*
539 * Write central directory (CEN) header for specified entry.
540 * REMIND: add support for file attributes
541 */
542 private void writeCEN(XEntry xentry) throws IOException {
543 ZipEntry e = xentry.entry;
544 int flag = e.flag;
545 int version = version(e);
546 long csize = e.csize;
547 long size = e.size;
548 long offset = xentry.offset;
|