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