1 /*
2 * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
124 * Creates a new ZIP output stream.
125 *
126 * @param out the actual output stream
127 *
128 * @param charset the {@linkplain java.nio.charset.Charset charset}
129 * to be used to encode the entry names and comments
130 *
131 * @since 1.7
132 */
133 public ZipOutputStream(OutputStream out, Charset charset) {
134 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
135 if (charset == null)
136 throw new NullPointerException("charset is null");
137 this.zc = ZipCoder.get(charset);
138 usesDefaultDeflater = true;
139 }
140
141 /**
142 * Sets the ZIP file comment.
143 * @param comment the comment string
144 * @exception IllegalArgumentException if the length of the specified
145 * ZIP file comment is greater than 0xFFFF bytes
146 */
147 public void setComment(String comment) {
148 if (comment != null) {
149 this.comment = zc.getBytes(comment);
150 if (this.comment.length > 0xffff)
151 throw new IllegalArgumentException("ZIP file comment too long.");
152 }
153 }
154
155 /**
156 * Sets the default compression method for subsequent entries. This
157 * default will be used whenever the compression method is not specified
158 * for an individual ZIP file entry, and is initially set to DEFLATED.
159 * @param method the default compression method
160 * @exception IllegalArgumentException if the specified compression method
161 * is invalid
162 */
163 public void setMethod(int method) {
164 if (method != DEFLATED && method != STORED) {
165 throw new IllegalArgumentException("invalid compression method");
166 }
167 this.method = method;
168 }
169
170 /**
171 * Sets the compression level for subsequent entries which are DEFLATED.
172 * The default setting is DEFAULT_COMPRESSION.
173 * @param level the compression level (0-9)
174 * @exception IllegalArgumentException if the compression level is invalid
175 */
176 public void setLevel(int level) {
177 def.setLevel(level);
178 }
179
180 /**
181 * Begins writing a new ZIP file entry and positions the stream to the
182 * start of the entry data. Closes the current entry if still active.
183 * The default compression method will be used if no compression method
184 * was specified for the entry, and the current time will be used if
185 * the entry has no set modification time.
186 * @param e the ZIP entry to be written
187 * @exception ZipException if a ZIP format error has occurred
188 * @exception IOException if an I/O error has occurred
189 */
190 public void putNextEntry(ZipEntry e) throws IOException {
191 ensureOpen();
192 if (current != null) {
193 closeEntry(); // close previous entry
194 }
195 if (e.xdostime == -1) {
196 // by default, do NOT use extended timestamps in extra
197 // data, for now.
198 e.setTime(System.currentTimeMillis());
199 }
200 if (e.method == -1) {
201 e.method = method; // use default method
202 }
203 // store size, compressed size, and crc-32 in LOC header
204 e.flag = 0;
205 switch (e.method) {
206 case DEFLATED:
207 // store size, compressed size, and crc-32 in data descriptor
208 // immediately following the compressed entry data
225 throw new ZipException(
226 "STORED entry missing size, compressed size, or crc-32");
227 }
228 break;
229 default:
230 throw new ZipException("unsupported compression method");
231 }
232 if (! names.add(e.name)) {
233 throw new ZipException("duplicate entry: " + e.name);
234 }
235 if (zc.isUTF8())
236 e.flag |= USE_UTF8;
237 current = new XEntry(e, written);
238 xentries.add(current);
239 writeLOC(current);
240 }
241
242 /**
243 * Closes the current ZIP entry and positions the stream for writing
244 * the next entry.
245 * @exception ZipException if a ZIP format error has occurred
246 * @exception IOException if an I/O error has occurred
247 */
248 public void closeEntry() throws IOException {
249 ensureOpen();
250 if (current != null) {
251 ZipEntry e = current.entry;
252 switch (e.method) {
253 case DEFLATED:
254 def.finish();
255 while (!def.finished()) {
256 deflate();
257 }
258 if ((e.flag & 8) == 0) {
259 // verify size, compressed size, and crc-32 settings
260 if (e.size != def.getBytesRead()) {
261 throw new ZipException(
262 "invalid entry size (expected " + e.size +
263 " but got " + def.getBytesRead() + " bytes)");
264 }
265 if (e.csize != def.getBytesWritten()) {
266 throw new ZipException(
293 throw new ZipException(
294 "invalid entry crc-32 (expected 0x" +
295 Long.toHexString(e.crc) + " but got 0x" +
296 Long.toHexString(crc.getValue()) + ")");
297 }
298 break;
299 default:
300 throw new ZipException("invalid compression method");
301 }
302 crc.reset();
303 current = null;
304 }
305 }
306
307 /**
308 * Writes an array of bytes to the current ZIP entry data. This method
309 * will block until all the bytes are written.
310 * @param b the data to be written
311 * @param off the start offset in the data
312 * @param len the number of bytes that are written
313 * @exception ZipException if a ZIP file error has occurred
314 * @exception IOException if an I/O error has occurred
315 */
316 public synchronized void write(byte[] b, int off, int len)
317 throws IOException
318 {
319 ensureOpen();
320 if (off < 0 || len < 0 || off > b.length - len) {
321 throw new IndexOutOfBoundsException();
322 } else if (len == 0) {
323 return;
324 }
325
326 if (current == null) {
327 throw new ZipException("no current ZIP entry");
328 }
329 ZipEntry entry = current.entry;
330 switch (entry.method) {
331 case DEFLATED:
332 super.write(b, off, len);
333 break;
334 case STORED:
335 written += len;
336 if (written - locoff > entry.size) {
337 throw new ZipException(
338 "attempt to write past end of STORED entry");
339 }
340 out.write(b, off, len);
341 break;
342 default:
343 throw new ZipException("invalid compression method");
344 }
345 crc.update(b, off, len);
346 }
347
348 /**
349 * Finishes writing the contents of the ZIP output stream without closing
350 * the underlying stream. Use this method when applying multiple filters
351 * in succession to the same output stream.
352 * @exception ZipException if a ZIP file error has occurred
353 * @exception IOException if an I/O exception has occurred
354 */
355 public void finish() throws IOException {
356 ensureOpen();
357 if (finished) {
358 return;
359 }
360 if (current != null) {
361 closeEntry();
362 }
363 // write central directory
364 long off = written;
365 for (XEntry xentry : xentries)
366 writeCEN(xentry);
367 writeEND(off, written - off);
368 finished = true;
369 }
370
371 /**
372 * Closes the ZIP output stream as well as the stream being filtered.
373 * @exception ZipException if a ZIP file error has occurred
374 * @exception IOException if an I/O error has occurred
375 */
376 public void close() throws IOException {
377 if (!closed) {
378 super.close();
379 closed = true;
380 }
381 }
382
383 /*
384 * Writes local file (LOC) header for specified entry.
385 */
386 private void writeLOC(XEntry xentry) throws IOException {
387 ZipEntry e = xentry.entry;
388 int flag = e.flag;
389 boolean hasZip64 = false;
390 int elen = getExtraLen(e.extra);
391
392 writeInt(LOCSIG); // LOC header signature
393 if ((flag & 8) == 8) {
394 writeShort(version(e)); // version needed to extract
|
1 /*
2 * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
124 * Creates a new ZIP output stream.
125 *
126 * @param out the actual output stream
127 *
128 * @param charset the {@linkplain java.nio.charset.Charset charset}
129 * to be used to encode the entry names and comments
130 *
131 * @since 1.7
132 */
133 public ZipOutputStream(OutputStream out, Charset charset) {
134 super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
135 if (charset == null)
136 throw new NullPointerException("charset is null");
137 this.zc = ZipCoder.get(charset);
138 usesDefaultDeflater = true;
139 }
140
141 /**
142 * Sets the ZIP file comment.
143 * @param comment the comment string
144 * @throws IllegalArgumentException if the length of the specified
145 * ZIP file comment is greater than 0xFFFF bytes
146 */
147 public void setComment(String comment) {
148 if (comment != null) {
149 this.comment = zc.getBytes(comment);
150 if (this.comment.length > 0xffff)
151 throw new IllegalArgumentException("ZIP file comment too long.");
152 }
153 }
154
155 /**
156 * Sets the default compression method for subsequent entries. This
157 * default will be used whenever the compression method is not specified
158 * for an individual ZIP file entry, and is initially set to DEFLATED.
159 * @param method the default compression method
160 * @throws IllegalArgumentException if the specified compression method
161 * is invalid
162 */
163 public void setMethod(int method) {
164 if (method != DEFLATED && method != STORED) {
165 throw new IllegalArgumentException("invalid compression method");
166 }
167 this.method = method;
168 }
169
170 /**
171 * Sets the compression level for subsequent entries which are DEFLATED.
172 * The default setting is DEFAULT_COMPRESSION.
173 * @param level the compression level (0-9)
174 * @throws IllegalArgumentException if the compression level is invalid
175 */
176 public void setLevel(int level) {
177 def.setLevel(level);
178 }
179
180 /**
181 * Begins writing a new ZIP file entry and positions the stream to the
182 * start of the entry data. Closes the current entry if still active.
183 * The default compression method will be used if no compression method
184 * was specified for the entry, and the current time will be used if
185 * the entry has no set modification time.
186 * @param e the ZIP entry to be written
187 * @throws ZipException if a ZIP format error has occurred
188 * @throws IOException if an I/O error has occurred
189 */
190 public void putNextEntry(ZipEntry e) throws IOException {
191 ensureOpen();
192 if (current != null) {
193 closeEntry(); // close previous entry
194 }
195 if (e.xdostime == -1) {
196 // by default, do NOT use extended timestamps in extra
197 // data, for now.
198 e.setTime(System.currentTimeMillis());
199 }
200 if (e.method == -1) {
201 e.method = method; // use default method
202 }
203 // store size, compressed size, and crc-32 in LOC header
204 e.flag = 0;
205 switch (e.method) {
206 case DEFLATED:
207 // store size, compressed size, and crc-32 in data descriptor
208 // immediately following the compressed entry data
225 throw new ZipException(
226 "STORED entry missing size, compressed size, or crc-32");
227 }
228 break;
229 default:
230 throw new ZipException("unsupported compression method");
231 }
232 if (! names.add(e.name)) {
233 throw new ZipException("duplicate entry: " + e.name);
234 }
235 if (zc.isUTF8())
236 e.flag |= USE_UTF8;
237 current = new XEntry(e, written);
238 xentries.add(current);
239 writeLOC(current);
240 }
241
242 /**
243 * Closes the current ZIP entry and positions the stream for writing
244 * the next entry.
245 * @throws ZipException if a ZIP format error has occurred
246 * @throws IOException if an I/O error has occurred
247 */
248 public void closeEntry() throws IOException {
249 ensureOpen();
250 if (current != null) {
251 ZipEntry e = current.entry;
252 switch (e.method) {
253 case DEFLATED:
254 def.finish();
255 while (!def.finished()) {
256 deflate();
257 }
258 if ((e.flag & 8) == 0) {
259 // verify size, compressed size, and crc-32 settings
260 if (e.size != def.getBytesRead()) {
261 throw new ZipException(
262 "invalid entry size (expected " + e.size +
263 " but got " + def.getBytesRead() + " bytes)");
264 }
265 if (e.csize != def.getBytesWritten()) {
266 throw new ZipException(
293 throw new ZipException(
294 "invalid entry crc-32 (expected 0x" +
295 Long.toHexString(e.crc) + " but got 0x" +
296 Long.toHexString(crc.getValue()) + ")");
297 }
298 break;
299 default:
300 throw new ZipException("invalid compression method");
301 }
302 crc.reset();
303 current = null;
304 }
305 }
306
307 /**
308 * Writes an array of bytes to the current ZIP entry data. This method
309 * will block until all the bytes are written.
310 * @param b the data to be written
311 * @param off the start offset in the data
312 * @param len the number of bytes that are written
313 * @throws ZipException if a ZIP file error has occurred
314 * @throws IOException if an I/O error has occurred
315 */
316 public synchronized void write(byte[] b, int off, int len)
317 throws IOException
318 {
319 ensureOpen();
320 if (off < 0 || len < 0 || off > b.length - len) {
321 throw new IndexOutOfBoundsException();
322 } else if (len == 0) {
323 return;
324 }
325
326 if (current == null) {
327 throw new ZipException("no current ZIP entry");
328 }
329 ZipEntry entry = current.entry;
330 switch (entry.method) {
331 case DEFLATED:
332 super.write(b, off, len);
333 break;
334 case STORED:
335 written += len;
336 if (written - locoff > entry.size) {
337 throw new ZipException(
338 "attempt to write past end of STORED entry");
339 }
340 out.write(b, off, len);
341 break;
342 default:
343 throw new ZipException("invalid compression method");
344 }
345 crc.update(b, off, len);
346 }
347
348 /**
349 * Finishes writing the contents of the ZIP output stream without closing
350 * the underlying stream. Use this method when applying multiple filters
351 * in succession to the same output stream.
352 * @throws ZipException if a ZIP file error has occurred
353 * @throws IOException if an I/O exception has occurred
354 */
355 public void finish() throws IOException {
356 ensureOpen();
357 if (finished) {
358 return;
359 }
360 if (current != null) {
361 closeEntry();
362 }
363 // write central directory
364 long off = written;
365 for (XEntry xentry : xentries)
366 writeCEN(xentry);
367 writeEND(off, written - off);
368 finished = true;
369 }
370
371 /**
372 * Closes the ZIP output stream as well as the stream being filtered.
373 * @throws ZipException if a ZIP file error has occurred
374 * @throws IOException if an I/O error has occurred
375 */
376 public void close() throws IOException {
377 if (!closed) {
378 super.close();
379 closed = true;
380 }
381 }
382
383 /*
384 * Writes local file (LOC) header for specified entry.
385 */
386 private void writeLOC(XEntry xentry) throws IOException {
387 ZipEntry e = xentry.entry;
388 int flag = e.flag;
389 boolean hasZip64 = false;
390 int elen = getExtraLen(e.extra);
391
392 writeInt(LOCSIG); // LOC header signature
393 if ((flag & 8) == 8) {
394 writeShort(version(e)); // version needed to extract
|