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