• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 package org.apache.commons.compress.archivers.zip;
20 
21 import java.util.zip.ZipException;
22 
23 import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
24 import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
25 
26 /**
27  * Holds size and other extended information for entries that use Zip64
28  * features.
29  *
30  * <p>Currently Commons Compress doesn't support encrypting the
31  * central directory so the note in APPNOTE.TXT about masking doesn't
32  * apply.</p>
33  *
34  * <p>The implementation relies on data being read from the local file
35  * header and assumes that both size values are always present.</p>
36  *
37  * @see <a href="https://www.pkware.com/documents/casestudies/APPNOTE.TXT">PKWARE
38  * APPNOTE.TXT, section 4.5.3</a>
39  *
40  * @since 1.2
41  * @NotThreadSafe
42  */
43 public class Zip64ExtendedInformationExtraField implements ZipExtraField {
44 
45     static final ZipShort HEADER_ID = new ZipShort(0x0001);
46 
47     private static final String LFH_MUST_HAVE_BOTH_SIZES_MSG =
48         "Zip64 extended information must contain"
49         + " both size values in the local file header.";
50     private static final byte[] EMPTY = new byte[0];
51 
52     private ZipEightByteInteger size, compressedSize, relativeHeaderOffset;
53     private ZipLong diskStart;
54 
55     /**
56      * Stored in {@link #parseFromCentralDirectoryData
57      * parseFromCentralDirectoryData} so it can be reused when ZipFile
58      * calls {@link #reparseCentralDirectoryData
59      * reparseCentralDirectoryData}.
60      *
61      * <p>Not used for anything else</p>
62      *
63      * @since 1.3
64      */
65     private byte[] rawCentralDirectoryData;
66 
67     /**
68      * This constructor should only be used by the code that reads
69      * archives inside of Commons Compress.
70      */
Zip64ExtendedInformationExtraField()71     public Zip64ExtendedInformationExtraField() { }
72 
73     /**
74      * Creates an extra field based on the original and compressed size.
75      *
76      * @param size the entry's original size
77      * @param compressedSize the entry's compressed size
78      *
79      * @throws IllegalArgumentException if size or compressedSize is null
80      */
Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, final ZipEightByteInteger compressedSize)81     public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size,
82                                               final ZipEightByteInteger compressedSize) {
83         this(size, compressedSize, null, null);
84     }
85 
86     /**
87      * Creates an extra field based on all four possible values.
88      *
89      * @param size the entry's original size
90      * @param compressedSize the entry's compressed size
91      * @param relativeHeaderOffset the entry's offset
92      * @param diskStart the disk start
93      *
94      * @throws IllegalArgumentException if size or compressedSize is null
95      */
Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, final ZipEightByteInteger compressedSize, final ZipEightByteInteger relativeHeaderOffset, final ZipLong diskStart)96     public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size,
97                                               final ZipEightByteInteger compressedSize,
98                                               final ZipEightByteInteger relativeHeaderOffset,
99                                               final ZipLong diskStart) {
100         this.size = size;
101         this.compressedSize = compressedSize;
102         this.relativeHeaderOffset = relativeHeaderOffset;
103         this.diskStart = diskStart;
104     }
105 
106     @Override
getHeaderId()107     public ZipShort getHeaderId() {
108         return HEADER_ID;
109     }
110 
111     @Override
getLocalFileDataLength()112     public ZipShort getLocalFileDataLength() {
113         return new ZipShort(size != null ? 2 * DWORD : 0);
114     }
115 
116     @Override
getCentralDirectoryLength()117     public ZipShort getCentralDirectoryLength() {
118         return new ZipShort((size != null ? DWORD : 0)
119                             + (compressedSize != null ? DWORD : 0)
120                             + (relativeHeaderOffset != null ? DWORD : 0)
121                             + (diskStart != null ? WORD : 0));
122     }
123 
124     @Override
getLocalFileDataData()125     public byte[] getLocalFileDataData() {
126         if (size != null || compressedSize != null) {
127             if (size == null || compressedSize == null) {
128                 throw new IllegalArgumentException(LFH_MUST_HAVE_BOTH_SIZES_MSG);
129             }
130             final byte[] data = new byte[2 * DWORD];
131             addSizes(data);
132             return data;
133         }
134         return EMPTY;
135     }
136 
137     @Override
getCentralDirectoryData()138     public byte[] getCentralDirectoryData() {
139         final byte[] data = new byte[getCentralDirectoryLength().getValue()];
140         int off = addSizes(data);
141         if (relativeHeaderOffset != null) {
142             System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD);
143             off += DWORD;
144         }
145         if (diskStart != null) {
146             System.arraycopy(diskStart.getBytes(), 0, data, off, WORD);
147             off += WORD; // NOSONAR - assignment as documentation
148         }
149         return data;
150     }
151 
152     @Override
parseFromLocalFileData(final byte[] buffer, int offset, final int length)153     public void parseFromLocalFileData(final byte[] buffer, int offset, final int length)
154         throws ZipException {
155         if (length == 0) {
156             // no local file data at all, may happen if an archive
157             // only holds a ZIP64 extended information extra field
158             // inside the central directory but not inside the local
159             // file header
160             return;
161         }
162         if (length < 2 * DWORD) {
163             throw new ZipException(LFH_MUST_HAVE_BOTH_SIZES_MSG);
164         }
165         size = new ZipEightByteInteger(buffer, offset);
166         offset += DWORD;
167         compressedSize = new ZipEightByteInteger(buffer, offset);
168         offset += DWORD;
169         int remaining = length - 2 * DWORD;
170         if (remaining >= DWORD) {
171             relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
172             offset += DWORD;
173             remaining -= DWORD;
174         }
175         if (remaining >= WORD) {
176             diskStart = new ZipLong(buffer, offset);
177             offset += WORD; // NOSONAR - assignment as documentation
178             remaining -= WORD; // NOSONAR - assignment as documentation
179         }
180     }
181 
182     @Override
parseFromCentralDirectoryData(final byte[] buffer, int offset, final int length)183     public void parseFromCentralDirectoryData(final byte[] buffer, int offset,
184                                               final int length)
185         throws ZipException {
186         // store for processing in reparseCentralDirectoryData
187         rawCentralDirectoryData = new byte[length];
188         System.arraycopy(buffer, offset, rawCentralDirectoryData, 0, length);
189 
190         // if there is no size information in here, we are screwed and
191         // can only hope things will get resolved by LFH data later
192         // But there are some cases that can be detected
193         // * all data is there
194         // * length == 24 -> both sizes and offset
195         // * length % 8 == 4 -> at least we can identify the diskStart field
196         if (length >= 3 * DWORD + WORD) {
197             parseFromLocalFileData(buffer, offset, length);
198         } else if (length == 3 * DWORD) {
199             size = new ZipEightByteInteger(buffer, offset);
200             offset += DWORD;
201             compressedSize = new ZipEightByteInteger(buffer, offset);
202             offset += DWORD;
203             relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
204         } else if (length % DWORD == WORD) {
205             diskStart = new ZipLong(buffer, offset + length - WORD);
206         }
207     }
208 
209     /**
210      * Parses the raw bytes read from the central directory extra
211      * field with knowledge which fields are expected to be there.
212      *
213      * <p>All four fields inside the zip64 extended information extra
214      * field are optional and must only be present if their corresponding
215      * entry inside the central directory contains the correct magic
216      * value.</p>
217      *
218      * @param hasUncompressedSize flag to read from central directory
219      * @param hasCompressedSize flag to read from central directory
220      * @param hasRelativeHeaderOffset flag to read from central directory
221      * @param hasDiskStart flag to read from central directory
222      * @throws ZipException on error
223      */
reparseCentralDirectoryData(final boolean hasUncompressedSize, final boolean hasCompressedSize, final boolean hasRelativeHeaderOffset, final boolean hasDiskStart)224     public void reparseCentralDirectoryData(final boolean hasUncompressedSize,
225                                             final boolean hasCompressedSize,
226                                             final boolean hasRelativeHeaderOffset,
227                                             final boolean hasDiskStart)
228         throws ZipException {
229         if (rawCentralDirectoryData != null) {
230             final int expectedLength = (hasUncompressedSize ? DWORD : 0)
231                 + (hasCompressedSize ? DWORD : 0)
232                 + (hasRelativeHeaderOffset ? DWORD : 0)
233                 + (hasDiskStart ? WORD : 0);
234             if (rawCentralDirectoryData.length < expectedLength) {
235                 throw new ZipException("central directory zip64 extended"
236                                        + " information extra field's length"
237                                        + " doesn't match central directory"
238                                        + " data.  Expected length "
239                                        + expectedLength + " but is "
240                                        + rawCentralDirectoryData.length);
241             }
242             int offset = 0;
243             if (hasUncompressedSize) {
244                 size = new ZipEightByteInteger(rawCentralDirectoryData, offset);
245                 offset += DWORD;
246             }
247             if (hasCompressedSize) {
248                 compressedSize = new ZipEightByteInteger(rawCentralDirectoryData,
249                                                          offset);
250                 offset += DWORD;
251             }
252             if (hasRelativeHeaderOffset) {
253                 relativeHeaderOffset =
254                     new ZipEightByteInteger(rawCentralDirectoryData, offset);
255                 offset += DWORD;
256             }
257             if (hasDiskStart) {
258                 diskStart = new ZipLong(rawCentralDirectoryData, offset);
259                 offset += WORD; // NOSONAR - assignment as documentation
260             }
261         }
262     }
263 
264     /**
265      * The uncompressed size stored in this extra field.
266      * @return The uncompressed size stored in this extra field.
267      */
getSize()268     public ZipEightByteInteger getSize() {
269         return size;
270     }
271 
272     /**
273      * The uncompressed size stored in this extra field.
274      * @param size The uncompressed size stored in this extra field.
275      */
setSize(final ZipEightByteInteger size)276     public void setSize(final ZipEightByteInteger size) {
277         this.size = size;
278     }
279 
280     /**
281      * The compressed size stored in this extra field.
282      * @return The compressed size stored in this extra field.
283      */
getCompressedSize()284     public ZipEightByteInteger getCompressedSize() {
285         return compressedSize;
286     }
287 
288     /**
289      * The uncompressed size stored in this extra field.
290      * @param compressedSize The uncompressed size stored in this extra field.
291      */
setCompressedSize(final ZipEightByteInteger compressedSize)292     public void setCompressedSize(final ZipEightByteInteger compressedSize) {
293         this.compressedSize = compressedSize;
294     }
295 
296     /**
297      * The relative header offset stored in this extra field.
298      * @return The relative header offset stored in this extra field.
299      */
getRelativeHeaderOffset()300     public ZipEightByteInteger getRelativeHeaderOffset() {
301         return relativeHeaderOffset;
302     }
303 
304     /**
305      * The relative header offset stored in this extra field.
306      * @param rho The relative header offset stored in this extra field.
307      */
setRelativeHeaderOffset(final ZipEightByteInteger rho)308     public void setRelativeHeaderOffset(final ZipEightByteInteger rho) {
309         relativeHeaderOffset = rho;
310     }
311 
312     /**
313      * The disk start number stored in this extra field.
314      * @return The disk start number stored in this extra field.
315      */
getDiskStartNumber()316     public ZipLong getDiskStartNumber() {
317         return diskStart;
318     }
319 
320     /**
321      * The disk start number stored in this extra field.
322      * @param ds The disk start number stored in this extra field.
323      */
setDiskStartNumber(final ZipLong ds)324     public void setDiskStartNumber(final ZipLong ds) {
325         diskStart = ds;
326     }
327 
addSizes(final byte[] data)328     private int addSizes(final byte[] data) {
329         int off = 0;
330         if (size != null) {
331             System.arraycopy(size.getBytes(), 0, data, 0, DWORD);
332             off += DWORD;
333         }
334         if (compressedSize != null) {
335             System.arraycopy(compressedSize.getBytes(), 0, data, off, DWORD);
336             off += DWORD;
337         }
338         return off;
339     }
340 }
341