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 }