1 /* 2 * Copyright (C) 2019 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 17 package com.android.tradefed.util.zip; 18 19 import com.android.tradefed.log.LogUtil.CLog; 20 import com.android.tradefed.util.ByteArrayUtil; 21 22 import java.io.File; 23 import java.io.FileInputStream; 24 import java.io.IOException; 25 import java.util.Arrays; 26 27 /** 28 * EndCentralDirectoryInfo is a class containing the overall information of a zip file. It's at the 29 * end of the zip file. 30 * 31 * <p>Overall zipfile format: [Local file header + Compressed data [+ Extended local header]?]* 32 * [Central directory]* [End of central directory record] 33 * 34 * <p>Refer to following link for more details: https://en.wikipedia.org/wiki/Zip_(file_format) 35 */ 36 public final class EndCentralDirectoryInfo { 37 38 // End central directory of a zip file is at the end of the file, and its size shouldn't be 39 // larger than 64k. 40 public static final int MAX_LOOKBACK = 64 * 1024; 41 42 private static final byte[] END_CENTRAL_DIRECTORY_SIGNATURE = {0x50, 0x4b, 0x05, 0x06}; 43 private static final byte[] ZIP64_END_CENTRAL_DIRECTORY_SIGNATURE = {0x50, 0x4b, 0x06, 0x06}; 44 // Central directory signature is always 4 bytes. 45 private static final int CENTRAL_DIRECTORY_MAGIC_LENGTH = 4; 46 47 private long mEntryNumber; 48 private long mCentralDirSize; 49 private long mCentralDirOffset; 50 getEntryNumber()51 public long getEntryNumber() { 52 return mEntryNumber; 53 } 54 getCentralDirSize()55 public long getCentralDirSize() { 56 return mCentralDirSize; 57 } 58 getCentralDirOffset()59 public long getCentralDirOffset() { 60 return mCentralDirOffset; 61 } 62 63 /** 64 * Constructor to collect end central directory information of a zip file. 65 * 66 * @param zipFile a {@link File} contains the end central directory information. It's likely the 67 * ending part of the zip file. 68 * @throws IOException 69 */ EndCentralDirectoryInfo(File zipFile)70 public EndCentralDirectoryInfo(File zipFile) throws IOException { 71 this(zipFile, false); 72 } 73 74 /** 75 * Constructor to collect end central directory information of a zip file. 76 * 77 * @param zipFile a {@link File} contains the end central directory information. It's likely the 78 * ending part of the zip file. 79 * @param useZip64 a boolean to support zip64 format in partial download. 80 * @throws IOException 81 */ EndCentralDirectoryInfo(File zipFile, boolean useZip64)82 public EndCentralDirectoryInfo(File zipFile, boolean useZip64) throws IOException { 83 // End of central directory record: 84 // Offset Length Contents 85 // 0 4 bytes End of central dir signature (0x06054b50) 86 // 4 2 bytes Number of this disk 87 // 6 2 bytes Number of the disk with the start of the central directory 88 // 8 2 bytes Total number of entries in the central dir on this disk 89 // 10 2 bytes Total number of entries in the central dir 90 // 12 4 bytes Size of the central directory 91 // 16 4 bytes Offset of start of central directory with respect to the starting 92 // disk number 93 // 20 2 bytes zipfile comment length (c) 94 // 22 (c)bytes zipfile comment 95 96 // ZIP64 End of central directory record: 97 // Offset Length Contents 98 // 0 4 bytes ZIPO64 End of central dir signature (0x06064b50) 99 // 4 8 bytes Size of the ZIP64 central directory 100 // 12 2 bytes Version made by 101 // 14 2 bytes Version needed to extract 102 // 16 4 bytes Number of this disk 103 // 20 4 bytes Number of the disk with the start of the central directory 104 // 24 8 bytes Total number of entries in the central dir on this disk 105 // 32 8 bytes Total number of entries in the central dir 106 // 40 8 bytes Size of the central directory 107 // 48 8 bytes Offset of start of central directory with respect to the starting 108 // disk number 109 110 byte[] data = getEndCentralDirectoryInfo(zipFile, END_CENTRAL_DIRECTORY_SIGNATURE); 111 // Get the total number of entries in the central directory 112 mEntryNumber = ByteArrayUtil.getInt(data, 10, 2); 113 // Get the size of the central directory block 114 mCentralDirSize = ByteArrayUtil.getLong(data, 12, 4); 115 // Get the offset of start of central directory 116 mCentralDirOffset = ByteArrayUtil.getLong(data, 16, 4); 117 if (!useZip64) { 118 if (mCentralDirOffset < 0) { 119 throw new IOException( 120 "Failed to get offset of EndCentralDirectoryInfo. Partial unzip doesn't " 121 + "support zip files larger than 4GB."); 122 } 123 return; 124 } 125 // Get the real data while use-zip64-in-partial-download is set and the 3 corresponding 126 // elements match the condition. 127 if (Long.toHexString(mEntryNumber).equals("ffff") || 128 Long.toHexString(mCentralDirSize).equals("ffffffff") || 129 Long.toHexString(mCentralDirOffset).equals("ffffffff")) { 130 CLog.i("Values(total number of entries, central directory size, and the offset of start" 131 + "of central directory header) in EndCentralDirectoryInfo reach limitation, " 132 + "getting real data from the ZIP64EndCentralDirectoryInfo."); 133 // Get the ZIP64 End Central Directory Info. 134 data = getEndCentralDirectoryInfo(zipFile, ZIP64_END_CENTRAL_DIRECTORY_SIGNATURE); 135 mEntryNumber = ByteArrayUtil.getLong(data, 32, 8); 136 mCentralDirSize = ByteArrayUtil.getLong(data, 40, 8); 137 mCentralDirOffset = ByteArrayUtil.getLong(data, 48, 8); 138 } 139 } 140 141 /** 142 * Get the EndCentralDirectoryInfo data in a zip file according to the given signature. 143 * 144 * @param zipFile a {@link File} contains the end central directory information. It's likely the 145 * ending part of the zip file. 146 * @param signature a {@code byte[]} end central directory info signature. 147 * @return a {@code byte[]} end central directory info data in a zip file. 148 * @throws IOException 149 */ getEndCentralDirectoryInfo(File zipFile, byte[] signature)150 private byte[] getEndCentralDirectoryInfo(File zipFile, byte[] signature) throws IOException { 151 try (FileInputStream stream = new FileInputStream(zipFile)) { 152 long size = stream.getChannel().size(); 153 if (size > MAX_LOOKBACK) { 154 stream.skip(size - MAX_LOOKBACK); 155 size = MAX_LOOKBACK; 156 } 157 byte[] endCentralDir = new byte[(int) size]; 158 stream.read(endCentralDir); 159 int offset = (int) size - CENTRAL_DIRECTORY_MAGIC_LENGTH - 1; 160 // Seek from the end of the file, searching for the end central directory signature. 161 while (offset >= 0) { 162 if (!java.util.Arrays.equals( 163 signature, 164 Arrays.copyOfRange(endCentralDir, offset, offset + 4))) { 165 offset--; 166 continue; 167 } 168 break; 169 } 170 if (offset < 0) { 171 throw new RuntimeException( 172 "Failed to find end central directory info for zip file: " 173 + zipFile.getPath()); 174 } 175 return Arrays.copyOfRange(endCentralDir, offset, offset + 64); 176 } 177 } 178 } 179