| /* |
| * 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.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| |
| class CentralDirectoryRecord { |
| |
| public static final int SIGNATURE = 0x02014b50; |
| public static final int SIZE = 46; |
| public static final int DATA_DESCRIPTOR_FLAG = 0x0008; |
| public static final int DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; |
| |
| // JDK 9 consider time&data field with value 0 as invalid. Use 1 instead. |
| // These are in MS-DOS 16-bit format. For actual specs, see: |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724247(v=vs.85).aspx |
| public static final short DEFAULT_TIME = 1 | 1 << 5 | 1 << 11; |
| public static final short DEFAULT_DATE = 1 | 1 << 5 | 1 << 9; |
| |
| // Zip64 extra format: |
| // uint16_t id (0x0001) |
| // uint16_t size payload (0x18) |
| // Payload: |
| // - uint64_t Uncompressed size. |
| // - uint64_t Compressed size. |
| // - uint64_t offset to LFH in archive. |
| private static final int ZIP64_PAYLOAD_SIZE = Long.BYTES * 3; |
| private static final int ZIP64_EXTRA_SIZE = Short.BYTES * 2 + ZIP64_PAYLOAD_SIZE; |
| |
| private final byte[] nameBytes; |
| private final int crc; |
| private final long compressedSize; |
| private final long uncompressedSize; |
| // Location of the Local file header to end of payload in file space. |
| private final Location location; |
| private final short compressionFlag; |
| private final Location payloadLocation; |
| private final boolean isZip64; |
| |
| CentralDirectoryRecord( |
| @NonNull byte[] nameBytes, |
| int crc, |
| long compressedSize, |
| long uncompressedSize, |
| Location location, |
| short compressionFlag, |
| Location payloadLocation) { |
| this.nameBytes = nameBytes; |
| this.crc = crc; |
| this.compressedSize = compressedSize; |
| this.uncompressedSize = uncompressedSize; |
| this.location = location; |
| this.compressionFlag = compressionFlag; |
| this.payloadLocation = payloadLocation; |
| this.isZip64 = |
| compressedSize > Zip64.LONG_MAGIC |
| || uncompressedSize > Zip64.LONG_MAGIC |
| || location.first > Zip64.LONG_MAGIC; |
| } |
| |
| void write(@NonNull ByteBuffer buf) { |
| 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); |
| int offset = isZip64 ? Zip64.INT_MAGIC : Ints.longToUint(location.first); |
| |
| ByteBuffer extra = buildExtraField(); |
| |
| buf.putInt(SIGNATURE); |
| buf.putShort((short) 0); // version made by |
| buf.putShort(versionNeeded); |
| buf.putShort((short) 0); // flag |
| buf.putShort(compressionFlag); |
| buf.putShort(DEFAULT_TIME); |
| buf.putShort(DEFAULT_DATE); |
| buf.putInt(crc); |
| buf.putInt(csize); // compressed size |
| buf.putInt(size); // size |
| buf.putShort(Ints.intToUshort(nameBytes.length)); |
| buf.putShort(Ints.intToUshort(extra.capacity())); |
| buf.putShort((short) 0); // comment size |
| buf.putShort((short) 0); // disk # start |
| buf.putShort((short) 0); // internal att |
| buf.putInt(0); // external att |
| buf.putInt(offset); |
| buf.put(nameBytes); |
| buf.put(extra); |
| } |
| |
| short getCompressionFlag() { |
| return compressionFlag; |
| } |
| |
| long getSize() { |
| long extraSize = isZip64 ? ZIP64_EXTRA_SIZE : 0; |
| return SIZE + nameBytes.length + extraSize; |
| } |
| |
| @NonNull |
| Location getPayloadLocation() { |
| return payloadLocation; |
| } |
| |
| @NonNull |
| Location getLocation() { |
| return location; |
| } |
| |
| @NonNull |
| private ByteBuffer buildExtraField() { |
| if (!isZip64) { |
| return ByteBuffer.allocate(0); |
| } |
| ByteBuffer buf = ByteBuffer.allocate(ZIP64_EXTRA_SIZE).order(ByteOrder.LITTLE_ENDIAN); |
| buf.putShort(Zip64.EXTRA_ID); |
| buf.putShort(Ints.intToUshort(ZIP64_PAYLOAD_SIZE)); |
| buf.putLong(uncompressedSize); |
| buf.putLong(compressedSize); |
| buf.putLong(location.first); |
| buf.rewind(); |
| return buf; |
| } |
| } |