• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  */
18 package org.apache.commons.compress.archivers.zip;
19 
20 import org.apache.commons.compress.archivers.ArchiveEntry;
21 import org.apache.commons.compress.archivers.EntryStreamOffsets;
22 
23 import java.io.File;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.zip.ZipException;
29 
30 /**
31  * Extension that adds better handling of extra fields and provides
32  * access to the internal and external file attributes.
33  *
34  * <p>The extra data is expected to follow the recommendation of
35  * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:</p>
36  * <ul>
37  *   <li>the extra byte array consists of a sequence of extra fields</li>
38  *   <li>each extra fields starts by a two byte header id followed by
39  *   a two byte sequence holding the length of the remainder of
40  *   data.</li>
41  * </ul>
42  *
43  * <p>Any extra data that cannot be parsed by the rules above will be
44  * consumed as "unparseable" extra data and treated differently by the
45  * methods of this class.  Versions prior to Apache Commons Compress
46  * 1.1 would have thrown an exception if any attempt was made to read
47  * or write extra data not conforming to the recommendation.</p>
48  *
49  * @NotThreadSafe
50  */
51 public class ZipArchiveEntry extends java.util.zip.ZipEntry
52     implements ArchiveEntry, EntryStreamOffsets
53 {
54 
55     public static final int PLATFORM_UNIX = 3;
56     public static final int PLATFORM_FAT  = 0;
57     public static final int CRC_UNKNOWN = -1;
58     private static final int SHORT_MASK = 0xFFFF;
59     private static final int SHORT_SHIFT = 16;
60     private static final byte[] EMPTY = new byte[0];
61 
62     /**
63      * Indicates how the name of this entry has been determined.
64      * @since 1.16
65      */
66     public enum NameSource {
67         /**
68          * The name has been read from the archive using the encoding
69          * of the archive specified when creating the {@link
70          * ZipArchiveInputStream} or {@link ZipFile} (defaults to the
71          * platform's default encoding).
72          */
73         NAME,
74         /**
75          * The name has been read from the archive and the archive
76          * specified the EFS flag which indicates the name has been
77          * encoded as UTF-8.
78          */
79         NAME_WITH_EFS_FLAG,
80         /**
81          * The name has been read from an {@link UnicodePathExtraField
82          * Unicode Extra Field}.
83          */
84         UNICODE_EXTRA_FIELD
85     }
86 
87     /**
88      * Indicates how the comment of this entry has been determined.
89      * @since 1.16
90      */
91     public enum CommentSource {
92         /**
93          * The comment has been read from the archive using the encoding
94          * of the archive specified when creating the {@link
95          * ZipArchiveInputStream} or {@link ZipFile} (defaults to the
96          * platform's default encoding).
97          */
98         COMMENT,
99         /**
100          * The comment has been read from an {@link UnicodeCommentExtraField
101          * Unicode Extra Field}.
102          */
103         UNICODE_EXTRA_FIELD
104     }
105 
106     /**
107      * The {@link java.util.zip.ZipEntry} base class only supports
108      * the compression methods STORED and DEFLATED. We override the
109      * field so that any compression methods can be used.
110      * <p>
111      * The default value -1 means that the method has not been specified.
112      *
113      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
114      *        >COMPRESS-93</a>
115      */
116     private int method = ZipMethod.UNKNOWN_CODE;
117 
118     /**
119      * The {@link java.util.zip.ZipEntry#setSize} method in the base
120      * class throws an IllegalArgumentException if the size is bigger
121      * than 2GB for Java versions &lt; 7 and even in Java 7+ if the
122      * implementation in java.util.zip doesn't support Zip64 itself
123      * (it is an optional feature).
124      *
125      * <p>We need to keep our own size information for Zip64 support.</p>
126      */
127     private long size = SIZE_UNKNOWN;
128 
129     private int internalAttributes = 0;
130     private int versionRequired;
131     private int versionMadeBy;
132     private int platform = PLATFORM_FAT;
133     private int rawFlag;
134     private long externalAttributes = 0;
135     private int alignment = 0;
136     private ZipExtraField[] extraFields;
137     private UnparseableExtraFieldData unparseableExtra = null;
138     private String name = null;
139     private byte[] rawName = null;
140     private GeneralPurposeBit gpb = new GeneralPurposeBit();
141     private static final ZipExtraField[] noExtraFields = new ZipExtraField[0];
142     private long localHeaderOffset = OFFSET_UNKNOWN;
143     private long dataOffset = OFFSET_UNKNOWN;
144     private boolean isStreamContiguous = false;
145     private NameSource nameSource = NameSource.NAME;
146     private CommentSource commentSource = CommentSource.COMMENT;
147 
148 
149     /**
150      * Creates a new zip entry with the specified name.
151      *
152      * <p>Assumes the entry represents a directory if and only if the
153      * name ends with a forward slash "/".</p>
154      *
155      * @param name the name of the entry
156      */
ZipArchiveEntry(final String name)157     public ZipArchiveEntry(final String name) {
158         super(name);
159         setName(name);
160     }
161 
162     /**
163      * Creates a new zip entry with fields taken from the specified zip entry.
164      *
165      * <p>Assumes the entry represents a directory if and only if the
166      * name ends with a forward slash "/".</p>
167      *
168      * @param entry the entry to get fields from
169      * @throws ZipException on error
170      */
ZipArchiveEntry(final java.util.zip.ZipEntry entry)171     public ZipArchiveEntry(final java.util.zip.ZipEntry entry) throws ZipException {
172         super(entry);
173         setName(entry.getName());
174         final byte[] extra = entry.getExtra();
175         if (extra != null) {
176             setExtraFields(ExtraFieldUtils.parse(extra, true,
177                                                  ExtraFieldUtils
178                                                  .UnparseableExtraField.READ));
179         } else {
180             // initializes extra data to an empty byte array
181             setExtra();
182         }
183         setMethod(entry.getMethod());
184         this.size = entry.getSize();
185     }
186 
187     /**
188      * Creates a new zip entry with fields taken from the specified zip entry.
189      *
190      * <p>Assumes the entry represents a directory if and only if the
191      * name ends with a forward slash "/".</p>
192      *
193      * @param entry the entry to get fields from
194      * @throws ZipException on error
195      */
ZipArchiveEntry(final ZipArchiveEntry entry)196     public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException {
197         this((java.util.zip.ZipEntry) entry);
198         setInternalAttributes(entry.getInternalAttributes());
199         setExternalAttributes(entry.getExternalAttributes());
200         setExtraFields(getAllExtraFieldsNoCopy());
201         setPlatform(entry.getPlatform());
202         final GeneralPurposeBit other = entry.getGeneralPurposeBit();
203         setGeneralPurposeBit(other == null ? null :
204                              (GeneralPurposeBit) other.clone());
205     }
206 
207     /**
208      */
ZipArchiveEntry()209     protected ZipArchiveEntry() {
210         this("");
211     }
212 
213     /**
214      * Creates a new zip entry taking some information from the given
215      * file and using the provided name.
216      *
217      * <p>The name will be adjusted to end with a forward slash "/" if
218      * the file is a directory.  If the file is not a directory a
219      * potential trailing forward slash will be stripped from the
220      * entry name.</p>
221      * @param inputFile file to create the entry from
222      * @param entryName name of the entry
223      */
ZipArchiveEntry(final File inputFile, final String entryName)224     public ZipArchiveEntry(final File inputFile, final String entryName) {
225         this(inputFile.isDirectory() && !entryName.endsWith("/") ?
226              entryName + "/" : entryName);
227         if (inputFile.isFile()){
228             setSize(inputFile.length());
229         }
230         setTime(inputFile.lastModified());
231         // TODO are there any other fields we can set here?
232     }
233 
234     /**
235      * Overwrite clone.
236      * @return a cloned copy of this ZipArchiveEntry
237      */
238     @Override
clone()239     public Object clone() {
240         final ZipArchiveEntry e = (ZipArchiveEntry) super.clone();
241 
242         e.setInternalAttributes(getInternalAttributes());
243         e.setExternalAttributes(getExternalAttributes());
244         e.setExtraFields(getAllExtraFieldsNoCopy());
245         return e;
246     }
247 
248     /**
249      * Returns the compression method of this entry, or -1 if the
250      * compression method has not been specified.
251      *
252      * @return compression method
253      *
254      * @since 1.1
255      */
256     @Override
getMethod()257     public int getMethod() {
258         return method;
259     }
260 
261     /**
262      * Sets the compression method of this entry.
263      *
264      * @param method compression method
265      *
266      * @since 1.1
267      */
268     @Override
setMethod(final int method)269     public void setMethod(final int method) {
270         if (method < 0) {
271             throw new IllegalArgumentException(
272                     "ZIP compression method can not be negative: " + method);
273         }
274         this.method = method;
275     }
276 
277     /**
278      * Retrieves the internal file attributes.
279      *
280      * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill
281      * this field, you must use {@link ZipFile} if you want to read
282      * entries using this attribute.</p>
283      *
284      * @return the internal file attributes
285      */
getInternalAttributes()286     public int getInternalAttributes() {
287         return internalAttributes;
288     }
289 
290     /**
291      * Sets the internal file attributes.
292      * @param value an <code>int</code> value
293      */
setInternalAttributes(final int value)294     public void setInternalAttributes(final int value) {
295         internalAttributes = value;
296     }
297 
298     /**
299      * Retrieves the external file attributes.
300      *
301      * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill
302      * this field, you must use {@link ZipFile} if you want to read
303      * entries using this attribute.</p>
304      *
305      * @return the external file attributes
306      */
getExternalAttributes()307     public long getExternalAttributes() {
308         return externalAttributes;
309     }
310 
311     /**
312      * Sets the external file attributes.
313      * @param value an <code>long</code> value
314      */
setExternalAttributes(final long value)315     public void setExternalAttributes(final long value) {
316         externalAttributes = value;
317     }
318 
319     /**
320      * Sets Unix permissions in a way that is understood by Info-Zip's
321      * unzip command.
322      * @param mode an <code>int</code> value
323      */
setUnixMode(final int mode)324     public void setUnixMode(final int mode) {
325         // CheckStyle:MagicNumberCheck OFF - no point
326         setExternalAttributes((mode << SHORT_SHIFT)
327                               // MS-DOS read-only attribute
328                               | ((mode & 0200) == 0 ? 1 : 0)
329                               // MS-DOS directory flag
330                               | (isDirectory() ? 0x10 : 0));
331         // CheckStyle:MagicNumberCheck ON
332         platform = PLATFORM_UNIX;
333     }
334 
335     /**
336      * Unix permission.
337      * @return the unix permissions
338      */
getUnixMode()339     public int getUnixMode() {
340         return platform != PLATFORM_UNIX ? 0 :
341             (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
342     }
343 
344     /**
345      * Returns true if this entry represents a unix symlink,
346      * in which case the entry's content contains the target path
347      * for the symlink.
348      *
349      * @since 1.5
350      * @return true if the entry represents a unix symlink, false otherwise.
351      */
isUnixSymlink()352     public boolean isUnixSymlink() {
353         return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG;
354     }
355 
356     /**
357      * Platform specification to put into the &quot;version made
358      * by&quot; part of the central file header.
359      *
360      * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
361      * has been called, in which case PLATFORM_UNIX will be returned.
362      */
getPlatform()363     public int getPlatform() {
364         return platform;
365     }
366 
367     /**
368      * Set the platform (UNIX or FAT).
369      * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX
370      */
setPlatform(final int platform)371     protected void setPlatform(final int platform) {
372         this.platform = platform;
373     }
374 
375     /**
376      * Gets currently configured alignment.
377      *
378      * @return
379      *      alignment for this entry.
380      * @since 1.14
381      */
getAlignment()382     protected int getAlignment() {
383         return this.alignment;
384     }
385 
386     /**
387      * Sets alignment for this entry.
388      *
389      * @param alignment
390      *      requested alignment, 0 for default.
391      * @since 1.14
392      */
setAlignment(int alignment)393     public void setAlignment(int alignment) {
394         if ((alignment & (alignment - 1)) != 0 || alignment > 0xffff) {
395             throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than "
396                 + 0xffff + " but is " + alignment);
397         }
398         this.alignment = alignment;
399     }
400 
401     /**
402      * Replaces all currently attached extra fields with the new array.
403      * @param fields an array of extra fields
404      */
setExtraFields(final ZipExtraField[] fields)405     public void setExtraFields(final ZipExtraField[] fields) {
406         final List<ZipExtraField> newFields = new ArrayList<>();
407         for (final ZipExtraField field : fields) {
408             if (field instanceof UnparseableExtraFieldData) {
409                 unparseableExtra = (UnparseableExtraFieldData) field;
410             } else {
411                 newFields.add( field);
412             }
413         }
414         extraFields = newFields.toArray(new ZipExtraField[newFields.size()]);
415         setExtra();
416     }
417 
418     /**
419      * Retrieves all extra fields that have been parsed successfully.
420      *
421      * <p><b>Note</b>: The set of extra fields may be incomplete when
422      * {@link ZipArchiveInputStream} has been used as some extra
423      * fields use the central directory to store additional
424      * information.</p>
425      *
426      * @return an array of the extra fields
427      */
getExtraFields()428     public ZipExtraField[] getExtraFields() {
429         return getParseableExtraFields();
430     }
431 
432     /**
433      * Retrieves extra fields.
434      * @param includeUnparseable whether to also return unparseable
435      * extra fields as {@link UnparseableExtraFieldData} if such data
436      * exists.
437      * @return an array of the extra fields
438      *
439      * @since 1.1
440      */
getExtraFields(final boolean includeUnparseable)441     public ZipExtraField[] getExtraFields(final boolean includeUnparseable) {
442         return includeUnparseable ?
443                 getAllExtraFields() :
444                 getParseableExtraFields();
445     }
446 
getParseableExtraFieldsNoCopy()447     private ZipExtraField[] getParseableExtraFieldsNoCopy() {
448         if (extraFields == null) {
449             return noExtraFields;
450         }
451         return extraFields;
452     }
453 
getParseableExtraFields()454     private ZipExtraField[] getParseableExtraFields() {
455         final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy();
456         return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields) : parseableExtraFields;
457     }
458 
459     /**
460      * Get all extra fields, including unparseable ones.
461      * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method
462      */
getAllExtraFieldsNoCopy()463     private ZipExtraField[] getAllExtraFieldsNoCopy() {
464         if (extraFields == null) {
465             return getUnparseableOnly();
466         }
467         return unparseableExtra != null ? getMergedFields() : extraFields;
468     }
469 
copyOf(final ZipExtraField[] src)470     private ZipExtraField[] copyOf(final ZipExtraField[] src){
471         return copyOf(src, src.length);
472     }
473 
copyOf(final ZipExtraField[] src, final int length)474     private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) {
475         final ZipExtraField[] cpy = new ZipExtraField[length];
476         System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length));
477         return cpy;
478     }
479 
getMergedFields()480     private ZipExtraField[] getMergedFields() {
481         final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
482         zipExtraFields[extraFields.length] = unparseableExtra;
483         return zipExtraFields;
484     }
485 
getUnparseableOnly()486     private ZipExtraField[] getUnparseableOnly() {
487         return unparseableExtra == null ? noExtraFields : new ZipExtraField[] { unparseableExtra };
488     }
489 
getAllExtraFields()490     private ZipExtraField[] getAllExtraFields() {
491         final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy();
492         return (allExtraFieldsNoCopy == extraFields) ? copyOf( allExtraFieldsNoCopy) : allExtraFieldsNoCopy;
493     }
494     /**
495      * Adds an extra field - replacing an already present extra field
496      * of the same type.
497      *
498      * <p>If no extra field of the same type exists, the field will be
499      * added as last field.</p>
500      * @param ze an extra field
501      */
addExtraField(final ZipExtraField ze)502     public void addExtraField(final ZipExtraField ze) {
503         if (ze instanceof UnparseableExtraFieldData) {
504             unparseableExtra = (UnparseableExtraFieldData) ze;
505         } else {
506             if (extraFields == null) {
507                 extraFields = new ZipExtraField[]{ ze};
508             } else {
509                 if (getExtraField(ze.getHeaderId())!= null){
510                     removeExtraField(ze.getHeaderId());
511                 }
512                 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
513                 zipExtraFields[zipExtraFields.length -1] = ze;
514                 extraFields = zipExtraFields;
515             }
516         }
517         setExtra();
518     }
519 
520     /**
521      * Adds an extra field - replacing an already present extra field
522      * of the same type.
523      *
524      * <p>The new extra field will be the first one.</p>
525      * @param ze an extra field
526      */
addAsFirstExtraField(final ZipExtraField ze)527     public void addAsFirstExtraField(final ZipExtraField ze) {
528         if (ze instanceof UnparseableExtraFieldData) {
529             unparseableExtra = (UnparseableExtraFieldData) ze;
530         } else {
531             if (getExtraField(ze.getHeaderId()) != null){
532                 removeExtraField(ze.getHeaderId());
533             }
534             final ZipExtraField[] copy = extraFields;
535             final int newLen = extraFields != null ? extraFields.length + 1: 1;
536             extraFields = new ZipExtraField[newLen];
537             extraFields[0] = ze;
538             if (copy != null){
539                 System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1);
540             }
541         }
542         setExtra();
543     }
544 
545     /**
546      * Remove an extra field.
547      * @param type the type of extra field to remove
548      */
removeExtraField(final ZipShort type)549     public void removeExtraField(final ZipShort type) {
550         if (extraFields == null) {
551             throw new java.util.NoSuchElementException();
552         }
553 
554         final List<ZipExtraField> newResult = new ArrayList<>();
555         for (final ZipExtraField extraField : extraFields) {
556             if (!type.equals(extraField.getHeaderId())){
557                 newResult.add( extraField);
558             }
559         }
560         if (extraFields.length == newResult.size()) {
561             throw new java.util.NoSuchElementException();
562         }
563         extraFields = newResult.toArray(new ZipExtraField[newResult.size()]);
564         setExtra();
565     }
566 
567     /**
568      * Removes unparseable extra field data.
569      *
570      * @since 1.1
571      */
removeUnparseableExtraFieldData()572     public void removeUnparseableExtraFieldData() {
573         if (unparseableExtra == null) {
574             throw new java.util.NoSuchElementException();
575         }
576         unparseableExtra = null;
577         setExtra();
578     }
579 
580     /**
581      * Looks up an extra field by its header id.
582      *
583      * @param type the header id
584      * @return null if no such field exists.
585      */
getExtraField(final ZipShort type)586     public ZipExtraField getExtraField(final ZipShort type) {
587         if (extraFields != null) {
588             for (final ZipExtraField extraField : extraFields) {
589                 if (type.equals(extraField.getHeaderId())) {
590                     return extraField;
591                 }
592             }
593         }
594         return null;
595     }
596 
597     /**
598      * Looks up extra field data that couldn't be parsed correctly.
599      *
600      * @return null if no such field exists.
601      *
602      * @since 1.1
603      */
getUnparseableExtraFieldData()604     public UnparseableExtraFieldData getUnparseableExtraFieldData() {
605         return unparseableExtra;
606     }
607 
608     /**
609      * Parses the given bytes as extra field data and consumes any
610      * unparseable data as an {@link UnparseableExtraFieldData}
611      * instance.
612      * @param extra an array of bytes to be parsed into extra fields
613      * @throws RuntimeException if the bytes cannot be parsed
614      * @throws RuntimeException on error
615      */
616     @Override
setExtra(final byte[] extra)617     public void setExtra(final byte[] extra) throws RuntimeException {
618         try {
619             final ZipExtraField[] local =
620                 ExtraFieldUtils.parse(extra, true,
621                                       ExtraFieldUtils.UnparseableExtraField.READ);
622             mergeExtraFields(local, true);
623         } catch (final ZipException e) {
624             // actually this is not possible as of Commons Compress 1.1
625             throw new RuntimeException("Error parsing extra fields for entry: " //NOSONAR
626                                        + getName() + " - " + e.getMessage(), e);
627         }
628     }
629 
630     /**
631      * Unfortunately {@link java.util.zip.ZipOutputStream
632      * java.util.zip.ZipOutputStream} seems to access the extra data
633      * directly, so overriding getExtra doesn't help - we need to
634      * modify super's data directly.
635      */
setExtra()636     protected void setExtra() {
637         super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy()));
638     }
639 
640     /**
641      * Sets the central directory part of extra fields.
642      * @param b an array of bytes to be parsed into extra fields
643      */
setCentralDirectoryExtra(final byte[] b)644     public void setCentralDirectoryExtra(final byte[] b) {
645         try {
646             final ZipExtraField[] central =
647                 ExtraFieldUtils.parse(b, false,
648                                       ExtraFieldUtils.UnparseableExtraField.READ);
649             mergeExtraFields(central, false);
650         } catch (final ZipException e) {
651             throw new RuntimeException(e.getMessage(), e); //NOSONAR
652         }
653     }
654 
655     /**
656      * Retrieves the extra data for the local file data.
657      * @return the extra data for local file
658      */
getLocalFileDataExtra()659     public byte[] getLocalFileDataExtra() {
660         final byte[] extra = getExtra();
661         return extra != null ? extra : EMPTY;
662     }
663 
664     /**
665      * Retrieves the extra data for the central directory.
666      * @return the central directory extra data
667      */
getCentralDirectoryExtra()668     public byte[] getCentralDirectoryExtra() {
669         return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy());
670     }
671 
672     /**
673      * Get the name of the entry.
674      *
675      * <p>This method returns the raw name as it is stored inside of the archive.</p>
676      *
677      * @return the entry name
678      */
679     @Override
getName()680     public String getName() {
681         return name == null ? super.getName() : name;
682     }
683 
684     /**
685      * Is this entry a directory?
686      * @return true if the entry is a directory
687      */
688     @Override
isDirectory()689     public boolean isDirectory() {
690         return getName().endsWith("/");
691     }
692 
693     /**
694      * Set the name of the entry.
695      * @param name the name to use
696      */
setName(String name)697     protected void setName(String name) {
698         if (name != null && getPlatform() == PLATFORM_FAT
699             && !name.contains("/")) {
700             name = name.replace('\\', '/');
701         }
702         this.name = name;
703     }
704 
705     /**
706      * Gets the uncompressed size of the entry data.
707      *
708      * <p><b>Note</b>: {@link ZipArchiveInputStream} may create
709      * entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long
710      * as the entry hasn't been read completely.</p>
711      *
712      * @return the entry size
713      */
714     @Override
getSize()715     public long getSize() {
716         return size;
717     }
718 
719     /**
720      * Sets the uncompressed size of the entry data.
721      * @param size the uncompressed size in bytes
722      * @throws IllegalArgumentException if the specified size is less
723      *            than 0
724      */
725     @Override
setSize(final long size)726     public void setSize(final long size) {
727         if (size < 0) {
728             throw new IllegalArgumentException("invalid entry size");
729         }
730         this.size = size;
731     }
732 
733     /**
734      * Sets the name using the raw bytes and the string created from
735      * it by guessing or using the configured encoding.
736      * @param name the name to use created from the raw bytes using
737      * the guessed or configured encoding
738      * @param rawName the bytes originally read as name from the
739      * archive
740      * @since 1.2
741      */
setName(final String name, final byte[] rawName)742     protected void setName(final String name, final byte[] rawName) {
743         setName(name);
744         this.rawName = rawName;
745     }
746 
747     /**
748      * Returns the raw bytes that made up the name before it has been
749      * converted using the configured or guessed encoding.
750      *
751      * <p>This method will return null if this instance has not been
752      * read from an archive.</p>
753      *
754      * @return the raw name bytes
755      * @since 1.2
756      */
getRawName()757     public byte[] getRawName() {
758         if (rawName != null) {
759             final byte[] b = new byte[rawName.length];
760             System.arraycopy(rawName, 0, b, 0, rawName.length);
761             return b;
762         }
763         return null;
764     }
765 
getLocalHeaderOffset()766     protected long getLocalHeaderOffset() {
767         return this.localHeaderOffset;
768     }
769 
setLocalHeaderOffset(long localHeaderOffset)770     protected void setLocalHeaderOffset(long localHeaderOffset) {
771         this.localHeaderOffset = localHeaderOffset;
772     }
773 
774     @Override
getDataOffset()775     public long getDataOffset() {
776         return dataOffset;
777     }
778 
779     /**
780      * Sets the data offset.
781      *
782      * @param dataOffset
783      *      new value of data offset.
784      */
setDataOffset(long dataOffset)785     protected void setDataOffset(long dataOffset) {
786         this.dataOffset = dataOffset;
787     }
788 
789     @Override
isStreamContiguous()790     public boolean isStreamContiguous() {
791         return isStreamContiguous;
792     }
793 
setStreamContiguous(boolean isStreamContiguous)794     protected void setStreamContiguous(boolean isStreamContiguous) {
795         this.isStreamContiguous = isStreamContiguous;
796     }
797 
798     /**
799      * Get the hashCode of the entry.
800      * This uses the name as the hashcode.
801      * @return a hashcode.
802      */
803     @Override
hashCode()804     public int hashCode() {
805         // this method has severe consequences on performance. We cannot rely
806         // on the super.hashCode() method since super.getName() always return
807         // the empty string in the current implemention (there's no setter)
808         // so it is basically draining the performance of a hashmap lookup
809         return getName().hashCode();
810     }
811 
812     /**
813      * The "general purpose bit" field.
814      * @return the general purpose bit
815      * @since 1.1
816      */
getGeneralPurposeBit()817     public GeneralPurposeBit getGeneralPurposeBit() {
818         return gpb;
819     }
820 
821     /**
822      * The "general purpose bit" field.
823      * @param b the general purpose bit
824      * @since 1.1
825      */
setGeneralPurposeBit(final GeneralPurposeBit b)826     public void setGeneralPurposeBit(final GeneralPurposeBit b) {
827         gpb = b;
828     }
829 
830     /**
831      * If there are no extra fields, use the given fields as new extra
832      * data - otherwise merge the fields assuming the existing fields
833      * and the new fields stem from different locations inside the
834      * archive.
835      * @param f the extra fields to merge
836      * @param local whether the new fields originate from local data
837      */
mergeExtraFields(final ZipExtraField[] f, final boolean local)838     private void mergeExtraFields(final ZipExtraField[] f, final boolean local)
839         throws ZipException {
840         if (extraFields == null) {
841             setExtraFields(f);
842         } else {
843             for (final ZipExtraField element : f) {
844                 ZipExtraField existing;
845                 if (element instanceof UnparseableExtraFieldData) {
846                     existing = unparseableExtra;
847                 } else {
848                     existing = getExtraField(element.getHeaderId());
849                 }
850                 if (existing == null) {
851                     addExtraField(element);
852                 } else {
853                     if (local) {
854                         final byte[] b = element.getLocalFileDataData();
855                         existing.parseFromLocalFileData(b, 0, b.length);
856                     } else {
857                         final byte[] b = element.getCentralDirectoryData();
858                         existing.parseFromCentralDirectoryData(b, 0, b.length);
859                     }
860                 }
861             }
862             setExtra();
863         }
864     }
865 
866     /**
867      * Wraps {@link java.util.zip.ZipEntry#getTime} with a {@link Date} as the
868      * entry's last modified date.
869      *
870      * <p>Changes to the implementation of {@link java.util.zip.ZipEntry#getTime}
871      * leak through and the returned value may depend on your local
872      * time zone as well as your version of Java.</p>
873      */
874     @Override
getLastModifiedDate()875     public Date getLastModifiedDate() {
876         return new Date(getTime());
877     }
878 
879     /* (non-Javadoc)
880      * @see java.lang.Object#equals(java.lang.Object)
881      */
882     @Override
equals(final Object obj)883     public boolean equals(final Object obj) {
884         if (this == obj) {
885             return true;
886         }
887         if (obj == null || getClass() != obj.getClass()) {
888             return false;
889         }
890         final ZipArchiveEntry other = (ZipArchiveEntry) obj;
891         final String myName = getName();
892         final String otherName = other.getName();
893         if (myName == null) {
894             if (otherName != null) {
895                 return false;
896             }
897         } else if (!myName.equals(otherName)) {
898             return false;
899         }
900         String myComment = getComment();
901         String otherComment = other.getComment();
902         if (myComment == null) {
903             myComment = "";
904         }
905         if (otherComment == null) {
906             otherComment = "";
907         }
908         return getTime() == other.getTime()
909             && myComment.equals(otherComment)
910             && getInternalAttributes() == other.getInternalAttributes()
911             && getPlatform() == other.getPlatform()
912             && getExternalAttributes() == other.getExternalAttributes()
913             && getMethod() == other.getMethod()
914             && getSize() == other.getSize()
915             && getCrc() == other.getCrc()
916             && getCompressedSize() == other.getCompressedSize()
917             && Arrays.equals(getCentralDirectoryExtra(),
918                              other.getCentralDirectoryExtra())
919             && Arrays.equals(getLocalFileDataExtra(),
920                              other.getLocalFileDataExtra())
921             && localHeaderOffset == other.localHeaderOffset
922             && dataOffset == other.dataOffset
923             && gpb.equals(other.gpb);
924     }
925 
926     /**
927      * Sets the "version made by" field.
928      * @param versionMadeBy "version made by" field
929      * @since 1.11
930      */
setVersionMadeBy(final int versionMadeBy)931     public void setVersionMadeBy(final int versionMadeBy) {
932         this.versionMadeBy = versionMadeBy;
933     }
934 
935     /**
936      * Sets the "version required to expand" field.
937      * @param versionRequired "version required to expand" field
938      * @since 1.11
939      */
setVersionRequired(final int versionRequired)940     public void setVersionRequired(final int versionRequired) {
941         this.versionRequired = versionRequired;
942     }
943 
944     /**
945      * The "version required to expand" field.
946      * @return "version required to expand" field
947      * @since 1.11
948      */
getVersionRequired()949     public int getVersionRequired() {
950         return versionRequired;
951     }
952 
953     /**
954      * The "version made by" field.
955      * @return "version made by" field
956      * @since 1.11
957      */
getVersionMadeBy()958     public int getVersionMadeBy() {
959         return versionMadeBy;
960     }
961 
962     /**
963      * The content of the flags field.
964      * @return content of the flags field
965      * @since 1.11
966      */
getRawFlag()967     public int getRawFlag() {
968         return rawFlag;
969     }
970 
971     /**
972      * Sets the content of the flags field.
973      * @param rawFlag content of the flags field
974      * @since 1.11
975      */
setRawFlag(final int rawFlag)976     public void setRawFlag(final int rawFlag) {
977         this.rawFlag = rawFlag;
978     }
979 
980     /**
981      * The source of the name field value.
982      * @return source of the name field value
983      * @since 1.16
984      */
getNameSource()985     public NameSource getNameSource() {
986         return nameSource;
987     }
988 
989     /**
990      * Sets the source of the name field value.
991      * @param nameSource source of the name field value
992      * @since 1.16
993      */
setNameSource(NameSource nameSource)994     public void setNameSource(NameSource nameSource) {
995         this.nameSource = nameSource;
996     }
997 
998     /**
999      * The source of the comment field value.
1000      * @return source of the comment field value
1001      * @since 1.16
1002      */
getCommentSource()1003     public CommentSource getCommentSource() {
1004         return commentSource;
1005     }
1006 
1007     /**
1008      * Sets the source of the comment field value.
1009      * @param commentSource source of the comment field value
1010      * @since 1.16
1011      */
setCommentSource(CommentSource commentSource)1012     public void setCommentSource(CommentSource commentSource) {
1013         this.commentSource = commentSource;
1014     }
1015 
1016 }
1017