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<>(); 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 register(ResourceAlignmentExtraField.class); 056 } 057 058 /** 059 * Register a ZipExtraField implementation. 060 * 061 * <p>The given class must have a no-arg constructor and implement 062 * the {@link ZipExtraField ZipExtraField interface}.</p> 063 * @param c the class to register 064 */ 065 public static void register(final Class<?> c) { 066 try { 067 final ZipExtraField ze = (ZipExtraField) c.newInstance(); 068 implementations.put(ze.getHeaderId(), c); 069 } catch (final ClassCastException cc) { 070 throw new RuntimeException(c + " doesn\'t implement ZipExtraField"); //NOSONAR 071 } catch (final InstantiationException ie) { 072 throw new RuntimeException(c + " is not a concrete class"); //NOSONAR 073 } catch (final IllegalAccessException ie) { 074 throw new RuntimeException(c + "\'s no-arg constructor is not public"); //NOSONAR 075 } 076 } 077 078 /** 079 * Create an instance of the appropriate ExtraField, falls back to 080 * {@link UnrecognizedExtraField UnrecognizedExtraField}. 081 * @param headerId the header identifier 082 * @return an instance of the appropriate ExtraField 083 * @throws InstantiationException if unable to instantiate the class 084 * @throws IllegalAccessException if not allowed to instantiate the class 085 */ 086 public static ZipExtraField createExtraField(final ZipShort headerId) 087 throws InstantiationException, IllegalAccessException { 088 final Class<?> c = implementations.get(headerId); 089 if (c != null) { 090 return (ZipExtraField) c.newInstance(); 091 } 092 final UnrecognizedExtraField u = new UnrecognizedExtraField(); 093 u.setHeaderId(headerId); 094 return u; 095 } 096 097 /** 098 * Split the array into ExtraFields and populate them with the 099 * 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 */ 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 */ 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 */ 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 if (local) { 179 ze.parseFromLocalFileData(data, start + WORD, length); 180 } else { 181 ze.parseFromCentralDirectoryData(data, start + WORD, 182 length); 183 } 184 v.add(ze); 185 } catch (final InstantiationException | IllegalAccessException ie) { 186 throw (ZipException) new ZipException(ie.getMessage()).initCause(ie); 187 } 188 start += length + WORD; 189 } 190 191 final ZipExtraField[] result = new ZipExtraField[v.size()]; 192 return v.toArray(result); 193 } 194 195 /** 196 * Merges the local file data fields of the given ZipExtraFields. 197 * @param data an array of ExtraFiles 198 * @return an array of bytes 199 */ 200 public static byte[] mergeLocalFileDataData(final ZipExtraField[] data) { 201 final boolean lastIsUnparseableHolder = data.length > 0 202 && data[data.length - 1] instanceof UnparseableExtraFieldData; 203 final int regularExtraFieldCount = 204 lastIsUnparseableHolder ? data.length - 1 : data.length; 205 206 int sum = WORD * regularExtraFieldCount; 207 for (final ZipExtraField element : data) { 208 sum += element.getLocalFileDataLength().getValue(); 209 } 210 211 final byte[] result = new byte[sum]; 212 int start = 0; 213 for (int i = 0; i < regularExtraFieldCount; i++) { 214 System.arraycopy(data[i].getHeaderId().getBytes(), 215 0, result, start, 2); 216 System.arraycopy(data[i].getLocalFileDataLength().getBytes(), 217 0, result, start + 2, 2); 218 start += WORD; 219 final byte[] local = data[i].getLocalFileDataData(); 220 if (local != null) { 221 System.arraycopy(local, 0, result, start, local.length); 222 start += local.length; 223 } 224 } 225 if (lastIsUnparseableHolder) { 226 final byte[] local = data[data.length - 1].getLocalFileDataData(); 227 if (local != null) { 228 System.arraycopy(local, 0, result, start, local.length); 229 } 230 } 231 return result; 232 } 233 234 /** 235 * Merges the central directory fields of the given ZipExtraFields. 236 * @param data an array of ExtraFields 237 * @return an array of bytes 238 */ 239 public static byte[] mergeCentralDirectoryData(final ZipExtraField[] data) { 240 final boolean lastIsUnparseableHolder = data.length > 0 241 && data[data.length - 1] instanceof UnparseableExtraFieldData; 242 final int regularExtraFieldCount = 243 lastIsUnparseableHolder ? data.length - 1 : data.length; 244 245 int sum = WORD * regularExtraFieldCount; 246 for (final ZipExtraField element : data) { 247 sum += element.getCentralDirectoryLength().getValue(); 248 } 249 final byte[] result = new byte[sum]; 250 int start = 0; 251 for (int i = 0; i < regularExtraFieldCount; i++) { 252 System.arraycopy(data[i].getHeaderId().getBytes(), 253 0, result, start, 2); 254 System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), 255 0, result, start + 2, 2); 256 start += WORD; 257 final byte[] local = data[i].getCentralDirectoryData(); 258 if (local != null) { 259 System.arraycopy(local, 0, result, start, local.length); 260 start += local.length; 261 } 262 } 263 if (lastIsUnparseableHolder) { 264 final byte[] local = data[data.length - 1].getCentralDirectoryData(); 265 if (local != null) { 266 System.arraycopy(local, 0, result, start, local.length); 267 } 268 } 269 return result; 270 } 271 272 /** 273 * "enum" for the possible actions to take if the extra field 274 * cannot be parsed. 275 * 276 * @since 1.1 277 */ 278 public static final class UnparseableExtraField { 279 /** 280 * Key for "throw an exception" action. 281 */ 282 public static final int THROW_KEY = 0; 283 /** 284 * Key for "skip" action. 285 */ 286 public static final int SKIP_KEY = 1; 287 /** 288 * Key for "read" action. 289 */ 290 public static final int READ_KEY = 2; 291 292 /** 293 * Throw an exception if field cannot be parsed. 294 */ 295 public static final UnparseableExtraField THROW 296 = new UnparseableExtraField(THROW_KEY); 297 298 /** 299 * Skip the extra field entirely and don't make its data 300 * available - effectively removing the extra field data. 301 */ 302 public static final UnparseableExtraField SKIP 303 = new UnparseableExtraField(SKIP_KEY); 304 305 /** 306 * Read the extra field data into an instance of {@link 307 * UnparseableExtraFieldData UnparseableExtraFieldData}. 308 */ 309 public static final UnparseableExtraField READ 310 = new UnparseableExtraField(READ_KEY); 311 312 private final int key; 313 314 private UnparseableExtraField(final int k) { 315 key = k; 316 } 317 318 /** 319 * Key of the action to take. 320 * @return the key 321 */ 322 public int getKey() { return key; } 323 } 324}