ZipUtils.java revision 914b37f4e3430606ec9850f05c51fb112279484b
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 17package com.android.signapk; 18 19import java.nio.ByteBuffer; 20import java.nio.ByteOrder; 21 22/** 23 * Assorted ZIP format helpers. 24 * 25 * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte 26 * order of these buffers is little-endian. 27 */ 28public abstract class ZipUtils { 29 private ZipUtils() {} 30 31 private static final int ZIP_EOCD_REC_MIN_SIZE = 22; 32 private static final int ZIP_EOCD_REC_SIG = 0x06054b50; 33 private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12; 34 private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; 35 private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; 36 37 private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; 38 private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50; 39 40 private static final int UINT16_MAX_VALUE = 0xffff; 41 42 /** 43 * Returns the position at which ZIP End of Central Directory record starts in the provided 44 * buffer or {@code -1} if the record is not present. 45 * 46 * <p>NOTE: Byte order of {@code zipContents} must be little-endian. 47 */ 48 public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) { 49 assertByteOrderLittleEndian(zipContents); 50 51 // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. 52 // The record can be identified by its 4-byte signature/magic which is located at the very 53 // beginning of the record. A complication is that the record is variable-length because of 54 // the comment field. 55 // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from 56 // end of the buffer for the EOCD record signature. Whenever we find a signature, we check 57 // the candidate record's comment length is such that the remainder of the record takes up 58 // exactly the remaining bytes in the buffer. The search is bounded because the maximum 59 // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. 60 61 int archiveSize = zipContents.capacity(); 62 if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { 63 return -1; 64 } 65 int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE); 66 int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; 67 for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; 68 expectedCommentLength++) { 69 int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; 70 if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { 71 int actualCommentLength = 72 getUnsignedInt16( 73 zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); 74 if (actualCommentLength == expectedCommentLength) { 75 return eocdStartPos; 76 } 77 } 78 } 79 80 return -1; 81 } 82 83 /** 84 * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory 85 * Locator. 86 * 87 * <p>NOTE: Byte order of {@code zipContents} must be little-endian. 88 */ 89 public static final boolean isZip64EndOfCentralDirectoryLocatorPresent( 90 ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) { 91 assertByteOrderLittleEndian(zipContents); 92 93 // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central 94 // Directory Record. 95 96 int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE; 97 if (locatorPosition < 0) { 98 return false; 99 } 100 101 return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG; 102 } 103 104 /** 105 * Returns 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 long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { 110 assertByteOrderLittleEndian(zipEndOfCentralDirectory); 111 return getUnsignedInt32( 112 zipEndOfCentralDirectory, 113 zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); 114 } 115 116 /** 117 * Sets the offset of the start of the ZIP Central Directory in the archive. 118 * 119 * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 120 */ 121 public static void setZipEocdCentralDirectoryOffset( 122 ByteBuffer zipEndOfCentralDirectory, long offset) { 123 assertByteOrderLittleEndian(zipEndOfCentralDirectory); 124 setUnsignedInt32( 125 zipEndOfCentralDirectory, 126 zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, 127 offset); 128 } 129 130 /** 131 * Returns the size (in bytes) of the ZIP Central Directory. 132 * 133 * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 134 */ 135 public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { 136 assertByteOrderLittleEndian(zipEndOfCentralDirectory); 137 return getUnsignedInt32( 138 zipEndOfCentralDirectory, 139 zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); 140 } 141 142 private static void assertByteOrderLittleEndian(ByteBuffer buffer) { 143 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 144 throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 145 } 146 } 147 148 private static int getUnsignedInt16(ByteBuffer buffer, int offset) { 149 return buffer.getShort(offset) & 0xffff; 150 } 151 152 private static long getUnsignedInt32(ByteBuffer buffer, int offset) { 153 return buffer.getInt(offset) & 0xffffffffL; 154 } 155 156 private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { 157 if ((value < 0) || (value > 0xffffffffL)) { 158 throw new IllegalArgumentException("uint32 value of out range: " + value); 159 } 160 buffer.putInt(buffer.position() + offset, (int) value); 161 } 162} 163