aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/ziputils/LocalFileHeader.java
blob: be7ce3a6d0afc5395431ba74ebde2174057cafc3 (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
// 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.android.ziputils;

import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.nio.charset.StandardCharsets.UTF_8;

import java.nio.ByteBuffer;

/**
 * Provides a view of a local file header for a zip file entry.
 */
public class LocalFileHeader extends View<LocalFileHeader> {

  /**
   * Returns a {@code LocalFileHeader} view of the given buffer. The buffer is assumed to contain a
   * valid "central directory entry" record beginning at the buffers current position.
   *
   * @param buffer containing the data of a "central directory entry" record.
   * @return a {@code LocalFileHeader} of the data at the current position of the given byte
   * buffer.
   */
  public static LocalFileHeader viewOf(ByteBuffer buffer) {
    LocalFileHeader view = new LocalFileHeader(buffer.slice());
    view.buffer.limit(view.getSize());
    buffer.position(buffer.position() + view.buffer.remaining());
    return view;
  }

  private LocalFileHeader(ByteBuffer buffer) {
    super(buffer);
  }

  /**
   * Creates a {@code LocalFileHeader} with a heap allocated buffer. Apart from the signature
   * and extra data data (if any), the returned object is uninitialized.
   *
   * @param name entry file name. Cannot be {@code null}.
   * @param extraData extra data, or {@code null}
   * @return a {@code LocalFileHeader} with a heap allocated buffer.
   */
  public static LocalFileHeader allocate(String name, byte[] extraData) {
    byte[] nameData = name.getBytes(UTF_8);
    if (extraData == null) {
      extraData = EMPTY;
    }
    int size = SIZE + nameData.length + extraData.length;
    ByteBuffer buffer = ByteBuffer.allocate(size).order(LITTLE_ENDIAN);
    return new LocalFileHeader(buffer).init(nameData, extraData, size);
  }

  /**
   * Creates a {@code LocalFileHeader} over a writable buffer. The given buffer's position is
   * advanced by the number of bytes consumed by the view. Apart from the signature and extra data
   * (if any), the returned view is uninitialized.
   *
   * @param buffer buffer to hold data for the "central directory entry" record.
   * @param name entry file name. Cannot be {@code null}.
   * @param extraData extra data, or {@code null}
   * @return a {@code DirectoryEntry} with a heap allocated buffer.
   */
  public static LocalFileHeader view(ByteBuffer buffer, String name, byte[] extraData) {
    byte[] nameData = name.getBytes(UTF_8);
    if (extraData == null) {
      extraData = EMPTY;
    }
    int size = SIZE + nameData.length + extraData.length;
    LocalFileHeader view = new LocalFileHeader(buffer.slice()).init(nameData, extraData, size);
    buffer.position(buffer.position() + size);
    return view;
  }

  /**
   * Copies this {@code LocalFileHeader} into a heap allocated buffer, overwriting the current
   * path name and extra data with the given values.
   *
   * @param name entry file name. Cannot be {@code null}.
   * @param extraData extra data, or {@code null}
   * @return a {@code LocalFileHeader} with a heap allocated buffer, initialized with the given
   * name and extra data, and otherwise a copy of this object.
   */
  public LocalFileHeader clone(String name, byte[] extraData) {
    return LocalFileHeader.allocate(name, extraData).copy(this, LOCCRC, LOCSIZ, LOCLEN, LOCFLG,
        LOCHOW, LOCTIM, LOCVER);
  }
  
  /**
   * Copies this {@code LocalFileHeader} into a writable buffer. The copy is made at the
   * buffer's current position, and the position is advanced, by the size of the copy.
   *
   * @param buffer buffer to hold data for the copy.
   * @return a {@code LocalFileHeader} backed by the given buffer.
   */
  public LocalFileHeader copy(ByteBuffer buffer) {
    int size = getSize();
    LocalFileHeader view = new LocalFileHeader(buffer.slice());
    this.buffer.rewind();
    view.buffer.put(this.buffer).flip();
    buffer.position(buffer.position() + size);
    this.buffer.rewind();
    return view;
  }

  private LocalFileHeader init(byte[] name, byte[] extra, int size) {
    buffer.putInt(0, SIGNATURE);
    set(LOCNAM, (short) name.length);
    set(LOCEXT, (short) extra.length);
    buffer.position(SIZE);
    buffer.put(name);
    if (extra.length > 0) {
      buffer.put(extra);
    }
    buffer.position(0).limit(size);
    return this;
  }

  /**
   * Flag used to mark a compressed entry, for which the size is unknown at the time
   * of writing the header.
   */
  public static final short SIZE_MASKED_FLAG = 0x8;

  /**
   * Signature of local file header.
   */
  public static final int SIGNATURE = 0x04034b50; // 67324752L

  /**
   * Size of local file header, not including variable data.
   * Also the offset of the filename.
   */
  public static final int SIZE = 30;

  /**
   * For accessing the local header signature, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, int)}
   * methods.
   */
  public static final IntFieldId<LocalFileHeader> LOCSIG = new IntFieldId<>(0);

  /**
   * For accessing the "needed version" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, short)}
   * methods.
   */
  public static final ShortFieldId<LocalFileHeader> LOCVER = new ShortFieldId<>(4);

  /**
   * For accessing the "flags" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, short)}
   * methods.
   */
  public static final ShortFieldId<LocalFileHeader> LOCFLG = new ShortFieldId<>(6);

  /**
   * For accessing the "method" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, short)}
   * methods.
   */
  public static final ShortFieldId<LocalFileHeader> LOCHOW = new ShortFieldId<>(8);

  /**
   * For accessing the "modified time" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, int)}
   * methods.
   */
  public static final IntFieldId<LocalFileHeader> LOCTIM = new IntFieldId<>(10);

  /**
   * For accessing the "crc" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, int)}
   * methods.
   */
  public static final IntFieldId<LocalFileHeader> LOCCRC = new IntFieldId<>(14);

  /**
   * For accessing the "compressed size" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, int)}
   * methods.
   */
  public static final IntFieldId<LocalFileHeader> LOCSIZ = new IntFieldId<>(18);

  /**
   * For accessing the "uncompressed size" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.IntFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.IntFieldId, int)}
   * methods.
   */
  public static final IntFieldId<LocalFileHeader> LOCLEN = new IntFieldId<>(22);

  /**
   * For accessing the "filename length" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, short)}
   * methods.
   */
  public static final ShortFieldId<LocalFileHeader> LOCNAM = new ShortFieldId<>(26);

  /**
   * For accessing the "extra data length" local header field, with the
   * {@link View#get(com.google.devtools.build.android.ziputils.View.ShortFieldId)}
   * and {@link View#set(com.google.devtools.build.android.ziputils.View.ShortFieldId, short)}
   * methods.
   */
  public static final ShortFieldId<LocalFileHeader> LOCEXT = new ShortFieldId<>(28);

  /**
   * Returns the filename for this entry.
   */
  public final String getFilename() {
    return getString(SIZE, get(LOCNAM));
  }

  /**
   * Returns the extra data for this entry.
   * @return an array of 0 or more bytes.
   */
  public final byte[] getExtraData() {
    return getBytes(SIZE + get(LOCNAM), get(LOCEXT));
  }

  /**
   * Returns the size of this header, including filename, and extra data (if any).
   */
  public final int getSize() {
    return SIZE + get(LOCNAM) + get(LOCEXT);
  }

  /**
   * Returns entry data size, based on directory entry information. For a valid zip file, this will
   * be the correct size of the entry data, or -1, if the size cannot be determined from the
   * header. Notice, if ths method returns 0, it may  be because the writer of the zip file forgot
   * to set the {@link #SIZE_MASKED_FLAG}.
   *
   * @return if the {@link #LOCHOW} field is 0, returns the value of the
   * {@link #LOCLEN} field (uncompressed size). If {@link #LOCHOW} is not 0, and the
   * {@link #SIZE_MASKED_FLAG} is not set, returns the value of the {@link #LOCSIZ} field
   * (compressed size). Otherwise return -1 (size unknown).
   */
  public int dataSize() {
    return get(LOCHOW) == 0 ? get(LOCLEN)
        : (get(LOCFLG) & SIZE_MASKED_FLAG) == 0 ? get(LOCSIZ) : -1;
  }
}