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 } 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"); //NOSONAR 070 } catch (final InstantiationException ie) { 071 throw new RuntimeException(c + " is not a concrete class"); //NOSONAR 072 } catch (final IllegalAccessException ie) { 073 throw new RuntimeException(c + "\'s no-arg constructor is not public"); //NOSONAR 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<>(); 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 | IllegalAccessException ie) { 185 throw (ZipException) new ZipException(ie.getMessage()).initCause(ie); 186 } 187 start += length + WORD; 188 } 189 190 final ZipExtraField[] result = new ZipExtraField[v.size()]; 191 return v.toArray(result); 192 } 193 194 /** 195 * Merges the local file data fields of the given ZipExtraFields. 196 * @param data an array of ExtraFiles 197 * @return an array of bytes 198 */ 199 public static byte[] mergeLocalFileDataData(final ZipExtraField[] data) { 200 final boolean lastIsUnparseableHolder = data.length > 0 201 && data[data.length - 1] instanceof UnparseableExtraFieldData; 202 final int regularExtraFieldCount = 203 lastIsUnparseableHolder ? data.length - 1 : data.length; 204 205 int sum = WORD * regularExtraFieldCount; 206 for (final ZipExtraField element : data) { 207 sum += element.getLocalFileDataLength().getValue(); 208 } 209 210 final byte[] result = new byte[sum]; 211 int start = 0; 212 for (int i = 0; i < regularExtraFieldCount; i++) { 213 System.arraycopy(data[i].getHeaderId().getBytes(), 214 0, result, start, 2); 215 System.arraycopy(data[i].getLocalFileDataLength().getBytes(), 216 0, result, start + 2, 2); 217 start += WORD; 218 final byte[] local = data[i].getLocalFileDataData(); 219 if (local != null) { 220 System.arraycopy(local, 0, result, start, local.length); 221 start += local.length; 222 } 223 } 224 if (lastIsUnparseableHolder) { 225 final byte[] local = data[data.length - 1].getLocalFileDataData(); 226 if (local != null) { 227 System.arraycopy(local, 0, result, start, local.length); 228 } 229 } 230 return result; 231 } 232 233 /** 234 * Merges the central directory fields of the given ZipExtraFields. 235 * @param data an array of ExtraFields 236 * @return an array of bytes 237 */ 238 public static byte[] mergeCentralDirectoryData(final ZipExtraField[] data) { 239 final boolean lastIsUnparseableHolder = data.length > 0 240 && data[data.length - 1] instanceof UnparseableExtraFieldData; 241 final int regularExtraFieldCount = 242 lastIsUnparseableHolder ? data.length - 1 : data.length; 243 244 int sum = WORD * regularExtraFieldCount; 245 for (final ZipExtraField element : data) { 246 sum += element.getCentralDirectoryLength().getValue(); 247 } 248 final byte[] result = new byte[sum]; 249 int start = 0; 250 for (int i = 0; i < regularExtraFieldCount; i++) { 251 System.arraycopy(data[i].getHeaderId().getBytes(), 252 0, result, start, 2); 253 System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), 254 0, result, start + 2, 2); 255 start += WORD; 256 final byte[] local = data[i].getCentralDirectoryData(); 257 if (local != null) { 258 System.arraycopy(local, 0, result, start, local.length); 259 start += local.length; 260 } 261 } 262 if (lastIsUnparseableHolder) { 263 final byte[] local = data[data.length - 1].getCentralDirectoryData(); 264 if (local != null) { 265 System.arraycopy(local, 0, result, start, local.length); 266 } 267 } 268 return result; 269 } 270 271 /** 272 * "enum" for the possible actions to take if the extra field 273 * cannot be parsed. 274 * 275 * @since 1.1 276 */ 277 public static final class UnparseableExtraField { 278 /** 279 * Key for "throw an exception" action. 280 */ 281 public static final int THROW_KEY = 0; 282 /** 283 * Key for "skip" action. 284 */ 285 public static final int SKIP_KEY = 1; 286 /** 287 * Key for "read" action. 288 */ 289 public static final int READ_KEY = 2; 290 291 /** 292 * Throw an exception if field cannot be parsed. 293 */ 294 public static final UnparseableExtraField THROW 295 = new UnparseableExtraField(THROW_KEY); 296 297 /** 298 * Skip the extra field entirely and don't make its data 299 * available - effectively removing the extra field data. 300 */ 301 public static final UnparseableExtraField SKIP 302 = new UnparseableExtraField(SKIP_KEY); 303 304 /** 305 * Read the extra field data into an instance of {@link 306 * UnparseableExtraFieldData UnparseableExtraFieldData}. 307 */ 308 public static final UnparseableExtraField READ 309 = new UnparseableExtraField(READ_KEY); 310 311 private final int key; 312 313 private UnparseableExtraField(final int k) { 314 key = k; 315 } 316 317 /** 318 * Key of the action to take. 319 * @return the key 320 */ 321 public int getKey() { return key; } 322 } 323}