blob: df9b43bc01217b913c63e0ad672c3a1c533dbd06 [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 java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.zip.ZipException;
/** A utility class for reading and writing {@link ZipFileEntry}s from byte arrays. */
public class ZipUtil {
/**
* Midnight Jan 1st 1980. Uses the current time zone as the DOS format does not support time zones
* and will always assume the current zone.
*/
public static final long DOS_EPOCH =
new GregorianCalendar(1980, Calendar.JANUARY, 1, 0, 0, 0).getTimeInMillis();
/** 23:59:59 Dec 31st 2107. The maximum date representable in DOS format. */
public static final long MAX_DOS_DATE =
new GregorianCalendar(2107, Calendar.DECEMBER, 31, 23, 59, 59).getTimeInMillis();
/* DOS format timestamp field offsets. */
private static final int DOS_MINUTE_OFFSET = 5;
private static final int DOS_HOUR_OFFSET = 11;
private static final int DOS_DAY_OFFSET = 16;
private static final int DOS_MONTH_OFFSET = 21;
private static final int DOS_YEAR_OFFSET = 25;
/** Converts a integral value to the corresponding little endian array. */
private static byte[] integerToLittleEndian(byte[] buf, int offset, long value, int numBytes) {
for (int i = 0; i < numBytes; i++) {
buf[i + offset] = (byte) ((value & (0xffL << (i * 8))) >> (i * 8));
}
return buf;
}
/** Converts a short to the corresponding 2-byte little endian array. */
static byte[] shortToLittleEndian(short value) {
return integerToLittleEndian(new byte[2], 0, value, 2);
}
/** Writes a short to the buffer as a 2-byte little endian array starting at offset. */
static byte[] shortToLittleEndian(byte[] buf, int offset, short value) {
return integerToLittleEndian(buf, offset, value, 2);
}
/** Converts an int to the corresponding 4-byte little endian array. */
static byte[] intToLittleEndian(int value) {
return integerToLittleEndian(new byte[4], 0, value, 4);
}
/** Writes an int to the buffer as a 4-byte little endian array starting at offset. */
static byte[] intToLittleEndian(byte[] buf, int offset, int value) {
return integerToLittleEndian(buf, offset, value, 4);
}
/** Converts a long to the corresponding 8-byte little endian array. */
static byte[] longToLittleEndian(long value) {
return integerToLittleEndian(new byte[8], 0, value, 8);
}
/** Writes a long to the buffer as a 8-byte little endian array starting at offset. */
static byte[] longToLittleEndian(byte[] buf, int offset, long value) {
return integerToLittleEndian(buf, offset, value, 8);
}
/** Reads 16 bits in little-endian byte order from the buffer at the given offset. */
static short get16(byte[] source, int offset) {
int a = source[offset + 0] & 0xff;
int b = source[offset + 1] & 0xff;
return (short) ((b << 8) | a);
}
/** Reads 32 bits in little-endian byte order from the buffer at the given offset. */
static int get32(byte[] source, int offset) {
int a = source[offset + 0] & 0xff;
int b = source[offset + 1] & 0xff;
int c = source[offset + 2] & 0xff;
int d = source[offset + 3] & 0xff;
return (d << 24) | (c << 16) | (b << 8) | a;
}
/** Reads 64 bits in little-endian byte order from the buffer at the given offset. */
static long get64(byte[] source, int offset) {
long a = source[offset + 0] & 0xffL;
long b = source[offset + 1] & 0xffL;
long c = source[offset + 2] & 0xffL;
long d = source[offset + 3] & 0xffL;
long e = source[offset + 4] & 0xffL;
long f = source[offset + 5] & 0xffL;
long g = source[offset + 6] & 0xffL;
long h = source[offset + 7] & 0xffL;
return (h << 56) | (g << 48) | (f << 40) | (e << 32) | (d << 24) | (c << 16) | (b << 8) | a;
}
/**
* Reads an unsigned short in little-endian byte order from the buffer at the given offset.
* Casts to an int to allow proper numerical comparison.
*/
static int getUnsignedShort(byte[] source, int offset) {
return get16(source, offset) & 0xffff;
}
/**
* Reads an unsigned int in little-endian byte order from the buffer at the given offset.
* Casts to a long to allow proper numerical comparison.
*/
static long getUnsignedInt(byte[] source, int offset) {
return get32(source, offset) & 0xffffffffL;
}
/**
* Reads an unsigned long in little-endian byte order from the buffer at the given offset.
* Performs bounds checking to see if the unsigned long will be properly represented in Java's
* signed value.
*/
static long getUnsignedLong(byte[] source, int offset) throws ZipException {
long result = get64(source, offset);
if (result < 0) {
throw new ZipException("The requested unsigned long value is too large for Java's signed"
+ "values. This Zip file is unsupported");
}
return result;
}
/** Checks if the unix timestamp is representable as a valid DOS timestamp.
*
* <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> for
* a general description of the date a time fields (Section 4.4.6) and
* <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724247.aspx">DOS date
* format</a> for a detailed description of the format.
*/
static boolean isValidInDos(long timeMillis) {
Calendar time = Calendar.getInstance();
time.setTimeInMillis(timeMillis);
Calendar minTime = Calendar.getInstance();
minTime.setTimeInMillis(DOS_EPOCH);
Calendar maxTime = Calendar.getInstance();
maxTime.setTimeInMillis(MAX_DOS_DATE);
return (!time.before(minTime) && !time.after(maxTime));
}
/** Converts a unix timestamp into a 32-bit DOS timestamp. */
static int unixToDosTime(long timeMillis) {
Calendar time = Calendar.getInstance();
time.setTimeInMillis(timeMillis);
if (!isValidInDos(timeMillis)) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
throw new IllegalArgumentException(String.format("%s is not representable in the DOS time"
+ " format. It must be in the range %s to %s", df.format(time.getTime()),
df.format(new Date(DOS_EPOCH)), df.format(new Date(MAX_DOS_DATE))));
}
int dos = time.get(Calendar.SECOND) / 2;
dos |= time.get(Calendar.MINUTE) << DOS_MINUTE_OFFSET;
dos |= time.get(Calendar.HOUR_OF_DAY) << DOS_HOUR_OFFSET;
dos |= time.get(Calendar.DAY_OF_MONTH) << DOS_DAY_OFFSET;
dos |= (time.get(Calendar.MONTH) + 1) << DOS_MONTH_OFFSET;
dos |= (time.get(Calendar.YEAR) - 1980) << DOS_YEAR_OFFSET;
return dos;
}
/** Converts a 32-bit DOS timestamp into a unix timestamp. */
static long dosToUnixTime(int timestamp) {
Calendar time = Calendar.getInstance();
time.clear();
time.set(Calendar.SECOND, (timestamp & 0x1f) * 2);
time.set(Calendar.MINUTE, (timestamp >> DOS_MINUTE_OFFSET) & 0x3f);
time.set(Calendar.HOUR_OF_DAY, (timestamp >> DOS_HOUR_OFFSET) & 0x1f);
time.set(Calendar.DAY_OF_MONTH, (timestamp >> DOS_DAY_OFFSET) & 0x1f);
time.set(Calendar.MONTH, ((timestamp >> DOS_MONTH_OFFSET) & 0x0f) - 1);
time.set(Calendar.YEAR, ((timestamp >> DOS_YEAR_OFFSET) & 0x7f) + 1980);
return time.getTimeInMillis();
}
/** Checks if array starts with target. */
static boolean arrayStartsWith(byte[] array, byte[] target) {
if (array == null) {
return false;
}
if (target == null) {
return true;
}
if (target.length > array.length) {
return false;
}
for (int i = 0; i < target.length; i++) {
if (array[i] != target[i]) {
return false;
}
}
return true;
}
/** Read from the input stream into the array until it is full. */
static int readFully(InputStream in, byte[] b) throws IOException {
return readFully(in, b, 0, b.length);
}
/** Read from the input stream into the array starting at off until len bytes have been read. */
static int readFully(InputStream in, byte[] b, int off, int len) throws IOException {
if (len < 0) {
throw new IndexOutOfBoundsException();
}
int n = 0;
while (n < len) {
int count = in.read(b, off + n, len - n);
if (count < 0) {
return n;
}
n += count;
}
return n;
}
}