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 com.google.common.annotations.VisibleForTesting; 23 24 import java.io.IOException; 25 import java.util.Arrays; 26 27 /** 28 * CentralDirectoryInfo is a class containing the information of a file/folder inside a zip file. 29 * 30 * <p>Overall zipfile format: [Local file header + Compressed data [+ Extended local header]?]* 31 * [Central directory]* [End of central directory record] 32 * 33 * <p>Refer to following link for more details: https://en.wikipedia.org/wiki/Zip_(file_format) 34 */ 35 public final class CentralDirectoryInfo { 36 37 private static final byte[] CENTRAL_DIRECTORY_SIGNATURE = {0x50, 0x4b, 0x01, 0x02}; 38 private static final byte[] ZIP64_FILE_FIELD_ID = {0x01, 0x00}; 39 40 private int mCompressionMethod; 41 private long mCrc; 42 private long mCompressedSize; 43 private long mUncompressedSize; 44 private long mLocalHeaderOffset; 45 private int mInternalFileAttributes; 46 private long mExternalFileAttributes; 47 private String mFileName; 48 private int mFileNameLength; 49 private int mExtraFieldLength; 50 private int mFileCommentLength; 51 52 /** Get the compression method. */ getCompressionMethod()53 public int getCompressionMethod() { 54 return mCompressionMethod; 55 } 56 57 /** Set the compression method. */ setCompressionMethod(int compressionMethod)58 public void setCompressionMethod(int compressionMethod) { 59 mCompressionMethod = compressionMethod; 60 } 61 62 /** Get the CRC of the file. */ getCrc()63 public long getCrc() { 64 return mCrc; 65 } 66 67 /** Set the CRC of the file. */ setCrc(long crc)68 public void setCrc(long crc) { 69 mCrc = crc; 70 } 71 72 /** Get the compressed size. */ getCompressedSize()73 public int getCompressedSize() { 74 return (int) mCompressedSize; 75 } 76 77 /** Set the compressed size. */ setCompressedSize(long compressionSize)78 public void setCompressedSize(long compressionSize) { 79 mCompressedSize = compressionSize; 80 } 81 82 /** Get the uncompressed size. */ getUncompressedSize()83 public long getUncompressedSize() { 84 return mUncompressedSize; 85 } 86 87 /** Set the uncompressed size. */ setUncompressedSize(long uncompressedSize)88 public void setUncompressedSize(long uncompressedSize) { 89 mUncompressedSize = uncompressedSize; 90 } 91 92 /** Get the offset of local file header entry. */ getLocalHeaderOffset()93 public long getLocalHeaderOffset() { 94 return mLocalHeaderOffset; 95 } 96 97 /** Set the offset of local file header entry. */ setLocalHeaderOffset(long localHeaderOffset)98 public void setLocalHeaderOffset(long localHeaderOffset) { 99 mLocalHeaderOffset = localHeaderOffset; 100 } 101 102 /** Get the internal file attributes. */ getInternalFileAttributes()103 public int getInternalFileAttributes() { 104 return mInternalFileAttributes; 105 } 106 107 /** Set the internal file attributes. */ setInternalFileAttributes(int internalFileAttributes)108 public void setInternalFileAttributes(int internalFileAttributes) { 109 mInternalFileAttributes = internalFileAttributes; 110 } 111 112 /** Get the external file attributes. */ getExternalFileAttributes()113 public long getExternalFileAttributes() { 114 return mExternalFileAttributes; 115 } 116 117 /** Set the external file attributes. */ setExternalFileAttributes(long externalFileAttributes)118 public void setExternalFileAttributes(long externalFileAttributes) { 119 mExternalFileAttributes = externalFileAttributes; 120 } 121 122 /** Get the Linux file permission, stored in the last 9 bits of external file attributes. */ getFilePermission()123 public int getFilePermission() { 124 return ((int) mExternalFileAttributes & (0777 << 16L)) >> 16L; 125 } 126 127 /** Get the file name including the relative path. */ getFileName()128 public String getFileName() { 129 return mFileName; 130 } 131 132 /** Set the file name including the relative path. */ setFileName(String fileName)133 public void setFileName(String fileName) { 134 mFileName = fileName; 135 } 136 137 /** Get the file name length. */ getFileNameLength()138 public int getFileNameLength() { 139 return mFileNameLength; 140 } 141 142 /** Set the file name length. */ setFileNameLength(int fileNameLength)143 public void setFileNameLength(int fileNameLength) { 144 mFileNameLength = fileNameLength; 145 } 146 147 /** Get the extra field length. */ getExtraFieldLength()148 public int getExtraFieldLength() { 149 return mExtraFieldLength; 150 } 151 152 /** Set the extra field length. */ setExtraFieldLength(int extraFieldLength)153 public void setExtraFieldLength(int extraFieldLength) { 154 mExtraFieldLength = extraFieldLength; 155 } 156 157 /** Get the file comment length. */ getFileCommentLength()158 public int getFileCommentLength() { 159 return mFileCommentLength; 160 } 161 162 /** Set the file comment length. */ setFileCommentLength(int fileCommentLength)163 public void setFileCommentLength(int fileCommentLength) { 164 mFileCommentLength = fileCommentLength; 165 } 166 167 /** Get the size of the central directory entry. */ getInfoSize()168 public int getInfoSize() { 169 return 46 + mFileNameLength + mExtraFieldLength + mFileCommentLength; 170 } 171 172 /** Default constructor used for unit test. */ 173 @VisibleForTesting CentralDirectoryInfo()174 protected CentralDirectoryInfo() {} 175 176 /** 177 * Constructor to collect the information of a file entry inside zip file. 178 * 179 * @param data {@code byte[]} of data that contains the information of a file entry. 180 * @param startOffset start offset of the information block. 181 * @throws IOException 182 */ CentralDirectoryInfo(byte[] data, int startOffset)183 public CentralDirectoryInfo(byte[] data, int startOffset) throws IOException { 184 this(data, startOffset, false); 185 } 186 187 /** 188 * Constructor to collect the information of a file entry inside zip file. 189 * 190 * @param data {@code byte[]} of data that contains the information of a file entry. 191 * @param startOffset start offset of the information block. 192 * @param useZip64 a boolean to support zip64 format in partial download. 193 * @throws IOException 194 */ CentralDirectoryInfo(byte[] data, int startOffset, boolean useZip64)195 public CentralDirectoryInfo(byte[] data, int startOffset, boolean useZip64) throws IOException { 196 // Central directory: 197 // Offset Length Contents 198 // 0 4 bytes Central file header signature (0x02014b50) 199 // 4 2 bytes Version made by 200 // 6 2 bytes Version needed to extract 201 // 8 2 bytes General purpose bit flag 202 // 10 2 bytes Compression method 203 // 12 2 bytes Last mod file time 204 // 14 2 bytes Last mod file date 205 // 16 4 bytes CRC-32 206 // 20 4 bytes Compressed size 207 // 24 4 bytes Uncompressed size 208 // 28 2 bytes Filename length (f) 209 // 30 2 bytes Extra field length (e) 210 // 32 2 bytes File comment length (c) 211 // 34 2 bytes Disk number start 212 // 36 2 bytes Internal file attributes 213 // 38 4 bytes External file attributes (file permission stored in the last 9 bits) 214 // 42 4 bytes Relative offset of local header 215 // 46 (f)bytes Filename 216 // (e)bytes Extra field 217 // (c)bytes File comment 218 219 // Check signature 220 if (!Arrays.equals( 221 CENTRAL_DIRECTORY_SIGNATURE, 222 Arrays.copyOfRange(data, startOffset, startOffset + 4))) { 223 throw new IOException("Invalid central directory info for zip file is found."); 224 } 225 mCompressionMethod = ByteArrayUtil.getInt(data, startOffset + 10, 2); 226 mCrc = ByteArrayUtil.getLong(data, startOffset + 16, 4); 227 mCompressedSize = ByteArrayUtil.getLong(data, startOffset + 20, 4); 228 mUncompressedSize = ByteArrayUtil.getLong(data, startOffset + 24, 4); 229 mInternalFileAttributes = ByteArrayUtil.getInt(data, startOffset + 36, 2); 230 mExternalFileAttributes = ByteArrayUtil.getLong(data, startOffset + 38, 4); 231 mLocalHeaderOffset = ByteArrayUtil.getLong(data, startOffset + 42, 4); 232 mFileNameLength = ByteArrayUtil.getInt(data, startOffset + 28, 2); 233 mFileName = ByteArrayUtil.getString(data, startOffset + 46, mFileNameLength); 234 mExtraFieldLength = ByteArrayUtil.getInt(data, startOffset + 30, 2); 235 mFileCommentLength = ByteArrayUtil.getInt(data, startOffset + 32, 2); 236 if (!useZip64) { 237 return; 238 } 239 // Get the real data while use-zip64-in-partial-download is set and the 3 corresponding 240 // elements match the condition. 241 if (Long.toHexString(mUncompressedSize).equals("ffffffff") || 242 Long.toHexString(mCompressedSize).equals("ffffffff") || 243 Long.toHexString(mLocalHeaderOffset).equals("ffffffff")) { 244 CLog.i("Values(compressed/uncompressed size, and relative offset of local header)) in " 245 + "CentralDirectoryInfo for file name: %s reaches the limitation(0xffffffff), " 246 + "getting the data from extra field.", mFileName); 247 byte[] zip64FieldId = Arrays.copyOfRange( 248 data, startOffset + mFileNameLength + 46, startOffset + mFileNameLength + 48); 249 // There should be a ZIP64 Field ID(0x0001) existing here. 250 if (!Arrays.equals(ZIP64_FILE_FIELD_ID, zip64FieldId)) { 251 throw new RuntimeException(String.format("Failed to find ZIP64 field id(0x0001) " 252 + "from the Central Directory Info for file: %s", mFileName)); 253 } 254 mUncompressedSize = ByteArrayUtil.getLong( 255 data, startOffset + mFileNameLength + 50, 8); 256 mCompressedSize = ByteArrayUtil.getLong( 257 data, startOffset + mFileNameLength + 58, 8); 258 mLocalHeaderOffset = ByteArrayUtil.getLong( 259 data, startOffset + mFileNameLength + 66, 8); 260 } 261 } 262 263 @Override equals(Object o)264 public boolean equals(Object o) { 265 return this.toString().equals(o.toString()); 266 } 267 268 @Override hashCode()269 public int hashCode() { 270 return this.toString().hashCode(); 271 } 272 273 @Override toString()274 public String toString() { 275 return String.format( 276 "Compression Method: %d\n" 277 + "Crc: %d\n" 278 + "Compressed Size: %d\n" 279 + "Uncompressed Size: %d\n" 280 + "Local Header Offset: %d\n" 281 + "Internal File Attributes: %d\n" 282 + "External File Attributes: %d\n" 283 + "File Name: %s\n" 284 + "File Name Length: %d\n" 285 + "Extra Field Length: %d\n" 286 + "File Comment Length: %d", 287 mCompressionMethod, 288 mCrc, 289 mCompressedSize, 290 mUncompressedSize, 291 mLocalHeaderOffset, 292 mInternalFileAttributes, 293 mExternalFileAttributes, 294 mFileName, 295 mFileNameLength, 296 mExtraFieldLength, 297 mFileCommentLength); 298 } 299 } 300