|
| 1 | +/* |
| 2 | + * Copyright (C) 2016 The Android Open Source Project |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package processing.mode.android; |
| 17 | + |
| 18 | +import java.nio.ByteBuffer; |
| 19 | +import java.nio.ByteOrder; |
| 20 | + |
| 21 | +/** |
| 22 | + * Assorted ZIP format helpers. |
| 23 | + * |
| 24 | + * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte |
| 25 | + * order of these buffers is little-endian. |
| 26 | + */ |
| 27 | +public abstract class ZipUtils { |
| 28 | + private ZipUtils() {} |
| 29 | + private static final int ZIP_EOCD_REC_MIN_SIZE = 22; |
| 30 | + private static final int ZIP_EOCD_REC_SIG = 0x06054b50; |
| 31 | + private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12; |
| 32 | + private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; |
| 33 | + private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; |
| 34 | + private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; |
| 35 | + private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50; |
| 36 | + private static final int UINT32_MAX_VALUE = 0xffff; |
| 37 | + /** |
| 38 | + * Returns the position at which ZIP End of Central Directory record starts in the provided |
| 39 | + * buffer or {@code -1} if the record is not present. |
| 40 | + * |
| 41 | + * <p>NOTE: Byte order of {@code zipContents} must be little-endian. |
| 42 | + */ |
| 43 | + public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) { |
| 44 | + assertByteOrderLittleEndian(zipContents); |
| 45 | + // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. |
| 46 | + // The record can be identified by its 4-byte signature/magic which is located at the very |
| 47 | + // beginning of the record. A complication is that the record is variable-length because of |
| 48 | + // the comment field. |
| 49 | + // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from |
| 50 | + // end of the buffer for the EOCD record signature. Whenever we find a signature, we check |
| 51 | + // the candidate record's comment length is such that the remainder of the record takes up |
| 52 | + // exactly the remaining bytes in the buffer. The search is bounded because the maximum |
| 53 | + // size of the comment field is 65535 bytes because the field is an unsigned 32-bit number. |
| 54 | + int archiveSize = zipContents.capacity(); |
| 55 | + System.out.println(archiveSize + " " + ZIP_EOCD_REC_MIN_SIZE); |
| 56 | + if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { |
| 57 | + System.out.println("File size smaller than EOCD min size"); |
| 58 | + return -1; |
| 59 | + } |
| 60 | + int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT32_MAX_VALUE); |
| 61 | + int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; |
| 62 | + for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; |
| 63 | + expectedCommentLength++) { |
| 64 | + int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; |
| 65 | + if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { |
| 66 | + int actualCommentLength = |
| 67 | + getUnsignedInt16( |
| 68 | + zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); |
| 69 | + if (actualCommentLength == expectedCommentLength) { |
| 70 | + return eocdStartPos; |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | + return -1; |
| 75 | + } |
| 76 | + /** |
| 77 | + * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory |
| 78 | + * Locator. |
| 79 | + * |
| 80 | + * <p>NOTE: Byte order of {@code zipContents} must be little-endian. |
| 81 | + */ |
| 82 | + public static final boolean isZip64EndOfCentralDirectoryLocatorPresent( |
| 83 | + ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) { |
| 84 | + assertByteOrderLittleEndian(zipContents); |
| 85 | + // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central |
| 86 | + // Directory Record. |
| 87 | + int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE; |
| 88 | + if (locatorPosition < 0) { |
| 89 | + return false; |
| 90 | + } |
| 91 | + return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG; |
| 92 | + } |
| 93 | + /** |
| 94 | + * Returns the offset of the start of the ZIP Central Directory in the archive. |
| 95 | + * |
| 96 | + * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. |
| 97 | + */ |
| 98 | + public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { |
| 99 | + assertByteOrderLittleEndian(zipEndOfCentralDirectory); |
| 100 | + return getUnsignedInt32( |
| 101 | + zipEndOfCentralDirectory, |
| 102 | + zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); |
| 103 | + } |
| 104 | + /** |
| 105 | + * Sets the offset of the start of the ZIP Central Directory in the archive. |
| 106 | + * |
| 107 | + * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. |
| 108 | + */ |
| 109 | + public static void setZipEocdCentralDirectoryOffset( |
| 110 | + ByteBuffer zipEndOfCentralDirectory, long offset) { |
| 111 | + assertByteOrderLittleEndian(zipEndOfCentralDirectory); |
| 112 | + setUnsignedInt32( |
| 113 | + zipEndOfCentralDirectory, |
| 114 | + zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, |
| 115 | + offset); |
| 116 | + } |
| 117 | + /** |
| 118 | + * Returns the size (in bytes) of the ZIP Central Directory. |
| 119 | + * |
| 120 | + * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. |
| 121 | + */ |
| 122 | + public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { |
| 123 | + assertByteOrderLittleEndian(zipEndOfCentralDirectory); |
| 124 | + return getUnsignedInt32( |
| 125 | + zipEndOfCentralDirectory, |
| 126 | + zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); |
| 127 | + } |
| 128 | + private static void assertByteOrderLittleEndian(ByteBuffer buffer) { |
| 129 | + if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { |
| 130 | + throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); |
| 131 | + } |
| 132 | + } |
| 133 | + private static int getUnsignedInt16(ByteBuffer buffer, int offset) { |
| 134 | + return buffer.getShort(offset) & 0xffff; |
| 135 | + } |
| 136 | + private static long getUnsignedInt32(ByteBuffer buffer, int offset) { |
| 137 | + return buffer.getInt(offset) & 0xffffffffL; |
| 138 | + } |
| 139 | + private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { |
| 140 | + if ((value < 0) || (value > 0xffffffffL)) { |
| 141 | + throw new IllegalArgumentException("uint32 value of out range: " + value); |
| 142 | + } |
| 143 | + buffer.putInt(buffer.position() + offset, (int) value); |
| 144 | + } |
| 145 | +} |
0 commit comments