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 1.1
126 */
127 public static ZipExtraField[] parse(byte[] data, boolean local,
128 UnparseableExtraField onUnparseableData)
129 throws ZipException {
130 List<ZipExtraField> v = new ArrayList<ZipExtraField>();
131 int start = 0;
132 LOOP:
133 while (start <= data.length - WORD) {
134 ZipShort headerId = new ZipShort(data, start);
135 int length = (new ZipShort(data, start + 2)).getValue();
136 if (start + WORD + length > data.length) {
137 switch(onUnparseableData.getKey()) {
138 case UnparseableExtraField.THROW_KEY:
139 throw new ZipException("bad extra field starting at "
140 + start + ". Block length of "
141 + length + " bytes exceeds remaining"
142 + " data of "
143 + (data.length - start - WORD)
144 + " bytes.");
145 case UnparseableExtraField.READ_KEY:
146 UnparseableExtraFieldData field =
147 new UnparseableExtraFieldData();
148 if (local) {
149 field.parseFromLocalFileData(data, start,
150 data.length - start);
151 } else {
152 field.parseFromCentralDirectoryData(data, start,
153 data.length - start);
154 }
155 v.add(field);
156 //$FALL-THROUGH$
157 case UnparseableExtraField.SKIP_KEY:
158 // since we cannot parse the data we must assume
159 // the extra field consumes the whole rest of the
160 // available data
161 break LOOP;
162 default:
163 throw new ZipException("unknown UnparseableExtraField key: "
164 + onUnparseableData.getKey());
165 }
166 }
167 try {
168 ZipExtraField ze = createExtraField(headerId);
169 if (local) {
170 ze.parseFromLocalFileData(data, start + WORD, length);
171 } else {
172 ze.parseFromCentralDirectoryData(data, start + WORD,
173 length);
174 }
175 v.add(ze);
176 } catch (InstantiationException ie) {
177 throw new ZipException(ie.getMessage());
178 } catch (IllegalAccessException iae) {
179 throw new ZipException(iae.getMessage());
180 }
181 start += (length + WORD);
182 }
183
184 ZipExtraField[] result = new ZipExtraField[v.size()];
185 return v.toArray(result);
186 }
187
188 /**
189 * Merges the local file data fields of the given ZipExtraFields.
190 * @param data an array of ExtraFiles
191 * @return an array of bytes
192 */
193 public static byte[] mergeLocalFileDataData(ZipExtraField[] data) {
194 final boolean lastIsUnparseableHolder = data.length > 0
195 && data[data.length - 1] instanceof UnparseableExtraFieldData;
196 int regularExtraFieldCount =
197 lastIsUnparseableHolder ? data.length - 1 : data.length;
198
199 int sum = WORD * regularExtraFieldCount;
200 for (ZipExtraField element : data) {
201 sum += element.getLocalFileDataLength().getValue();
202 }
203
204 byte[] result = new byte[sum];
205 int start = 0;
206 for (int i = 0; i < regularExtraFieldCount; i++) {
207 System.arraycopy(data[i].getHeaderId().getBytes(),
208 0, result, start, 2);
209 System.arraycopy(data[i].getLocalFileDataLength().getBytes(),
210 0, result, start + 2, 2);
211 byte[] local = data[i].getLocalFileDataData();
212 System.arraycopy(local, 0, result, start + WORD, local.length);
213 start += (local.length + WORD);
214 }
215 if (lastIsUnparseableHolder) {
216 byte[] local = data[data.length - 1].getLocalFileDataData();
217 System.arraycopy(local, 0, result, start, local.length);
218 }
219 return result;
220 }
221
222 /**
223 * Merges the central directory fields of the given ZipExtraFields.
224 * @param data an array of ExtraFields
225 * @return an array of bytes
226 */
227 public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) {
228 final boolean lastIsUnparseableHolder = data.length > 0
229 && data[data.length - 1] instanceof UnparseableExtraFieldData;
230 int regularExtraFieldCount =
231 lastIsUnparseableHolder ? data.length - 1 : data.length;
232
233 int sum = WORD * regularExtraFieldCount;
234 for (ZipExtraField element : data) {
235 sum += element.getCentralDirectoryLength().getValue();
236 }
237 byte[] result = new byte[sum];
238 int start = 0;
239 for (int i = 0; i < regularExtraFieldCount; i++) {
240 System.arraycopy(data[i].getHeaderId().getBytes(),
241 0, result, start, 2);
242 System.arraycopy(data[i].getCentralDirectoryLength().getBytes(),
243 0, result, start + 2, 2);
244 byte[] local = data[i].getCentralDirectoryData();
245 System.arraycopy(local, 0, result, start + WORD, local.length);
246 start += (local.length + WORD);
247 }
248 if (lastIsUnparseableHolder) {
249 byte[] local = data[data.length - 1].getCentralDirectoryData();
250 System.arraycopy(local, 0, result, start, local.length);
251 }
252 return result;
253 }
254
255 /**
256 * "enum" for the possible actions to take if the extra field
257 * cannot be parsed.
258 *
259 * @since 1.1
260 */
261 public static final class UnparseableExtraField {
262 /**
263 * Key for "throw an exception" action.
264 */
265 public static final int THROW_KEY = 0;
266 /**
267 * Key for "skip" action.
268 */
269 public static final int SKIP_KEY = 1;
270 /**
271 * Key for "read" action.
272 */
273 public static final int READ_KEY = 2;
274
275 /**
276 * Throw an exception if field cannot be parsed.
277 */
278 public static final UnparseableExtraField THROW
279 = new UnparseableExtraField(THROW_KEY);
280
281 /**
282 * Skip the extra field entirely and don't make its data
283 * available - effectively removing the extra field data.
284 */
285 public static final UnparseableExtraField SKIP
286 = new UnparseableExtraField(SKIP_KEY);
287
288 /**
289 * Read the extra field data into an instance of {@link
290 * UnparseableExtraFieldData UnparseableExtraFieldData}.
291 */
292 public static final UnparseableExtraField READ
293 = new UnparseableExtraField(READ_KEY);
294
295 private final int key;
296
297 private UnparseableExtraField(int k) {
298 key = k;
299 }
300
301 /**
302 * Key of the action to take.
303 */
304 public int getKey() { return key; }
305 }
306 }