001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 */
018 package org.apache.commons.compress.archivers.zip;
019
020 import java.util.ArrayList;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024 import java.util.zip.ZipException;
025
026 /**
027 * ZipExtraField related methods
028 * @NotThreadSafe because the HashMap is not synch.
029 */
030 // CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
031 public class ExtraFieldUtils {
032
033 private static final int WORD = 4;
034
035 /**
036 * Static registry of known extra fields.
037 */
038 private static final Map<ZipShort, Class<?>> implementations;
039
040 static {
041 implementations = new HashMap<ZipShort, Class<?>>();
042 register(AsiExtraField.class);
043 register(JarMarker.class);
044 register(UnicodePathExtraField.class);
045 register(UnicodeCommentExtraField.class);
046 register(Zip64ExtendedInformationExtraField.class);
047 }
048
049 /**
050 * Register a ZipExtraField implementation.
051 *
052 * <p>The given class must have a no-arg constructor and implement
053 * the {@link ZipExtraField ZipExtraField interface}.</p>
054 * @param c the class to register
055 */
056 public static void register(Class<?> c) {
057 try {
058 ZipExtraField ze = (ZipExtraField) c.newInstance();
059 implementations.put(ze.getHeaderId(), c);
060 } catch (ClassCastException cc) {
061 throw new RuntimeException(c + " doesn\'t implement ZipExtraField");
062 } catch (InstantiationException ie) {
063 throw new RuntimeException(c + " is not a concrete class");
064 } catch (IllegalAccessException ie) {
065 throw new RuntimeException(c + "\'s no-arg constructor is not public");
066 }
067 }
068
069 /**
070 * Create an instance of the approriate ExtraField, falls back to
071 * {@link UnrecognizedExtraField UnrecognizedExtraField}.
072 * @param headerId the header identifier
073 * @return an instance of the appropiate ExtraField
074 * @exception InstantiationException if unable to instantiate the class
075 * @exception IllegalAccessException if not allowed to instatiate the class
076 */
077 public static ZipExtraField createExtraField(ZipShort headerId)
078 throws InstantiationException, IllegalAccessException {
079 Class<?> c = implementations.get(headerId);
080 if (c != null) {
081 return (ZipExtraField) c.newInstance();
082 }
083 UnrecognizedExtraField u = new UnrecognizedExtraField();
084 u.setHeaderId(headerId);
085 return u;
086 }
087
088 /**
089 * Split the array into ExtraFields and populate them with the
090 * given data as local file data, throwing an exception if the
091 * data cannot be parsed.
092 * @param data an array of bytes as it appears in local file data
093 * @return an array of ExtraFields
094 * @throws ZipException on error
095 */
096 public static ZipExtraField[] parse(byte[] data) throws ZipException {
097 return parse(data, true, UnparseableExtraField.THROW);
098 }
099
100 /**
101 * Split the array into ExtraFields and populate them with the
102 * given data, throwing an exception if the data cannot be parsed.
103 * @param data an array of bytes
104 * @param local whether data originates from the local file data
105 * or the central directory
106 * @return an array of ExtraFields
107 * @throws ZipException on error
108 */
109 public static ZipExtraField[] parse(byte[] data, boolean local)
110 throws ZipException {
111 return parse(data, local, UnparseableExtraField.THROW);
112 }
113
114 /**
115 * Split the array into ExtraFields and populate them with the
116 * given data.
117 * @param data an array of bytes
118 * @param local whether data originates from the local file data
119 * or the central directory
120 * @param onUnparseableData what to do if the extra field data
121 * cannot be parsed.
122 * @return an array of ExtraFields
123 * @throws ZipException on error
124 *
125 * @since Apache Commons Compress 1.1
126 */
127 @SuppressWarnings("fallthrough")
128 public static ZipExtraField[] parse(byte[] data, boolean local,
129 UnparseableExtraField onUnparseableData)
130 throws ZipException {
131 List<ZipExtraField> v = new ArrayList<ZipExtraField>();
132 int start = 0;
133 LOOP:
134 while (start <= data.length - WORD) {
135 ZipShort headerId = new ZipShort(data, start);
136 int length = (new ZipShort(data, start + 2)).getValue();
137 if (start + WORD + length > data.length) {
138 switch(onUnparseableData.getKey()) {
139 case UnparseableExtraField.THROW_KEY:
140 throw new ZipException("bad extra field starting at "
141 + start + ". Block length of "
142 + length + " bytes exceeds remaining"
143 + " data of "
144 + (data.length - start - WORD)
145 + " bytes.");
146 case UnparseableExtraField.READ_KEY:
147 UnparseableExtraFieldData field =
148 new UnparseableExtraFieldData();
149 if (local) {
150 field.parseFromLocalFileData(data, start,
151 data.length - start);
152 } else {
153 field.parseFromCentralDirectoryData(data, start,
154 data.length - start);
155 }
156 v.add(field);
157 //$FALL-THROUGH$
158 case UnparseableExtraField.SKIP_KEY:
159 // since we cannot parse the data we must assume
160 // the extra field consumes the whole rest of the
161 // available data
162 break LOOP;
163 default:
164 throw new ZipException("unknown UnparseableExtraField key: "
165 + onUnparseableData.getKey());
166 }
167 }
168 try {
169 ZipExtraField ze = createExtraField(headerId);
170 if (local) {
171 ze.parseFromLocalFileData(data, start + WORD, length);
172 } else {
173 ze.parseFromCentralDirectoryData(data, start + WORD,
174 length);
175 }
176 v.add(ze);
177 } catch (InstantiationException ie) {
178 throw new ZipException(ie.getMessage());
179 } catch (IllegalAccessException iae) {
180 throw new ZipException(iae.getMessage());
181 }
182 start += (length + WORD);
183 }
184
185 ZipExtraField[] result = new ZipExtraField[v.size()];
186 return v.toArray(result);
187 }
188
189 /**
190 * Merges the local file data fields of the given ZipExtraFields.
191 * @param data an array of ExtraFiles
192 * @return an array of bytes
193 */
194 public static byte[] mergeLocalFileDataData(ZipExtraField[] data) {
195 final boolean lastIsUnparseableHolder = data.length > 0
196 && data[data.length - 1] instanceof UnparseableExtraFieldData;
197 int regularExtraFieldCount =
198 lastIsUnparseableHolder ? data.length - 1 : data.length;
199
200 int sum = WORD * regularExtraFieldCount;
201 for (int i = 0; i < data.length; i++) {
202 sum += data[i].getLocalFileDataLength().getValue();
203 }
204
205 byte[] result = new byte[sum];
206 int start = 0;
207 for (int i = 0; i < regularExtraFieldCount; i++) {
208 System.arraycopy(data[i].getHeaderId().getBytes(),
209 0, result, start, 2);
210 System.arraycopy(data[i].getLocalFileDataLength().getBytes(),
211 0, result, start + 2, 2);
212 byte[] local = data[i].getLocalFileDataData();
213 System.arraycopy(local, 0, result, start + WORD, local.length);
214 start += (local.length + WORD);
215 }
216 if (lastIsUnparseableHolder) {
217 byte[] local = data[data.length - 1].getLocalFileDataData();
218 System.arraycopy(local, 0, result, start, local.length);
219 }
220 return result;
221 }
222
223 /**
224 * Merges the central directory fields of the given ZipExtraFields.
225 * @param data an array of ExtraFields
226 * @return an array of bytes
227 */
228 public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) {
229 final boolean lastIsUnparseableHolder = data.length > 0
230 && data[data.length - 1] instanceof UnparseableExtraFieldData;
231 int regularExtraFieldCount =
232 lastIsUnparseableHolder ? data.length - 1 : data.length;
233
234 int sum = WORD * regularExtraFieldCount;
235 for (int i = 0; i < data.length; i++) {
236 sum += data[i].getCentralDirectoryLength().getValue();
237 }
238 byte[] result = new byte[sum];
239 int start = 0;
240 for (int i = 0; i < regularExtraFieldCount; i++) {
241 System.arraycopy(data[i].getHeaderId().getBytes(),
242 0, result, start, 2);
243 System.arraycopy(data[i].getCentralDirectoryLength().getBytes(),
244 0, result, start + 2, 2);
245 byte[] local = data[i].getCentralDirectoryData();
246 System.arraycopy(local, 0, result, start + WORD, local.length);
247 start += (local.length + WORD);
248 }
249 if (lastIsUnparseableHolder) {
250 byte[] local = data[data.length - 1].getCentralDirectoryData();
251 System.arraycopy(local, 0, result, start, local.length);
252 }
253 return result;
254 }
255
256 /**
257 * "enum" for the possible actions to take if the extra field
258 * cannot be parsed.
259 *
260 * @since Apache Commons Compress 1.1
261 */
262 public static final class UnparseableExtraField {
263 /**
264 * Key for "throw an exception" action.
265 */
266 public static final int THROW_KEY = 0;
267 /**
268 * Key for "skip" action.
269 */
270 public static final int SKIP_KEY = 1;
271 /**
272 * Key for "read" action.
273 */
274 public static final int READ_KEY = 2;
275
276 /**
277 * Throw an exception if field cannot be parsed.
278 */
279 public static final UnparseableExtraField THROW
280 = new UnparseableExtraField(THROW_KEY);
281
282 /**
283 * Skip the extra field entirely and don't make its data
284 * available - effectively removing the extra field data.
285 */
286 public static final UnparseableExtraField SKIP
287 = new UnparseableExtraField(SKIP_KEY);
288
289 /**
290 * Read the extra field data into an instance of {@link
291 * UnparseableExtraFieldData UnparseableExtraFieldData}.
292 */
293 public static final UnparseableExtraField READ
294 = new UnparseableExtraField(READ_KEY);
295
296 private final int key;
297
298 private UnparseableExtraField(int k) {
299 key = k;
300 }
301
302 /**
303 * Key of the action to take.
304 */
305 public int getKey() { return key; }
306 }
307 }