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 }