blob: 44a4db5182894615a8d3fdf9ede03f95579058d5 [file] [log] [blame]
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.zip;
import com.google.devtools.build.zip.ZipFileEntry.Compression;
import com.google.devtools.build.zip.ZipFileEntry.Feature;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.zip.ZipException;
class CentralDirectoryFileHeader {
static final int SIGNATURE = 0x02014b50;
static final int FIXED_DATA_SIZE = 46;
static final int SIGNATURE_OFFSET = 0;
static final int VERSION_OFFSET = 4;
static final int VERSION_NEEDED_OFFSET = 6;
static final int FLAGS_OFFSET = 8;
static final int METHOD_OFFSET = 10;
static final int MOD_TIME_OFFSET = 12;
static final int CRC_OFFSET = 16;
static final int COMPRESSED_SIZE_OFFSET = 20;
static final int UNCOMPRESSED_SIZE_OFFSET = 24;
static final int FILENAME_LENGTH_OFFSET = 28;
static final int EXTRA_FIELD_LENGTH_OFFSET = 30;
static final int COMMENT_LENGTH_OFFSET = 32;
static final int DISK_START_OFFSET = 34;
static final int INTERNAL_ATTRIBUTES_OFFSET = 36;
static final int EXTERNAL_ATTRIBUTES_OFFSET = 38;
static final int LOCAL_HEADER_OFFSET_OFFSET = 42;
/**
* Reads a {@link ZipFileEntry} from the input stream, using the specified {@link Charset} to
* decode the filename and comment.
*/
static ZipFileEntry read(InputStream in, Charset charset)
throws IOException {
byte[] fixedSizeData = new byte[FIXED_DATA_SIZE];
if (ZipUtil.readFully(in, fixedSizeData) != FIXED_DATA_SIZE) {
throw new ZipException(
"Unexpected end of file while reading Central Directory File Header.");
}
if (!ZipUtil.arrayStartsWith(fixedSizeData, ZipUtil.intToLittleEndian(SIGNATURE))) {
throw new ZipException(String.format(
"Malformed Central Directory File Header; does not start with %08x", SIGNATURE));
}
byte[] name = new byte[ZipUtil.getUnsignedShort(fixedSizeData, FILENAME_LENGTH_OFFSET)];
byte[] extraField = new byte[
ZipUtil.getUnsignedShort(fixedSizeData, EXTRA_FIELD_LENGTH_OFFSET)];
byte[] comment = new byte[ZipUtil.getUnsignedShort(fixedSizeData, COMMENT_LENGTH_OFFSET)];
if (name.length > 0 && ZipUtil.readFully(in, name) != name.length) {
throw new ZipException(
"Unexpected end of file while reading Central Directory File Header.");
}
if (extraField.length > 0 && ZipUtil.readFully(in, extraField) != extraField.length) {
throw new ZipException(
"Unexpected end of file while reading Central Directory File Header.");
}
if (comment.length > 0 && ZipUtil.readFully(in, comment) != comment.length) {
throw new ZipException(
"Unexpected end of file while reading Central Directory File Header.");
}
ExtraDataList extra = new ExtraDataList(extraField);
long csize = ZipUtil.getUnsignedInt(fixedSizeData, COMPRESSED_SIZE_OFFSET);
long size = ZipUtil.getUnsignedInt(fixedSizeData, UNCOMPRESSED_SIZE_OFFSET);
long offset = ZipUtil.getUnsignedInt(fixedSizeData, LOCAL_HEADER_OFFSET_OFFSET);
if (csize == 0xffffffffL || size == 0xffffffffL || offset == 0xffffffffL) {
ExtraData zip64Extra = extra.get((short) 0x0001);
if (zip64Extra != null) {
int index = 0;
if (size == 0xffffffffL) {
size = ZipUtil.getUnsignedLong(zip64Extra.getData(), index);
index += 8;
}
if (csize == 0xffffffffL) {
csize = ZipUtil.getUnsignedLong(zip64Extra.getData(), index);
index += 8;
}
if (offset == 0xffffffffL) {
offset = ZipUtil.getUnsignedLong(zip64Extra.getData(), index);
index += 8;
}
}
}
ZipFileEntry entry = new ZipFileEntry(new String(name, charset));
entry.setVersion(ZipUtil.get16(fixedSizeData, VERSION_OFFSET));
entry.setVersionNeeded(ZipUtil.get16(fixedSizeData, VERSION_NEEDED_OFFSET));
entry.setFlags(ZipUtil.get16(fixedSizeData, FLAGS_OFFSET));
entry.setMethod(Compression.fromValue(ZipUtil.get16(fixedSizeData, METHOD_OFFSET)));
long time = ZipUtil.dosToUnixTime(ZipUtil.get32(fixedSizeData, MOD_TIME_OFFSET));
entry.setTime(ZipUtil.isValidInDos(time) ? time : ZipUtil.DOS_EPOCH);
entry.setCrc(ZipUtil.getUnsignedInt(fixedSizeData, CRC_OFFSET));
entry.setCompressedSize(csize);
entry.setSize(size);
entry.setInternalAttributes(ZipUtil.get16(fixedSizeData, INTERNAL_ATTRIBUTES_OFFSET));
entry.setExternalAttributes(ZipUtil.get32(fixedSizeData, EXTERNAL_ATTRIBUTES_OFFSET));
entry.setLocalHeaderOffset(offset);
entry.setExtra(extra);
entry.setComment(new String(comment, charset));
return entry;
}
/**
* Generates the raw byte data of the central directory file header for the ZipEntry. Uses the
* specified {@link ZipFileData} to encode the file name and comment.
* @throws ZipException
*/
static byte[] create(ZipFileEntry entry, ZipFileData file, boolean allowZip64)
throws ZipException {
if (allowZip64) {
addZip64Extra(entry);
} else {
entry.getExtra().remove((short) 0x0001);
}
byte[] name = file.getBytes(entry.getName());
byte[] extra = entry.getExtra().getBytes();
byte[] comment = entry.getComment() != null
? file.getBytes(entry.getComment()) : new byte[]{};
byte[] buf = new byte[FIXED_DATA_SIZE + name.length + extra.length + comment.length];
fillFixedSizeData(buf, entry, name.length, extra.length, comment.length, allowZip64);
System.arraycopy(name, 0, buf, FIXED_DATA_SIZE, name.length);
System.arraycopy(extra, 0, buf, FIXED_DATA_SIZE + name.length, extra.length);
System.arraycopy(comment, 0, buf, FIXED_DATA_SIZE + name.length + extra.length,
comment.length);
return buf;
}
/**
* Writes the central directory file header for the ZipEntry to an output stream. Uses the
* specified {@link ZipFileData} to encode the file name and comment.
*/
static int write(ZipFileEntry entry, ZipFileData file, boolean allowZip64, byte[] buf,
OutputStream stream) throws IOException {
if (buf == null || buf.length < FIXED_DATA_SIZE) {
buf = new byte[FIXED_DATA_SIZE];
}
ExtraDataList extra = new ExtraDataList(entry.getExtra());
if (allowZip64) {
addZip64Extra(entry);
} else {
extra.remove((short) 0x0001);
}
extra.remove(ExtraDataList.EXTENDED_TIMESTAMP);
extra.remove(ExtraDataList.INFOZIP_UNIX_NEW);
byte[] name = entry.getName().getBytes(file.getCharset());
byte[] extraBytes = extra.getBytes();
byte[] comment = entry.getComment() != null
? entry.getComment().getBytes(file.getCharset()) : new byte[]{};
fillFixedSizeData(buf, entry, name.length, extraBytes.length, comment.length, allowZip64);
stream.write(buf, 0, FIXED_DATA_SIZE);
stream.write(name);
stream.write(extraBytes);
stream.write(comment);
return FIXED_DATA_SIZE + name.length + extraBytes.length + comment.length;
}
/**
* Write the fixed size data portion for the specified ZIP entry to the buffer.
* @throws ZipException
*/
private static void fillFixedSizeData(byte[] buf, ZipFileEntry entry, int nameLength,
int extraLength, int commentLength, boolean allowZip64) throws ZipException {
if (!allowZip64 && entry.getFeatureSet().contains(Feature.ZIP64_CSIZE)) {
throw new ZipException(String.format("Writing an entry with compressed size %d without"
+ " Zip64 extensions is not supported.", entry.getCompressedSize()));
}
if (!allowZip64 && entry.getFeatureSet().contains(Feature.ZIP64_SIZE)) {
throw new ZipException(String.format("Writing an entry of size %d without"
+ " Zip64 extensions is not supported.", entry.getSize()));
}
if (!allowZip64 && entry.getFeatureSet().contains(Feature.ZIP64_OFFSET)) {
throw new ZipException(String.format("Writing an entry with local header offset %d without"
+ " Zip64 extensions is not supported.", entry.getLocalHeaderOffset()));
}
int csize = (int) (entry.getFeatureSet().contains(Feature.ZIP64_CSIZE)
? -1 : entry.getCompressedSize());
int size = (int) (entry.getFeatureSet().contains(Feature.ZIP64_SIZE)
? -1 : entry.getSize());
int offset = (int) (entry.getFeatureSet().contains(Feature.ZIP64_OFFSET)
? -1 : entry.getLocalHeaderOffset());
ZipUtil.intToLittleEndian(buf, SIGNATURE_OFFSET, SIGNATURE);
ZipUtil.shortToLittleEndian(buf, VERSION_OFFSET, entry.getVersion());
ZipUtil.shortToLittleEndian(buf, VERSION_NEEDED_OFFSET, entry.getVersionNeeded());
ZipUtil.shortToLittleEndian(buf, FLAGS_OFFSET, entry.getFlags());
ZipUtil.shortToLittleEndian(buf, METHOD_OFFSET, entry.getMethod().getValue());
ZipUtil.intToLittleEndian(buf, MOD_TIME_OFFSET, ZipUtil.unixToDosTime(entry.getTime()));
ZipUtil.intToLittleEndian(buf, CRC_OFFSET, (int) (entry.getCrc() & 0xffffffff));
ZipUtil.intToLittleEndian(buf, COMPRESSED_SIZE_OFFSET, csize);
ZipUtil.intToLittleEndian(buf, UNCOMPRESSED_SIZE_OFFSET, size);
ZipUtil.shortToLittleEndian(buf, FILENAME_LENGTH_OFFSET, (short) (nameLength & 0xffff));
ZipUtil.shortToLittleEndian(buf, EXTRA_FIELD_LENGTH_OFFSET, (short) (extraLength & 0xffff));
ZipUtil.shortToLittleEndian(buf, COMMENT_LENGTH_OFFSET, (short) (commentLength & 0xffff));
ZipUtil.shortToLittleEndian(buf, DISK_START_OFFSET, (short) 0);
ZipUtil.shortToLittleEndian(buf, INTERNAL_ATTRIBUTES_OFFSET, entry.getInternalAttributes());
ZipUtil.intToLittleEndian(buf, EXTERNAL_ATTRIBUTES_OFFSET, entry.getExternalAttributes());
ZipUtil.intToLittleEndian(buf, LOCAL_HEADER_OFFSET_OFFSET, offset);
}
/**
* Update the extra data fields to contain a Zip64 extended information field if required
*/
private static void addZip64Extra(ZipFileEntry entry) {
EnumSet<Feature> features = entry.getFeatureSet();
ExtraDataList extra = entry.getExtra();
int extraSize = 0;
if (features.contains(Feature.ZIP64_SIZE)) {
extraSize += 8;
}
if (features.contains(Feature.ZIP64_CSIZE)) {
extraSize += 8;
}
if (features.contains(Feature.ZIP64_OFFSET)) {
extraSize += 8;
}
if (extraSize > 0) {
extra.remove((short) 0x0001);
byte[] zip64Extra = new byte[ExtraData.FIXED_DATA_SIZE + extraSize];
ZipUtil.shortToLittleEndian(zip64Extra, ExtraData.ID_OFFSET, (short) 0x0001);
ZipUtil.shortToLittleEndian(zip64Extra, ExtraData.LENGTH_OFFSET, (short) extraSize);
int offset = ExtraData.FIXED_DATA_SIZE;
if (features.contains(Feature.ZIP64_SIZE)) {
ZipUtil.longToLittleEndian(zip64Extra, offset, entry.getSize());
offset += 8;
}
if (features.contains(Feature.ZIP64_CSIZE)) {
ZipUtil.longToLittleEndian(zip64Extra, offset, entry.getCompressedSize());
offset += 8;
}
if (features.contains(Feature.ZIP64_OFFSET)) {
ZipUtil.longToLittleEndian(zip64Extra, offset, entry.getLocalHeaderOffset());
}
extra.add(new ExtraData(zip64Extra, 0));
}
}
}