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