blob: d6342d05dd5f809a4f5a71264d34a0909c970f2b [file] [log] [blame]
// Copyright 2016 Google Inc. 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.archivepatcher.generator;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
/**
* A class that contains <em>just enough data</em> to generate a patch.
*/
public class MinimalZipEntry {
/**
* The compression method that was used, typically 8 (for deflate) or 0 (for stored).
*/
private final int compressionMethod;
/**
* The CRC32 of the <em>uncompressed</em> data.
*/
private final long crc32OfUncompressedData;
/**
* The size of the data as it exists in the archive. For compressed entries, this is the size of
* the compressed data; for uncompressed entries, this is the same as {@link #uncompressedSize}.
*/
private final long compressedSize;
/**
* The size of the <em>uncompressed</em data.
*/
private final long uncompressedSize;
/**
* The file name for the entry. By convention, names ending with '/' denote directories. The
* encoding is controlled by the general purpose flags, bit 11. See {@link #getFileName()} for
* more information.
*/
private final byte[] fileNameBytes;
/**
* The value of the 11th bit of the general purpose flag, which controls the encoding of file
* names and comments. See {@link #getFileName()} for more information.
*/
private final boolean generalPurposeFlagBit11;
/**
* The file offset at which the first byte of the local entry header begins.
*/
private final long fileOffsetOfLocalEntry;
/**
* The file offset at which the first byte of the data for the entry begins. For compressed data,
* this is the first byte of the deflated data; for uncompressed data, this is the first byte of
* the uncompressed data.
*/
private long fileOffsetOfCompressedData = -1;
/**
* Create a new Central Directory entry with the corresponding data.
* @param compressionMethod the method used to compress the data
* @param crc32OfUncompressedData the CRC32 of the uncompressed data
* @param compressedSize the size of the data in its compressed form
* @param uncompressedSize the size of the data in its uncompressed form
* @param fileNameBytes the name of the file, as a byte array; see {@link #getFileName()} for
* information on encoding
* @param generalPurposeFlagBit11 the value of the 11th bit of the general purpose flag, which
* nominally controls the default character encoding for file names and comments; see
* {@link #getFileName()} for more information on encoding
* @param fileOffsetOfLocalEntry the file offset at which the local entry begins
*/
public MinimalZipEntry(
int compressionMethod,
long crc32OfUncompressedData,
long compressedSize,
long uncompressedSize,
byte[] fileNameBytes,
boolean generalPurposeFlagBit11,
long fileOffsetOfLocalEntry) {
this.compressionMethod = compressionMethod;
this.crc32OfUncompressedData = crc32OfUncompressedData;
this.compressedSize = compressedSize;
this.uncompressedSize = uncompressedSize;
this.fileNameBytes = fileNameBytes == null ? null : fileNameBytes.clone();
this.generalPurposeFlagBit11 = generalPurposeFlagBit11;
this.fileOffsetOfLocalEntry = fileOffsetOfLocalEntry;
}
/**
* Sets the file offset at which the data for this entry begins.
* @param offset the offset
*/
public void setFileOffsetOfCompressedData(long offset) {
fileOffsetOfCompressedData = offset;
}
/**
* Returns the compression method that was used, typically 8 (for deflate) or 0 (for stored).
* @return as described
*/
public int getCompressionMethod() {
return compressionMethod;
}
/**
* Returns the CRC32 of the uncompressed data.
* @return as described
*/
public long getCrc32OfUncompressedData() {
return crc32OfUncompressedData;
}
/**
* Returns the size of the data as it exists in the archive. For compressed entries, this is the
* size of the compressed data; for uncompressed entries, this is the same as
* {@link #getUncompressedSize()}.
* @return as described
*/
public long getCompressedSize() {
return compressedSize;
}
/**
* Returns the size of the uncompressed data.
* @return as described
*/
public long getUncompressedSize() {
return uncompressedSize;
}
/**
* Returns a copy of the bytes of the file name, exactly the same as they were in the archive
* file. See {@link #getFileName()} for an explanation of why this is useful.
* @return as described
*/
public byte[] getFileNameBytes() {
return fileNameBytes == null ? null : fileNameBytes.clone();
}
/**
* Returns a best-effort conversion of the file name into a string, based on strict adherence to
* the PKWARE APPNOTE that defines this behavior. If the value of the 11th bit of the general
* purpose flag was set to 1, these bytes should be encoded with the UTF8 character set; otherwise
* the character set should be Cp437. Adherence to this standard varies significantly, and some
* systems use the default character set for the environment instead of Cp437 when writing these
* bytes. For such instances, callers can obtain the raw bytes by using
* {@link #getFileNameBytes()} instead and checking the value of the 11th bit of the general
* purpose bit flag for a hint using {@link #getGeneralPurposeFlagBit11()}. There is also
* something called EFS ("0x0008 extra field storage") that specifies additional behavior for
* character encoding, but this tool doesn't support it as the use is not standardized.
* @return as described
*/
// TODO(andrewhayden): Support EFS
public String getFileName() {
String charsetName = generalPurposeFlagBit11 ? "UTF8" : "Cp437";
try {
return new String(fileNameBytes, charsetName);
} catch (UnsupportedEncodingException e) {
// Cp437 has been supported at least since JDK 1.6.0, so this should rarely occur in practice.
// Older versions of the JDK also support Cp437, but as part of charsets.jar, which didn't
// ship in every distribution; it is conceivable that those systems might have problems here.
throw new RuntimeException("System doesn't support " + charsetName, e);
}
}
/**
* Returns the value of the 11th bit of the general purpose flag; true for 1, false for 0. See
* {@link #getFileName()} for more information on the usefulness of this flag.
* @return as described
*/
public boolean getGeneralPurposeFlagBit11() {
return generalPurposeFlagBit11;
}
/**
* Returns the file offset at which the first byte of the local entry header begins.
* @return as described
*/
public long getFileOffsetOfLocalEntry() {
return fileOffsetOfLocalEntry;
}
/**
* Returns the file offset at which the first byte of the data for the entry begins. For
* compressed data, this is the first byte of the deflated data; for uncompressed data, this is
* the first byte of the uncompressed data.
* @return as described
*/
public long getFileOffsetOfCompressedData() {
return fileOffsetOfCompressedData;
}
/**
* Convenience methods that returns true if and only if the entry is compressed with deflate.
* @return as described
*/
public boolean isDeflateCompressed() {
// 8 is deflate according to the zip spec.
if (getCompressionMethod() != 8) {
return false;
}
// Some tools may list compression method deflate but set level to zero (store), so they will
// have a compressed size equal to the uncompresesd size. Don't consider such things to be
// compressed, even if they are "deflated".
return getCompressedSize() != getUncompressedSize();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (compressedSize ^ (compressedSize >>> 32));
result = prime * result + compressionMethod;
result = prime * result + (int) (crc32OfUncompressedData ^ (crc32OfUncompressedData >>> 32));
result = prime * result + Arrays.hashCode(fileNameBytes);
result =
prime * result + (int) (fileOffsetOfCompressedData ^ (fileOffsetOfCompressedData >>> 32));
result = prime * result + (int) (fileOffsetOfLocalEntry ^ (fileOffsetOfLocalEntry >>> 32));
result = prime * result + (generalPurposeFlagBit11 ? 1231 : 1237);
result = prime * result + (int) (uncompressedSize ^ (uncompressedSize >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MinimalZipEntry other = (MinimalZipEntry) obj;
if (compressedSize != other.compressedSize) {
return false;
}
if (compressionMethod != other.compressionMethod) {
return false;
}
if (crc32OfUncompressedData != other.crc32OfUncompressedData) {
return false;
}
if (!Arrays.equals(fileNameBytes, other.fileNameBytes)) {
return false;
}
if (fileOffsetOfCompressedData != other.fileOffsetOfCompressedData) {
return false;
}
if (fileOffsetOfLocalEntry != other.fileOffsetOfLocalEntry) {
return false;
}
if (generalPurposeFlagBit11 != other.generalPurposeFlagBit11) {
return false;
}
if (uncompressedSize != other.uncompressedSize) {
return false;
}
return true;
}
}