aboutsummaryrefslogtreecommitdiff
path: root/generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java
blob: d6342d05dd5f809a4f5a71264d34a0909c970f2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.archivepatcher.generator;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * A class that contains <em>just enough data</em> to generate a patch.
 */
public class MinimalZipEntry {
  /**
   * The compression method that was used, typically 8 (for deflate) or 0 (for stored).
   */
  private final int compressionMethod;

  /**
   * The CRC32 of the <em>uncompressed</em> data.
   */
  private final long crc32OfUncompressedData;

  /**
   * The size of the data as it exists in the archive. For compressed entries, this is the size of
   * the compressed data; for uncompressed entries, this is the same as {@link #uncompressedSize}.
   */
  private final long compressedSize;

  /**
   * The size of the <em>uncompressed</em data.
   */
  private final long uncompressedSize;

  /**
   * The file name for the entry. By convention, names ending with '/' denote directories. The
   * encoding is controlled by the general purpose flags, bit 11. See {@link #getFileName()} for
   * more information.
   */
  private final byte[] fileNameBytes;

  /**
   * The value of the 11th bit of the general purpose flag, which controls the encoding of file
   * names and comments. See {@link #getFileName()} for more information.
   */
  private final boolean generalPurposeFlagBit11;

  /**
   * The file offset at which the first byte of the local entry header begins.
   */
  private final long fileOffsetOfLocalEntry;

  /**
   * The file offset at which the first byte of the data for the entry begins. For compressed data,
   * this is the first byte of the deflated data; for uncompressed data, this is the first byte of
   * the uncompressed data.
   */
  private long fileOffsetOfCompressedData = -1;

  /**
   * Create a new Central Directory entry with the corresponding data.
   * @param compressionMethod the method used to compress the data
   * @param crc32OfUncompressedData the CRC32 of the uncompressed data
   * @param compressedSize the size of the data in its compressed form
   * @param uncompressedSize the size of the data in its uncompressed form
   * @param fileNameBytes the name of the file, as a byte array; see {@link #getFileName()} for
   * information on encoding
   * @param generalPurposeFlagBit11 the value of the 11th bit of the general purpose flag, which
   * nominally controls the default character encoding for file names and comments; see
   * {@link #getFileName()} for more information on encoding
   * @param fileOffsetOfLocalEntry the file offset at which the local entry begins
   */
  public MinimalZipEntry(
      int compressionMethod,
      long crc32OfUncompressedData,
      long compressedSize,
      long uncompressedSize,
      byte[] fileNameBytes,
      boolean generalPurposeFlagBit11,
      long fileOffsetOfLocalEntry) {
    this.compressionMethod = compressionMethod;
    this.crc32OfUncompressedData = crc32OfUncompressedData;
    this.compressedSize = compressedSize;
    this.uncompressedSize = uncompressedSize;
    this.fileNameBytes = fileNameBytes == null ? null : fileNameBytes.clone();
    this.generalPurposeFlagBit11 = generalPurposeFlagBit11;
    this.fileOffsetOfLocalEntry = fileOffsetOfLocalEntry;
  }

  /**
   * Sets the file offset at which the data for this entry begins.
   * @param offset the offset
   */
  public void setFileOffsetOfCompressedData(long offset) {
    fileOffsetOfCompressedData = offset;
  }

  /**
   * Returns the compression method that was used, typically 8 (for deflate) or 0 (for stored).
   * @return as described
   */
  public int getCompressionMethod() {
    return compressionMethod;
  }

  /**
   * Returns the CRC32 of the uncompressed data.
   * @return as described
   */
  public long getCrc32OfUncompressedData() {
    return crc32OfUncompressedData;
  }

  /**
   * Returns the size of the data as it exists in the archive. For compressed entries, this is the
   * size of the compressed data; for uncompressed entries, this is the same as
   * {@link #getUncompressedSize()}.
   * @return as described
   */
  public long getCompressedSize() {
    return compressedSize;
  }

  /**
   * Returns the size of the uncompressed data.
   * @return as described
   */
  public long getUncompressedSize() {
    return uncompressedSize;
  }

  /**
   * Returns a copy of the bytes of the file name, exactly the same as they were in the archive
   * file. See {@link #getFileName()} for an explanation of why this is useful.
   * @return as described
   */
  public byte[] getFileNameBytes() {
    return fileNameBytes == null ? null : fileNameBytes.clone();
  }

