blob: 8fab25c6990bf830852c5d252699ee27e1cf0a35 [file] [log] [blame]
Torsten Curdtca165392008-07-10 10:17:44 +00001/*
Torsten Curdtab9ebfc2009-01-12 11:15:34 +00002 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
Torsten Curdtca165392008-07-10 10:17:44 +00008 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +00009 * http://www.apache.org/licenses/LICENSE-2.0
Torsten Curdtca165392008-07-10 10:17:44 +000010 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000011 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
Torsten Curdtca165392008-07-10 10:17:44 +000017 */
18package org.apache.commons.compress.archivers.zip;
19
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000020import java.io.File;
21import java.io.FileOutputStream;
22import java.io.FilterOutputStream;
Torsten Curdtca165392008-07-10 10:17:44 +000023import java.io.IOException;
24import java.io.OutputStream;
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000025import java.io.RandomAccessFile;
Torsten Curdtca165392008-07-10 10:17:44 +000026import java.io.UnsupportedEncodingException;
Torsten Curdtca165392008-07-10 10:17:44 +000027import java.util.Date;
28import java.util.Hashtable;
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000029import java.util.Vector;
Torsten Curdtca165392008-07-10 10:17:44 +000030import java.util.zip.CRC32;
31import java.util.zip.Deflater;
Torsten Curdtca165392008-07-10 10:17:44 +000032import java.util.zip.ZipException;
33
34/**
35 * Reimplementation of {@link java.util.zip.ZipOutputStream
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000036 * java.util.zip.ZipOutputStream} that does handle the extended
37 * functionality of this package, especially internal/external file
38 * attributes and extra fields with different layouts for local file
39 * data and central directory entries.
Torsten Curdtca165392008-07-10 10:17:44 +000040 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000041 * <p>This class will try to use {@link java.io.RandomAccessFile
42 * RandomAccessFile} when you know that the output is going to go to a
43 * file.</p>
44 *
45 * <p>If RandomAccessFile cannot be used, this implementation will use
46 * a Data Descriptor to store size and CRC information for {@link
47 * #DEFLATED DEFLATED} entries, this means, you don't need to
48 * calculate them yourself. Unfortunately this is not possible for
49 * the {@link #STORED STORED} method, here setting the CRC and
50 * uncompressed size information is required before {@link
51 * #putNextEntry putNextEntry} can be called.</p>
52 *
Torsten Curdtca165392008-07-10 10:17:44 +000053 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +000054public class ZipOutputStream extends FilterOutputStream {
55
56 private static final int BYTE_MASK = 0xFF;
57 private static final int SHORT = 2;
58 private static final int WORD = 4;
59 private static final int BUFFER_SIZE = 512;
60 /*
61 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
62 * when it gets handed a really big buffer. See
63 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
64 *
65 * Using a buffer size of 8 kB proved to be a good compromise
66 */
67 private static final int DEFLATER_BLOCK_SIZE = 8192;
68
69 /**
70 * Compression method for deflated entries.
71 *
72 * @since 1.1
73 */
74 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
75
76 /**
77 * Default compression level for deflated entries.
78 *
79 * @since Ant 1.7
80 */
81 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
82
83 /**
84 * Compression method for stored entries.
85 *
86 * @since 1.1
87 */
88 public static final int STORED = java.util.zip.ZipEntry.STORED;
89
90 /**
91 * Current entry.
92 *
93 * @since 1.1
94 */
95 private ZipEntry entry;
96
97 /**
98 * The file comment.
99 *
100 * @since 1.1
101 */
102 private String comment = "";
103
104 /**
105 * Compression level for next entry.
106 *
107 * @since 1.1
108 */
109 private int level = DEFAULT_COMPRESSION;
110
111 /**
112 * Has the compression level changed when compared to the last
113 * entry?
114 *
115 * @since 1.5
116 */
117 private boolean hasCompressionLevelChanged = false;
118
119 /**
120 * Default compression method for next entry.
121 *
122 * @since 1.1
123 */
124 private int method = java.util.zip.ZipEntry.DEFLATED;
125
126 /**
127 * List of ZipEntries written so far.
128 *
129 * @since 1.1
130 */
131 private Vector entries = new Vector();
132
133 /**
134 * CRC instance to avoid parsing DEFLATED data twice.
135 *
136 * @since 1.1
137 */
138 private CRC32 crc = new CRC32();
139
140 /**
141 * Count the bytes written to out.
142 *
143 * @since 1.1
144 */
145 private long written = 0;
146
147 /**
148 * Data for local header data
149 *
150 * @since 1.1
151 */
152 private long dataStart = 0;
153
154 /**
155 * Offset for CRC entry in the local file header data for the
156 * current entry starts here.
157 *
158 * @since 1.15
159 */
160 private long localDataStart = 0;
161
162 /**
163 * Start of central directory.
164 *
165 * @since 1.1
166 */
167 private long cdOffset = 0;
168
169 /**
170 * Length of central directory.
171 *
172 * @since 1.1
173 */
174 private long cdLength = 0;
175
Torsten Curdtca165392008-07-10 10:17:44 +0000176 /**
177 * Helper, a 0 as ZipShort.
178 *
179 * @since 1.1
180 */
181 private static final byte[] ZERO = {0, 0};
182
183 /**
184 * Helper, a 0 as ZipLong.
185 *
186 * @since 1.1
187 */
188 private static final byte[] LZERO = {0, 0, 0, 0};
189
190 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000191 * Holds the offsets of the LFH starts for each entry.
Torsten Curdtca165392008-07-10 10:17:44 +0000192 *
193 * @since 1.1
194 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000195 private Hashtable offsets = new Hashtable();
Torsten Curdtca165392008-07-10 10:17:44 +0000196
197 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000198 * The encoding to use for filenames and the file comment.
Torsten Curdtca165392008-07-10 10:17:44 +0000199 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000200 * <p>For a list of possible values see <a
201 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
202 * Defaults to the platform's default character encoding.</p>
203 *
204 * @since 1.3
205 */
206 private String encoding = null;
207
208 // CheckStyle:VisibilityModifier OFF - bc
209
210 /**
211 * This Deflater object is used for output.
212 *
213 * <p>This attribute is only protected to provide a level of API
214 * backwards compatibility. This class used to extend {@link
215 * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
216 * Revision 1.13.</p>
217 *
218 * @since 1.14
219 */
220 protected Deflater def = new Deflater(level, true);
221
222 /**
223 * This buffer servers as a Deflater.
224 *
225 * <p>This attribute is only protected to provide a level of API
226 * backwards compatibility. This class used to extend {@link
227 * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
228 * Revision 1.13.</p>
229 *
230 * @since 1.14
231 */
232 protected byte[] buf = new byte[BUFFER_SIZE];
233
234 // CheckStyle:VisibilityModifier ON
235
236 /**
237 * Optional random access output.
238 *
239 * @since 1.14
240 */
241 private RandomAccessFile raf = null;
242
243 /**
244 * Creates a new ZIP OutputStream filtering the underlying stream.
245 * @param out the outputstream to zip
Torsten Curdtca165392008-07-10 10:17:44 +0000246 * @since 1.1
247 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000248 public ZipOutputStream(OutputStream out) {
249 super(out);
250 }
251
252 /**
253 * Creates a new ZIP OutputStream writing to a File. Will use
254 * random access if possible.
255 * @param file the file to zip to
256 * @since 1.14
257 * @throws IOException on error
258 */
259 public ZipOutputStream(File file) throws IOException {
260 super(null);
261
262 try {
263 raf = new RandomAccessFile(file, "rw");
264 raf.setLength(0);
265 } catch (IOException e) {
266 if (raf != null) {
267 try {
268 raf.close();
269 } catch (IOException inner) {
270 // ignore
271 }
272 raf = null;
273 }
274 out = new FileOutputStream(file);
275 }
276 }
277
278 /**
279 * This method indicates whether this archive is writing to a seekable stream (i.e., to a random
280 * access file).
281 *
282 * <p>For seekable streams, you don't need to calculate the CRC or
283 * uncompressed size for {@link #STORED} entries before
284 * invoking {@link #putNextEntry}.
285 * @return true if seekable
286 * @since 1.17
287 */
288 public boolean isSeekable() {
289 return raf != null;
290 }
291
292 /**
293 * The encoding to use for filenames and the file comment.
294 *
295 * <p>For a list of possible values see <a
296 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
297 * Defaults to the platform's default character encoding.</p>
298 * @param encoding the encoding value
299 * @since 1.3
300 */
301 public void setEncoding(String encoding) {
302 this.encoding = encoding;
303 }
304
305 /**
306 * The encoding to use for filenames and the file comment.
307 *
308 * @return null if using the platform's default character encoding.
309 *
310 * @since 1.3
311 */
312 public String getEncoding() {
313 return encoding;
314 }
315
316 /**
317 * Finishs writing the contents and closes this as well as the
318 * underlying stream.
319 *
320 * @since 1.1
321 * @throws IOException on error
322 */
323 public void finish() throws IOException {
324 closeEntry();
325 cdOffset = written;
326 for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
327 writeCentralFileHeader((ZipEntry) entries.elementAt(i));
328 }
329 cdLength = written - cdOffset;
330 writeCentralDirectoryEnd();
331 offsets.clear();
332 entries.removeAllElements();
333 }
334
335 /**
336 * Writes all necessary data for this entry.
337 *
338 * @since 1.1
339 * @throws IOException on error
340 */
341 public void closeEntry() throws IOException {
342 if (entry == null) {
343 return;
344 }
345
346 long realCrc = crc.getValue();
347 crc.reset();
348
349 if (entry.getMethod() == DEFLATED) {
350 def.finish();
351 while (!def.finished()) {
352 deflate();
353 }
354
355 entry.setSize(adjustToLong(def.getTotalIn()));
356 entry.setCompressedSize(adjustToLong(def.getTotalOut()));
357 entry.setCrc(realCrc);
358
359 def.reset();
360
361 written += entry.getCompressedSize();
362 } else if (raf == null) {
363 if (entry.getCrc() != realCrc) {
364 throw new ZipException("bad CRC checksum for entry "
365 + entry.getName() + ": "
366 + Long.toHexString(entry.getCrc())
367 + " instead of "
368 + Long.toHexString(realCrc));
369 }
370
371 if (entry.getSize() != written - dataStart) {
372 throw new ZipException("bad size for entry "
373 + entry.getName() + ": "
374 + entry.getSize()
375 + " instead of "
376 + (written - dataStart));
377 }
378 } else { /* method is STORED and we used RandomAccessFile */
379 long size = written - dataStart;
380
381 entry.setSize(size);
382 entry.setCompressedSize(size);
383 entry.setCrc(realCrc);
384 }
385
386 // If random access output, write the local file header containing
387 // the correct CRC and compressed/uncompressed sizes
388 if (raf != null) {
389 long save = raf.getFilePointer();
390
391 raf.seek(localDataStart);
392 writeOut(ZipLong.getBytes(entry.getCrc()));
393 writeOut(ZipLong.getBytes(entry.getCompressedSize()));
394 writeOut(ZipLong.getBytes(entry.getSize()));
395 raf.seek(save);
396 }
397
398 writeDataDescriptor(entry);
399 entry = null;
400 }
401
402 /**
403 * Begin writing next entry.
404 * @param ze the entry to write
405 * @since 1.1
406 * @throws IOException on error
407 */
408 public void putNextEntry(ZipEntry ze) throws IOException {
409 closeEntry();
410
411 entry = ze;
412 entries.addElement(entry);
413
414 if (entry.getMethod() == -1) { // not specified
415 entry.setMethod(method);
416 }
417
418 if (entry.getTime() == -1) { // not specified
419 entry.setTime(System.currentTimeMillis());
420 }
421
422 // Size/CRC not required if RandomAccessFile is used
423 if (entry.getMethod() == STORED && raf == null) {
424 if (entry.getSize() == -1) {
425 throw new ZipException("uncompressed size is required for"
426 + " STORED method when not writing to a"
427 + " file");
428 }
429 if (entry.getCrc() == -1) {
430 throw new ZipException("crc checksum is required for STORED"
431 + " method when not writing to a file");
432 }
433 entry.setCompressedSize(entry.getSize());
434 }
435
436 if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
437 def.setLevel(level);
438 hasCompressionLevelChanged = false;
439 }
440 writeLocalFileHeader(entry);
441 }
442
443 /**
444 * Set the file comment.
445 * @param comment the comment
446 * @since 1.1
447 */
448 public void setComment(String comment) {
449 this.comment = comment;
450 }
451
452 /**
453 * Sets the compression level for subsequent entries.
454 *
455 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
456 * @param level the compression level.
457 * @throws IllegalArgumentException if an invalid compression level is specified.
458 * @since 1.1
459 */
460 public void setLevel(int level) {
461 if (level < Deflater.DEFAULT_COMPRESSION
462 || level > Deflater.BEST_COMPRESSION) {
463 throw new IllegalArgumentException(
464 "Invalid compression level: " + level);
465 }
466 hasCompressionLevelChanged = (this.level != level);
467 this.level = level;
468 }
469
470 /**
471 * Sets the default compression method for subsequent entries.
472 *
473 * <p>Default is DEFLATED.</p>
474 * @param method an <code>int</code> from java.util.zip.ZipEntry
475 * @since 1.1
476 */
477 public void setMethod(int method) {
478 this.method = method;
479 }
480
481 /**
482 * Writes bytes to ZIP entry.
483 * @param b the byte array to write
484 * @param offset the start position to write from
485 * @param length the number of bytes to write
486 * @throws IOException on error
487 */
488 public void write(byte[] b, int offset, int length) throws IOException {
489 if (entry.getMethod() == DEFLATED) {
490 if (length > 0) {
491 if (!def.finished()) {
492 if (length <= DEFLATER_BLOCK_SIZE) {
493 def.setInput(b, offset, length);
494 deflateUntilInputIsNeeded();
495 } else {
496 final int fullblocks = length / DEFLATER_BLOCK_SIZE;
497 for (int i = 0; i < fullblocks; i++) {
498 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
499 DEFLATER_BLOCK_SIZE);
500 deflateUntilInputIsNeeded();
501 }
502 final int done = fullblocks * DEFLATER_BLOCK_SIZE;
503 if (done < length) {
504 def.setInput(b, offset + done, length - done);
505 deflateUntilInputIsNeeded();
506 }
507 }
508 }
509 }
510 } else {
511 writeOut(b, offset, length);
512 written += length;
513 }
514 crc.update(b, offset, length);
515 }
516
517 /**
518 * Writes a single byte to ZIP entry.
519 *
520 * <p>Delegates to the three arg method.</p>
521 * @param b the byte to write
522 * @since 1.14
523 * @throws IOException on error
524 */
525 public void write(int b) throws IOException {
526 byte[] buff = new byte[1];
527 buff[0] = (byte) (b & BYTE_MASK);
528 write(buff, 0, 1);
529 }
530
531 /**
532 * Closes this output stream and releases any system resources
533 * associated with the stream.
534 *
535 * @exception IOException if an I/O error occurs.
536 * @since 1.14
537 */
538 public void close() throws IOException {
539 finish();
540
541 if (raf != null) {
542 raf.close();
543 }
544 if (out != null) {
545 out.close();
546 }
547 }
548
549 /**
550 * Flushes this output stream and forces any buffered output bytes
551 * to be written out to the stream.
552 *
553 * @exception IOException if an I/O error occurs.
554 * @since 1.14
555 */
556 public void flush() throws IOException {
557 if (out != null) {
558 out.flush();
559 }
560 }
Torsten Curdtca165392008-07-10 10:17:44 +0000561
562 /*
563 * Various ZIP constants
564 */
565 /**
566 * local file header signature
567 *
568 * @since 1.1
569 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000570 protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);
Torsten Curdtca165392008-07-10 10:17:44 +0000571 /**
572 * data descriptor signature
573 *
574 * @since 1.1
575 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000576 protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
Torsten Curdtca165392008-07-10 10:17:44 +0000577 /**
578 * central file header signature
579 *
580 * @since 1.1
581 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000582 protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);
Torsten Curdtca165392008-07-10 10:17:44 +0000583 /**
584 * end of central dir signature
585 *
586 * @since 1.1
587 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000588 protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
589
590 /**
591 * Writes next block of compressed data to the output stream.
592 * @throws IOException on error
593 *
594 * @since 1.14
595 */
596 protected final void deflate() throws IOException {
597 int len = def.deflate(buf, 0, buf.length);
598 if (len > 0) {
599 writeOut(buf, 0, len);
600 }
601 }
602
603 /**
604 * Writes the local file header entry
605 * @param ze the entry to write
606 * @throws IOException on error
607 *
608 * @since 1.1
609 */
610 protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
611 offsets.put(ze, ZipLong.getBytes(written));
612
613 writeOut(LFH_SIG);
614 written += WORD;
615
616 //store method in local variable to prevent multiple method calls
617 final int zipMethod = ze.getMethod();
618
619 // version needed to extract
620 // general purpose bit flag
621 // CheckStyle:MagicNumber OFF
622 if (zipMethod == DEFLATED && raf == null) {
623 // requires version 2 as we are going to store length info
624 // in the data descriptor
625 writeOut(ZipShort.getBytes(20));
626
627 // bit3 set to signal, we use a data descriptor
628 writeOut(ZipShort.getBytes(8));
629 } else {
630 writeOut(ZipShort.getBytes(10));
631 writeOut(ZERO);
632 }
633 // CheckStyle:MagicNumber ON
634 written += WORD;
635
636 // compression method
637 writeOut(ZipShort.getBytes(zipMethod));
638 written += SHORT;
639
640 // last mod. time and date
641 writeOut(toDosTime(ze.getTime()));
642 written += WORD;
643
644 // CRC
645 // compressed length
646 // uncompressed length
647 localDataStart = written;
648 if (zipMethod == DEFLATED || raf != null) {
649 writeOut(LZERO);
650 writeOut(LZERO);
651 writeOut(LZERO);
652 } else {
653 writeOut(ZipLong.getBytes(ze.getCrc()));
654 writeOut(ZipLong.getBytes(ze.getSize()));
655 writeOut(ZipLong.getBytes(ze.getSize()));
656 }
657 // CheckStyle:MagicNumber OFF
658 written += 12;
659 // CheckStyle:MagicNumber ON
660
661 // file name length
662 byte[] name = getBytes(ze.getName());
663 writeOut(ZipShort.getBytes(name.length));
664 written += SHORT;
665
666 // extra field length
667 byte[] extra = ze.getLocalFileDataExtra();
668 writeOut(ZipShort.getBytes(extra.length));
669 written += SHORT;
670
671 // file name
672 writeOut(name);
673 written += name.length;
674
675 // extra field
676 writeOut(extra);
677 written += extra.length;
678
679 dataStart = written;
680 }
681
682 /**
683 * Writes the data descriptor entry.
684 * @param ze the entry to write
685 * @throws IOException on error
686 *
687 * @since 1.1
688 */
689 protected void writeDataDescriptor(ZipEntry ze) throws IOException {
690 if (ze.getMethod() != DEFLATED || raf != null) {
691 return;
692 }
693 writeOut(DD_SIG);
694 writeOut(ZipLong.getBytes(entry.getCrc()));
695 writeOut(ZipLong.getBytes(entry.getCompressedSize()));
696 writeOut(ZipLong.getBytes(entry.getSize()));
697 // CheckStyle:MagicNumber OFF
698 written += 16;
699 // CheckStyle:MagicNumber ON
700 }
701
702 /**
703 * Writes the central file header entry.
704 * @param ze the entry to write
705 * @throws IOException on error
706 *
707 * @since 1.1
708 */
709 protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
710 writeOut(CFH_SIG);
711 written += WORD;
712
713 // version made by
714 // CheckStyle:MagicNumber OFF
715 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
716 written += SHORT;
717
718 // version needed to extract
719 // general purpose bit flag
720 if (ze.getMethod() == DEFLATED && raf == null) {
721 // requires version 2 as we are going to store length info
722 // in the data descriptor
723 writeOut(ZipShort.getBytes(20));
724
725 // bit3 set to signal, we use a data descriptor
726 writeOut(ZipShort.getBytes(8));
727 } else {
728 writeOut(ZipShort.getBytes(10));
729 writeOut(ZERO);
730 }
731 // CheckStyle:MagicNumber ON
732 written += WORD;
733
734 // compression method
735 writeOut(ZipShort.getBytes(ze.getMethod()));
736 written += SHORT;
737
738 // last mod. time and date
739 writeOut(toDosTime(ze.getTime()));
740 written += WORD;
741
742 // CRC
743 // compressed length
744 // uncompressed length
745 writeOut(ZipLong.getBytes(ze.getCrc()));
746 writeOut(ZipLong.getBytes(ze.getCompressedSize()));
747 writeOut(ZipLong.getBytes(ze.getSize()));
748 // CheckStyle:MagicNumber OFF
749 written += 12;
750 // CheckStyle:MagicNumber ON
751
752 // file name length
753 byte[] name = getBytes(ze.getName());
754 writeOut(ZipShort.getBytes(name.length));
755 written += SHORT;
756
757 // extra field length
758 byte[] extra = ze.getCentralDirectoryExtra();
759 writeOut(ZipShort.getBytes(extra.length));
760 written += SHORT;
761
762 // file comment length
763 String comm = ze.getComment();
764 if (comm == null) {
765 comm = "";
766 }
767 byte[] commentB = getBytes(comm);
768 writeOut(ZipShort.getBytes(commentB.length));
769 written += SHORT;
770
771 // disk number start
772 writeOut(ZERO);
773 written += SHORT;
774
775 // internal file attributes
776 writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
777 written += SHORT;
778
779 // external file attributes
780 writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
781 written += WORD;
782
783 // relative offset of LFH
784 writeOut((byte[]) offsets.get(ze));
785 written += WORD;
786
787 // file name
788 writeOut(name);
789 written += name.length;
790
791 // extra field
792 writeOut(extra);
793 written += extra.length;
794
795 // file comment
796 writeOut(commentB);
797 written += commentB.length;
798 }
799
800 /**
801 * Writes the &quot;End of central dir record&quot;.
802 * @throws IOException on error
803 *
804 * @since 1.1
805 */
806 protected void writeCentralDirectoryEnd() throws IOException {
807 writeOut(EOCD_SIG);
808
809 // disk numbers
810 writeOut(ZERO);
811 writeOut(ZERO);
812
813 // number of entries
814 byte[] num = ZipShort.getBytes(entries.size());
815 writeOut(num);
816 writeOut(num);
817
818 // length and location of CD
819 writeOut(ZipLong.getBytes(cdLength));
820 writeOut(ZipLong.getBytes(cdOffset));
821
822 // ZIP file comment
823 byte[] data = getBytes(comment);
824 writeOut(ZipShort.getBytes(data.length));
825 writeOut(data);
826 }
Torsten Curdtca165392008-07-10 10:17:44 +0000827
828 /**
829 * Smallest date/time ZIP can handle.
830 *
831 * @since 1.1
832 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000833 private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
Torsten Curdtca165392008-07-10 10:17:44 +0000834
835 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000836 * Convert a Date object to a DOS date/time field.
837 * @param time the <code>Date</code> to convert
838 * @return the date as a <code>ZipLong</code>
Torsten Curdtca165392008-07-10 10:17:44 +0000839 * @since 1.1
840 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000841 protected static ZipLong toDosTime(Date time) {
842 return new ZipLong(toDosTime(time.getTime()));
Torsten Curdtca165392008-07-10 10:17:44 +0000843 }
844
845 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000846 * Convert a Date object to a DOS date/time field.
Torsten Curdtca165392008-07-10 10:17:44 +0000847 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000848 * <p>Stolen from InfoZip's <code>fileio.c</code></p>
849 * @param t number of milliseconds since the epoch
850 * @return the date as a byte array
851 * @since 1.26
Torsten Curdtca165392008-07-10 10:17:44 +0000852 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000853 protected static byte[] toDosTime(long t) {
854 Date time = new Date(t);
855 // CheckStyle:MagicNumberCheck OFF - I do not think that using constants
856 // here will improve the readablity
857 int year = time.getYear() + 1900;
858 if (year < 1980) {
Torsten Curdtca165392008-07-10 10:17:44 +0000859 return DOS_TIME_MIN;
860 }
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000861 int month = time.getMonth() + 1;
862 long value = ((year - 1980) << 25)
863 | (month << 21)
864 | (time.getDate() << 16)
865 | (time.getHours() << 11)
866 | (time.getMinutes() << 5)
867 | (time.getSeconds() >> 1);
868 return ZipLong.getBytes(value);
869 // CheckStyle:MagicNumberCheck ON
Torsten Curdtca165392008-07-10 10:17:44 +0000870 }
871
872 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000873 * Retrieve the bytes for the given String in the encoding set for
874 * this Stream.
875 * @param name the string to get bytes from
876 * @return the bytes as a byte array
877 * @throws ZipException on error
Torsten Curdtca165392008-07-10 10:17:44 +0000878 *
Torsten Curdtca165392008-07-10 10:17:44 +0000879 * @since 1.3
880 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000881 protected byte[] getBytes(String name) throws ZipException {
882 if (encoding == null) {
Torsten Curdtca165392008-07-10 10:17:44 +0000883 return name.getBytes();
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000884 } else {
885 try {
886 return name.getBytes(encoding);
887 } catch (UnsupportedEncodingException uee) {
888 throw new ZipException(uee.getMessage());
Torsten Curdtca165392008-07-10 10:17:44 +0000889 }
890 }
891 }
892
893 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000894 * Write bytes to output or random access file.
895 * @param data the byte array to write
896 * @throws IOException on error
Torsten Curdtca165392008-07-10 10:17:44 +0000897 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000898 * @since 1.14
Torsten Curdtca165392008-07-10 10:17:44 +0000899 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000900 protected final void writeOut(byte[] data) throws IOException {
901 writeOut(data, 0, data.length);
Torsten Curdtca165392008-07-10 10:17:44 +0000902 }
903
904 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000905 * Write bytes to output or random access file.
906 * @param data the byte array to write
907 * @param offset the start position to write from
908 * @param length the number of bytes to write
909 * @throws IOException on error
Torsten Curdtca165392008-07-10 10:17:44 +0000910 *
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000911 * @since 1.14
Torsten Curdtca165392008-07-10 10:17:44 +0000912 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000913 protected final void writeOut(byte[] data, int offset, int length)
914 throws IOException {
915 if (raf != null) {
916 raf.write(data, offset, length);
917 } else {
918 out.write(data, offset, length);
Torsten Curdtca165392008-07-10 10:17:44 +0000919 }
Torsten Curdtca165392008-07-10 10:17:44 +0000920 }
921
922 /**
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000923 * Assumes a negative integer really is a positive integer that
924 * has wrapped around and re-creates the original value.
925 * @param i the value to treat as unsigned int.
926 * @return the unsigned int as a long.
927 * @since 1.34
Torsten Curdtca165392008-07-10 10:17:44 +0000928 */
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000929 protected static long adjustToLong(int i) {
930 if (i < 0) {
931 return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
932 } else {
933 return i;
Torsten Curdtca165392008-07-10 10:17:44 +0000934 }
Torsten Curdtca165392008-07-10 10:17:44 +0000935 }
936
Torsten Curdtab9ebfc2009-01-12 11:15:34 +0000937 private void deflateUntilInputIsNeeded() throws IOException {
938 while (!def.needsInput()) {
939 deflate();
Torsten Curdtca165392008-07-10 10:17:44 +0000940 }
Torsten Curdtca165392008-07-10 10:17:44 +0000941 }
942
943}