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