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