You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by bo...@apache.org on 2012/06/04 18:35:07 UTC
svn commit: r1346025 [3/3] - in /ant/core/trunk: ./
src/main/org/apache/tools/zip/ src/tests/junit/org/apache/tools/ant/taskdefs/
src/tests/junit/org/apache/tools/zip/
Modified: ant/core/trunk/src/main/org/apache/tools/zip/ZipOutputStream.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/zip/ZipOutputStream.java?rev=1346025&r1=1346024&r2=1346025&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/zip/ZipOutputStream.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/zip/ZipOutputStream.java Mon Jun 4 16:35:06 2012
@@ -27,7 +27,6 @@ import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -35,6 +34,15 @@ import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.ZipException;
+import static org.apache.tools.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
+import static org.apache.tools.zip.ZipConstants.DWORD;
+import static org.apache.tools.zip.ZipConstants.INITIAL_VERSION;
+import static org.apache.tools.zip.ZipConstants.SHORT;
+import static org.apache.tools.zip.ZipConstants.WORD;
+import static org.apache.tools.zip.ZipConstants.ZIP64_MAGIC;
+import static org.apache.tools.zip.ZipConstants.ZIP64_MAGIC_SHORT;
+import static org.apache.tools.zip.ZipConstants.ZIP64_MIN_VERSION;
+
/**
* Reimplementation of {@link java.util.zip.ZipOutputStream
* java.util.zip.ZipOutputStream} that does handle the extended
@@ -54,13 +62,22 @@ import java.util.zip.ZipException;
* uncompressed size information is required before {@link
* #putNextEntry putNextEntry} can be called.</p>
*
+ * <p>As of Apache Ant 1.9.0 it transparently supports Zip64
+ * extensions and thus individual entries and archives larger than 4
+ * GB or with more than 65536 entries in most cases but explicit
+ * control is provided via {@link #setUseZip64}. If the stream can not
+ * user RandomAccessFile and you try to write a ZipEntry of
+ * unknown size then Zip64 extensions will be disabled by default.</p>
*/
public class ZipOutputStream extends FilterOutputStream {
- private static final int BYTE_MASK = 0xFF;
- private static final int SHORT = 2;
- private static final int WORD = 4;
private static final int BUFFER_SIZE = 512;
+
+ /**
+ * indicates if this archive is finished.
+ */
+ private boolean finished = false;
+
/*
* Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
* when it gets handed a really big buffer. See
@@ -99,22 +116,17 @@ public class ZipOutputStream extends Fil
/**
* General purpose flag, which indicates that filenames are
* written in utf-8.
+ * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead
*/
- public static final int UFT8_NAMES_FLAG = 1 << 11;
-
- /**
- * General purpose flag, which indicates that filenames are
- * written in utf-8.
- * @deprecated use {@link #UFT8_NAMES_FLAG} instead
- */
- public static final int EFS_FLAG = UFT8_NAMES_FLAG;
+ @Deprecated
+ public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
/**
* Current entry.
*
* @since 1.1
*/
- private ZipEntry entry;
+ private CurrentEntry entry;
/**
* The file comment.
@@ -150,7 +162,7 @@ public class ZipOutputStream extends Fil
*
* @since 1.1
*/
- private final List entries = new LinkedList();
+ private final List<ZipEntry> entries = new LinkedList<ZipEntry>();
/**
* CRC instance to avoid parsing DEFLATED data twice.
@@ -167,21 +179,6 @@ public class ZipOutputStream extends Fil
private long written = 0;
/**
- * Data for local header data
- *
- * @since 1.1
- */
- private long dataStart = 0;
-
- /**
- * Offset for CRC entry in the local file header data for the
- * current entry starts here.
- *
- * @since 1.15
- */
- private long localDataStart = 0;
-
- /**
* Start of central directory.
*
* @since 1.1
@@ -214,7 +211,7 @@ public class ZipOutputStream extends Fil
*
* @since 1.1
*/
- private final Map offsets = new HashMap();
+ private final Map<ZipEntry, Long> offsets = new HashMap<ZipEntry, Long>();
/**
* The encoding to use for filenames and the file comment.
@@ -241,17 +238,11 @@ public class ZipOutputStream extends Fil
/**
* This Deflater object is used for output.
*
- * <p>This attribute is only protected to provide a level of API
- * backwards compatibility. This class used to extend {@link
- * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
- * Revision 1.13.</p>
- *
- * @since 1.14
*/
- protected Deflater def = new Deflater(level, true);
+ protected final Deflater def = new Deflater(level, true);
/**
- * This buffer servers as a Deflater.
+ * This buffer serves as a Deflater.
*
* <p>This attribute is only protected to provide a level of API
* backwards compatibility. This class used to extend {@link
@@ -269,7 +260,7 @@ public class ZipOutputStream extends Fil
*
* @since 1.14
*/
- private RandomAccessFile raf = null;
+ private final RandomAccessFile raf;
/**
* whether to use the general purpose bit flag when writing UTF-8
@@ -285,8 +276,14 @@ public class ZipOutputStream extends Fil
/**
* whether to create UnicodePathExtraField-s for each entry.
*/
- private UnicodeExtraFieldPolicy createUnicodeExtraFields =
- UnicodeExtraFieldPolicy.NEVER;
+ private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
+
+ /**
+ * Whether anything inside this archive has used a ZIP64 feature.
+ */
+ private boolean hasUsedZip64 = false;
+
+ private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;
/**
* Creates a new ZIP OutputStream filtering the underlying stream.
@@ -295,6 +292,7 @@ public class ZipOutputStream extends Fil
*/
public ZipOutputStream(OutputStream out) {
super(out);
+ this.raf = null;
}
/**
@@ -306,21 +304,22 @@ public class ZipOutputStream extends Fil
*/
public ZipOutputStream(File file) throws IOException {
super(null);
-
+ RandomAccessFile _raf = null;
try {
- raf = new RandomAccessFile(file, "rw");
- raf.setLength(0);
+ _raf = new RandomAccessFile(file, "rw");
+ _raf.setLength(0);
} catch (IOException e) {
- if (raf != null) {
+ if (_raf != null) {
try {
- raf.close();
- } catch (IOException inner) {
+ _raf.close();
+ } catch (IOException inner) { // NOPMD
// ignore
}
- raf = null;
+ _raf = null;
}
out = new FileOutputStream(file);
}
+ raf = _raf;
}
/**
@@ -349,7 +348,9 @@ public class ZipOutputStream extends Fil
public void setEncoding(final String encoding) {
this.encoding = encoding;
this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
- useUTF8Flag = useUTF8Flag && ZipEncodingHelper.isUTF8(encoding);
+ if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
+ useUTF8Flag = false;
+ }
}
/**
@@ -393,23 +394,79 @@ public class ZipOutputStream extends Fil
}
/**
- * Finishs writing the contents and closes this as well as the
- * underlying stream.
+ * Whether Zip64 extensions will be used.
*
- * @since 1.1
- * @throws IOException on error
+ * <p>When setting the mode to {@link Zip64Mode#Never Never},
+ * {@link #putNextEntry}, {@link #closeEntry}, {@link
+ * #finish} or {@link #close} may throw a {@link
+ * Zip64RequiredException} if the entry's size or the total size
+ * of the archive exceeds 4GB or there are more than 65536 entries
+ * inside the archive. Any archive created in this mode will be
+ * readable by implementations that don't support Zip64.</p>
+ *
+ * <p>When setting the mode to {@link Zip64Mode#Always Always},
+ * Zip64 extensions will be used for all entries. Any archive
+ * created in this mode may be unreadable by implementations that
+ * don't support Zip64 even if all its contents would be.</p>
+ *
+ * <p>When setting the mode to {@link Zip64Mode#AsNeeded
+ * AsNeeded}, Zip64 extensions will transparently be used for
+ * those entries that require them. This mode can only be used if
+ * the uncompressed size of the {@link ZipEntry} is known
+ * when calling {@link #putNextEntry} or the archive is written
+ * to a seekable output (i.e. you have used the {@link
+ * #ZipOutputStream(java.io.File) File-arg constructor}) -
+ * this mode is not valid when the output stream is not seekable
+ * and the uncompressed size is unknown when {@link
+ * #putNextEntry} is called.</p>
+ *
+ * <p>If no entry inside the resulting archive requires Zip64
+ * extensions then {@link Zip64Mode#Never Never} will create the
+ * smallest archive. {@link Zip64Mode#AsNeeded AsNeeded} will
+ * create a slightly bigger archive if the uncompressed size of
+ * any entry has initially been unknown and create an archive
+ * identical to {@link Zip64Mode#Never Never} otherwise. {@link
+ * Zip64Mode#Always Always} will create an archive that is at
+ * least 24 bytes per entry bigger than the one {@link
+ * Zip64Mode#Never Never} would create.</p>
+ *
+ * <p>Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless
+ * {@link #putNextEntry} is called with an entry of unknown
+ * size and data is written to a non-seekable stream - in this
+ * case the default is {@link Zip64Mode#Never Never}.</p>
+ *
+ * @since 1.3
+ */
+ public void setUseZip64(Zip64Mode mode) {
+ zip64Mode = mode;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws Zip64RequiredException if the archive's size exceeds 4
+ * GByte or there are more than 65535 entries inside the archive
+ * and {@link #setUseZip64} is {@link Zip64Mode#Never}.
*/
public void finish() throws IOException {
- closeEntry();
+ if (finished) {
+ throw new IOException("This archive has already been finished");
+ }
+
+ if (entry != null) {
+ closeEntry();
+ }
+
cdOffset = written;
- for (Iterator i = entries.iterator(); i.hasNext(); ) {
- writeCentralFileHeader((ZipEntry) i.next());
+ for (ZipEntry ze : entries) {
+ writeCentralFileHeader(ze);
}
cdLength = written - cdOffset;
+ writeZip64CentralDirectory();
writeCentralDirectoryEnd();
offsets.clear();
entries.clear();
def.end();
+ finished = true;
}
/**
@@ -417,80 +474,206 @@ public class ZipOutputStream extends Fil
*
* @since 1.1
* @throws IOException on error
+ * @throws Zip64RequiredException if the entry's uncompressed or
+ * compressed size exceeds 4 GByte and {@link #setUseZip64}
+ * is {@link Zip64Mode#Never}.
*/
public void closeEntry() throws IOException {
+ if (finished) {
+ throw new IOException("Stream has already been finished");
+ }
+
if (entry == null) {
- return;
+ throw new IOException("No current entry to close");
+ }
+
+ if (!entry.hasWritten) {
+ write(new byte[0], 0, 0);
}
+ flushDeflater();
+
+ final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
+ long bytesWritten = written - entry.dataStart;
long realCrc = crc.getValue();
crc.reset();
- if (entry.getMethod() == DEFLATED) {
+ final boolean actuallyNeedsZip64 =
+ handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
+
+ if (raf != null) {
+ rewriteSizesAndCrc(actuallyNeedsZip64);
+ }
+
+ writeDataDescriptor(entry.entry);
+ entry = null;
+ }
+
+ /**
+ * Ensures all bytes sent to the deflater are written to the stream.
+ */
+ private void flushDeflater() throws IOException {
+ if (entry.entry.getMethod() == DEFLATED) {
def.finish();
while (!def.finished()) {
deflate();
}
+ }
+ }
- entry.setSize(adjustToLong(def.getTotalIn()));
- entry.setCompressedSize(adjustToLong(def.getTotalOut()));
- entry.setCrc(realCrc);
+ /**
+ * Ensures the current entry's size and CRC information is set to
+ * the values just written, verifies it isn't too big in the
+ * Zip64Mode.Never case and returns whether the entry would
+ * require a Zip64 extra field.
+ */
+ private boolean handleSizesAndCrc(long bytesWritten, long crc,
+ Zip64Mode effectiveMode)
+ throws ZipException {
+ if (entry.entry.getMethod() == DEFLATED) {
+ /* It turns out def.getBytesRead() returns wrong values if
+ * the size exceeds 4 GB on Java < Java7
+ entry.entry.setSize(def.getBytesRead());
+ */
+ entry.entry.setSize(entry.bytesRead);
+ entry.entry.setCompressedSize(bytesWritten);
+ entry.entry.setCrc(crc);
def.reset();
-
- written += entry.getCompressedSize();
} else if (raf == null) {
- if (entry.getCrc() != realCrc) {
+ if (entry.entry.getCrc() != crc) {
throw new ZipException("bad CRC checksum for entry "
- + entry.getName() + ": "
- + Long.toHexString(entry.getCrc())
+ + entry.entry.getName() + ": "
+ + Long.toHexString(entry.entry.getCrc())
+ " instead of "
- + Long.toHexString(realCrc));
+ + Long.toHexString(crc));
}
- if (entry.getSize() != written - dataStart) {
+ if (entry.entry.getSize() != bytesWritten) {
throw new ZipException("bad size for entry "
- + entry.getName() + ": "
- + entry.getSize()
+ + entry.entry.getName() + ": "
+ + entry.entry.getSize()
+ " instead of "
- + (written - dataStart));
+ + bytesWritten);
}
} else { /* method is STORED and we used RandomAccessFile */
- long size = written - dataStart;
+ entry.entry.setSize(bytesWritten);
+ entry.entry.setCompressedSize(bytesWritten);
+ entry.entry.setCrc(crc);
+ }
- entry.setSize(size);
- entry.setCompressedSize(size);
- entry.setCrc(realCrc);
+ final boolean actuallyNeedsZip64 = effectiveMode == Zip64Mode.Always
+ || entry.entry.getSize() >= ZIP64_MAGIC
+ || entry.entry.getCompressedSize() >= ZIP64_MAGIC;
+ if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
+ throw new Zip64RequiredException(Zip64RequiredException
+ .getEntryTooBigMessage(entry.entry));
}
+ return actuallyNeedsZip64;
+ }
- // If random access output, write the local file header containing
- // the correct CRC and compressed/uncompressed sizes
- if (raf != null) {
- long save = raf.getFilePointer();
+ /**
+ * When using random access output, write the local file header
+ * and potentiall the ZIP64 extra containing the correct CRC and
+ * compressed/uncompressed sizes.
+ */
+ private void rewriteSizesAndCrc(boolean actuallyNeedsZip64)
+ throws IOException {
+ long save = raf.getFilePointer();
- raf.seek(localDataStart);
- writeOut(ZipLong.getBytes(entry.getCrc()));
- writeOut(ZipLong.getBytes(entry.getCompressedSize()));
- writeOut(ZipLong.getBytes(entry.getSize()));
- raf.seek(save);
+ raf.seek(entry.localDataStart);
+ writeOut(ZipLong.getBytes(entry.entry.getCrc()));
+ if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
+ writeOut(ZipLong.getBytes(entry.entry.getCompressedSize()));
+ writeOut(ZipLong.getBytes(entry.entry.getSize()));
+ } else {
+ writeOut(ZipLong.ZIP64_MAGIC.getBytes());
+ writeOut(ZipLong.ZIP64_MAGIC.getBytes());
}
- writeDataDescriptor(entry);
- entry = null;
+ if (hasZip64Extra(entry.entry)) {
+ // seek to ZIP64 extra, skip header and size information
+ raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT
+ + getName(entry.entry).limit() + 2 * SHORT);
+ // inside the ZIP64 extra uncompressed size comes
+ // first, unlike the LFH, CD or data descriptor
+ writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize()));
+ writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()));
+
+ if (!actuallyNeedsZip64) {
+ // do some cleanup:
+ // * rewrite version needed to extract
+ raf.seek(entry.localDataStart - 5 * SHORT);
+ writeOut(ZipShort.getBytes(INITIAL_VERSION));
+
+ // * remove ZIP64 extra so it doesn't get written
+ // to the central directory
+ entry.entry.removeExtraField(Zip64ExtendedInformationExtraField
+ .HEADER_ID);
+ entry.entry.setExtra();
+
+ // * reset hasUsedZip64 if it has been set because
+ // of this entry
+ if (entry.causedUseOfZip64) {
+ hasUsedZip64 = false;
+ }
+ }
+ }
+ raf.seek(save);
}
/**
- * Begin writing next entry.
- * @param ze the entry to write
- * @since 1.1
- * @throws IOException on error
+ * {@inheritDoc}
+ * @throws Zip64RequiredException if the entry's uncompressed or
+ * compressed size is known to exceed 4 GByte and {@link #setUseZip64}
+ * is {@link Zip64Mode#Never}.
*/
- public void putNextEntry(ZipEntry ze) throws IOException {
- closeEntry();
+ public void putNextEntry(ZipEntry archiveEntry) throws IOException {
+ if (finished) {
+ throw new IOException("Stream has already been finished");
+ }
+
+ if (entry != null) {
+ closeEntry();
+ }
+
+ entry = new CurrentEntry(archiveEntry);
+ entries.add(entry.entry);
- entry = ze;
- entries.add(entry);
+ setDefaults(entry.entry);
+
+ final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
+ validateSizeInformation(effectiveMode);
+
+ if (shouldAddZip64Extra(entry.entry, effectiveMode)) {
+
+ Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);
+
+ // just a placeholder, real data will be in data
+ // descriptor or inserted later via RandomAccessFile
+ ZipEightByteInteger size = ZipEightByteInteger.ZERO;
+ if (entry.entry.getMethod() == STORED
+ && entry.entry.getSize() != -1) {
+ // actually, we already know the sizes
+ size = new ZipEightByteInteger(entry.entry.getSize());
+ }
+ z64.setSize(size);
+ z64.setCompressedSize(size);
+ entry.entry.setExtra();
+ }
+
+ if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
+ def.setLevel(level);
+ hasCompressionLevelChanged = false;
+ }
+ writeLocalFileHeader(entry.entry);
+ }
+ /**
+ * Provides default values for compression method and last
+ * modification time.
+ */
+ private void setDefaults(ZipEntry entry) {
if (entry.getMethod() == -1) { // not specified
entry.setMethod(method);
}
@@ -498,32 +681,63 @@ public class ZipOutputStream extends Fil
if (entry.getTime() == -1) { // not specified
entry.setTime(System.currentTimeMillis());
}
+ }
+ /**
+ * Throws an exception if the size is unknown for a stored entry
+ * that is written to a non-seekable output or the entry is too
+ * big to be written without Zip64 extra but the mode has been set
+ * to Never.
+ */
+ private void validateSizeInformation(Zip64Mode effectiveMode)
+ throws ZipException {
// Size/CRC not required if RandomAccessFile is used
- if (entry.getMethod() == STORED && raf == null) {
- if (entry.getSize() == -1) {
+ if (entry.entry.getMethod() == STORED && raf == null) {
+ if (entry.entry.getSize() == -1) {
throw new ZipException("uncompressed size is required for"
+ " STORED method when not writing to a"
+ " file");
}
- if (entry.getCrc() == -1) {
+ if (entry.entry.getCrc() == -1) {
throw new ZipException("crc checksum is required for STORED"
+ " method when not writing to a file");
}
- entry.setCompressedSize(entry.getSize());
+ entry.entry.setCompressedSize(entry.entry.getSize());
}
- if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
- def.setLevel(level);
- hasCompressionLevelChanged = false;
+ if ((entry.entry.getSize() >= ZIP64_MAGIC
+ || entry.entry.getCompressedSize() >= ZIP64_MAGIC)
+ && effectiveMode == Zip64Mode.Never) {
+ throw new Zip64RequiredException(Zip64RequiredException
+ .getEntryTooBigMessage(entry.entry));
}
- writeLocalFileHeader(entry);
+ }
+
+ /**
+ * Whether to addd a Zip64 extended information extra field to the
+ * local file header.
+ *
+ * <p>Returns true if</p>
+ *
+ * <ul>
+ * <li>mode is Always</li>
+ * <li>or we already know it is going to be needed</li>
+ * <li>or the size is unknown and we can ensure it won't hurt
+ * other implementations if we add it (i.e. we can erase its
+ * usage</li>
+ * </ul>
+ */
+ private boolean shouldAddZip64Extra(ZipEntry entry, Zip64Mode mode) {
+ return mode == Zip64Mode.Always
+ || entry.getSize() >= ZIP64_MAGIC
+ || entry.getCompressedSize() >= ZIP64_MAGIC
+ || (entry.getSize() == -1
+ && raf != null && mode != Zip64Mode.Never);
}
/**
* Set the file comment.
* @param comment the comment
- * @since 1.1
*/
public void setComment(String comment) {
this.comment = comment;
@@ -560,34 +774,28 @@ public class ZipOutputStream extends Fil
}
/**
+ * Whether this stream is able to write the given entry.
+ *
+ * <p>May return false if it is set up to use encryption or a
+ * compression method that hasn't been implemented yet.</p>
+ */
+ public boolean canWriteEntryData(ZipEntry ae) {
+ return ZipUtil.canHandleEntryData(ae);
+ }
+
+ /**
* Writes bytes to ZIP entry.
* @param b the byte array to write
* @param offset the start position to write from
* @param length the number of bytes to write
* @throws IOException on error
*/
+ @Override
public void write(byte[] b, int offset, int length) throws IOException {
- if (entry.getMethod() == DEFLATED) {
- if (length > 0) {
- if (!def.finished()) {
- if (length <= DEFLATER_BLOCK_SIZE) {
- def.setInput(b, offset, length);
- deflateUntilInputIsNeeded();
- } else {
- final int fullblocks = length / DEFLATER_BLOCK_SIZE;
- for (int i = 0; i < fullblocks; i++) {
- def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
- DEFLATER_BLOCK_SIZE);
- deflateUntilInputIsNeeded();
- }
- final int done = fullblocks * DEFLATER_BLOCK_SIZE;
- if (done < length) {
- def.setInput(b, offset + done, length - done);
- deflateUntilInputIsNeeded();
- }
- }
- }
- }
+ ZipUtil.checkRequestedFeatures(entry.entry);
+ entry.hasWritten = true;
+ if (entry.entry.getMethod() == DEFLATED) {
+ writeDeflated(b, offset, length);
} else {
writeOut(b, offset, length);
written += length;
@@ -596,17 +804,29 @@ public class ZipOutputStream extends Fil
}
/**
- * Writes a single byte to ZIP entry.
- *
- * <p>Delegates to the three arg method.</p>
- * @param b the byte to write
- * @since 1.14
- * @throws IOException on error
+ * write implementation for DEFLATED entries.
*/
- public void write(int b) throws IOException {
- byte[] buff = new byte[1];
- buff[0] = (byte) (b & BYTE_MASK);
- write(buff, 0, 1);
+ private void writeDeflated(byte[]b, int offset, int length)
+ throws IOException {
+ if (length > 0 && !def.finished()) {
+ entry.bytesRead += length;
+ if (length <= DEFLATER_BLOCK_SIZE) {
+ def.setInput(b, offset, length);
+ deflateUntilInputIsNeeded();
+ } else {
+ final int fullblocks = length / DEFLATER_BLOCK_SIZE;
+ for (int i = 0; i < fullblocks; i++) {
+ def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
+ DEFLATER_BLOCK_SIZE);
+ deflateUntilInputIsNeeded();
+ }
+ final int done = fullblocks * DEFLATER_BLOCK_SIZE;
+ if (done < length) {
+ def.setInput(b, offset + done, length - done);
+ deflateUntilInputIsNeeded();
+ }
+ }
+ }
}
/**
@@ -614,17 +834,16 @@ public class ZipOutputStream extends Fil
* associated with the stream.
*
* @exception IOException if an I/O error occurs.
- * @since 1.14
+ * @throws Zip64RequiredException if the archive's size exceeds 4
+ * GByte or there are more than 65535 entries inside the archive
+ * and {@link #setUseZip64} is {@link Zip64Mode#Never}.
*/
+ @Override
public void close() throws IOException {
- finish();
-
- if (raf != null) {
- raf.close();
- }
- if (out != null) {
- out.close();
+ if (!finished) {
+ finish();
}
+ destroy();
}
/**
@@ -632,8 +851,8 @@ public class ZipOutputStream extends Fil
* to be written out to the stream.
*
* @exception IOException if an I/O error occurs.
- * @since 1.14
*/
+ @Override
public void flush() throws IOException {
if (out != null) {
out.flush();
@@ -648,25 +867,33 @@ public class ZipOutputStream extends Fil
*
* @since 1.1
*/
- protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);
+ protected static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
/**
* data descriptor signature
*
* @since 1.1
*/
- protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
+ protected static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
/**
* central file header signature
*
* @since 1.1
*/
- protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);
+ protected static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
/**
* end of central dir signature
*
* @since 1.1
*/
protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
+ /**
+ * ZIP64 end of central dir signature
+ */
+ static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L);
+ /**
+ * ZIP64 end of central dir locator signature
+ */
+ static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L);
/**
* Writes next block of compressed data to the output stream.
@@ -678,6 +905,7 @@ public class ZipOutputStream extends Fil
int len = def.deflate(buf, 0, buf.length);
if (len > 0) {
writeOut(buf, 0, len);
+ written += len;
}
}
@@ -691,45 +919,13 @@ public class ZipOutputStream extends Fil
protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
boolean encodable = zipEncoding.canEncode(ze.getName());
-
- final ZipEncoding entryEncoding;
-
- if (!encodable && fallbackToUTF8) {
- entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
- } else {
- entryEncoding = zipEncoding;
- }
-
- ByteBuffer name = entryEncoding.encode(ze.getName());
+ ByteBuffer name = getName(ze);
if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
-
- if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
- || !encodable) {
- ze.addExtraField(new UnicodePathExtraField(ze.getName(),
- name.array(),
- name.arrayOffset(),
- name.limit()));
- }
-
- String comm = ze.getComment();
- if (comm != null && !"".equals(comm)) {
-
- boolean commentEncodable = this.zipEncoding.canEncode(comm);
-
- if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
- || !commentEncodable) {
- ByteBuffer commentB = entryEncoding.encode(comm);
- ze.addExtraField(new UnicodeCommentExtraField(comm,
- commentB.array(),
- commentB.arrayOffset(),
- commentB.limit())
- );
- }
- }
+ addUnicodeExtraFields(ze, encodable, name);
}
- offsets.put(ze, ZipLong.getBytes(written));
+ offsets.put(ze, Long.valueOf(written));
writeOut(LFH_SIG);
written += WORD;
@@ -739,7 +935,8 @@ public class ZipOutputStream extends Fil
writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
!encodable
- && fallbackToUTF8);
+ && fallbackToUTF8,
+ hasZip64Extra(ze));
written += WORD;
// compression method
@@ -747,21 +944,33 @@ public class ZipOutputStream extends Fil
written += SHORT;
// last mod. time and date
- writeOut(toDosTime(ze.getTime()));
+ writeOut(ZipUtil.toDosTime(ze.getTime()));
written += WORD;
// CRC
// compressed length
// uncompressed length
- localDataStart = written;
+ entry.localDataStart = written;
if (zipMethod == DEFLATED || raf != null) {
writeOut(LZERO);
- writeOut(LZERO);
- writeOut(LZERO);
+ if (hasZip64Extra(entry.entry)) {
+ // point to ZIP64 extended information extra field for
+ // sizes, may get rewritten once sizes are known if
+ // stream is seekable
+ writeOut(ZipLong.ZIP64_MAGIC.getBytes());
+ writeOut(ZipLong.ZIP64_MAGIC.getBytes());
+ } else {
+ writeOut(LZERO);
+ writeOut(LZERO);
+ }
} else {
writeOut(ZipLong.getBytes(ze.getCrc()));
- writeOut(ZipLong.getBytes(ze.getSize()));
- writeOut(ZipLong.getBytes(ze.getSize()));
+ byte[] size = ZipLong.ZIP64_MAGIC.getBytes();
+ if (!hasZip64Extra(ze)) {
+ size = ZipLong.getBytes(ze.getSize());
+ }
+ writeOut(size);
+ writeOut(size);
}
// CheckStyle:MagicNumber OFF
written += 12;
@@ -784,7 +993,40 @@ public class ZipOutputStream extends Fil
writeOut(extra);
written += extra.length;
- dataStart = written;
+ entry.dataStart = written;
+ }
+
+ /**
+ * Adds UnicodeExtra fields for name and file comment if mode is
+ * ALWAYS or the data cannot be encoded using the configured
+ * encoding.
+ */
+ private void addUnicodeExtraFields(ZipEntry ze, boolean encodable,
+ ByteBuffer name)
+ throws IOException {
+ if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
+ || !encodable) {
+ ze.addExtraField(new UnicodePathExtraField(ze.getName(),
+ name.array(),
+ name.arrayOffset(),
+ name.limit()));
+ }
+
+ String comm = ze.getComment();
+ if (comm != null && !"".equals(comm)) {
+
+ boolean commentEncodable = zipEncoding.canEncode(comm);
+
+ if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
+ || !commentEncodable) {
+ ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
+ ze.addExtraField(new UnicodeCommentExtraField(comm,
+ commentB.array(),
+ commentB.arrayOffset(),
+ commentB.limit())
+ );
+ }
+ }
}
/**
@@ -799,35 +1041,60 @@ public class ZipOutputStream extends Fil
return;
}
writeOut(DD_SIG);
- writeOut(ZipLong.getBytes(entry.getCrc()));
- writeOut(ZipLong.getBytes(entry.getCompressedSize()));
- writeOut(ZipLong.getBytes(entry.getSize()));
- // CheckStyle:MagicNumber OFF
- written += 16;
- // CheckStyle:MagicNumber ON
+ writeOut(ZipLong.getBytes(ze.getCrc()));
+ int sizeFieldSize = WORD;
+ if (!hasZip64Extra(ze)) {
+ writeOut(ZipLong.getBytes(ze.getCompressedSize()));
+ writeOut(ZipLong.getBytes(ze.getSize()));
+ } else {
+ sizeFieldSize = DWORD;
+ writeOut(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
+ writeOut(ZipEightByteInteger.getBytes(ze.getSize()));
+ }
+ written += 2 * WORD + 2 * sizeFieldSize;
}
/**
* Writes the central file header entry.
* @param ze the entry to write
* @throws IOException on error
- *
- * @since 1.1
+ * @throws Zip64RequiredException if the archive's size exceeds 4
+ * GByte and {@link Zip64Mode #setUseZip64} is {@link
+ * Zip64Mode#Never}.
*/
protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
writeOut(CFH_SIG);
written += WORD;
+ final long lfhOffset = offsets.get(ze).longValue();
+ final boolean needsZip64Extra = hasZip64Extra(ze)
+ || ze.getCompressedSize() >= ZIP64_MAGIC
+ || ze.getSize() >= ZIP64_MAGIC
+ || lfhOffset >= ZIP64_MAGIC;
+
+ if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
+ // must be the offset that is too big, otherwise an
+ // exception would have been throw in putNextEntry or
+ // closeEntry
+ throw new Zip64RequiredException(Zip64RequiredException
+ .ARCHIVE_TOO_BIG_MESSAGE);
+ }
+
+ handleZip64Extra(ze, lfhOffset, needsZip64Extra);
+
// version made by
// CheckStyle:MagicNumber OFF
- writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
+ writeOut(ZipShort.getBytes((ze.getPlatform() << 8) |
+ (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION
+ : ZIP64_MIN_VERSION)));
written += SHORT;
final int zipMethod = ze.getMethod();
final boolean encodable = zipEncoding.canEncode(ze.getName());
writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
!encodable
- && fallbackToUTF8);
+ && fallbackToUTF8,
+ needsZip64Extra);
written += WORD;
// compression method
@@ -835,29 +1102,26 @@ public class ZipOutputStream extends Fil
written += SHORT;
// last mod. time and date
- writeOut(toDosTime(ze.getTime()));
+ writeOut(ZipUtil.toDosTime(ze.getTime()));
written += WORD;
// CRC
// compressed length
// uncompressed length
writeOut(ZipLong.getBytes(ze.getCrc()));
- writeOut(ZipLong.getBytes(ze.getCompressedSize()));
- writeOut(ZipLong.getBytes(ze.getSize()));
+ if (ze.getCompressedSize() >= ZIP64_MAGIC
+ || ze.getSize() >= ZIP64_MAGIC) {
+ writeOut(ZipLong.ZIP64_MAGIC.getBytes());
+ writeOut(ZipLong.ZIP64_MAGIC.getBytes());
+ } else {
+ writeOut(ZipLong.getBytes(ze.getCompressedSize()));
+ writeOut(ZipLong.getBytes(ze.getSize()));
+ }
// CheckStyle:MagicNumber OFF
written += 12;
// CheckStyle:MagicNumber ON
- // file name length
- final ZipEncoding entryEncoding;
-
- if (!encodable && fallbackToUTF8) {
- entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
- } else {
- entryEncoding = zipEncoding;
- }
-
- ByteBuffer name = entryEncoding.encode(ze.getName());
+ ByteBuffer name = getName(ze);
writeOut(ZipShort.getBytes(name.limit()));
written += SHORT;
@@ -872,9 +1136,9 @@ public class ZipOutputStream extends Fil
if (comm == null) {
comm = "";
}
-
- ByteBuffer commentB = entryEncoding.encode(comm);
-
+
+ ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
+
writeOut(ZipShort.getBytes(commentB.limit()));
written += SHORT;
@@ -891,7 +1155,7 @@ public class ZipOutputStream extends Fil
written += WORD;
// relative offset of LFH
- writeOut((byte[]) offsets.get(ze));
+ writeOut(ZipLong.getBytes(Math.min(lfhOffset, ZIP64_MAGIC)));
written += WORD;
// file name
@@ -908,10 +1172,35 @@ public class ZipOutputStream extends Fil
}
/**
+ * If the entry needs Zip64 extra information inside the central
+ * directory then configure its data.
+ */
+ private void handleZip64Extra(ZipEntry ze, long lfhOffset,
+ boolean needsZip64Extra) {
+ if (needsZip64Extra) {
+ Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
+ if (ze.getCompressedSize() >= ZIP64_MAGIC
+ || ze.getSize() >= ZIP64_MAGIC) {
+ z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
+ z64.setSize(new ZipEightByteInteger(ze.getSize()));
+ } else {
+ // reset value that may have been set for LFH
+ z64.setCompressedSize(null);
+ z64.setSize(null);
+ }
+ if (lfhOffset >= ZIP64_MAGIC) {
+ z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
+ }
+ ze.setExtra();
+ }
+ }
+
+ /**
* Writes the "End of central dir record".
* @throws IOException on error
- *
- * @since 1.1
+ * @throws Zip64RequiredException if the archive's size exceeds 4
+ * GByte or there are more than 65535 entries inside the archive
+ * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}.
*/
protected void writeCentralDirectoryEnd() throws IOException {
writeOut(EOCD_SIG);
@@ -921,13 +1210,25 @@ public class ZipOutputStream extends Fil
writeOut(ZERO);
// number of entries
- byte[] num = ZipShort.getBytes(entries.size());
+ int numberOfEntries = entries.size();
+ if (numberOfEntries > ZIP64_MAGIC_SHORT
+ && zip64Mode == Zip64Mode.Never) {
+ throw new Zip64RequiredException(Zip64RequiredException
+ .TOO_MANY_ENTRIES_MESSAGE);
+ }
+ if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) {
+ throw new Zip64RequiredException(Zip64RequiredException
+ .ARCHIVE_TOO_BIG_MESSAGE);
+ }
+
+ byte[] num = ZipShort.getBytes(Math.min(numberOfEntries,
+ ZIP64_MAGIC_SHORT));
writeOut(num);
writeOut(num);
// length and location of CD
- writeOut(ZipLong.getBytes(cdLength));
- writeOut(ZipLong.getBytes(cdOffset));
+ writeOut(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC)));
+ writeOut(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC)));
// ZIP file comment
ByteBuffer data = this.zipEncoding.encode(comment);
@@ -936,20 +1237,14 @@ public class ZipOutputStream extends Fil
}
/**
- * Smallest date/time ZIP can handle.
- *
- * @since 1.1
- */
- private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
-
- /**
* Convert a Date object to a DOS date/time field.
* @param time the <code>Date</code> to convert
* @return the date as a <code>ZipLong</code>
* @since 1.1
+ * @deprecated use ZipUtil#toDosTime
*/
protected static ZipLong toDosTime(Date time) {
- return new ZipLong(toDosTime(time.getTime()));
+ return ZipUtil.toDosTime(time);
}
/**
@@ -959,24 +1254,10 @@ public class ZipOutputStream extends Fil
* @param t number of milliseconds since the epoch
* @return the date as a byte array
* @since 1.26
+ * @deprecated use ZipUtil#toDosTime
*/
protected static byte[] toDosTime(long t) {
- Date time = new Date(t);
- // CheckStyle:MagicNumberCheck OFF - I do not think that using constants
- // here will improve the readablity
- int year = time.getYear() + 1900;
- if (year < 1980) {
- return DOS_TIME_MIN;
- }
- int month = time.getMonth() + 1;
- long value = ((year - 1980) << 25)
- | (month << 21)
- | (time.getDate() << 16)
- | (time.getHours() << 11)
- | (time.getMinutes() << 5)
- | (time.getSeconds() >> 1);
- return ZipLong.getBytes(value);
- // CheckStyle:MagicNumberCheck ON
+ return ZipUtil.toDosTime(t);
}
/**
@@ -1001,6 +1282,76 @@ public class ZipOutputStream extends Fil
}
}
+ private static final byte[] ONE = ZipLong.getBytes(1L);
+
+ /**
+ * Writes the "ZIP64 End of central dir record" and
+ * "ZIP64 End of central dir locator".
+ * @throws IOException on error
+ * @since 1.3
+ */
+ protected void writeZip64CentralDirectory() throws IOException {
+ if (zip64Mode == Zip64Mode.Never) {
+ return;
+ }
+
+ if (!hasUsedZip64
+ && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC
+ || entries.size() >= ZIP64_MAGIC_SHORT)) {
+ // actually "will use"
+ hasUsedZip64 = true;
+ }
+
+ if (!hasUsedZip64) {
+ return;
+ }
+
+ long offset = written;
+
+ writeOut(ZIP64_EOCD_SIG);
+ // size, we don't have any variable length as we don't support
+ // the extensible data sector, yet
+ writeOut(ZipEightByteInteger
+ .getBytes(SHORT /* version made by */
+ + SHORT /* version needed to extract */
+ + WORD /* disk number */
+ + WORD /* disk with central directory */
+ + DWORD /* number of entries in CD on this disk */
+ + DWORD /* total number of entries */
+ + DWORD /* size of CD */
+ + DWORD /* offset of CD */
+ ));
+
+ // version made by and version needed to extract
+ writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
+ writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
+
+ // disk numbers - four bytes this time
+ writeOut(LZERO);
+ writeOut(LZERO);
+
+ // number of entries
+ byte[] num = ZipEightByteInteger.getBytes(entries.size());
+ writeOut(num);
+ writeOut(num);
+
+ // length and location of CD
+ writeOut(ZipEightByteInteger.getBytes(cdLength));
+ writeOut(ZipEightByteInteger.getBytes(cdOffset));
+
+ // no "zip64 extensible data sector" for now
+
+ // and now the "ZIP64 end of central directory locator"
+ writeOut(ZIP64_EOCD_LOC_SIG);
+
+ // disk number holding the ZIP64 EOCD record
+ writeOut(LZERO);
+ // relative offset of ZIP64 EOCD record
+ writeOut(ZipEightByteInteger.getBytes(offset));
+ // total number of disks
+ writeOut(ONE);
+ }
+
/**
* Write bytes to output or random access file.
* @param data the byte array to write
@@ -1036,13 +1387,10 @@ public class ZipOutputStream extends Fil
* @param i the value to treat as unsigned int.
* @return the unsigned int as a long.
* @since 1.34
+ * @deprecated use ZipUtil#adjustToLong
*/
protected static long adjustToLong(int i) {
- if (i < 0) {
- return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
- } else {
- return i;
- }
+ return ZipUtil.adjustToLong(i);
}
private void deflateUntilInputIsNeeded() throws IOException {
@@ -1054,25 +1402,116 @@ public class ZipOutputStream extends Fil
private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
zipMethod,
final boolean
- utfFallback)
+ utfFallback,
+ final boolean
+ zip64)
throws IOException {
// CheckStyle:MagicNumber OFF
- int versionNeededToExtract = 10;
- int generalPurposeFlag = (useUTF8Flag || utfFallback) ? UFT8_NAMES_FLAG : 0;
+ int versionNeededToExtract = INITIAL_VERSION;
+ GeneralPurposeBit b = new GeneralPurposeBit();
+ b.useUTF8ForNames(useUTF8Flag || utfFallback);
if (zipMethod == DEFLATED && raf == null) {
// requires version 2 as we are going to store length info
// in the data descriptor
- versionNeededToExtract = 20;
- // bit3 set to signal, we use a data descriptor
- generalPurposeFlag |= 8;
+ versionNeededToExtract = DATA_DESCRIPTOR_MIN_VERSION;
+ b.useDataDescriptor(true);
+ }
+ if (zip64) {
+ versionNeededToExtract = ZIP64_MIN_VERSION;
}
// CheckStyle:MagicNumber ON
// version needed to extract
writeOut(ZipShort.getBytes(versionNeededToExtract));
// general purpose bit flag
- writeOut(ZipShort.getBytes(generalPurposeFlag));
+ writeOut(b.encode());
+ }
+
+ /**
+ * Get the existing ZIP64 extended information extra field or
+ * create a new one and add it to the entry.
+ *
+ * @since 1.3
+ */
+ private Zip64ExtendedInformationExtraField getZip64Extra(ZipEntry ze) {
+ if (entry != null) {
+ entry.causedUseOfZip64 = !hasUsedZip64;
+ }
+ hasUsedZip64 = true;
+ Zip64ExtendedInformationExtraField z64 =
+ (Zip64ExtendedInformationExtraField)
+ ze.getExtraField(Zip64ExtendedInformationExtraField
+ .HEADER_ID);
+ if (z64 == null) {
+ /*
+ System.err.println("Adding z64 for " + ze.getName()
+ + ", method: " + ze.getMethod()
+ + " (" + (ze.getMethod() == STORED) + ")"
+ + ", raf: " + (raf != null));
+ */
+ z64 = new Zip64ExtendedInformationExtraField();
+ }
+
+ // even if the field is there already, make sure it is the first one
+ ze.addAsFirstExtraField(z64);
+
+ return z64;
+ }
+
+ /**
+ * Is there a ZIP64 extended information extra field for the
+ * entry?
+ *
+ * @since 1.3
+ */
+ private boolean hasZip64Extra(ZipEntry ze) {
+ return ze.getExtraField(Zip64ExtendedInformationExtraField
+ .HEADER_ID)
+ != null;
+ }
+
+ /**
+ * If the mode is AsNeeded and the entry is a compressed entry of
+ * unknown size that gets written to a non-seekable stream the
+ * change the default to Never.
+ *
+ * @since 1.3
+ */
+ private Zip64Mode getEffectiveZip64Mode(ZipEntry ze) {
+ if (zip64Mode != Zip64Mode.AsNeeded
+ || raf != null
+ || ze.getMethod() != DEFLATED
+ || ze.getSize() != -1) {
+ return zip64Mode;
+ }
+ return Zip64Mode.Never;
+ }
+
+ private ZipEncoding getEntryEncoding(ZipEntry ze) {
+ boolean encodable = zipEncoding.canEncode(ze.getName());
+ return !encodable && fallbackToUTF8
+ ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
+ }
+
+ private ByteBuffer getName(ZipEntry ze) throws IOException {
+ return getEntryEncoding(ze).encode(ze.getName());
+ }
+
+ /**
+ * Closes the underlying stream/file without finishing the
+ * archive, the result will likely be a corrupt archive.
+ *
+ * <p>This method only exists to support tests that generate
+ * corrupt archives so they can clean up any temporary files.</p>
+ */
+ void destroy() throws IOException {
+ if (raf != null) {
+ raf.close();
+ }
+ if (out != null) {
+ out.close();
+ }
}
/**
@@ -1101,8 +1540,51 @@ public class ZipOutputStream extends Fil
private UnicodeExtraFieldPolicy(String n) {
name = n;
}
+ @Override
public String toString() {
return name;
}
}
+
+ /**
+ * Structure collecting information for the entry that is
+ * currently being written.
+ */
+ private static final class CurrentEntry {
+ private CurrentEntry(ZipEntry entry) {
+ this.entry = entry;
+ }
+ /**
+ * Current ZIP entry.
+ */
+ private final ZipEntry entry;
+ /**
+ * Offset for CRC entry in the local file header data for the
+ * current entry starts here.
+ */
+ private long localDataStart = 0;
+ /**
+ * Data for local header data
+ */
+ private long dataStart = 0;
+ /**
+ * Number of bytes read for the current entry (can't rely on
+ * Deflater#getBytesRead) when using DEFLATED.
+ */
+ private long bytesRead = 0;
+ /**
+ * Whether current entry was the first one using ZIP64 features.
+ */
+ private boolean causedUseOfZip64 = false;
+ /**
+ * Whether write() has been called at all.
+ *
+ * <p>In order to create a valid archive {@link
+ * #closeEntry closeEntry} will write an empty
+ * array to get the CRC right if nothing has been written to
+ * the stream at all.</p>
+ */
+ private boolean hasWritten;
+ }
+
}
Modified: ant/core/trunk/src/main/org/apache/tools/zip/ZipShort.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/zip/ZipShort.java?rev=1346025&r1=1346024&r2=1346025&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/zip/ZipShort.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/zip/ZipShort.java Mon Jun 4 16:35:06 2012
@@ -18,17 +18,18 @@
package org.apache.tools.zip;
+import static org.apache.tools.zip.ZipConstants.BYTE_MASK;
+
/**
* Utility class that represents a two byte integer with conversion
* rules for the big endian byte order of ZIP files.
*
*/
public final class ZipShort implements Cloneable {
- private static final int BYTE_MASK = 0xFF;
private static final int BYTE_1_MASK = 0xFF00;
private static final int BYTE_1_SHIFT = 8;
- private int value;
+ private final int value;
/**
* Create instance from a number.
@@ -95,7 +96,7 @@ public final class ZipShort implements C
* Helper method to get the value as a java int from two bytes starting at given array offset
* @param bytes the array of bytes
* @param offset the offset to start
- * @return the correspondanding java int value
+ * @return the corresponding java int value
*/
public static int getValue(byte[] bytes, int offset) {
int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK;
@@ -106,7 +107,7 @@ public final class ZipShort implements C
/**
* Helper method to get the value as a java int from a two-byte array
* @param bytes the array of bytes
- * @return the correspondanding java int value
+ * @return the corresponding java int value
*/
public static int getValue(byte[] bytes) {
return getValue(bytes, 0);
@@ -118,6 +119,7 @@ public final class ZipShort implements C
* @return true if the objects are equal
* @since 1.1
*/
+ @Override
public boolean equals(Object o) {
if (o == null || !(o instanceof ZipShort)) {
return false;
@@ -130,10 +132,12 @@ public final class ZipShort implements C
* @return the value stored in the ZipShort
* @since 1.1
*/
+ @Override
public int hashCode() {
return value;
}
+ @Override
public Object clone() {
try {
return super.clone();
@@ -142,4 +146,9 @@ public final class ZipShort implements C
throw new RuntimeException(cnfe);
}
}
+
+ @Override
+ public String toString() {
+ return "ZipShort value: " + value;
+ }
}
Modified: ant/core/trunk/src/main/org/apache/tools/zip/ZipUtil.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/zip/ZipUtil.java?rev=1346025&r1=1346024&r2=1346025&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/zip/ZipUtil.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/zip/ZipUtil.java Mon Jun 4 16:35:06 2012
@@ -17,12 +17,160 @@
*/
package org.apache.tools.zip;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.zip.CRC32;
+
/**
* Utility class for handling DOS and Java time conversions.
* @since Ant 1.8.1
*/
public abstract class ZipUtil {
/**
+ * Smallest date/time ZIP can handle.
+ */
+ private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
+
+ /**
+ * Convert a Date object to a DOS date/time field.
+ * @param time the <code>Date</code> to convert
+ * @return the date as a <code>ZipLong</code>
+ */
+ public static ZipLong toDosTime(Date time) {
+ return new ZipLong(toDosTime(time.getTime()));
+ }
+
+ /**
+ * Convert a Date object to a DOS date/time field.
+ *
+ * <p>Stolen from InfoZip's <code>fileio.c</code></p>
+ * @param t number of milliseconds since the epoch
+ * @return the date as a byte array
+ */
+ public static byte[] toDosTime(long t) {
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(t);
+
+ int year = c.get(Calendar.YEAR);
+ if (year < 1980) {
+ return copy(DOS_TIME_MIN); // stop callers from changing the array
+ }
+ int month = c.get(Calendar.MONTH) + 1;
+ long value = ((year - 1980) << 25)
+ | (month << 21)
+ | (c.get(Calendar.DAY_OF_MONTH) << 16)
+ | (c.get(Calendar.HOUR_OF_DAY) << 11)
+ | (c.get(Calendar.MINUTE) << 5)
+ | (c.get(Calendar.SECOND) >> 1);
+ return ZipLong.getBytes(value);
+ }
+
+ /**
+ * Assumes a negative integer really is a positive integer that
+ * has wrapped around and re-creates the original value.
+ *
+ * <p>This methods is no longer used as of Apache Ant 1.9.0</p>
+ *
+ * @param i the value to treat as unsigned int.
+ * @return the unsigned int as a long.
+ */
+ public static long adjustToLong(int i) {
+ if (i < 0) {
+ return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
+ } else {
+ return i;
+ }
+ }
+
+ /**
+ * Convert a DOS date/time field to a Date object.
+ *
+ * @param zipDosTime contains the stored DOS time.
+ * @return a Date instance corresponding to the given time.
+ */
+ public static Date fromDosTime(ZipLong zipDosTime) {
+ long dosTime = zipDosTime.getValue();
+ return new Date(dosToJavaTime(dosTime));
+ }
+
+ /**
+ * Converts DOS time to Java time (number of milliseconds since
+ * epoch).
+ */
+ public static long dosToJavaTime(long dosTime) {
+ Calendar cal = Calendar.getInstance();
+ // CheckStyle:MagicNumberCheck OFF - no point
+ cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
+ cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
+ cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
+ cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
+ cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
+ cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
+ // CheckStyle:MagicNumberCheck ON
+ return cal.getTime().getTime();
+ }
+
+ /**
+ * If the entry has Unicode*ExtraFields and the CRCs of the
+ * names/comments match those of the extra fields, transfer the
+ * known Unicode values from the extra field.
+ */
+ static void setNameAndCommentFromExtraFields(ZipEntry ze,
+ byte[] originalNameBytes,
+ byte[] commentBytes) {
+ UnicodePathExtraField name = (UnicodePathExtraField)
+ ze.getExtraField(UnicodePathExtraField.UPATH_ID);
+ String originalName = ze.getName();
+ String newName = getUnicodeStringIfOriginalMatches(name,
+ originalNameBytes);
+ if (newName != null && !originalName.equals(newName)) {
+ ze.setName(newName);
+ }
+
+ if (commentBytes != null && commentBytes.length > 0) {
+ UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
+ ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
+ String newComment =
+ getUnicodeStringIfOriginalMatches(cmt, commentBytes);
+ if (newComment != null) {
+ ze.setComment(newComment);
+ }
+ }
+ }
+
+ /**
+ * If the stored CRC matches the one of the given name, return the
+ * Unicode name of the given field.
+ *
+ * <p>If the field is null or the CRCs don't match, return null
+ * instead.</p>
+ */
+ private static
+ String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
+ byte[] orig) {
+ if (f != null) {
+ CRC32 crc32 = new CRC32();
+ crc32.update(orig);
+ long origCRC32 = crc32.getValue();
+
+ if (origCRC32 == f.getNameCRC32()) {
+ try {
+ return ZipEncodingHelper
+ .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
+ } catch (IOException ex) {
+ // UTF-8 unsupported? should be impossible the
+ // Unicode*ExtraField must contain some bad bytes
+
+ // TODO log this anywhere?
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Create a copy of the given array - or return null if the
* argument is null.
*/
@@ -35,4 +183,49 @@ public abstract class ZipUtil {
return null;
}
+ /**
+ * Whether this library is able to read or write the given entry.
+ */
+ static boolean canHandleEntryData(ZipEntry entry) {
+ return supportsEncryptionOf(entry) && supportsMethodOf(entry);
+ }
+
+ /**
+ * Whether this library supports the encryption used by the given
+ * entry.
+ *
+ * @return true if the entry isn't encrypted at all
+ */
+ private static boolean supportsEncryptionOf(ZipEntry entry) {
+ return !entry.getGeneralPurposeBit().usesEncryption();
+ }
+
+ /**
+ * Whether this library supports the compression method used by
+ * the given entry.
+ *
+ * @return true if the compression method is STORED or DEFLATED
+ */
+ private static boolean supportsMethodOf(ZipEntry entry) {
+ return entry.getMethod() == ZipEntry.STORED
+ || entry.getMethod() == ZipEntry.DEFLATED;
+ }
+
+ /**
+ * Checks whether the entry requires features not (yet) supported
+ * by the library and throws an exception if it does.
+ */
+ static void checkRequestedFeatures(ZipEntry ze)
+ throws UnsupportedZipFeatureException {
+ if (!supportsEncryptionOf(ze)) {
+ throw
+ new UnsupportedZipFeatureException(UnsupportedZipFeatureException
+ .Feature.ENCRYPTION, ze);
+ }
+ if (!supportsMethodOf(ze)) {
+ throw
+ new UnsupportedZipFeatureException(UnsupportedZipFeatureException
+ .Feature.METHOD, ze);
+ }
+ }
}
\ No newline at end of file
Modified: ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/ZipExtraFieldTest.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/ZipExtraFieldTest.java?rev=1346025&r1=1346024&r2=1346025&view=diff
==============================================================================
--- ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/ZipExtraFieldTest.java (original)
+++ ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/ZipExtraFieldTest.java Mon Jun 4 16:35:06 2012
@@ -32,6 +32,7 @@ import org.apache.tools.ant.types.Resour
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.ZipResource;
import org.apache.tools.zip.JarMarker;
+import org.apache.tools.zip.Zip64ExtendedInformationExtraField;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipExtraField;
import org.apache.tools.zip.ZipFile;
@@ -79,8 +80,10 @@ public class ZipExtraFieldTest extends T
zf = new ZipFile(f);
ZipEntry ze = zf.getEntry("x");
assertNotNull(ze);
- assertEquals(1, ze.getExtraFields().length);
+ assertEquals(2, ze.getExtraFields().length);
assertTrue(ze.getExtraFields()[0] instanceof JarMarker);
+ assertTrue(ze.getExtraFields()[1]
+ instanceof Zip64ExtendedInformationExtraField);
} finally {
ZipFile.closeQuietly(zf);
if (f.exists()) {
Modified: ant/core/trunk/src/tests/junit/org/apache/tools/zip/ExtraFieldUtilsTest.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/tests/junit/org/apache/tools/zip/ExtraFieldUtilsTest.java?rev=1346025&r1=1346024&r2=1346025&view=diff
==============================================================================
--- ant/core/trunk/src/tests/junit/org/apache/tools/zip/ExtraFieldUtilsTest.java (original)
+++ ant/core/trunk/src/tests/junit/org/apache/tools/zip/ExtraFieldUtilsTest.java Mon Jun 4 16:35:06 2012
@@ -29,17 +29,26 @@ public class ExtraFieldUtilsTest extends
super(name);
}
+ /**
+ * Header-ID of a ZipExtraField not supported by Ant.
+ *
+ * <p>Used to be ZipShort(1) but this is the ID of the Zip64 extra
+ * field.</p>
+ */
+ static final ZipShort UNRECOGNIZED_HEADER = new ZipShort(0x5555);
+
private AsiExtraField a;
private UnrecognizedExtraField dummy;
private byte[] data;
private byte[] aLocal;
+ @Override
public void setUp() {
a = new AsiExtraField();
a.setMode(0755);
a.setDirectory(true);
dummy = new UnrecognizedExtraField();
- dummy.setHeaderId(new ZipShort(1));
+ dummy.setHeaderId(UNRECOGNIZED_HEADER);
dummy.setLocalFileDataData(new byte[] {0});
dummy.setCentralDirectoryData(new byte[] {0});
@@ -167,7 +176,8 @@ public class ExtraFieldUtilsTest extends
public void testMergeWithUnparseableData() throws Exception {
ZipExtraField d = new UnparseableExtraFieldData();
- d.parseFromLocalFileData(new byte[] {1, 0, 1, 0}, 0, 4);
+ byte[] b = UNRECOGNIZED_HEADER.getBytes();
+ d.parseFromLocalFileData(new byte[] {b[0], b[1], 1, 0}, 0, 4);
byte[] local =
ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] {a, d});
assertEquals("local length", data.length - 1, local.length);
Modified: ant/core/trunk/src/tests/junit/org/apache/tools/zip/ZipEntryTest.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/tests/junit/org/apache/tools/zip/ZipEntryTest.java?rev=1346025&r1=1346024&r2=1346025&view=diff
==============================================================================
--- ant/core/trunk/src/tests/junit/org/apache/tools/zip/ZipEntryTest.java (original)
+++ ant/core/trunk/src/tests/junit/org/apache/tools/zip/ZipEntryTest.java Mon Jun 4 16:35:06 2012
@@ -38,7 +38,7 @@ public class ZipEntryTest extends TestCa
a.setDirectory(true);
a.setMode(0755);
UnrecognizedExtraField u = new UnrecognizedExtraField();
- u.setHeaderId(new ZipShort(1));
+ u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
u.setLocalFileDataData(new byte[0]);
ZipEntry ze = new ZipEntry("test/");
@@ -50,7 +50,7 @@ public class ZipEntryTest extends TestCa
assertSame(u, result[1]);
UnrecognizedExtraField u2 = new UnrecognizedExtraField();
- u2.setHeaderId(new ZipShort(1));
+ u2.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
u2.setLocalFileDataData(new byte[] {1});
ze.addExtraField(u2);
@@ -68,7 +68,7 @@ public class ZipEntryTest extends TestCa
result = ze.getExtraFields();
assertEquals("third pass", 3, result.length);
- ze.removeExtraField(new ZipShort(1));
+ ze.removeExtraField(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
byte[] data3 = ze.getExtra();
result = ze.getExtraFields();
assertEquals("fourth pass", 2, result.length);
@@ -77,7 +77,7 @@ public class ZipEntryTest extends TestCa
assertEquals("length fourth pass", data2.length, data3.length);
try {
- ze.removeExtraField(new ZipShort(1));
+ ze.removeExtraField(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
fail("should be no such element");
} catch (java.util.NoSuchElementException nse) {
}
@@ -91,7 +91,7 @@ public class ZipEntryTest extends TestCa
a.setDirectory(true);
a.setMode(0755);
UnrecognizedExtraField u = new UnrecognizedExtraField();
- u.setHeaderId(new ZipShort(1));
+ u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
u.setLocalFileDataData(new byte[0]);
ZipEntry ze = new ZipEntry("test/");
@@ -99,12 +99,14 @@ public class ZipEntryTest extends TestCa
// merge
// Header-ID 1 + length 1 + one byte of data
- ze.setCentralDirectoryExtra(new byte[] {1, 0, 1, 0, 127});
+ byte[] b = ExtraFieldUtilsTest.UNRECOGNIZED_HEADER.getBytes();
+ ze.setCentralDirectoryExtra(new byte[] {b[0], b[1], 1, 0, 127});
ZipExtraField[] result = ze.getExtraFields();
assertEquals("first pass", 2, result.length);
assertSame(a, result[0]);
- assertEquals(new ZipShort(1), result[1].getHeaderId());
+ assertEquals(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER,
+ result[1].getHeaderId());
assertEquals(new ZipShort(0), result[1].getLocalFileDataLength());
assertEquals(new ZipShort(1), result[1].getCentralDirectoryLength());
@@ -135,7 +137,7 @@ public class ZipEntryTest extends TestCa
a.setDirectory(true);
a.setMode(0755);
UnrecognizedExtraField u = new UnrecognizedExtraField();
- u.setHeaderId(new ZipShort(1));
+ u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
u.setLocalFileDataData(new byte[0]);
ZipEntry ze = new ZipEntry("test/");
@@ -143,7 +145,7 @@ public class ZipEntryTest extends TestCa
byte[] data1 = ze.getExtra();
UnrecognizedExtraField u2 = new UnrecognizedExtraField();
- u2.setHeaderId(new ZipShort(1));
+ u2.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER);
u2.setLocalFileDataData(new byte[] {1});
ze.addAsFirstExtraField(u2);