1 // Copyright 2016 Google Inc. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.archivepatcher.generator;
16 
17 import java.io.UnsupportedEncodingException;
18 import java.util.Arrays;
19 
20 /**
21  * A class that contains <em>just enough data</em> to generate a patch.
22  */
23 public class MinimalZipEntry {
24   /**
25    * The compression method that was used, typically 8 (for deflate) or 0 (for stored).
26    */
27   private final int compressionMethod;
28 
29   /**
30    * The CRC32 of the <em>uncompressed</em> data.
31    */
32   private final long crc32OfUncompressedData;
33 
34   /**
35    * The size of the data as it exists in the archive. For compressed entries, this is the size of
36    * the compressed data; for uncompressed entries, this is the same as {@link #uncompressedSize}.
37    */
38   private final long compressedSize;
39 
40   /**
41    * The size of the <em>uncompressed</em data.
42    */
43   private final long uncompressedSize;
44 
45   /**
46    * The file name for the entry. By convention, names ending with '/' denote directories. The
47    * encoding is controlled by the general purpose flags, bit 11. See {@link #getFileName()} for
48    * more information.
49    */
50   private final byte[] fileNameBytes;
51 
52   /**
53    * The value of the 11th bit of the general purpose flag, which controls the encoding of file
54    * names and comments. See {@link #getFileName()} for more information.
55    */
56   private final boolean generalPurposeFlagBit11;
57 
58   /**
59    * The file offset at which the first byte of the local entry header begins.
60    */
61   private final long fileOffsetOfLocalEntry;
62 
63   /**
64    * The file offset at which the first byte of the data for the entry begins. For compressed data,
65    * this is the first byte of the deflated data; for uncompressed data, this is the first byte of
66    * the uncompressed data.
67    */
68   private long fileOffsetOfCompressedData = -1;
69 
70   /**
71    * Create a new Central Directory entry with the corresponding data.
72    * @param compressionMethod the method used to compress the data
73    * @param crc32OfUncompressedData the CRC32 of the uncompressed data
74    * @param compressedSize the size of the data in its compressed form
75    * @param uncompressedSize the size of the data in its uncompressed form
76    * @param fileNameBytes the name of the file, as a byte array; see {@link #getFileName()} for
77    * information on encoding
78    * @param generalPurposeFlagBit11 the value of the 11th bit of the general purpose flag, which
79    * nominally controls the default character encoding for file names and comments; see
80    * {@link #getFileName()} for more information on encoding
81    * @param fileOffsetOfLocalEntry the file offset at which the local entry begins
82    */
MinimalZipEntry( int compressionMethod, long crc32OfUncompressedData, long compressedSize, long uncompressedSize, byte[] fileNameBytes, boolean generalPurposeFlagBit11, long fileOffsetOfLocalEntry)83   public MinimalZipEntry(
84       int compressionMethod,
85       long crc32OfUncompressedData,
86       long compressedSize,
87       long uncompressedSize,
88       byte[] fileNameBytes,
89       boolean generalPurposeFlagBit11,
90       long fileOffsetOfLocalEntry) {
91     this.compressionMethod = compressionMethod;
92     this.crc32OfUncompressedData = crc32OfUncompressedData;
93     this.compressedSize = compressedSize;
94     this.uncompressedSize = uncompressedSize;
95     this.fileNameBytes = fileNameBytes == null ? null : fileNameBytes.clone();
96     this.generalPurposeFlagBit11 = generalPurposeFlagBit11;
97     this.fileOffsetOfLocalEntry = fileOffsetOfLocalEntry;
98   }
99 
100   /**
101    * Sets the file offset at which the data for this entry begins.
102    * @param offset the offset
103    */
setFileOffsetOfCompressedData(long offset)104   public void setFileOffsetOfCompressedData(long offset) {
105     fileOffsetOfCompressedData = offset;
106   }
107 
108   /**
109    * Returns the compression method that was used, typically 8 (for deflate) or 0 (for stored).
110    * @return as described
111    */
getCompressionMethod()112   public int getCompressionMethod() {
113     return compressionMethod;
114   }
115 
116   /**
117    * Returns the CRC32 of the uncompressed data.
118    * @return as described
119    */
getCrc32OfUncompressedData()120   public long getCrc32OfUncompressedData() {
121     return crc32OfUncompressedData;
122   }
123 
124   /**
125    * Returns the size of the data as it exists in the archive. For compressed entries, this is the
126    * size of the compressed data; for uncompressed entries, this is the same as
127    * {@link #getUncompressedSize()}.
128    * @return as described
129    */
getCompressedSize()130   public long getCompressedSize() {
131     return compressedSize;
132   }
133 
134   /**
135    * Returns the size of the uncompressed data.
136    * @return as described
137    */
getUncompressedSize()138   public long getUncompressedSize() {
139     return uncompressedSize;
140   }
141 
142   /**
143    * Returns a copy of the bytes of the file name, exactly the same as they were in the archive
144    * file. See {@link #getFileName()} for an explanation of why this is useful.
145    * @return as described
146    */
getFileNameBytes()147   public byte[] getFileNameBytes() {
148     return fileNameBytes == null ? null : fileNameBytes.clone();
149   }
150 
151   /**
152    * Returns a best-effort conversion of the file name into a string, based on strict adherence to
153    * the PKWARE APPNOTE that defines this behavior. If the value of the 11th bit of the general
154    * purpose flag was set to 1, these bytes should be encoded with the UTF8 character set; otherwise
155    * the character set should be Cp437. Adherence to this standard varies significantly, and some
156    * systems use the default character set for the environment instead of Cp437 when writing these
157    * bytes. For such instances, callers can obtain the raw bytes by using
158    * {@link #getFileNameBytes()} instead and checking the value of the 11th bit of the general
159    * purpose bit flag for a hint using {@link #getGeneralPurposeFlagBit11()}. There is also
160    * something called EFS ("0x0008 extra field storage") that specifies additional behavior for
161    * character encoding, but this tool doesn't support it as the use is not standardized.
162    * @return as described
163    */
164   // TODO(andrewhayden): Support EFS
getFileName()165   public String getFileName() {
166     String charsetName = generalPurposeFlagBit11 ? "UTF8" : "Cp437";
167     try {
168       return new String(fileNameBytes, charsetName);
169     } catch (UnsupportedEncodingException e) {
170       // Cp437 has been supported at least since JDK 1.6.0, so this should rarely occur in practice.
171       // Older versions of the JDK also support Cp437, but as part of charsets.jar, which didn't
172       // ship in every distribution; it is conceivable that those systems might have problems here.
173       throw new RuntimeException("System doesn't support " + charsetName, e);
174     }
175   }
176 
177   /**
178    * Returns the value of the 11th bit of the general purpose flag; true for 1, false for 0. See
179    * {@link #getFileName()} for more information on the usefulness of this flag.
180    * @return as described
181    */
getGeneralPurposeFlagBit11()182   public boolean getGeneralPurposeFlagBit11() {
183     return generalPurposeFlagBit11;
184   }
185 
186   /**
187    * Returns the file offset at which the first byte of the local entry header begins.
188    * @return as described
189    */
getFileOffsetOfLocalEntry()190   public long getFileOffsetOfLocalEntry() {
191     return fileOffsetOfLocalEntry;
192   }
193 
194   /**
195    * Returns the file offset at which the first byte of the data for the entry begins. For
196    * compressed data, this is the first byte of the deflated data; for uncompressed data, this is
197    * the first byte of the uncompressed data.
198    * @return as described
199    */
getFileOffsetOfCompressedData()200   public long getFileOffsetOfCompressedData() {
201     return fileOffsetOfCompressedData;
202   }
203 
204   /**
205    * Convenience methods that returns true if and only if the entry is compressed with deflate.
206    * @return as described
207    */
isDeflateCompressed()208   public boolean isDeflateCompressed() {
209     // 8 is deflate according to the zip spec.
210     if (getCompressionMethod() != 8) {
211       return false;
212     }
213     // Some tools may list compression method deflate but set level to zero (store), so they will
214     // have a compressed size equal to the uncompresesd size. Don't consider such things to be
215     // compressed, even if they are "deflated".
216     return getCompressedSize() != getUncompressedSize();
217   }
218 
219   @Override
hashCode()220   public int hashCode() {
221     final int prime = 31;
222     int result = 1;
223     result = prime * result + (int) (compressedSize ^ (compressedSize >>> 32));
224     result = prime * result + compressionMethod;
225     result = prime * result + (int) (crc32OfUncompressedData ^ (crc32OfUncompressedData >>> 32));
226     result = prime * result + Arrays.hashCode(fileNameBytes);
227     result =
228         prime * result + (int) (fileOffsetOfCompressedData ^ (fileOffsetOfCompressedData >>> 32));
229     result = prime * result + (int) (fileOffsetOfLocalEntry ^ (fileOffsetOfLocalEntry >>> 32));
230     result = prime * result + (generalPurposeFlagBit11 ? 1231 : 1237);
231     result = prime * result + (int) (uncompressedSize ^ (uncompressedSize >>> 32));
232     return result;
233   }
234 
235   @Override
equals(Object obj)236   public boolean equals(Object obj) {
237     if (this == obj) {
238       return true;
239     }
240     if (obj == null) {
241       return false;
242     }
243     if (getClass() != obj.getClass()) {
244       return false;
245     }
246     MinimalZipEntry other = (MinimalZipEntry) obj;
247     if (compressedSize != other.compressedSize) {
248       return false;
249     }
250     if (compressionMethod != other.compressionMethod) {
251       return false;
252     }
253     if (crc32OfUncompressedData != other.crc32OfUncompressedData) {
254       return false;
255     }
256     if (!Arrays.equals(fileNameBytes, other.fileNameBytes)) {
257       return false;
258     }
259     if (fileOffsetOfCompressedData != other.fileOffsetOfCompressedData) {
260       return false;
261     }
262     if (fileOffsetOfLocalEntry != other.fileOffsetOfLocalEntry) {
263       return false;
264     }
265     if (generalPurposeFlagBit11 != other.generalPurposeFlagBit11) {
266       return false;
267     }
268     if (uncompressedSize != other.uncompressedSize) {
269       return false;
270     }
271     return true;
272   }
273 }
274