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