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 */ 017package org.apache.commons.compress.archivers.zip; 018 019import java.lang.reflect.Constructor; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.Objects; 023import java.util.concurrent.ConcurrentHashMap; 024import java.util.concurrent.ConcurrentMap; 025import java.util.function.Supplier; 026import java.util.zip.ZipException; 027 028/** 029 * {@link ZipExtraField} related methods. 030 */ 031// CheckStyle:HideUtilityClassConstructorCheck OFF (bc) 032public class ExtraFieldUtils { 033 034 /** 035 * "enum" for the possible actions to take if the extra field cannot be parsed. 036 * <p> 037 * This class has been created long before Java 5 and would have been a real enum ever since. 038 * </p> 039 * 040 * @since 1.1 041 */ 042 public static final class UnparseableExtraField implements UnparseableExtraFieldBehavior { 043 044 /** 045 * Key for "throw an exception" action. 046 */ 047 public static final int THROW_KEY = 0; 048 /** 049 * Key for "skip" action. 050 */ 051 public static final int SKIP_KEY = 1; 052 /** 053 * Key for "read" action. 054 */ 055 public static final int READ_KEY = 2; 056 057 /** 058 * Throw an exception if field cannot be parsed. 059 */ 060 public static final UnparseableExtraField THROW = new UnparseableExtraField(THROW_KEY); 061 062 /** 063 * Skip the extra field entirely and don't make its data available - effectively removing the extra field data. 064 */ 065 public static final UnparseableExtraField SKIP = new UnparseableExtraField(SKIP_KEY); 066 067 /** 068 * Read the extra field data into an instance of {@link UnparseableExtraFieldData UnparseableExtraFieldData}. 069 */ 070 public static final UnparseableExtraField READ = new UnparseableExtraField(READ_KEY); 071 072 private final int key; 073 074 private UnparseableExtraField(final int k) { 075 key = k; 076 } 077 078 /** 079 * Key of the action to take. 080 * 081 * @return the key 082 */ 083 public int getKey() { 084 return key; 085 } 086 087 @Override 088 public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength) 089 throws ZipException { 090 switch (key) { 091 case THROW_KEY: 092 throw new ZipException("Bad extra field starting at " + off + ". Block length of " + claimedLength + " bytes exceeds remaining" + " data of " 093 + (len - WORD) + " bytes."); 094 case READ_KEY: 095 final UnparseableExtraFieldData field = new UnparseableExtraFieldData(); 096 if (local) { 097 field.parseFromLocalFileData(data, off, len); 098 } else { 099 field.parseFromCentralDirectoryData(data, off, len); 100 } 101 return field; 102 case SKIP_KEY: 103 return null; 104 default: 105 throw new ZipException("Unknown UnparseableExtraField key: " + key); 106 } 107 } 108 109 } 110 111 private static final int WORD = 4; 112 113 /** 114 * Static registry of known extra fields. 115 */ 116 private static final ConcurrentMap<ZipShort, Supplier<ZipExtraField>> IMPLEMENTATIONS; 117 118 static { 119 IMPLEMENTATIONS = new ConcurrentHashMap<>(); 120 IMPLEMENTATIONS.put(AsiExtraField.HEADER_ID, AsiExtraField::new); 121 IMPLEMENTATIONS.put(X5455_ExtendedTimestamp.HEADER_ID, X5455_ExtendedTimestamp::new); 122 IMPLEMENTATIONS.put(X7875_NewUnix.HEADER_ID, X7875_NewUnix::new); 123 IMPLEMENTATIONS.put(JarMarker.ID, JarMarker::new); 124 IMPLEMENTATIONS.put(UnicodePathExtraField.UPATH_ID, UnicodePathExtraField::new); 125 IMPLEMENTATIONS.put(UnicodeCommentExtraField.UCOM_ID, UnicodeCommentExtraField::new); 126 IMPLEMENTATIONS.put(Zip64ExtendedInformationExtraField.HEADER_ID, Zip64ExtendedInformationExtraField::new); 127 IMPLEMENTATIONS.put(X000A_NTFS.HEADER_ID, X000A_NTFS::new); 128 IMPLEMENTATIONS.put(X0014_X509Certificates.HEADER_ID, X0014_X509Certificates::new); 129 IMPLEMENTATIONS.put(X0015_CertificateIdForFile.HEADER_ID, X0015_CertificateIdForFile::new); 130 IMPLEMENTATIONS.put(X0016_CertificateIdForCentralDirectory.HEADER_ID, X0016_CertificateIdForCentralDirectory::new); 131 IMPLEMENTATIONS.put(X0017_StrongEncryptionHeader.HEADER_ID, X0017_StrongEncryptionHeader::new); 132 IMPLEMENTATIONS.put(X0019_EncryptionRecipientCertificateList.HEADER_ID, X0019_EncryptionRecipientCertificateList::new); 133 IMPLEMENTATIONS.put(ResourceAlignmentExtraField.ID, ResourceAlignmentExtraField::new); 134 } 135 136 static final ZipExtraField[] EMPTY_ZIP_EXTRA_FIELD_ARRAY = {}; 137 138 /** 139 * Creates an instance of the appropriate ExtraField, falls back to {@link UnrecognizedExtraField UnrecognizedExtraField}. 140 * 141 * @param headerId the header identifier 142 * @return an instance of the appropriate ExtraField 143 */ 144 public static ZipExtraField createExtraField(final ZipShort headerId) { 145 final ZipExtraField field = createExtraFieldNoDefault(headerId); 146 if (field != null) { 147 return field; 148 } 149 final UnrecognizedExtraField u = new UnrecognizedExtraField(); 150 u.setHeaderId(headerId); 151 return u; 152 } 153 154 /** 155 * Creates an instance of the appropriate {@link ZipExtraField}. 156 * 157 * @param headerId the header identifier 158 * @return an instance of the appropriate {@link ZipExtraField} or null if the id is not supported 159 * @since 1.19 160 */ 161 public static ZipExtraField createExtraFieldNoDefault(final ZipShort headerId) { 162 final Supplier<ZipExtraField> provider = IMPLEMENTATIONS.get(headerId); 163 return provider != null ? provider.get() : null; 164 } 165 166 /** 167 * Fills in the extra field data into the given instance. 168 * 169 * <p> 170 * Calls {@link ZipExtraField#parseFromCentralDirectoryData} or {@link ZipExtraField#parseFromLocalFileData} internally and wraps any 171 * {@link ArrayIndexOutOfBoundsException} thrown into a {@link ZipException}. 172 * </p> 173 * 174 * @param ze the extra field instance to fill 175 * @param data the array of extra field data 176 * @param off offset into data where this field's data starts 177 * @param len the length of this field's data 178 * @param local whether the extra field data stems from the local file header. If this is false then the data is part if the central directory header extra 179 * data. 180 * @return the filled field, will never be {@code null} 181 * @throws ZipException if an error occurs 182 * 183 * @since 1.19 184 */ 185 public static ZipExtraField fillExtraField(final ZipExtraField ze, final byte[] data, final int off, final int len, final boolean local) 186 throws ZipException { 187 try { 188 if (local) { 189 ze.parseFromLocalFileData(data, off, len); 190 } else { 191 ze.parseFromCentralDirectoryData(data, off, len); 192 } 193 return ze; 194 } catch (final ArrayIndexOutOfBoundsException e) { 195 throw (ZipException) new ZipException("Failed to parse corrupt ZIP extra field of type " + Integer.toHexString(ze.getHeaderId().getValue())) 196 .initCause(e); 197 } 198 } 199 200 /** 201 * Merges the central directory fields of the given ZipExtraFields. 202 * 203 * @param data an array of ExtraFields 204 * @return an array of bytes 205 */ 206 public static byte[] mergeCentralDirectoryData(final ZipExtraField[] data) { 207 final int dataLength = data.length; 208 final boolean lastIsUnparseableHolder = dataLength > 0 && data[dataLength - 1] instanceof UnparseableExtraFieldData; 209 final int regularExtraFieldCount = lastIsUnparseableHolder ? dataLength - 1 : dataLength; 210 211 int sum = WORD * regularExtraFieldCount; 212 for (final ZipExtraField element : data) { 213 sum += element.getCentralDirectoryLength().getValue(); 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(), 0, result, start, 2); 219 System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), 0, result, start + 2, 2); 220 start += WORD; 221 final byte[] central = data[i].getCentralDirectoryData(); 222 if (central != null) { 223 System.arraycopy(central, 0, result, start, central.length); 224 start += central.length; 225 } 226 } 227 if (lastIsUnparseableHolder) { 228 final byte[] central = data[dataLength - 1].getCentralDirectoryData(); 229 if (central != null) { 230 System.arraycopy(central, 0, result, start, central.length); 231 } 232 } 233 return result; 234 } 235 236 /** 237 * Merges the local file data fields of the given ZipExtraFields. 238 * 239 * @param data an array of ExtraFiles 240 * @return an array of bytes 241 */ 242 public static byte[] mergeLocalFileDataData(final ZipExtraField[] data) { 243 final int dataLength = data.length; 244 final boolean lastIsUnparseableHolder = dataLength > 0 && data[dataLength - 1] instanceof UnparseableExtraFieldData; 245 final int regularExtraFieldCount = lastIsUnparseableHolder ? dataLength - 1 : dataLength; 246 247 int sum = WORD * regularExtraFieldCount; 248 for (final ZipExtraField element : data) { 249 sum += element.getLocalFileDataLength().getValue(); 250 } 251 252 final byte[] result = new byte[sum]; 253 int start = 0; 254 for (int i = 0; i < regularExtraFieldCount; i++) { 255 System.arraycopy(data[i].getHeaderId().getBytes(), 0, result, start, 2); 256 System.arraycopy(data[i].getLocalFileDataLength().getBytes(), 0, result, start + 2, 2); 257 start += WORD; 258 final byte[] local = data[i].getLocalFileDataData(); 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[dataLength - 1].getLocalFileDataData(); 266 if (local != null) { 267 System.arraycopy(local, 0, result, start, local.length); 268 } 269 } 270 return result; 271 } 272 273 /** 274 * Parses the array into ExtraFields and populate them with the given data as local file data, throwing an exception if the data cannot be parsed. 275 * 276 * @param data an array of bytes as it appears in local file data 277 * @return an array of ExtraFields 278 * @throws ZipException on error 279 */ 280 public static ZipExtraField[] parse(final byte[] data) throws ZipException { 281 return parse(data, true, UnparseableExtraField.THROW); 282 } 283 284 /** 285 * Parses the array into ExtraFields and populate them with the given data, throwing an exception if the data cannot be parsed. 286 * 287 * @param data an array of bytes 288 * @param local whether data originates from the local file data or the central directory 289 * @return an array of ExtraFields 290 * @throws ZipException on error 291 */ 292 public static ZipExtraField[] parse(final byte[] data, final boolean local) throws ZipException { 293 return parse(data, local, UnparseableExtraField.THROW); 294 } 295 296 /** 297 * Parses the array into ExtraFields and populate them with the given data. 298 * 299 * @param data an array of bytes 300 * @param parsingBehavior controls parsing of extra fields. 301 * @param local whether data originates from the local file data or the central directory 302 * @return an array of ExtraFields 303 * @throws ZipException on error 304 * 305 * @since 1.19 306 */ 307 public static ZipExtraField[] parse(final byte[] data, final boolean local, final ExtraFieldParsingBehavior parsingBehavior) throws ZipException { 308 final List<ZipExtraField> v = new ArrayList<>(); 309 int start = 0; 310 final int dataLength = data.length; 311 LOOP: while (start <= dataLength - WORD) { 312 final ZipShort headerId = new ZipShort(data, start); 313 final int length = new ZipShort(data, start + 2).getValue(); 314 if (start + WORD + length > dataLength) { 315 final ZipExtraField field = parsingBehavior.onUnparseableExtraField(data, start, dataLength - start, local, length); 316 if (field != null) { 317 v.add(field); 318 } 319 // since we cannot parse the data we must assume 320 // the extra field consumes the whole rest of the 321 // available data 322 break LOOP; 323 } 324 try { 325 final ZipExtraField ze = Objects.requireNonNull(parsingBehavior.createExtraField(headerId), "createExtraField must not return null"); 326 v.add(Objects.requireNonNull(parsingBehavior.fill(ze, data, start + WORD, length, local), "fill must not return null")); 327 start += length + WORD; 328 } catch (final InstantiationException | IllegalAccessException e) { 329 throw (ZipException) new ZipException(e.getMessage()).initCause(e); 330 } 331 } 332 333 return v.toArray(EMPTY_ZIP_EXTRA_FIELD_ARRAY); 334 } 335 336 /** 337 * Parses the array into ExtraFields and populate them with the given data. 338 * 339 * @param data an array of bytes 340 * @param local whether data originates from the local file data or the central directory 341 * @param onUnparseableData what to do if the extra field data cannot be parsed. 342 * @return an array of ExtraFields 343 * @throws ZipException on error 344 * 345 * @since 1.1 346 */ 347 public static ZipExtraField[] parse(final byte[] data, final boolean local, final UnparseableExtraField onUnparseableData) throws ZipException { 348 return parse(data, local, new ExtraFieldParsingBehavior() { 349 350 @Override 351 public ZipExtraField createExtraField(final ZipShort headerId) { 352 return ExtraFieldUtils.createExtraField(headerId); 353 } 354 355 @Override 356 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException { 357 return fillExtraField(field, data, off, len, local); 358 } 359 360 @Override 361 public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength) 362 throws ZipException { 363 return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength); 364 } 365 }); 366 } 367 368 /** 369 * Registers a ZipExtraField implementation, overriding a matching existing entry. 370 * <p> 371 * The given class must have a no-arg constructor and implement the {@link ZipExtraField ZipExtraField interface}. 372 * </p> 373 * 374 * @param clazz the class to register. 375 * 376 * @deprecated Use {@link ZipArchiveInputStream#setExtraFieldSupport} instead 377 * to not leak instances between archives and applications. 378 */ 379 @Deprecated // note: when dropping update registration to move to a HashMap (static init) 380 public static void register(final Class<?> clazz) { 381 try { 382 final Constructor<? extends ZipExtraField> constructor = clazz.asSubclass(ZipExtraField.class).getConstructor(); 383 final ZipExtraField zef = clazz.asSubclass(ZipExtraField.class).getConstructor().newInstance(); 384 IMPLEMENTATIONS.put(zef.getHeaderId(), () -> { 385 try { 386 return constructor.newInstance(); 387 } catch (final ReflectiveOperationException e) { 388 throw new IllegalStateException(clazz.toString(), e); 389 } 390 }); 391 } catch (final ReflectiveOperationException e) { 392 throw new IllegalArgumentException(clazz.toString(), e); 393 } 394 } 395}