1
7
8 package java.util.zip;
9
10 import java.io.OutputStream;
11 import java.io.IOException;
12 import java.util.Vector;
13 import java.util.HashSet;
14
15
23 public
24 class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
25
26 private static class XEntry {
27 public final ZipEntry entry;
28 public final long offset;
29 public final int flag;
30 public XEntry(ZipEntry entry, long offset) {
31 this.entry = entry;
32 this.offset = offset;
33 this.flag = (entry.method == DEFLATED &&
34 (entry.size == -1 ||
35 entry.csize == -1 ||
36 entry.crc == -1))
37 ? 8
40 : 0;
42 }
43 }
44
45 private XEntry current;
46 private Vector<XEntry> xentries = new Vector<XEntry>();
47 private HashSet<String> names = new HashSet<String>();
48 private CRC32 crc = new CRC32();
49 private long written = 0;
50 private long locoff = 0;
51 private String comment;
52 private int method = DEFLATED;
53 private boolean finished;
54
55 private boolean closed = false;
56
57 private static int version(ZipEntry e) throws ZipException {
58 switch (e.method) {
59 case DEFLATED: return 20;
60 case STORED: return 10;
61 default: throw new ZipException("unsupported compression method");
62 }
63 }
64
65
68 private void ensureOpen() throws IOException {
69 if (closed) {
70 throw new IOException("Stream closed");
71 }
72 }
73
76 public static final int STORED = ZipEntry.STORED;
77
78
81 public static final int DEFLATED = ZipEntry.DEFLATED;
82
83
87 public ZipOutputStream(OutputStream out) {
88 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
89 usesDefaultDeflater = true;
90 }
91
92
98 public void setComment(String comment) {
99 if (comment != null && comment.length() > 0xffff/3
100 && getUTF8Length(comment) > 0xffff) {
101 throw new IllegalArgumentException("ZIP file comment too long.");
102 }
103 this.comment = comment;
104 }
105
106
114 public void setMethod(int method) {
115 if (method != DEFLATED && method != STORED) {
116 throw new IllegalArgumentException("invalid compression method");
117 }
118 this.method = method;
119 }
120
121
127 public void setLevel(int level) {
128 def.setLevel(level);
129 }
130
131
141 public void putNextEntry(ZipEntry e) throws IOException {
142 ensureOpen();
143 if (current != null) {
144 closeEntry(); }
146 if (e.time == -1) {
147 e.setTime(System.currentTimeMillis());
148 }
149 if (e.method == -1) {
150 e.method = method; }
152 switch (e.method) {
153 case DEFLATED:
154 break;
155 case STORED:
156 if (e.size == -1) {
159 e.size = e.csize;
160 } else if (e.csize == -1) {
161 e.csize = e.size;
162 } else if (e.size != e.csize) {
163 throw new ZipException(
164 "STORED entry where compressed != uncompressed size");
165 }
166 if (e.size == -1 || e.crc == -1) {
167 throw new ZipException(
168 "STORED entry missing size, compressed size, or crc-32");
169 }
170 break;
171 default:
172 throw new ZipException("unsupported compression method");
173 }
174 if (! names.add(e.name)) {
175 throw new ZipException("duplicate entry: " + e.name);
176 }
177 current = new XEntry(e, written);
178 xentries.add(current);
179 writeLOC(current);
180 }
181
182
188 public void closeEntry() throws IOException {
189 ensureOpen();
190 if (current != null) {
191 ZipEntry e = current.entry;
192 switch (e.method) {
193 case DEFLATED:
194 def.finish();
195 while (!def.finished()) {
196 deflate();
197 }
198 if ((current.flag & 8) == 0) {
199 if (e.size != def.getBytesRead()) {
201 throw new ZipException(
202 "invalid entry size (expected " + e.size +
203 " but got " + def.getBytesRead() + " bytes)");
204 }
205 if (e.csize != def.getBytesWritten()) {
206 throw new ZipException(
207 "invalid entry compressed size (expected " +
208 e.csize + " but got " + def.getBytesWritten() + " bytes)");
209 }
210 if (e.crc != crc.getValue()) {
211 throw new ZipException(
212 "invalid entry CRC-32 (expected 0x" +
213 Long.toHexString(e.crc) + " but got 0x" +
214 Long.toHexString(crc.getValue()) + ")");
215 }
216 } else {
217 e.size = def.getBytesRead();
218 e.csize = def.getBytesWritten();
219 e.crc = crc.getValue();
220 writeEXT(e);
221 }
222 def.reset();
223 written += e.csize;
224 break;
225 case STORED:
226 if (e.size != written - locoff) {
228 throw new ZipException(
229 "invalid entry size (expected " + e.size +
230 " but got " + (written - locoff) + " bytes)");
231 }
232 if (e.crc != crc.getValue()) {
233 throw new ZipException(
234 "invalid entry crc-32 (expected 0x" +
235 Long.toHexString(e.crc) + " but got 0x" +
236 Long.toHexString(crc.getValue()) + ")");
237 }
238 break;
239 default:
240 throw new ZipException("invalid compression method");
241 }
242 crc.reset();
243 current = null;
244 }
245 }
246
247
256 public synchronized void write(byte[] b, int off, int len)
257 throws IOException
258 {
259 ensureOpen();
260 if (off < 0 || len < 0 || off > b.length - len) {
261 throw new IndexOutOfBoundsException();
262 } else if (len == 0) {
263 return;
264 }
265
266 if (current == null) {
267 throw new ZipException("no current ZIP entry");
268 }
269 ZipEntry entry = current.entry;
270 switch (entry.method) {
271 case DEFLATED:
272 super.write(b, off, len);
273 break;
274 case STORED:
275 written += len;
276 if (written - locoff > entry.size) {
277 throw new ZipException(
278 "attempt to write past end of STORED entry");
279 }
280 out.write(b, off, len);
281 break;
282 default:
283 throw new ZipException("invalid compression method");
284 }
285 crc.update(b, off, len);
286 }
287
288
295 public void finish() throws IOException {
296 ensureOpen();
297 if (finished) {
298 return;
299 }
300 if (current != null) {
301 closeEntry();
302 }
303 if (xentries.size() < 1) {
304 throw new ZipException("ZIP file must have at least one entry");
305 }
306 long off = written;
308 for (XEntry xentry : xentries)
309 writeCEN(xentry);
310 writeEND(off, written - off);
311 finished = true;
312 }
313
314
319 public void close() throws IOException {
320 if (!closed) {
321 super.close();
322 closed = true;
323 }
324 }
325
326
329 private void writeLOC(XEntry xentry) throws IOException {
330 ZipEntry e = xentry.entry;
331 int flag = xentry.flag;
332 writeInt(LOCSIG); writeShort(version(e)); writeShort(flag); writeShort(e.method); writeInt(e.time); if ((flag & 8) == 8) {
338 writeInt(0);
341 writeInt(0);
342 writeInt(0);
343 } else {
344 writeInt(e.crc); writeInt(e.csize); writeInt(e.size); }
348 byte[] nameBytes = getUTF8Bytes(e.name);
349 writeShort(nameBytes.length);
350 writeShort(e.extra != null ? e.extra.length : 0);
351 writeBytes(nameBytes, 0, nameBytes.length);
352 if (e.extra != null) {
353 writeBytes(e.extra, 0, e.extra.length);
354 }
355 locoff = written;
356 }
357
358
361 private void writeEXT(ZipEntry e) throws IOException {
362 writeInt(EXTSIG); writeInt(e.crc); writeInt(e.csize); writeInt(e.size); }
367
368
372 private void writeCEN(XEntry xentry) throws IOException {
373 ZipEntry e = xentry.entry;
374 int flag = xentry.flag;
375 int version = version(e);
376 writeInt(CENSIG); writeShort(version); writeShort(version); writeShort(flag); writeShort(e.method); writeInt(e.time); writeInt(e.crc); writeInt(e.csize); writeInt(e.size); byte[] nameBytes = getUTF8Bytes(e.name);
386 writeShort(nameBytes.length);
387 writeShort(e.extra != null ? e.extra.length : 0);
388 byte[] commentBytes;
389 if (e.comment != null) {
390 commentBytes = getUTF8Bytes(e.comment);
391 writeShort(commentBytes.length);
392 } else {
393 commentBytes = null;
394 writeShort(0);
395 }
396 writeShort(0); writeShort(0); writeInt(0); writeInt(xentry.offset); writeBytes(nameBytes, 0, nameBytes.length);
401 if (e.extra != null) {
402 writeBytes(e.extra, 0, e.extra.length);
403 }
404 if (commentBytes != null) {
405 writeBytes(commentBytes, 0, commentBytes.length);
406 }
407 }
408
409
412 private void writeEND(long off, long len) throws IOException {
413 int count = xentries.size();
414 writeInt(ENDSIG); writeShort(0); writeShort(0); writeShort(count); writeShort(count); writeInt(len); writeInt(off); if (comment != null) { byte[] b = getUTF8Bytes(comment);
423 writeShort(b.length);
424 writeBytes(b, 0, b.length);
425 } else {
426 writeShort(0);
427 }
428 }
429
430
433 private void writeShort(int v) throws IOException {
434 OutputStream out = this.out;
435 out.write((v >>> 0) & 0xff);
436 out.write((v >>> 8) & 0xff);
437 written += 2;
438 }
439
440
443 private void writeInt(long v) throws IOException {
444 OutputStream out = this.out;
445 out.write((int)((v >>> 0) & 0xff));
446 out.write((int)((v >>> 8) & 0xff));
447 out.write((int)((v >>> 16) & 0xff));
448 out.write((int)((v >>> 24) & 0xff));
449 written += 4;
450 }
451
452
455 private void writeBytes(byte[] b, int off, int len) throws IOException {
456 super.out.write(b, off, len);
457 written += len;
458 }
459
460
463 static int getUTF8Length(String s) {
464 int count = 0;
465 for (int i = 0; i < s.length(); i++) {
466 char ch = s.charAt(i);
467 if (ch <= 0x7f) {
468 count++;
469 } else if (ch <= 0x7ff) {
470 count += 2;
471 } else {
472 count += 3;
473 }
474 }
475 return count;
476 }
477
478
482 private static byte[] getUTF8Bytes(String s) {
483 char[] c = s.toCharArray();
484 int len = c.length;
485 int count = 0;
487 for (int i = 0; i < len; i++) {
488 int ch = c[i];
489 if (ch <= 0x7f) {
490 count++;
491 } else if (ch <= 0x7ff) {
492 count += 2;
493 } else {
494 count += 3;
495 }
496 }
497 byte[] b = new byte[count];
499 int off = 0;
500 for (int i = 0; i < len; i++) {
501 int ch = c[i];
502 if (ch <= 0x7f) {
503 b[off++] = (byte)ch;
504 } else if (ch <= 0x7ff) {
505 b[off++] = (byte)((ch >> 6) | 0xc0);
506 b[off++] = (byte)((ch & 0x3f) | 0x80);
507 } else {
508 b[off++] = (byte)((ch >> 12) | 0xe0);
509 b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80);
510 b[off++] = (byte)((ch & 0x3f) | 0x80);
511 }
512 }
513 return b;
514 }
515 }
516