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 */
018package org.apache.commons.compress.archivers.zip;
019
020import org.apache.commons.compress.archivers.ArchiveEntry;
021import org.apache.commons.compress.archivers.EntryStreamOffsets;
022
023import java.io.File;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Date;
027import java.util.List;
028import java.util.zip.ZipException;
029
030/**
031 * Extension that adds better handling of extra fields and provides
032 * access to the internal and external file attributes.
033 *
034 * <p>The extra data is expected to follow the recommendation of
035 * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:</p>
036 * <ul>
037 *   <li>the extra byte array consists of a sequence of extra fields</li>
038 *   <li>each extra fields starts by a two byte header id followed by
039 *   a two byte sequence holding the length of the remainder of
040 *   data.</li>
041 * </ul>
042 *
043 * <p>Any extra data that cannot be parsed by the rules above will be
044 * consumed as "unparseable" extra data and treated differently by the
045 * methods of this class.  Versions prior to Apache Commons Compress
046 * 1.1 would have thrown an exception if any attempt was made to read
047 * or write extra data not conforming to the recommendation.</p>
048 *
049 * @NotThreadSafe
050 */
051public class ZipArchiveEntry extends java.util.zip.ZipEntry
052    implements ArchiveEntry, EntryStreamOffsets
053{
054
055    public static final int PLATFORM_UNIX = 3;
056    public static final int PLATFORM_FAT  = 0;
057    public static final int CRC_UNKNOWN = -1;
058    private static final int SHORT_MASK = 0xFFFF;
059    private static final int SHORT_SHIFT = 16;
060    private static final byte[] EMPTY = new byte[0];
061
062    /**
063     * Indicates how the name of this entry has been determined.
064     * @since 1.16
065     */
066    public enum NameSource {
067        /**
068         * The name has been read from the archive using the encoding
069         * of the archive specified when creating the {@link
070         * ZipArchiveInputStream} or {@link ZipFile} (defaults to the
071         * platform's default encoding).
072         */
073        NAME,
074        /**
075         * The name has been read from the archive and the archive
076         * specified the EFS flag which indicates the name has been
077         * encoded as UTF-8.
078         */
079        NAME_WITH_EFS_FLAG,
080        /**
081         * The name has been read from an {@link UnicodePathExtraField
082         * Unicode Extra Field}.
083         */
084        UNICODE_EXTRA_FIELD
085    }
086
087    /**
088     * Indicates how the comment of this entry has been determined.
089     * @since 1.16
090     */
091    public enum CommentSource {
092        /**
093         * The comment has been read from the archive using the encoding
094         * of the archive specified when creating the {@link
095         * ZipArchiveInputStream} or {@link ZipFile} (defaults to the
096         * platform's default encoding).
097         */
098        COMMENT,
099        /**
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     */
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     */
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     */
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     */
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     */
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
441    public ZipExtraField[] getExtraFields(final boolean includeUnparseable) {
442        return includeUnparseable ?
443                getAllExtraFields() :
444                getParseableExtraFields();
445    }
446
447    private ZipExtraField[] getParseableExtraFieldsNoCopy() {
448        if (extraFields == null) {
449            return noExtraFields;
450        }
451        return extraFields;
452    }
453
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     */
463    private ZipExtraField[] getAllExtraFieldsNoCopy() {
464        if (extraFields == null) {
465            return getUnparseableOnly();
466        }
467        return unparseableExtra != null ? getMergedFields() : extraFields;
468    }
469
470    private ZipExtraField[] copyOf(final ZipExtraField[] src){
471        return copyOf(src, src.length);
472    }
473
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
480    private ZipExtraField[] getMergedFields() {
481        final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
482        zipExtraFields[extraFields.length] = unparseableExtra;
483        return zipExtraFields;
484    }
485
486    private ZipExtraField[] getUnparseableOnly() {
487        return unparseableExtra == null ? noExtraFields : new ZipExtraField[] { unparseableExtra };
488    }
489
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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
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
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     */
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
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
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     */
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     */
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
766    protected long getLocalHeaderOffset() {
767        return this.localHeaderOffset;
768    }
769
770    protected void setLocalHeaderOffset(long localHeaderOffset) {
771        this.localHeaderOffset = localHeaderOffset;
772    }
773
774    @Override
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     */
785    protected void setDataOffset(long dataOffset) {
786        this.dataOffset = dataOffset;
787    }
788
789    @Override
790    public boolean isStreamContiguous() {
791        return isStreamContiguous;
792    }
793
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
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     */
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     */
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
1012    public void setCommentSource(CommentSource commentSource) {
1013        this.commentSource = commentSource;
1014    }
1015
1016}