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