ZipUtils.java revision dd910c5945272e9820dfd9d7798ba32aa7dfc73f
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 UINT32_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 32-bit number. 60 61 int archiveSize = zipContents.capacity(); 62 if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { 63 System.out.println("File size smaller than EOCD min size"); 64 return -1; 65 } 66 int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT32_MAX_VALUE); 67 int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; 68 for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; 69 expectedCommentLength++) { 70 int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; 71 if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { 72 int actualCommentLength = 73 getUnsignedInt16( 74 zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); 75 if (actualCommentLength == expectedCommentLength) { 76 return eocdStartPos; 77 } 78 } 79 } 80 81 return -1; 82 } 83 84 /** 85 * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory 86 * Locator. 87 * 88 * <p>NOTE: Byte order of {@code zipContents} must be little-endian. 89 */ 90 public static final boolean isZip64EndOfCentralDirectoryLocatorPresent( 91 ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) { 92 assertByteOrderLittleEndian(zipContents); 93 94 // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central 95 // Directory Record. 96 97 int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE; 98 if (locatorPosition < 0) { 99 return false; 100 } 101 102 return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG; 103 } 104 105 /** 106 * Returns the offset of the start of the ZIP Central Directory in the archive. 107 * 108 * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 109 */ 110 public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { 111 assertByteOrderLittleEndian(zipEndOfCentralDirectory); 112 return getUnsignedInt32( 113 zipEndOfCentralDirectory, 114 zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); 115 } 116 117 /** 118 * Sets the offset of the start of the ZIP Central Directory in the archive. 119 * 120 * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 121 */ 122 public static void setZipEocdCentralDirectoryOffset( 123 ByteBuffer zipEndOfCentralDirectory, long offset) { 124 assertByteOrderLittleEndian(zipEndOfCentralDirectory); 125 setUnsignedInt32( 126 zipEndOfCentralDirectory, 127 zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, 128 offset); 129 } 130 131 /** 132 * Returns the size (in bytes) of the ZIP Central Directory. 133 * 134 * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. 135 */ 136 public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { 137 assertByteOrderLittleEndian(zipEndOfCentralDirectory); 138 return getUnsignedInt32( 139 zipEndOfCentralDirectory, 140 zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); 141 } 142 143 private static void assertByteOrderLittleEndian(ByteBuffer buffer) { 144 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 145 throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 146 } 147 } 148 149 private static int getUnsignedInt16(ByteBuffer buffer, int offset) { 150 return buffer.getShort(offset) & 0xffff; 151 } 152 153 private static long getUnsignedInt32(ByteBuffer buffer, int offset) { 154 return buffer.getInt(offset) & 0xffffffffL; 155 } 156 157 private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { 158 if ((value < 0) || (value > 0xffffffffL)) { 159 throw new IllegalArgumentException("uint32 value of out range: " + value); 160 } 161 buffer.putInt(buffer.position() + offset, (int) value); 162 } 163} 164