837 lines
31 KiB
Java
837 lines
31 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package java.util.zip;
|
|
|
|
import java.io.OutputStream;
|
|
import java.io.IOException;
|
|
import java.nio.charset.Charset;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Vector;
|
|
import java.util.HashSet;
|
|
import static java.util.zip.ZipConstants64.*;
|
|
import static java.util.zip.ZipUtils.*;
|
|
import sun.security.action.GetPropertyAction;
|
|
|
|
/**
|
|
* This class implements an output stream filter for writing files in the
|
|
* ZIP file format. Includes support for both compressed and uncompressed
|
|
* entries.
|
|
*
|
|
* @author David Connelly
|
|
* @since 1.1
|
|
*/
|
|
public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
|
|
|
|
/**
|
|
* Whether to use ZIP64 for zip files with more than 64k entries.
|
|
* Until ZIP64 support in zip implementations is ubiquitous, this
|
|
* system property allows the creation of zip files which can be
|
|
* read by legacy zip implementations which tolerate "incorrect"
|
|
* total entry count fields, such as the ones in jdk6, and even
|
|
* some in jdk7.
|
|
*/
|
|
// Android-changed: Always allow use of Zip64.
|
|
private static final boolean inhibitZip64 = false;
|
|
// Boolean.parseBoolean(
|
|
// GetPropertyAction.privilegedGetProperty("jdk.util.zip.inhibitZip64"));
|
|
|
|
private static class XEntry {
|
|
final ZipEntry entry;
|
|
final long offset;
|
|
public XEntry(ZipEntry entry, long offset) {
|
|
this.entry = entry;
|
|
this.offset = offset;
|
|
}
|
|
}
|
|
|
|
private XEntry current;
|
|
private Vector<XEntry> xentries = new Vector<>();
|
|
private HashSet<String> names = new HashSet<>();
|
|
private CRC32 crc = new CRC32();
|
|
private long written = 0;
|
|
private long locoff = 0;
|
|
private byte[] comment;
|
|
private int method = DEFLATED;
|
|
private boolean finished;
|
|
|
|
private boolean closed = false;
|
|
|
|
private final ZipCoder zc;
|
|
|
|
private static int version(ZipEntry e) throws ZipException {
|
|
return switch (e.method) {
|
|
case DEFLATED -> 20;
|
|
case STORED -> 10;
|
|
default -> throw new ZipException("unsupported compression method");
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Checks to make sure that this stream has not been closed.
|
|
*/
|
|
private void ensureOpen() throws IOException {
|
|
if (closed) {
|
|
throw new IOException("Stream closed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compression method for uncompressed (STORED) entries.
|
|
*/
|
|
public static final int STORED = ZipEntry.STORED;
|
|
|
|
/**
|
|
* Compression method for compressed (DEFLATED) entries.
|
|
*/
|
|
public static final int DEFLATED = ZipEntry.DEFLATED;
|
|
|
|
/**
|
|
* Creates a new ZIP output stream.
|
|
*
|
|
* <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
|
|
* to encode the entry names and comments.
|
|
*
|
|
* @param out the actual output stream
|
|
*/
|
|
public ZipOutputStream(OutputStream out) {
|
|
this(out, StandardCharsets.UTF_8);
|
|
}
|
|
|
|
/**
|
|
* Creates a new ZIP output stream.
|
|
*
|
|
* @param out the actual output stream
|
|
*
|
|
* @param charset the {@linkplain java.nio.charset.Charset charset}
|
|
* to be used to encode the entry names and comments
|
|
*
|
|
* @since 1.7
|
|
*/
|
|
public ZipOutputStream(OutputStream out, Charset charset) {
|
|
super(out, out != null ? new Deflater(Deflater.DEFAULT_COMPRESSION, true) : null);
|
|
if (charset == null)
|
|
throw new NullPointerException("charset is null");
|
|
this.zc = ZipCoder.get(charset);
|
|
usesDefaultDeflater = true;
|
|
}
|
|
|
|
/**
|
|
* Sets the ZIP file comment.
|
|
* @param comment the comment string
|
|
* @throws IllegalArgumentException if the length of the specified
|
|
* ZIP file comment is greater than 0xFFFF bytes
|
|
*/
|
|
public void setComment(String comment) {
|
|
byte[] bytes = null;
|
|
if (comment != null) {
|
|
bytes = zc.getBytes(comment);
|
|
if (bytes.length > 0xffff) {
|
|
throw new IllegalArgumentException("ZIP file comment too long");
|
|
}
|
|
}
|
|
this.comment = bytes;
|
|
}
|
|
|
|
/**
|
|
* Sets the default compression method for subsequent entries. This
|
|
* default will be used whenever the compression method is not specified
|
|
* for an individual ZIP file entry, and is initially set to DEFLATED.
|
|
* @param method the default compression method
|
|
* @throws IllegalArgumentException if the specified compression method
|
|
* is invalid
|
|
*/
|
|
public void setMethod(int method) {
|
|
if (method != DEFLATED && method != STORED) {
|
|
throw new IllegalArgumentException("invalid compression method");
|
|
}
|
|
this.method = method;
|
|
}
|
|
|
|
/**
|
|
* Sets the compression level for subsequent entries which are DEFLATED.
|
|
* The default setting is DEFAULT_COMPRESSION.
|
|
* @param level the compression level (0-9)
|
|
* @throws IllegalArgumentException if the compression level is invalid
|
|
*/
|
|
public void setLevel(int level) {
|
|
def.setLevel(level);
|
|
}
|
|
|
|
/**
|
|
* Begins writing a new ZIP file entry and positions the stream to the
|
|
* start of the entry data. Closes the current entry if still active.
|
|
* <p>
|
|
* The default compression method will be used if no compression method
|
|
* was specified for the entry. When writing a compressed (DEFLATED)
|
|
* entry, and the compressed size has not been explicitly set with the
|
|
* {@link ZipEntry#setCompressedSize(long)} method, then the compressed
|
|
* size will be set to the actual compressed size after deflation.
|
|
* <p>
|
|
* The current time will be used if the entry has no set modification time.
|
|
*
|
|
* @param e the ZIP entry to be written
|
|
* @throws ZipException if a ZIP format error has occurred
|
|
* @throws IOException if an I/O error has occurred
|
|
*/
|
|
public void putNextEntry(ZipEntry e) throws IOException {
|
|
ensureOpen();
|
|
if (current != null) {
|
|
closeEntry(); // close previous entry
|
|
}
|
|
if (e.xdostime == -1) {
|
|
// by default, do NOT use extended timestamps in extra
|
|
// data, for now.
|
|
e.setTime(System.currentTimeMillis());
|
|
}
|
|
if (e.method == -1) {
|
|
e.method = method; // use default method
|
|
}
|
|
// store size, compressed size, and crc-32 in LOC header
|
|
e.flag = 0;
|
|
switch (e.method) {
|
|
case DEFLATED:
|
|
// If not set, store size, compressed size, and crc-32 in data
|
|
// descriptor immediately following the compressed entry data.
|
|
// Ignore the compressed size of a ZipEntry if it was implcitely set
|
|
// while reading that ZipEntry from a ZipFile or ZipInputStream because
|
|
// we can't know the compression level of the source zip file/stream.
|
|
if (e.size == -1 || e.csize == -1 || e.crc == -1 || !e.csizeSet) {
|
|
e.flag = 8;
|
|
}
|
|
break;
|
|
case STORED:
|
|
// compressed size, uncompressed size, and crc-32 must all be
|
|
// set for entries using STORED compression method
|
|
if (e.size == -1) {
|
|
e.size = e.csize;
|
|
} else if (e.csize == -1) {
|
|
e.csize = e.size;
|
|
} else if (e.size != e.csize) {
|
|
throw new ZipException(
|
|
"STORED entry where compressed != uncompressed size");
|
|
}
|
|
if (e.size == -1 || e.crc == -1) {
|
|
throw new ZipException(
|
|
"STORED entry missing size, compressed size, or crc-32");
|
|
}
|
|
break;
|
|
default:
|
|
throw new ZipException("unsupported compression method");
|
|
}
|
|
if (! names.add(e.name)) {
|
|
throw new ZipException("duplicate entry: " + e.name);
|
|
}
|
|
if (zc.isUTF8())
|
|
e.flag |= USE_UTF8;
|
|
current = new XEntry(e, written);
|
|
xentries.add(current);
|
|
writeLOC(current);
|
|
}
|
|
|
|
/**
|
|
* Closes the current ZIP entry and positions the stream for writing
|
|
* the next entry.
|
|
* @throws ZipException if a ZIP format error has occurred
|
|
* @throws IOException if an I/O error has occurred
|
|
*/
|
|
public void closeEntry() throws IOException {
|
|
ensureOpen();
|
|
if (current != null) {
|
|
try {
|
|
ZipEntry e = current.entry;
|
|
switch (e.method) {
|
|
case DEFLATED -> {
|
|
def.finish();
|
|
while (!def.finished()) {
|
|
deflate();
|
|
}
|
|
if ((e.flag & 8) == 0) {
|
|
// verify size, compressed size, and crc-32 settings
|
|
if (e.size != def.getBytesRead()) {
|
|
throw new ZipException(
|
|
"invalid entry size (expected " + e.size +
|
|
" but got " + def.getBytesRead() + " bytes)");
|
|
}
|
|
if (e.csize != def.getBytesWritten()) {
|
|
throw new ZipException(
|
|
"invalid entry compressed size (expected " +
|
|
e.csize + " but got " + def.getBytesWritten() + " bytes)");
|
|
}
|
|
if (e.crc != crc.getValue()) {
|
|
throw new ZipException(
|
|
"invalid entry CRC-32 (expected 0x" +
|
|
Long.toHexString(e.crc) + " but got 0x" +
|
|
Long.toHexString(crc.getValue()) + ")");
|
|
}
|
|
} else {
|
|
e.size = def.getBytesRead();
|
|
e.csize = def.getBytesWritten();
|
|
e.crc = crc.getValue();
|
|
writeEXT(e);
|
|
}
|
|
def.reset();
|
|
written += e.csize;
|
|
}
|
|
case STORED -> {
|
|
// we already know that both e.size and e.csize are the same
|
|
if (e.size != written - locoff) {
|
|
throw new ZipException(
|
|
"invalid entry size (expected " + e.size +
|
|
" but got " + (written - locoff) + " bytes)");
|
|
}
|
|
if (e.crc != crc.getValue()) {
|
|
throw new ZipException(
|
|
"invalid entry crc-32 (expected 0x" +
|
|
Long.toHexString(e.crc) + " but got 0x" +
|
|
Long.toHexString(crc.getValue()) + ")");
|
|
}
|
|
}
|
|
default -> throw new ZipException("invalid compression method");
|
|
}
|
|
crc.reset();
|
|
current = null;
|
|
} catch (IOException e) {
|
|
if (def.shouldFinish() && usesDefaultDeflater && !(e instanceof ZipException))
|
|
def.end();
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes an array of bytes to the current ZIP entry data. This method
|
|
* will block until all the bytes are written.
|
|
* @param b the data to be written
|
|
* @param off the start offset in the data
|
|
* @param len the number of bytes that are written
|
|
* @throws ZipException if a ZIP file error has occurred
|
|
* @throws IOException if an I/O error has occurred
|
|
*/
|
|
public synchronized void write(byte[] b, int off, int len)
|
|
throws IOException
|
|
{
|
|
ensureOpen();
|
|
if (off < 0 || len < 0 || off > b.length - len) {
|
|
throw new IndexOutOfBoundsException();
|
|
} else if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
if (current == null) {
|
|
throw new ZipException("no current ZIP entry");
|
|
}
|
|
ZipEntry entry = current.entry;
|
|
switch (entry.method) {
|
|
case DEFLATED -> super.write(b, off, len);
|
|
case STORED -> {
|
|
written += len;
|
|
if (written - locoff > entry.size) {
|
|
throw new ZipException(
|
|
"attempt to write past end of STORED entry");
|
|
}
|
|
out.write(b, off, len);
|
|
}
|
|
default -> throw new ZipException("invalid compression method");
|
|
}
|
|
crc.update(b, off, len);
|
|
}
|
|
|
|
/**
|
|
* Finishes writing the contents of the ZIP output stream without closing
|
|
* the underlying stream. Use this method when applying multiple filters
|
|
* in succession to the same output stream.
|
|
* @throws ZipException if a ZIP file error has occurred
|
|
* @throws IOException if an I/O exception has occurred
|
|
*/
|
|
public void finish() throws IOException {
|
|
ensureOpen();
|
|
if (finished) {
|
|
return;
|
|
}
|
|
if (current != null) {
|
|
closeEntry();
|
|
}
|
|
// write central directory
|
|
long off = written;
|
|
for (XEntry xentry : xentries)
|
|
writeCEN(xentry);
|
|
writeEND(off, written - off);
|
|
finished = true;
|
|
}
|
|
|
|
/**
|
|
* Closes the ZIP output stream as well as the stream being filtered.
|
|
* @throws ZipException if a ZIP file error has occurred
|
|
* @throws IOException if an I/O error has occurred
|
|
*/
|
|
public void close() throws IOException {
|
|
if (!closed) {
|
|
super.close();
|
|
closed = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Writes local file (LOC) header for specified entry.
|
|
*/
|
|
private void writeLOC(XEntry xentry) throws IOException {
|
|
ZipEntry e = xentry.entry;
|
|
int flag = e.flag;
|
|
boolean hasZip64 = false;
|
|
int elen = getExtraLen(e.extra);
|
|
|
|
writeInt(LOCSIG); // LOC header signature
|
|
if ((flag & 8) == 8) {
|
|
writeShort(version(e)); // version needed to extract
|
|
writeShort(flag); // general purpose bit flag
|
|
writeShort(e.method); // compression method
|
|
writeInt(e.xdostime); // last modification time
|
|
// store size, uncompressed size, and crc-32 in data descriptor
|
|
// immediately following compressed entry data
|
|
writeInt(0);
|
|
writeInt(0);
|
|
writeInt(0);
|
|
} else {
|
|
if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
|
|
hasZip64 = true;
|
|
writeShort(45); // ver 4.5 for zip64
|
|
} else {
|
|
writeShort(version(e)); // version needed to extract
|
|
}
|
|
writeShort(flag); // general purpose bit flag
|
|
writeShort(e.method); // compression method
|
|
writeInt(e.xdostime); // last modification time
|
|
writeInt(e.crc); // crc-32
|
|
if (hasZip64) {
|
|
writeInt(ZIP64_MAGICVAL);
|
|
writeInt(ZIP64_MAGICVAL);
|
|
elen += 20; //headid(2) + size(2) + size(8) + csize(8)
|
|
} else {
|
|
writeInt(e.csize); // compressed size
|
|
writeInt(e.size); // uncompressed size
|
|
}
|
|
}
|
|
byte[] nameBytes = zc.getBytes(e.name);
|
|
writeShort(nameBytes.length);
|
|
|
|
int elenEXTT = 0; // info-zip extended timestamp
|
|
int flagEXTT = 0;
|
|
long umtime = -1;
|
|
long uatime = -1;
|
|
long uctime = -1;
|
|
if (e.mtime != null) {
|
|
elenEXTT += 4;
|
|
flagEXTT |= EXTT_FLAG_LMT;
|
|
umtime = fileTimeToUnixTime(e.mtime);
|
|
}
|
|
if (e.atime != null) {
|
|
elenEXTT += 4;
|
|
flagEXTT |= EXTT_FLAG_LAT;
|
|
uatime = fileTimeToUnixTime(e.atime);
|
|
}
|
|
if (e.ctime != null) {
|
|
elenEXTT += 4;
|
|
flagEXTT |= EXTT_FLAT_CT;
|
|
uctime = fileTimeToUnixTime(e.ctime);
|
|
}
|
|
if (flagEXTT != 0) {
|
|
// to use ntfs time if any m/a/ctime is beyond unixtime upper bound
|
|
if (umtime > UPPER_UNIXTIME_BOUND ||
|
|
uatime > UPPER_UNIXTIME_BOUND ||
|
|
uctime > UPPER_UNIXTIME_BOUND) {
|
|
elen += 36; // NTFS time, total 36 bytes
|
|
} else {
|
|
elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data
|
|
}
|
|
}
|
|
writeShort(elen);
|
|
writeBytes(nameBytes, 0, nameBytes.length);
|
|
if (hasZip64) {
|
|
writeShort(ZIP64_EXTID);
|
|
writeShort(16);
|
|
writeLong(e.size);
|
|
writeLong(e.csize);
|
|
}
|
|
if (flagEXTT != 0) {
|
|
if (umtime > UPPER_UNIXTIME_BOUND ||
|
|
uatime > UPPER_UNIXTIME_BOUND ||
|
|
uctime > UPPER_UNIXTIME_BOUND) {
|
|
writeShort(EXTID_NTFS); // id
|
|
writeShort(32); // data size
|
|
writeInt(0); // reserved
|
|
writeShort(0x0001); // NTFS attr tag
|
|
writeShort(24);
|
|
writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE
|
|
: fileTimeToWinTime(e.mtime));
|
|
writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE
|
|
: fileTimeToWinTime(e.atime));
|
|
writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE
|
|
: fileTimeToWinTime(e.ctime));
|
|
} else {
|
|
writeShort(EXTID_EXTT);
|
|
writeShort(elenEXTT + 1); // flag + data
|
|
writeByte(flagEXTT);
|
|
if (e.mtime != null)
|
|
writeInt(umtime);
|
|
if (e.atime != null)
|
|
writeInt(uatime);
|
|
if (e.ctime != null)
|
|
writeInt(uctime);
|
|
}
|
|
}
|
|
writeExtra(e.extra);
|
|
locoff = written;
|
|
}
|
|
|
|
/*
|
|
* Writes extra data descriptor (EXT) for specified entry.
|
|
*/
|
|
private void writeEXT(ZipEntry e) throws IOException {
|
|
writeInt(EXTSIG); // EXT header signature
|
|
writeInt(e.crc); // crc-32
|
|
if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
|
|
writeLong(e.csize);
|
|
writeLong(e.size);
|
|
} else {
|
|
writeInt(e.csize); // compressed size
|
|
writeInt(e.size); // uncompressed size
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds information about compatibility of file attribute information
|
|
* to a version value.
|
|
*/
|
|
private int versionMadeBy(ZipEntry e, int version) {
|
|
return (e.extraAttributes < 0) ? version :
|
|
VERSION_MADE_BY_BASE_UNIX | (version & 0xff);
|
|
}
|
|
|
|
/*
|
|
* Write central directory (CEN) header for specified entry.
|
|
* REMIND: add support for file attributes
|
|
*/
|
|
private void writeCEN(XEntry xentry) throws IOException {
|
|
ZipEntry e = xentry.entry;
|
|
int flag = e.flag;
|
|
int version = version(e);
|
|
long csize = e.csize;
|
|
long size = e.size;
|
|
long offset = xentry.offset;
|
|
int elenZIP64 = 0;
|
|
boolean hasZip64 = false;
|
|
|
|
if (e.csize >= ZIP64_MAGICVAL) {
|
|
csize = ZIP64_MAGICVAL;
|
|
elenZIP64 += 8; // csize(8)
|
|
hasZip64 = true;
|
|
}
|
|
if (e.size >= ZIP64_MAGICVAL) {
|
|
size = ZIP64_MAGICVAL; // size(8)
|
|
elenZIP64 += 8;
|
|
hasZip64 = true;
|
|
}
|
|
if (xentry.offset >= ZIP64_MAGICVAL) {
|
|
offset = ZIP64_MAGICVAL;
|
|
elenZIP64 += 8; // offset(8)
|
|
hasZip64 = true;
|
|
}
|
|
writeInt(CENSIG); // CEN header signature
|
|
if (hasZip64) {
|
|
writeShort(versionMadeBy(e,45)); // ver 4.5 for zip64
|
|
writeShort(45);
|
|
} else {
|
|
writeShort(versionMadeBy(e, version)); // version made by
|
|
writeShort(version); // version needed to extract
|
|
}
|
|
writeShort(flag); // general purpose bit flag
|
|
writeShort(e.method); // compression method
|
|
writeInt(e.xdostime); // last modification time
|
|
writeInt(e.crc); // crc-32
|
|
writeInt(csize); // compressed size
|
|
writeInt(size); // uncompressed size
|
|
byte[] nameBytes = zc.getBytes(e.name);
|
|
writeShort(nameBytes.length);
|
|
|
|
int elen = getExtraLen(e.extra);
|
|
if (hasZip64) {
|
|
elen += (elenZIP64 + 4);// + headid(2) + datasize(2)
|
|
}
|
|
// cen info-zip extended timestamp only outputs mtime
|
|
// but set the flag for a/ctime, if present in loc
|
|
int flagEXTT = 0;
|
|
long umtime = -1;
|
|
long uatime = -1;
|
|
long uctime = -1;
|
|
if (e.mtime != null) {
|
|
flagEXTT |= EXTT_FLAG_LMT;
|
|
umtime = fileTimeToUnixTime(e.mtime);
|
|
}
|
|
if (e.atime != null) {
|
|
flagEXTT |= EXTT_FLAG_LAT;
|
|
uatime = fileTimeToUnixTime(e.atime);
|
|
}
|
|
if (e.ctime != null) {
|
|
flagEXTT |= EXTT_FLAT_CT;
|
|
uctime = fileTimeToUnixTime(e.ctime);
|
|
}
|
|
if (flagEXTT != 0) {
|
|
// to use ntfs time if any m/a/ctime is beyond unixtime upper bound
|
|
if (umtime > UPPER_UNIXTIME_BOUND ||
|
|
uatime > UPPER_UNIXTIME_BOUND ||
|
|
uctime > UPPER_UNIXTIME_BOUND) {
|
|
elen += 36; // NTFS time total 36 bytes
|
|
} else {
|
|
elen += 5; // headid(2) + sz(2) + flag(1)
|
|
if (e.mtime != null)
|
|
elen += 4; // + mtime (4)
|
|
}
|
|
}
|
|
writeShort(elen);
|
|
byte[] commentBytes;
|
|
if (e.comment != null) {
|
|
commentBytes = zc.getBytes(e.comment);
|
|
writeShort(Math.min(commentBytes.length, 0xffff));
|
|
} else {
|
|
commentBytes = null;
|
|
writeShort(0);
|
|
}
|
|
writeShort(0); // starting disk number
|
|
writeShort(0); // internal file attributes (unused)
|
|
// extra file attributes, used for storing posix permissions etc.
|
|
writeInt(e.extraAttributes > 0 ? e.extraAttributes << 16 : 0);
|
|
writeInt(offset); // relative offset of local header
|
|
writeBytes(nameBytes, 0, nameBytes.length);
|
|
|
|
// take care of EXTID_ZIP64 and EXTID_EXTT
|
|
if (hasZip64) {
|
|
writeShort(ZIP64_EXTID);// Zip64 extra
|
|
writeShort(elenZIP64);
|
|
if (size == ZIP64_MAGICVAL)
|
|
writeLong(e.size);
|
|
if (csize == ZIP64_MAGICVAL)
|
|
writeLong(e.csize);
|
|
if (offset == ZIP64_MAGICVAL)
|
|
writeLong(xentry.offset);
|
|
}
|
|
if (flagEXTT != 0) {
|
|
if (umtime > UPPER_UNIXTIME_BOUND ||
|
|
uatime > UPPER_UNIXTIME_BOUND ||
|
|
uctime > UPPER_UNIXTIME_BOUND) {
|
|
writeShort(EXTID_NTFS); // id
|
|
writeShort(32); // data size
|
|
writeInt(0); // reserved
|
|
writeShort(0x0001); // NTFS attr tag
|
|
writeShort(24);
|
|
writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE
|
|
: fileTimeToWinTime(e.mtime));
|
|
writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE
|
|
: fileTimeToWinTime(e.atime));
|
|
writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE
|
|
: fileTimeToWinTime(e.ctime));
|
|
} else {
|
|
writeShort(EXTID_EXTT);
|
|
if (e.mtime != null) {
|
|
writeShort(5); // flag + mtime
|
|
writeByte(flagEXTT);
|
|
writeInt(umtime);
|
|
} else {
|
|
writeShort(1); // flag only
|
|
writeByte(flagEXTT);
|
|
}
|
|
}
|
|
}
|
|
writeExtra(e.extra);
|
|
if (commentBytes != null) {
|
|
writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Writes end of central directory (END) header.
|
|
*/
|
|
private void writeEND(long off, long len) throws IOException {
|
|
boolean hasZip64 = false;
|
|
long xlen = len;
|
|
long xoff = off;
|
|
if (xlen >= ZIP64_MAGICVAL) {
|
|
xlen = ZIP64_MAGICVAL;
|
|
hasZip64 = true;
|
|
}
|
|
if (xoff >= ZIP64_MAGICVAL) {
|
|
xoff = ZIP64_MAGICVAL;
|
|
hasZip64 = true;
|
|
}
|
|
int count = xentries.size();
|
|
if (count >= ZIP64_MAGICCOUNT) {
|
|
hasZip64 |= !inhibitZip64;
|
|
if (hasZip64) {
|
|
count = ZIP64_MAGICCOUNT;
|
|
}
|
|
}
|
|
if (hasZip64) {
|
|
long off64 = written;
|
|
//zip64 end of central directory record
|
|
writeInt(ZIP64_ENDSIG); // zip64 END record signature
|
|
writeLong(ZIP64_ENDHDR - 12); // size of zip64 end
|
|
writeShort(45); // version made by
|
|
writeShort(45); // version needed to extract
|
|
writeInt(0); // number of this disk
|
|
writeInt(0); // central directory start disk
|
|
writeLong(xentries.size()); // number of directory entires on disk
|
|
writeLong(xentries.size()); // number of directory entires
|
|
writeLong(len); // length of central directory
|
|
writeLong(off); // offset of central directory
|
|
|
|
//zip64 end of central directory locator
|
|
writeInt(ZIP64_LOCSIG); // zip64 END locator signature
|
|
writeInt(0); // zip64 END start disk
|
|
writeLong(off64); // offset of zip64 END
|
|
writeInt(1); // total number of disks (?)
|
|
}
|
|
writeInt(ENDSIG); // END record signature
|
|
writeShort(0); // number of this disk
|
|
writeShort(0); // central directory start disk
|
|
writeShort(count); // number of directory entries on disk
|
|
writeShort(count); // total number of directory entries
|
|
writeInt(xlen); // length of central directory
|
|
writeInt(xoff); // offset of central directory
|
|
if (comment != null) { // zip file comment
|
|
writeShort(comment.length);
|
|
writeBytes(comment, 0, comment.length);
|
|
} else {
|
|
writeShort(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the length of extra data without EXTT and ZIP64.
|
|
*/
|
|
private int getExtraLen(byte[] extra) {
|
|
if (extra == null)
|
|
return 0;
|
|
int skipped = 0;
|
|
int len = extra.length;
|
|
int off = 0;
|
|
while (off + 4 <= len) {
|
|
int tag = get16(extra, off);
|
|
int sz = get16(extra, off + 2);
|
|
if (sz < 0 || (off + 4 + sz) > len) {
|
|
break;
|
|
}
|
|
if (tag == EXTID_EXTT || tag == EXTID_ZIP64) {
|
|
skipped += (sz + 4);
|
|
}
|
|
off += (sz + 4);
|
|
}
|
|
return len - skipped;
|
|
}
|
|
|
|
/*
|
|
* Writes extra data without EXTT and ZIP64.
|
|
*
|
|
* Extra timestamp and ZIP64 data is handled/output separately
|
|
* in writeLOC and writeCEN.
|
|
*/
|
|
private void writeExtra(byte[] extra) throws IOException {
|
|
if (extra != null) {
|
|
int len = extra.length;
|
|
int off = 0;
|
|
while (off + 4 <= len) {
|
|
int tag = get16(extra, off);
|
|
int sz = get16(extra, off + 2);
|
|
if (sz < 0 || (off + 4 + sz) > len) {
|
|
writeBytes(extra, off, len - off);
|
|
return;
|
|
}
|
|
if (tag != EXTID_EXTT && tag != EXTID_ZIP64) {
|
|
writeBytes(extra, off, sz + 4);
|
|
}
|
|
off += (sz + 4);
|
|
}
|
|
if (off < len) {
|
|
writeBytes(extra, off, len - off);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Writes a 8-bit byte to the output stream.
|
|
*/
|
|
private void writeByte(int v) throws IOException {
|
|
OutputStream out = this.out;
|
|
out.write(v & 0xff);
|
|
written += 1;
|
|
}
|
|
|
|
/*
|
|
* Writes a 16-bit short to the output stream in little-endian byte order.
|
|
*/
|
|
private void writeShort(int v) throws IOException {
|
|
OutputStream out = this.out;
|
|
out.write((v >>> 0) & 0xff);
|
|
out.write((v >>> 8) & 0xff);
|
|
written += 2;
|
|
}
|
|
|
|
/*
|
|
* Writes a 32-bit int to the output stream in little-endian byte order.
|
|
*/
|
|
private void writeInt(long v) throws IOException {
|
|
OutputStream out = this.out;
|
|
out.write((int)((v >>> 0) & 0xff));
|
|
out.write((int)((v >>> 8) & 0xff));
|
|
out.write((int)((v >>> 16) & 0xff));
|
|
out.write((int)((v >>> 24) & 0xff));
|
|
written += 4;
|
|
}
|
|
|
|
/*
|
|
* Writes a 64-bit int to the output stream in little-endian byte order.
|
|
*/
|
|
private void writeLong(long v) throws IOException {
|
|
OutputStream out = this.out;
|
|
out.write((int)((v >>> 0) & 0xff));
|
|
out.write((int)((v >>> 8) & 0xff));
|
|
out.write((int)((v >>> 16) & 0xff));
|
|
out.write((int)((v >>> 24) & 0xff));
|
|
out.write((int)((v >>> 32) & 0xff));
|
|
out.write((int)((v >>> 40) & 0xff));
|
|
out.write((int)((v >>> 48) & 0xff));
|
|
out.write((int)((v >>> 56) & 0xff));
|
|
written += 8;
|
|
}
|
|
|
|
/*
|
|
* Writes an array of bytes to the output stream.
|
|
*/
|
|
private void writeBytes(byte[] b, int off, int len) throws IOException {
|
|
super.out.write(b, off, len);
|
|
written += len;
|
|
}
|
|
}
|