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 Apache Commons Compress 1.1 126 */ 127 @SuppressWarnings("fallthrough") 128 public static ZipExtraField[] parse(byte[] data, boolean local, 129 UnparseableExtraField onUnparseableData) 130 throws ZipException { 131 List<ZipExtraField> v = new ArrayList<ZipExtraField>(); 132 int start = 0; 133 LOOP: 134 while (start <= data.length - WORD) { 135 ZipShort headerId = new ZipShort(data, start); 136 int length = (new ZipShort(data, start + 2)).getValue(); 137 if (start + WORD + length > data.length) { 138 switch(onUnparseableData.getKey()) { 139 case UnparseableExtraField.THROW_KEY: 140 throw new ZipException("bad extra field starting at " 141 + start + ". Block length of " 142 + length + " bytes exceeds remaining" 143 + " data of " 144 + (data.length - start - WORD) 145 + " bytes."); 146 case UnparseableExtraField.READ_KEY: 147 UnparseableExtraFieldData field = 148 new UnparseableExtraFieldData(); 149 if (local) { 150 field.parseFromLocalFileData(data, start, 151 data.length - start); 152 } else { 153 field.parseFromCentralDirectoryData(data, start, 154 data.length - start); 155 } 156 v.add(field); 157 //$FALL-THROUGH$ 158 case UnparseableExtraField.SKIP_KEY: 159 // since we cannot parse the data we must assume 160 // the extra field consumes the whole rest of the 161 // available data 162 break LOOP; 163 default: 164 throw new ZipException("unknown UnparseableExtraField key: " 165 + onUnparseableData.getKey()); 166 } 167 } 168 try { 169 ZipExtraField ze = createExtraField(headerId); 170 if (local) { 171 ze.parseFromLocalFileData(data, start + WORD, length); 172 } else { 173 ze.parseFromCentralDirectoryData(data, start + WORD, 174 length); 175 } 176 v.add(ze); 177 } catch (InstantiationException ie) { 178 throw new ZipException(ie.getMessage()); 179 } catch (IllegalAccessException iae) { 180 throw new ZipException(iae.getMessage()); 181 } 182 start += (length + WORD); 183 } 184 185 ZipExtraField[] result = new ZipExtraField[v.size()]; 186 return v.toArray(result); 187 } 188 189 /** 190 * Merges the local file data fields of the given ZipExtraFields. 191 * @param data an array of ExtraFiles 192 * @return an array of bytes 193 */ 194 public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { 195 final boolean lastIsUnparseableHolder = data.length > 0 196 && data[data.length - 1] instanceof UnparseableExtraFieldData; 197 int regularExtraFieldCount = 198 lastIsUnparseableHolder ? data.length - 1 : data.length; 199 200 int sum = WORD * regularExtraFieldCount; 201 for (int i = 0; i < data.length; i++) { 202 sum += data[i].getLocalFileDataLength().getValue(); 203 } 204 205 byte[] result = new byte[sum]; 206 int start = 0; 207 for (int i = 0; i < regularExtraFieldCount; i++) { 208 System.arraycopy(data[i].getHeaderId().getBytes(), 209 0, result, start, 2); 210 System.arraycopy(data[i].getLocalFileDataLength().getBytes(), 211 0, result, start + 2, 2); 212 byte[] local = data[i].getLocalFileDataData(); 213 System.arraycopy(local, 0, result, start + WORD, local.length); 214 start += (local.length + WORD); 215 } 216 if (lastIsUnparseableHolder) { 217 byte[] local = data[data.length - 1].getLocalFileDataData(); 218 System.arraycopy(local, 0, result, start, local.length); 219 } 220 return result; 221 } 222 223 /** 224 * Merges the central directory fields of the given ZipExtraFields. 225 * @param data an array of ExtraFields 226 * @return an array of bytes 227 */ 228 public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) { 229 final boolean lastIsUnparseableHolder = data.length > 0 230 && data[data.length - 1] instanceof UnparseableExtraFieldData; 231 int regularExtraFieldCount = 232 lastIsUnparseableHolder ? data.length - 1 : data.length; 233 234 int sum = WORD * regularExtraFieldCount; 235 for (int i = 0; i < data.length; i++) { 236 sum += data[i].getCentralDirectoryLength().getValue(); 237 } 238 byte[] result = new byte[sum]; 239 int start = 0; 240 for (int i = 0; i < regularExtraFieldCount; i++) { 241 System.arraycopy(data[i].getHeaderId().getBytes(), 242 0, result, start, 2); 243 System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), 244 0, result, start + 2, 2); 245 byte[] local = data[i].getCentralDirectoryData(); 246 System.arraycopy(local, 0, result, start + WORD, local.length); 247 start += (local.length + WORD); 248 } 249 if (lastIsUnparseableHolder) { 250 byte[] local = data[data.length - 1].getCentralDirectoryData(); 251 System.arraycopy(local, 0, result, start, local.length); 252 } 253 return result; 254 } 255 256 /** 257 * "enum" for the possible actions to take if the extra field 258 * cannot be parsed. 259 * 260 * @since Apache Commons Compress 1.1 261 */ 262 public static final class UnparseableExtraField { 263 /** 264 * Key for "throw an exception" action. 265 */ 266 public static final int THROW_KEY = 0; 267 /** 268 * Key for "skip" action. 269 */ 270 public static final int SKIP_KEY = 1; 271 /** 272 * Key for "read" action. 273 */ 274 public static final int READ_KEY = 2; 275 276 /** 277 * Throw an exception if field cannot be parsed. 278 */ 279 public static final UnparseableExtraField THROW 280 = new UnparseableExtraField(THROW_KEY); 281 282 /** 283 * Skip the extra field entirely and don't make its data 284 * available - effectively removing the extra field data. 285 */ 286 public static final UnparseableExtraField SKIP 287 = new UnparseableExtraField(SKIP_KEY); 288 289 /** 290 * Read the extra field data into an instance of {@link 291 * UnparseableExtraFieldData UnparseableExtraFieldData}. 292 */ 293 public static final UnparseableExtraField READ 294 = new UnparseableExtraField(READ_KEY); 295 296 private final int key; 297 298 private UnparseableExtraField(int k) { 299 key = k; 300 } 301 302 /** 303 * Key of the action to take. 304 */ 305 public int getKey() { return key; } 306 } 307 }