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