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