• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  */
18 package org.apache.commons.compress.archivers.zip;
19 
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.zip.ZipException;
25 
26 /**
27  * ZipExtraField related methods
28  * @NotThreadSafe because the HashMap is not synch.
29  */
30 // CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
31 public class ExtraFieldUtils {
32 
33     private static final int WORD = 4;
34 
35     /**
36      * Static registry of known extra fields.
37      */
38     private static final Map<ZipShort, Class<?>> implementations;
39 
40     static {
41         implementations = new ConcurrentHashMap<>();
42         register(AsiExtraField.class);
43         register(X5455_ExtendedTimestamp.class);
44         register(X7875_NewUnix.class);
45         register(JarMarker.class);
46         register(UnicodePathExtraField.class);
47         register(UnicodeCommentExtraField.class);
48         register(Zip64ExtendedInformationExtraField.class);
49         register(X000A_NTFS.class);
50         register(X0014_X509Certificates.class);
51         register(X0015_CertificateIdForFile.class);
52         register(X0016_CertificateIdForCentralDirectory.class);
53         register(X0017_StrongEncryptionHeader.class);
54         register(X0019_EncryptionRecipientCertificateList.class);
55         register(ResourceAlignmentExtraField.class);
56     }
57 
58     /**
59      * Register a ZipExtraField implementation.
60      *
61      * <p>The given class must have a no-arg constructor and implement
62      * the {@link ZipExtraField ZipExtraField interface}.</p>
63      * @param c the class to register
64      */
register(final Class<?> c)65     public static void register(final Class<?> c) {
66         try {
67             final ZipExtraField ze = (ZipExtraField) c.newInstance();
68             implementations.put(ze.getHeaderId(), c);
69         } catch (final ClassCastException cc) {
70             throw new RuntimeException(c + " doesn\'t implement ZipExtraField"); //NOSONAR
71         } catch (final InstantiationException ie) {
72             throw new RuntimeException(c + " is not a concrete class"); //NOSONAR
73         } catch (final IllegalAccessException ie) {
74             throw new RuntimeException(c + "\'s no-arg constructor is not public"); //NOSONAR
75         }
76     }
77 
78     /**
79      * Create an instance of the appropriate ExtraField, falls back to
80      * {@link UnrecognizedExtraField UnrecognizedExtraField}.
81      * @param headerId the header identifier
82      * @return an instance of the appropriate ExtraField
83      * @throws InstantiationException if unable to instantiate the class
84      * @throws IllegalAccessException if not allowed to instantiate the class
85      */
createExtraField(final ZipShort headerId)86     public static ZipExtraField createExtraField(final ZipShort headerId)
87         throws InstantiationException, IllegalAccessException {
88         final Class<?> c = implementations.get(headerId);
89         if (c != null) {
90             return (ZipExtraField) c.newInstance();
91         }
92         final UnrecognizedExtraField u = new UnrecognizedExtraField();
93         u.setHeaderId(headerId);
94         return u;
95     }
96 
97     /**
98      * Split the array into ExtraFields and populate them with the
99      * given data as local file data, throwing an exception if the
100      * data cannot be parsed.
101      * @param data an array of bytes as it appears in local file data
102      * @return an array of ExtraFields
103      * @throws ZipException on error
104      */
parse(final byte[] data)105     public static ZipExtraField[] parse(final byte[] data) throws ZipException {
106         return parse(data, true, UnparseableExtraField.THROW);
107     }
108 
109     /**
110      * Split the array into ExtraFields and populate them with the
111      * given data, throwing an exception if the data cannot be parsed.
112      * @param data an array of bytes
113      * @param local whether data originates from the local file data
114      * or the central directory
115      * @return an array of ExtraFields
116      * @throws ZipException on error
117      */
parse(final byte[] data, final boolean local)118     public static ZipExtraField[] parse(final byte[] data, final boolean local)
119         throws ZipException {
120         return parse(data, local, UnparseableExtraField.THROW);
121     }
122 
123     /**
124      * Split the array into ExtraFields and populate them with the
125      * given data.
126      * @param data an array of bytes
127      * @param local whether data originates from the local file data
128      * or the central directory
129      * @param onUnparseableData what to do if the extra field data
130      * cannot be parsed.
131      * @return an array of ExtraFields
132      * @throws ZipException on error
133      *
134      * @since 1.1
135      */
parse(final byte[] data, final boolean local, final UnparseableExtraField onUnparseableData)136     public static ZipExtraField[] parse(final byte[] data, final boolean local,
137                                         final UnparseableExtraField onUnparseableData)
138         throws ZipException {
139         final List<ZipExtraField> v = new ArrayList<>();
140         int start = 0;
141         LOOP:
142         while (start <= data.length - WORD) {
143             final ZipShort headerId = new ZipShort(data, start);
144             final int length = new ZipShort(data, start + 2).getValue();
145             if (start + WORD + length > data.length) {
146                 switch(onUnparseableData.getKey()) {
147                 case UnparseableExtraField.THROW_KEY:
148                     throw new ZipException("bad extra field starting at "
149                                            + start + ".  Block length of "
150                                            + length + " bytes exceeds remaining"
151                                            + " data of "
152                                            + (data.length - start - WORD)
153                                            + " bytes.");
154                 case UnparseableExtraField.READ_KEY:
155                     final UnparseableExtraFieldData field =
156                         new UnparseableExtraFieldData();
157                     if (local) {
158                         field.parseFromLocalFileData(data, start,
159                                                      data.length - start);
160                     } else {
161                         field.parseFromCentralDirectoryData(data, start,
162                                                             data.length - start);
163                     }
164                     v.add(field);
165                     //$FALL-THROUGH$
166                 case UnparseableExtraField.SKIP_KEY:
167                     // since we cannot parse the data we must assume
168                     // the extra field consumes the whole rest of the
169                     // available data
170                     break LOOP;
171                 default:
172                     throw new ZipException("unknown UnparseableExtraField key: "
173                                            + onUnparseableData.getKey());
174                 }
175             }
176             try {
177                 final ZipExtraField ze = createExtraField(headerId);
178                 try {
179                     if (local) {
180                         ze.parseFromLocalFileData(data, start + WORD, length);
181                     } else {
182                         ze.parseFromCentralDirectoryData(data, start + WORD, length);
183                     }
184                 } catch (ArrayIndexOutOfBoundsException aiobe) {
185                     throw (ZipException) new ZipException("Failed to parse corrupt ZIP extra field of type "
186                         + Integer.toHexString(headerId.getValue())).initCause(aiobe);
187                 }
188                 v.add(ze);
189             } catch (final InstantiationException | IllegalAccessException ie) {
190                 throw (ZipException) new ZipException(ie.getMessage()).initCause(ie);
191             }
192             start += length + WORD;
193         }
194 
195         final ZipExtraField[] result = new ZipExtraField[v.size()];
196         return v.toArray(result);
197     }
198 
199     /**
200      * Merges the local file data fields of the given ZipExtraFields.
201      * @param data an array of ExtraFiles
202      * @return an array of bytes
203      */
mergeLocalFileDataData(final ZipExtraField[] data)204     public static byte[] mergeLocalFileDataData(final ZipExtraField[] data) {
205         final boolean lastIsUnparseableHolder = data.length > 0
206             && data[data.length - 1] instanceof UnparseableExtraFieldData;
207         final int regularExtraFieldCount =
208             lastIsUnparseableHolder ? data.length - 1 : data.length;
209 
210         int sum = WORD * regularExtraFieldCount;
211         for (final ZipExtraField element : data) {
212             sum += element.getLocalFileDataLength().getValue();
213         }
214 
215         final byte[] result = new byte[sum];
216         int start = 0;
217         for (int i = 0; i < regularExtraFieldCount; i++) {
218             System.arraycopy(data[i].getHeaderId().getBytes(),
219                              0, result, start, 2);
220             System.arraycopy(data[i].getLocalFileDataLength().getBytes(),
221                              0, result, start + 2, 2);
222             start += WORD;
223             final byte[] local = data[i].getLocalFileDataData();
224             if (local != null) {
225                 System.arraycopy(local, 0, result, start, local.length);
226                 start += local.length;
227             }
228         }
229         if (lastIsUnparseableHolder) {
230             final byte[] local = data[data.length - 1].getLocalFileDataData();
231             if (local != null) {
232                 System.arraycopy(local, 0, result, start, local.length);
233             }
234         }
235         return result;
236     }
237 
238     /**
239      * Merges the central directory fields of the given ZipExtraFields.
240      * @param data an array of ExtraFields
241      * @return an array of bytes
242      */
mergeCentralDirectoryData(final ZipExtraField[] data)243     public static byte[] mergeCentralDirectoryData(final ZipExtraField[] data) {
244         final boolean lastIsUnparseableHolder = data.length > 0
245             && data[data.length - 1] instanceof UnparseableExtraFieldData;
246         final int regularExtraFieldCount =
247             lastIsUnparseableHolder ? data.length - 1 : data.length;
248 
249         int sum = WORD * regularExtraFieldCount;
250         for (final ZipExtraField element : data) {
251             sum += element.getCentralDirectoryLength().getValue();
252         }
253         final byte[] result = new byte[sum];
254         int start = 0;
255         for (int i = 0; i < regularExtraFieldCount; i++) {
256             System.arraycopy(data[i].getHeaderId().getBytes(),
257                              0, result, start, 2);
258             System.arraycopy(data[i].getCentralDirectoryLength().getBytes(),
259                              0, result, start + 2, 2);
260             start += WORD;
261             final byte[] local = data[i].getCentralDirectoryData();
262             if (local != null) {
263                 System.arraycopy(local, 0, result, start, local.length);
264                 start += local.length;
265             }
266         }
267         if (lastIsUnparseableHolder) {
268             final byte[] local = data[data.length - 1].getCentralDirectoryData();
269             if (local != null) {
270                 System.arraycopy(local, 0, result, start, local.length);
271             }
272         }
273         return result;
274     }
275 
276     /**
277      * "enum" for the possible actions to take if the extra field
278      * cannot be parsed.
279      *
280      * @since 1.1
281      */
282     public static final class UnparseableExtraField {
283         /**
284          * Key for "throw an exception" action.
285          */
286         public static final int THROW_KEY = 0;
287         /**
288          * Key for "skip" action.
289          */
290         public static final int SKIP_KEY = 1;
291         /**
292          * Key for "read" action.
293          */
294         public static final int READ_KEY = 2;
295 
296         /**
297          * Throw an exception if field cannot be parsed.
298          */
299         public static final UnparseableExtraField THROW
300             = new UnparseableExtraField(THROW_KEY);
301 
302         /**
303          * Skip the extra field entirely and don't make its data
304          * available - effectively removing the extra field data.
305          */
306         public static final UnparseableExtraField SKIP
307             = new UnparseableExtraField(SKIP_KEY);
308 
309         /**
310          * Read the extra field data into an instance of {@link
311          * UnparseableExtraFieldData UnparseableExtraFieldData}.
312          */
313         public static final UnparseableExtraField READ
314             = new UnparseableExtraField(READ_KEY);
315 
316         private final int key;
317 
UnparseableExtraField(final int k)318         private UnparseableExtraField(final int k) {
319             key = k;
320         }
321 
322         /**
323          * Key of the action to take.
324          * @return the key
325          */
getKey()326         public int getKey() { return key; }
327     }
328 }
329