| /* |
| * Copyright (C) 2019 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.zipflinger; |
| |
| import com.android.annotations.NonNull; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| |
| class LocalFileHeader { |
| private static final int SIGNATURE = 0x04034b50; |
| |
| public static final int LOCAL_FILE_HEADER_SIZE = 30; |
| |
| // Minimum number of bytes needed to create a virtual zip entry (an entry not present in |
| // the Central Directory with name length = 0 and an extra field containing padding data). |
| public static final long VIRTUAL_HEADER_SIZE = LOCAL_FILE_HEADER_SIZE; |
| |
| public static final short COMPRESSION_NONE = 0; |
| public static final short COMPRESSION_DEFLATE = 8; |
| |
| static final long VIRTUAL_ENTRY_MAX_SIZE = LOCAL_FILE_HEADER_SIZE + Ints.USHRT_MAX; |
| static final long OFFSET_TO_NAME = 26; |
| |
| // Zip64 extra payload must only include uncompressed size and compressed size. It differs |
| // from the Central Directory Record which also features an uint64_t offset to the LFH. |
| private static final int ZIP64_PAYLOAD_SIZE = Long.BYTES * 2; |
| private static final int ZIP64_EXTRA_SIZE = Short.BYTES * 2 + ZIP64_PAYLOAD_SIZE; |
| |
| private final byte[] nameBytes; |
| private final short compressionFlag; |
| private final int crc; |
| private final long compressedSize; |
| private final long uncompressedSize; |
| private final boolean isZip64; |
| private int padding; |
| |
| LocalFileHeader(Source source) { |
| this.nameBytes = source.getNameBytes(); |
| this.compressionFlag = source.getCompressionFlag(); |
| this.crc = source.getCrc(); |
| this.compressedSize = source.getCompressedSize(); |
| this.uncompressedSize = source.getUncompressedSize(); |
| this.isZip64 = compressedSize > Zip64.LONG_MAGIC || uncompressedSize > Zip64.LONG_MAGIC; |
| this.padding = 0; |
| } |
| |
| public static void fillVirtualEntry(@NonNull ByteBuffer virtualEntry) { |
| int sizeToFill = virtualEntry.capacity(); |
| if (sizeToFill < VIRTUAL_HEADER_SIZE) { |
| String message = String.format("Not enough space for virtual entry (%d)", sizeToFill); |
| throw new IllegalStateException(message); |
| } |
| virtualEntry.order(ByteOrder.LITTLE_ENDIAN); |
| virtualEntry.putInt(SIGNATURE); |
| virtualEntry.putShort((short) 0); // Version needed |
| virtualEntry.putShort((short) 0); // general purpose flag |
| virtualEntry.putShort(COMPRESSION_NONE); |
| virtualEntry.putShort(CentralDirectoryRecord.DEFAULT_TIME); |
| virtualEntry.putShort(CentralDirectoryRecord.DEFAULT_DATE); |
| virtualEntry.putInt(0); // CRC-32 |
| virtualEntry.putInt(0); // compressed size |
| virtualEntry.putInt(0); // uncompressed size |
| virtualEntry.putShort((short) 0); // file name length |
| // -2 for the extra length ushort we have to write |
| virtualEntry.putShort(Ints.intToUshort(virtualEntry.remaining() - 2)); // extra length |
| virtualEntry.rewind(); |
| } |
| |
| public void setPadding(int padding) { |
| if (padding > Ints.USHRT_MAX) { |
| String err = String.format("Padding cannot be more than %s bytes", Ints.USHRT_MAX); |
| throw new IllegalStateException(err); |
| } |
| this.padding = padding; |
| } |
| |
| public void write(@NonNull ZipWriter writer) throws IOException { |
| ByteBuffer extraField = buildExtraField(); |
| int bytesNeeded = LOCAL_FILE_HEADER_SIZE + nameBytes.length + extraField.capacity(); |
| |
| short versionNeeded = isZip64 ? Zip64.VERSION_NEEDED : 0; |
| int size = isZip64 ? Zip64.INT_MAGIC : Ints.longToUint(uncompressedSize); |
| int csize = isZip64 ? Zip64.INT_MAGIC : Ints.longToUint(compressedSize); |
| |
| ByteBuffer buffer = ByteBuffer.allocate(bytesNeeded).order(ByteOrder.LITTLE_ENDIAN); |
| buffer.putInt(SIGNATURE); |
| buffer.putShort(versionNeeded); |
| |
| buffer.putShort((short) 0); // general purpose flag |
| buffer.putShort(compressionFlag); |
| buffer.putShort(CentralDirectoryRecord.DEFAULT_TIME); |
| buffer.putShort(CentralDirectoryRecord.DEFAULT_DATE); |
| buffer.putInt(crc); |
| buffer.putInt(csize); // compressed size |
| buffer.putInt(size); // size |
| buffer.putShort(Ints.intToUshort(nameBytes.length)); |
| buffer.putShort(Ints.intToUshort(extraField.capacity())); // Extra size |
| buffer.put(nameBytes); |
| buffer.put(extraField); |
| |
| buffer.rewind(); |
| writer.write(buffer); |
| } |
| |
| public long getSize() { |
| long extraSize = isZip64 ? ZIP64_EXTRA_SIZE : 0; |
| return LOCAL_FILE_HEADER_SIZE + nameBytes.length + extraSize; |
| } |
| |
| @NonNull |
| private ByteBuffer buildExtraField() { |
| if (!isZip64) { |
| return ByteBuffer.allocate(padding); |
| } |
| |
| ByteBuffer zip64extra = ByteBuffer.allocate(ZIP64_EXTRA_SIZE + padding); |
| zip64extra.order(ByteOrder.LITTLE_ENDIAN); |
| zip64extra.putShort(Zip64.EXTRA_ID); |
| zip64extra.putShort(Ints.intToUshort(ZIP64_PAYLOAD_SIZE)); |
| zip64extra.putLong(uncompressedSize); |
| zip64extra.putLong(compressedSize); |
| zip64extra.rewind(); |
| return zip64extra; |
| } |
| } |