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.io.File; 021 import java.util.LinkedHashMap; 022 import java.util.zip.ZipException; 023 import org.apache.commons.compress.archivers.ArchiveEntry; 024 025 /** 026 * Extension that adds better handling of extra fields and provides 027 * access to the internal and external file attributes. 028 * 029 * @NotThreadSafe 030 */ 031 public class ZipArchiveEntry extends java.util.zip.ZipEntry 032 implements ArchiveEntry, Cloneable { 033 034 public static final int PLATFORM_UNIX = 3; 035 public static final int PLATFORM_FAT = 0; 036 private static final int SHORT_MASK = 0xFFFF; 037 private static final int SHORT_SHIFT = 16; 038 039 private int internalAttributes = 0; 040 private int platform = PLATFORM_FAT; 041 private long externalAttributes = 0; 042 private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null; 043 private String name = null; 044 045 /** 046 * Creates a new zip entry with the specified name. 047 * @param name the name of the entry 048 */ 049 public ZipArchiveEntry(String name) { 050 super(name); 051 } 052 053 /** 054 * Creates a new zip entry with fields taken from the specified zip entry. 055 * @param entry the entry to get fields from 056 * @throws ZipException on error 057 */ 058 public ZipArchiveEntry(java.util.zip.ZipEntry entry) throws ZipException { 059 super(entry); 060 setName(entry.getName()); 061 byte[] extra = entry.getExtra(); 062 if (extra != null) { 063 setExtraFields(ExtraFieldUtils.parse(extra)); 064 } else { 065 // initializes extra data to an empty byte array 066 setExtra(); 067 } 068 } 069 070 /** 071 * Creates a new zip entry with fields taken from the specified zip entry. 072 * @param entry the entry to get fields from 073 * @throws ZipException on error 074 */ 075 public ZipArchiveEntry(ZipArchiveEntry entry) throws ZipException { 076 this((java.util.zip.ZipEntry) entry); 077 setInternalAttributes(entry.getInternalAttributes()); 078 setExternalAttributes(entry.getExternalAttributes()); 079 setExtraFields(entry.getExtraFields()); 080 } 081 082 /** 083 */ 084 protected ZipArchiveEntry() { 085 super(""); 086 } 087 088 public ZipArchiveEntry(File inputFile, String entryName) { 089 this(entryName); 090 if (inputFile.isFile()){ 091 setSize(inputFile.length()); 092 } 093 setTime(inputFile.lastModified()); 094 // TODO are there any other fields we can set here? 095 } 096 097 /** 098 * Overwrite clone. 099 * @return a cloned copy of this ZipArchiveEntry 100 */ 101 public Object clone() { 102 ZipArchiveEntry e = (ZipArchiveEntry) super.clone(); 103 104 e.extraFields = extraFields != null ? (LinkedHashMap) extraFields.clone() : null; 105 e.setInternalAttributes(getInternalAttributes()); 106 e.setExternalAttributes(getExternalAttributes()); 107 e.setExtraFields(getExtraFields()); 108 return e; 109 } 110 111 /** 112 * Retrieves the internal file attributes. 113 * 114 * @return the internal file attributes 115 */ 116 public int getInternalAttributes() { 117 return internalAttributes; 118 } 119 120 /** 121 * Sets the internal file attributes. 122 * @param value an <code>int</code> value 123 */ 124 public void setInternalAttributes(int value) { 125 internalAttributes = value; 126 } 127 128 /** 129 * Retrieves the external file attributes. 130 * @return the external file attributes 131 */ 132 public long getExternalAttributes() { 133 return externalAttributes; 134 } 135 136 /** 137 * Sets the external file attributes. 138 * @param value an <code>long</code> value 139 */ 140 public void setExternalAttributes(long value) { 141 externalAttributes = value; 142 } 143 144 /** 145 * Sets Unix permissions in a way that is understood by Info-Zip's 146 * unzip command. 147 * @param mode an <code>int</code> value 148 */ 149 public void setUnixMode(int mode) { 150 // CheckStyle:MagicNumberCheck OFF - no point 151 setExternalAttributes((mode << SHORT_SHIFT) 152 // MS-DOS read-only attribute 153 | ((mode & 0200) == 0 ? 1 : 0) 154 // MS-DOS directory flag 155 | (isDirectory() ? 0x10 : 0)); 156 // CheckStyle:MagicNumberCheck ON 157 platform = PLATFORM_UNIX; 158 } 159 160 /** 161 * Unix permission. 162 * @return the unix permissions 163 */ 164 public int getUnixMode() { 165 return platform != PLATFORM_UNIX ? 0 : 166 (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); 167 } 168 169 /** 170 * Platform specification to put into the "version made 171 * by" part of the central file header. 172 * 173 * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} 174 * has been called, in which case PLATORM_UNIX will be returned. 175 */ 176 public int getPlatform() { 177 return platform; 178 } 179 180 /** 181 * Set the platform (UNIX or FAT). 182 * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX 183 */ 184 protected void setPlatform(int platform) { 185 this.platform = platform; 186 } 187 188 /** 189 * Replaces all currently attached extra fields with the new array. 190 * @param fields an array of extra fields 191 */ 192 public void setExtraFields(ZipExtraField[] fields) { 193 extraFields = new LinkedHashMap(); 194 for (int i = 0; i < fields.length; i++) { 195 extraFields.put(fields[i].getHeaderId(), fields[i]); 196 } 197 setExtra(); 198 } 199 200 /** 201 * Retrieves extra fields. 202 * @return an array of the extra fields 203 */ 204 public ZipExtraField[] getExtraFields() { 205 if (extraFields == null) { 206 return new ZipExtraField[0]; 207 } 208 ZipExtraField[] result = new ZipExtraField[extraFields.size()]; 209 return (ZipExtraField[]) extraFields.values().toArray(result); 210 } 211 212 /** 213 * Adds an extra fields - replacing an already present extra field 214 * of the same type. 215 * 216 * <p>If no extra field of the same type exists, the field will be 217 * added as last field.</p> 218 * @param ze an extra field 219 */ 220 public void addExtraField(ZipExtraField ze) { 221 if (extraFields == null) { 222 extraFields = new LinkedHashMap(); 223 } 224 extraFields.put(ze.getHeaderId(), ze); 225 setExtra(); 226 } 227 228 /** 229 * Adds an extra fields - replacing an already present extra field 230 * of the same type. 231 * 232 * <p>The new extra field will be the first one.</p> 233 * @param ze an extra field 234 */ 235 public void addAsFirstExtraField(ZipExtraField ze) { 236 LinkedHashMap copy = extraFields; 237 extraFields = new LinkedHashMap(); 238 extraFields.put(ze.getHeaderId(), ze); 239 if (copy != null) { 240 copy.remove(ze.getHeaderId()); 241 extraFields.putAll(copy); 242 } 243 setExtra(); 244 } 245 246 /** 247 * Remove an extra fields. 248 * @param type the type of extra field to remove 249 */ 250 public void removeExtraField(ZipShort type) { 251 if (extraFields == null) { 252 throw new java.util.NoSuchElementException(); 253 } 254 if (extraFields.remove(type) == null) { 255 throw new java.util.NoSuchElementException(); 256 } 257 setExtra(); 258 } 259 260 /** 261 * Looks up an extra field by its header id. 262 * 263 * @return null if no such field exists. 264 */ 265 public ZipExtraField getExtraField(ZipShort type) { 266 if (extraFields != null) { 267 return (ZipExtraField) extraFields.get(type); 268 } 269 return null; 270 } 271 272 /** 273 * Throws an Exception if extra data cannot be parsed into extra fields. 274 * @param extra an array of bytes to be parsed into extra fields 275 * @throws RuntimeException if the bytes cannot be parsed 276 * @throws RuntimeException on error 277 */ 278 public void setExtra(byte[] extra) throws RuntimeException { 279 try { 280 ZipExtraField[] local = ExtraFieldUtils.parse(extra, true); 281 mergeExtraFields(local, true); 282 } catch (ZipException e) { 283 throw new RuntimeException(e.getMessage(), e); 284 } 285 } 286 287 /** 288 * Unfortunately {@link java.util.zip.ZipOutputStream 289 * java.util.zip.ZipOutputStream} seems to access the extra data 290 * directly, so overriding getExtra doesn't help - we need to 291 * modify super's data directly. 292 */ 293 protected void setExtra() { 294 super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields())); 295 } 296 297 /** 298 * Sets the central directory part of extra fields. 299 */ 300 public void setCentralDirectoryExtra(byte[] b) { 301 try { 302 ZipExtraField[] central = ExtraFieldUtils.parse(b, false); 303 mergeExtraFields(central, false); 304 } catch (ZipException e) { 305 throw new RuntimeException(e.getMessage(), e); 306 } 307 } 308 309 /** 310 * Retrieves the extra data for the local file data. 311 * @return the extra data for local file 312 */ 313 public byte[] getLocalFileDataExtra() { 314 byte[] extra = getExtra(); 315 return extra != null ? extra : new byte[0]; 316 } 317 318 /** 319 * Retrieves the extra data for the central directory. 320 * @return the central directory extra data 321 */ 322 public byte[] getCentralDirectoryExtra() { 323 return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields()); 324 } 325 326 /** 327 * Get the name of the entry. 328 * @return the entry name 329 */ 330 public String getName() { 331 return name == null ? super.getName() : name; 332 } 333 334 /** 335 * Is this entry a directory? 336 * @return true if the entry is a directory 337 */ 338 public boolean isDirectory() { 339 return getName().endsWith("/"); 340 } 341 342 /** 343 * Set the name of the entry. 344 * @param name the name to use 345 */ 346 protected void setName(String name) { 347 this.name = name; 348 } 349 350 /** 351 * Get the hashCode of the entry. 352 * This uses the name as the hashcode. 353 * @return a hashcode. 354 */ 355 public int hashCode() { 356 // this method has severe consequences on performance. We cannot rely 357 // on the super.hashCode() method since super.getName() always return 358 // the empty string in the current implemention (there's no setter) 359 // so it is basically draining the performance of a hashmap lookup 360 return getName().hashCode(); 361 } 362 363 /** 364 * If there are no extra fields, use the given fields as new extra 365 * data - otherwise merge the fields assuming the existing fields 366 * and the new fields stem from different locations inside the 367 * archive. 368 * @param f the extra fields to merge 369 * @param local whether the new fields originate from local data 370 */ 371 private void mergeExtraFields(ZipExtraField[] f, boolean local) 372 throws ZipException { 373 if (extraFields == null) { 374 setExtraFields(f); 375 } else { 376 for (int i = 0; i < f.length; i++) { 377 ZipExtraField existing = getExtraField(f[i].getHeaderId()); 378 if (existing == null) { 379 addExtraField(f[i]); 380 } else { 381 if (local) { 382 byte[] b = f[i].getLocalFileDataData(); 383 existing.parseFromLocalFileData(b, 0, b.length); 384 } else { 385 byte[] b = f[i].getCentralDirectoryData(); 386 existing.parseFromCentralDirectoryData(b, 0, b.length); 387 } 388 } 389 } 390 setExtra(); 391 } 392 } 393 394 /* (non-Javadoc) 395 * @see java.lang.Object#equals(java.lang.Object) 396 */ 397 public boolean equals(Object obj) { 398 if (this == obj) { 399 return true; 400 } 401 if (obj == null || getClass() != obj.getClass()) { 402 return false; 403 } 404 ZipArchiveEntry other = (ZipArchiveEntry) obj; 405 if (name == null) { 406 if (other.name != null) { 407 return false; 408 } 409 } else if (!name.equals(other.name)) { 410 return false; 411 } 412 return true; 413 } 414 }