blob: f10477fad870b5c964c21abf28f0e90f572c990f [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.apkzlib.zip;
import com.android.apkzlib.zip.utils.MsDosDateTimeUtils;
import com.google.common.base.Verify;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.annotation.Nonnull;
/**
* The Central Directory Header contains information about files stored in the zip. Instances of
* this class contain information for files that already are in the zip and, for which the data was
* read from the Central Directory. But some instances of this class are used for new files.
* Because instances of this class can refer to files not yet on the zip, some of the fields may
* not be filled in, or may be filled in with default values.
* <p>
* Because compression decision is done lazily, some data is stored with futures.
*/
public class CentralDirectoryHeader implements Cloneable {
/**
* Default "version made by" field: upper byte needs to be 0 to set to MS-DOS compatibility.
* Lower byte can be anything, really. We use 18 because aapt uses 17 :)
*/
private static final int DEFAULT_VERSION_MADE_BY = 0x0018;
/**
* Name of the file.
*/
@Nonnull
private String name;
/**
* CRC32 of the data. 0 if not yet computed.
*/
private long crc32;
/**
* Size of the file uncompressed. 0 if the file has no data.
*/
private long uncompressedSize;
/**
* Code of the program that made the zip. We actually don't care about this.
*/
private long madeBy;
/**
* General-purpose bit flag.
*/
@Nonnull
private GPFlags gpBit;
/**
* Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packTime(long)}).
*/
private long lastModTime;
/**
* Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packDate(long)}).
*/
private long lastModDate;
/**
* Extra data field contents. This field follows a specific structure according to the
* specification.
*/
@Nonnull
private ExtraField extraField;
/**
* File comment.
*/
@Nonnull
private byte[] comment;
/**
* File internal attributes.
*/
private long internalAttributes;
/**
* File external attributes.
*/
private long externalAttributes;
/**
* Offset in the file where the data is located. This will be -1 if the header corresponds to
* a new file that is not yet written in the zip and, therefore, has no written data.
*/
private long offset;
/**
* Encoded file name.
*/
private byte[] encodedFileName;
/**
* Compress information that may not have been computed yet due to lazy compression.
*/
@Nonnull
private Future<CentralDirectoryHeaderCompressInfo> compressInfo;
/**
* The file this header belongs to.
*/
@Nonnull
private final ZFile file;
/**
* Creates data for a file.
*
* @param name the file name
* @param encodedFileName the encoded file name, this array will be owned by the header
* @param uncompressedSize the uncompressed file size
* @param compressInfo computation that defines the compression information
* @param flags flags used in the entry
* @param zFile the file this header belongs to
*/
CentralDirectoryHeader(
@Nonnull String name,
@Nonnull byte[] encodedFileName,
long uncompressedSize,
@Nonnull Future<CentralDirectoryHeaderCompressInfo> compressInfo,
@Nonnull GPFlags flags,
@Nonnull ZFile zFile) {
this.name = name;
this.uncompressedSize = uncompressedSize;
crc32 = 0;
/*
* Set sensible defaults for the rest.
*/
madeBy = DEFAULT_VERSION_MADE_BY;
gpBit = flags;
lastModTime = MsDosDateTimeUtils.packCurrentTime();
lastModDate = MsDosDateTimeUtils.packCurrentDate();
extraField = new ExtraField();
comment = new byte[0];
internalAttributes = 0;
externalAttributes = 0;
offset = -1;
this.encodedFileName = encodedFileName;
this.compressInfo = compressInfo;
file = zFile;
}
/**
* Obtains the name of the file.
*
* @return the name
*/
@Nonnull
public String getName() {
return name;
}
/**
* Obtains the size of the uncompressed file.
*
* @return the size of the file
*/
public long getUncompressedSize() {
return uncompressedSize;
}
/**
* Obtains the CRC32 of the data.
*
* @return the CRC32, 0 if not yet computed
*/
public long getCrc32() {
return crc32;
}
/**
* Sets the CRC32 of the data.
*
* @param crc32 the CRC 32
*/
void setCrc32(long crc32) {
this.crc32 = crc32;
}
/**
* Obtains the code of the program that made the zip.
*
* @return the code
*/
public long getMadeBy() {
return madeBy;
}
/**
* Sets the code of the progtram that made the zip.
*
* @param madeBy the code
*/
void setMadeBy(long madeBy) {
this.madeBy = madeBy;
}
/**
* Obtains the general-purpose bit flag.
*
* @return the bit flag
*/
@Nonnull
public GPFlags getGpBit() {
return gpBit;
}
/**
* Obtains the last modification time of the entry.
*
* @return the last modification time in MS-DOS format (see
* {@link MsDosDateTimeUtils#packTime(long)})
*/
public long getLastModTime() {
return lastModTime;
}
/**
* Sets the last modification time of the entry.
*
* @param lastModTime the last modification time in MS-DOS format (see
* {@link MsDosDateTimeUtils#packTime(long)})
*/
void setLastModTime(long lastModTime) {
this.lastModTime = lastModTime;
}
/**
* Obtains the last modification date of the entry.
*
* @return the last modification date in MS-DOS format (see
* {@link MsDosDateTimeUtils#packDate(long)})
*/
public long getLastModDate() {
return lastModDate;
}
/**
* Sets the last modification date of the entry.
*
* @param lastModDate the last modification date in MS-DOS format (see
* {@link MsDosDateTimeUtils#packDate(long)})
*/
void setLastModDate(long lastModDate) {
this.lastModDate = lastModDate;
}
/**
* Obtains the data in the extra field.
*
* @return the data (returns an empty array if there is none)
*/
@Nonnull
public ExtraField getExtraField() {
return extraField;
}
/**
* Sets the data in the extra field.
*
* @param extraField the data to set
*/
public void setExtraField(@Nonnull ExtraField extraField) {
setExtraFieldNoNotify(extraField);
file.centralDirectoryChanged();
}
/**
* Sets the data in the extra field, but does not notify {@link ZFile}. This method is invoked
* when the {@link ZFile} knows the extra field is being set.
*
* @param extraField the data to set
*/
void setExtraFieldNoNotify(@Nonnull ExtraField extraField) {
this.extraField = extraField;
}
/**
* Obtains the entry's comment.
*
* @return the comment (returns an empty array if there is no comment)
*/
@Nonnull
public byte[] getComment() {
return comment;
}
/**
* Sets the entry's comment.
*
* @param comment the comment
*/
void setComment(@Nonnull byte[] comment) {
this.comment = comment;
}
/**
* Obtains the entry's internal attributes.
*
* @return the entry's internal attributes
*/
public long getInternalAttributes() {
return internalAttributes;
}
/**
* Sets the entry's internal attributes.
*
* @param internalAttributes the entry's internal attributes
*/
void setInternalAttributes(long internalAttributes) {
this.internalAttributes = internalAttributes;
}
/**
* Obtains the entry's external attributes.
*
* @return the entry's external attributes
*/
public long getExternalAttributes() {
return externalAttributes;
}
/**
* Sets the entry's external attributes.
*
* @param externalAttributes the entry's external attributes
*/
void setExternalAttributes(long externalAttributes) {
this.externalAttributes = externalAttributes;
}
/**
* Obtains the offset in the zip file where this entry's data is.
*
* @return the offset or {@code -1} if the file has no data in the zip and, therefore, data
* is stored in memory
*/
public long getOffset() {
return offset;
}
/**
* Sets the offset in the zip file where this entry's data is.
*
* @param offset the offset or {@code -1} if the file is new and has no data in the zip yet
*/
void setOffset(long offset) {
this.offset = offset;
}
/**
* Obtains the encoded file name.
*
* @return the encoded file name
*/
public byte[] getEncodedFileName() {
return encodedFileName;
}
/**
* Resets the deferred CRC flag in the GP flags.
*/
void resetDeferredCrc() {
/*
* We actually create a new set of flags. Since the only information we care about is the
* UTF-8 encoding, we'll just create a brand new object.
*/
gpBit = GPFlags.make(gpBit.isUtf8FileName());
}
@Override
protected CentralDirectoryHeader clone() throws CloneNotSupportedException {
CentralDirectoryHeader cdr = (CentralDirectoryHeader) super.clone();
cdr.extraField = extraField;
cdr.comment = Arrays.copyOf(comment, comment.length);
cdr.encodedFileName = Arrays.copyOf(encodedFileName, encodedFileName.length);
return cdr;
}
/**
* Obtains the future with the compression information.
*
* @return the information
*/
@Nonnull
public Future<CentralDirectoryHeaderCompressInfo> getCompressionInfo() {
return compressInfo;
}
/**
* Equivalent to {@code getCompressionInfo().get()} but masking the possible exceptions and
* guaranteeing non-{@code null} return.
*
* @return the result of the future
* @throws IOException failed to get the information
*/
@Nonnull
public CentralDirectoryHeaderCompressInfo getCompressionInfoWithWait()
throws IOException {
try {
CentralDirectoryHeaderCompressInfo info = getCompressionInfo().get();
Verify.verifyNotNull(info, "info == null");
return info;
} catch (InterruptedException e) {
throw new IOException("Interrupted while waiting for compression information.", e);
} catch (ExecutionException e) {
throw new IOException("Execution of compression failed.", e);
}
}
}