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