aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/singlejar/java/com/google/devtools/build/zip/ZipFileEntry.java
blob: ad476715507ff691b5e0314cceacc93eb02caf72 (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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
// Copyright 2015 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.devtools.build.zip;

import java.util.EnumSet;

import javax.annotation.Nullable;

/**
 * A full representation of a ZIP file entry.
 *
 * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> for
 * a description of the entry fields. (Section 4.3.7 and 4.4)
 */
public final class ZipFileEntry {

  /** Compression method for ZIP entries. */
  public enum Compression {
    STORED((short) 0, Feature.STORED),
    DEFLATED((short) 8, Feature.DEFLATED);

    public static Compression fromValue(int value) {
      for (Compression c : Compression.values()) {
        if (c.getValue() == value) {
          return c;
        }
      }
      return null;
    }

    private short value;
    private Feature feature;

    private Compression(short value, Feature feature) {
      this.value = value;
      this.feature = feature;
    }

    public short getValue() {
      return value;
    }

    public short getMinVersion() {
      return feature.getMinVersion();
    }

    Feature getFeature() {
      return feature;
    }
  }

  /** General purpose bit flag for ZIP entries. */
  public enum Flag {
    DATA_DESCRIPTOR(3);

    private int bit;

    private Flag(int bit) {
      this.bit = bit;
    }

    public int getBit() {
      return bit;
    }
  }

  /** Zip file features that entries may use. */
  enum Feature {
    DEFAULT((short) 0x0a),
    STORED((short) 0x0a),
    DEFLATED((short) 0x14),
    ZIP64_SIZE((short) 0x2d),
    ZIP64_CSIZE((short) 0x2d),
    ZIP64_OFFSET((short) 0x2d);

    private short minVersion;

    private Feature(short minVersion) {
      this.minVersion = minVersion;
    }

    public short getMinVersion() {
      return minVersion;
    }

    static short getMinRequiredVersion(EnumSet<Feature> featureSet) {
      short minVersion = Feature.DEFAULT.getMinVersion();
      for (Feature feature : featureSet) {
        minVersion = (short) Math.max(minVersion, feature.getMinVersion());
      }
      return minVersion;
    }
  }

  private String name;
  private long time = -1;
  private long crc = -1;
  private long size = -1;
  private long csize = -1;
  private Compression method;
  private short version = -1;
  private short versionNeeded = -1;
  private short flags;
  private short internalAttributes;
  private int externalAttributes;
  private long localHeaderOffset = -1;
  private ExtraDataList extra;
  @Nullable private String comment;

  private EnumSet<Feature> featureSet;

  /**
   * Creates a new ZIP entry with the specified name.
   *
   * @throws NullPointerException if the entry name is null
   */
  public ZipFileEntry(String name) {
    this.featureSet = EnumSet.of(Feature.DEFAULT);
    setName(name);
    setMethod(Compression.STORED);
    setExtra(new ExtraDataList());
  }

  /**
   * Creates a new ZIP entry with fields taken from the specified ZIP entry.
   */
  public ZipFileEntry(ZipFileEntry e) {
    this.name = e.getName();
    this.time = e.getTime();
    this.crc = e.getCrc();
    this.size = e.getSize();
    this.csize = e.getCompressedSize();
    this.method = e.getMethod();
    this.version = e.getVersion();
    this.versionNeeded = e.getVersionNeeded();
    this.flags = e.getFlags();
    this.internalAttributes = e.getInternalAttributes();
    this.externalAttributes = e.getExternalAttributes();
    this.localHeaderOffset = e.getLocalHeaderOffset();
    this.extra = new ExtraDataList(e.getExtra());
    this.comment = e.getComment();
    this.featureSet = EnumSet.copyOf(e.getFeatureSet());
  }

  /**
   * Sets the name of the entry.
   */
  public void setName(String name) {
    if (name == null) {
      throw new NullPointerException();
    }
    this.name = name;
  }

  /**
   * Returns the name of the entry.
   */
  public String getName() {
    return name;
  }

  /**
   * Sets the modification time of the entry.
   *
   * @param time the entry modification time in number of milliseconds since the epoch
   */
  public void setTime(long time) {
    this.time = time;
  }

  /**
   * Returns the modification time of the entry, or -1 if not specified.
   */
  public long getTime() {
    return time;
  }

  /**
   * Sets the CRC-32 checksum of the uncompressed entry data.
   *
   * @throws IllegalArgumentException if the specified CRC-32 value is less than 0 or greater than
   *     0xFFFFFFFF
   */
  public void setCrc(long crc) {
    if (crc < 0 || crc > 0xffffffffL) {
      throw new IllegalArgumentException("invalid entry crc-32");
    }
    this.crc = crc;
  }

  /**
   * Returns the CRC-32 checksum of the uncompressed entry data, or -1 if not known.
   */
  public long getCrc() {
    return crc;
  }

  /**
   * Sets the uncompressed size of the entry data in bytes.
   *
   * @throws IllegalArgumentException if the specified size is less than 0
   */
  public void setSize(long size) {
    if (size < 0) {
      throw new IllegalArgumentException("invalid entry size");
    }
    if (size > 0xffffffffL) {
      featureSet.add(Feature.ZIP64_SIZE);
    } else {
      featureSet.remove(Feature.ZIP64_SIZE);
    }
    this.size = size;
  }

  /**
   * Returns the uncompressed size of the entry data, or -1 if not known.
   */
  public long getSize() {
    return size;
  }

  /**
   * Sets the size of the compressed entry data in bytes.
   *
   * @throws IllegalArgumentException if the specified size is less than 0
   */
  public void setCompressedSize(long csize) {
    if (csize < 0) {
      throw new IllegalArgumentException("invalid entry size");
    }
    if (csize > 0xffffffffL) {
      featureSet.add(Feature.ZIP64_CSIZE);
    } else {
      featureSet.remove(Feature.ZIP64_CSIZE);
    }
    this.csize = csize;
  }

  /**
   * Returns the size of the compressed entry data, or -1 if not known. In the case of a stored
   * entry, the compressed size will be the same as the uncompressed size of the entry.
   */
  public long getCompressedSize() {
    return csize;
  }

  /**
   * Sets the compression method for the entry.
   */
  public void setMethod(Compression method) {
    if (method == null) {
      throw new NullPointerException();
    }
    if (this.method != null) {
      featureSet.remove(this.method.getFeature());
    }
    this.method = method;
    featureSet.add(this.method.getFeature());
  }

  /**
   * Returns the compression method of the entry.
   */
  public Compression getMethod() {
    return method;
  }

  /**
   * Sets the made by version for the entry.
   */
  public void setVersion(short version) {
    this.version = version;
  }

  /**
   * Returns the made by version of the entry, accounting for assigned version and feature set.
   */
  public short getVersion() {
    return (short) Math.max(version, Feature.getMinRequiredVersion(featureSet));
  }

  /**
   * Sets the version needed to extract the entry.
   */
  public void setVersionNeeded(short versionNeeded) {
    this.versionNeeded = versionNeeded;
  }

  /**
   * Returns the version needed to extract the entry, accounting for assigned version and feature
   * set.
   */
  public short getVersionNeeded() {
    return (short) Math.max(versionNeeded, Feature.getMinRequiredVersion(featureSet));
  }

  /**
   * Sets the general purpose bit flags for the entry.
   */
  public void setFlags(short flags) {
    this.flags = flags;
  }

  /**
   * Sets or clears the specified bit of the general purpose bit flags.
   *
   * @param flag the flag to set or clear
   * @param set whether the flag is to be set or cleared
   */
  public void setFlag(Flag flag, boolean set) {
    short mask = 0x0000;
    mask |= 1 << flag.getBit();
    if (set) {
      flags |= mask;
    } else {
      flags &= ~mask;
    }
  }

  /**
   * Returns the general purpose bit flags of the entry.
   *
   * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a>
   * section 4.4.4.
   */
  public short getFlags() {
    return flags;
  }

  /**
   * Sets the internal file attributes of the entry.
   */
  public void setInternalAttributes(short internalAttributes) {
    this.internalAttributes = internalAttributes;
  }

  /**
   * Returns the internal file attributes of the entry.
   */
  public short getInternalAttributes() {
    return internalAttributes;
  }

  /**
   * Sets the external file attributes of the entry.
   */
  public void setExternalAttributes(int externalAttributes) {
    this.externalAttributes = externalAttributes;
  }

  /**
   * Returns the external file attributes of the entry.
   */
  public int getExternalAttributes() {
    return externalAttributes;
  }

  /**
   * Sets the file offset, in bytes, of the location of the local file header for the entry.
   *
   * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a>
   * section 4.4.16
   *
   * @throws IllegalArgumentException if the specified local header offset is less than 0
   */
  void setLocalHeaderOffset(long localHeaderOffset) {
    if (localHeaderOffset < 0) {
      throw new IllegalArgumentException("invalid local header offset");
    }
    if (localHeaderOffset > 0xffffffffL) {
      featureSet.add(Feature.ZIP64_OFFSET);
    } else {
      featureSet.remove(Feature.ZIP64_OFFSET);
    }
    this.localHeaderOffset = localHeaderOffset;
  }

  /**
   * Returns the file offset of the local header of the entry.
   */
  public long getLocalHeaderOffset() {
    return localHeaderOffset;
  }

  /**
   * Sets the optional extra field data for the entry.
   *
   * @throws IllegalArgumentException if the length of the specified extra field data is greater
   *    than 0xFFFF bytes
   */
  public void setExtra(ExtraDataList extra) {
    if (extra == null) {
      throw new NullPointerException();
    }
    if (extra.getLength() > 0xffff) {
      throw new IllegalArgumentException("invalid extra field length");
    }
    this.extra = extra;
  }

  /**
   * Returns the extra field data for the entry.
   */
  public ExtraDataList getExtra() {
    return extra;
  }

  /**
   * Sets the optional comment string for the entry.
   */
  public void setComment(@Nullable String comment) {
    this.comment = comment;
  }

  /**
   * Returns the comment string for the entry, or null if none.
   */
  public String getComment() {
    return comment;
  }

  /**
   * Returns the feature set that this entry uses.
   */
  EnumSet<Feature> getFeatureSet() {
    return featureSet;
  }

  @Override
  public String toString() {
    return "ZipFileEntry[" + name + "]";
  }
}