  /**
   * Returns a best-effort conversion of the file name into a string, based on strict adherence to
   * the PKWARE APPNOTE that defines this behavior. If the value of the 11th bit of the general
   * purpose flag was set to 1, these bytes should be encoded with the UTF8 character set; otherwise
   * the character set should be Cp437. Adherence to this standard varies significantly, and some
   * systems use the default character set for the environment instead of Cp437 when writing these
   * bytes. For such instances, callers can obtain the raw bytes by using
   * {@link #getFileNameBytes()} instead and checking the value of the 11th bit of the general
   * purpose bit flag for a hint using {@link #getGeneralPurposeFlagBit11()}. There is also
   * something called EFS ("0x0008 extra field storage") that specifies additional behavior for
   * character encoding, but this tool doesn't support it as the use is not standardized.
   * @return as described
   */
  // TODO(andrewhayden): Support EFS
  public String getFileName() {
    String charsetName = generalPurposeFlagBit11 ? "UTF8" : "Cp437";
    try {
      return new String(fileNameBytes, charsetName);
    } catch (UnsupportedEncodingException e) {
      // Cp437 has been supported at least since JDK 1.6.0, so this should rarely occur in practice.
      // Older versions of the JDK also support Cp437, but as part of charsets.jar, which didn't
      // ship in every distribution; it is conceivable that those systems might have problems here.
      throw new RuntimeException("System doesn't support " + charsetName, e);
    }
  }

  /**
   * Returns the value of the 11th bit of the general purpose flag; true for 1, false for 0. See
   * {@link #getFileName()} for more information on the usefulness of this flag.
   * @return as described
   */
  public boolean getGeneralPurposeFlagBit11() {
    return generalPurposeFlagBit11;
  }

  /**
   * Returns the file offset at which the first byte of the local entry header begins.
   * @return as described
   */
  public long getFileOffsetOfLocalEntry() {
    return fileOffsetOfLocalEntry;
  }

  /**
   * Returns the file offset at which the first byte of the data for the entry begins. For
   * compressed data, this is the first byte of the deflated data; for uncompressed data, this is
   * the first byte of the uncompressed data.
   * @return as described
   */
  public long getFileOffsetOfCompressedData() {
    return fileOffsetOfCompressedData;
  }

  /**
   * Convenience methods that returns true if and only if the entry is compressed with deflate.
   * @return as described
   */
  public boolean isDeflateCompressed() {
    // 8 is deflate according to the zip spec.
    if (getCompressionMethod() != 8) {
      return false;
    }
    // Some tools may list compression method deflate but set level to zero (store), so they will
    // have a compressed size equal to the uncompresesd size. Don't consider such things to be
    // compressed, even if they are "deflated".
    return getCompressedSize() != getUncompressedSize();
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + (int) (compressedSize ^ (compressedSize >>> 32));
    result = prime * result + compressionMethod;
    result = prime * result + (int) (crc32OfUncompressedData ^ (crc32OfUncompressedData >>> 32));
    result = prime * result + Arrays.hashCode(fileNameBytes);
    result =
        prime * result + (int) (fileOffsetOfCompressedData ^ (fileOffsetOfCompressedData >>> 32));
    result = prime * result + (int) (fileOffsetOfLocalEntry ^ (fileOffsetOfLocalEntry >>> 32));
    result = prime * result + (generalPurposeFlagBit11 ? 1231 : 1237);
    result = prime * result + (int) (uncompressedSize ^ (uncompressedSize >>> 32));
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    MinimalZipEntry other = (MinimalZipEntry) obj;
    if (compressedSize != other.compressedSize) {
      return false;
    }
    if (compressionMethod != other.compressionMethod) {
      return false;
    }
    if (crc32OfUncompressedData != other.crc32OfUncompressedData) {
      return false;
    }
    if (!Arrays.equals(fileNameBytes, other.fileNameBytes)) {
      return false;
    }
    if (fileOffsetOfCompressedData != other.fileOffsetOfCompressedData) {
      return false;
    }
    if (fileOffsetOfLocalEntry != other.fileOffsetOfLocalEntry) {
      return false;
    }
    if (generalPurposeFlagBit11 != other.generalPurposeFlagBit11) {
      return false;
    }
    if (uncompressedSize != other.uncompressedSize) {
      return false;
    }
    return true;
  }
}