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
030import static java.util.Arrays.copyOf;
031
032/**
033 * Extension that adds better handling of extra fields and provides
034 * access to the internal and external file attributes.
035 *
036 * <p>The extra data is expected to follow the recommendation of
037 * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:</p>
038 * <ul>
039 *   <li>the extra byte array consists of a sequence of extra fields</li>
040 *   <li>each extra fields starts by a two byte header id followed by
041 *   a two byte sequence holding the length of the remainder of
042 *   data.</li>
043 * </ul>
044 *
045 * <p>Any extra data that cannot be parsed by the rules above will be
046 * consumed as "unparseable" extra data and treated differently by the
047 * methods of this class.  Versions prior to Apache Commons Compress
048 * 1.1 would have thrown an exception if any attempt was made to read
049 * or write extra data not conforming to the recommendation.</p>
050 *
051 * @NotThreadSafe
052 */
053public class ZipArchiveEntry extends java.util.zip.ZipEntry
054    implements ArchiveEntry, EntryStreamOffsets
055{
056
057    public static final int PLATFORM_UNIX = 3;
058    public static final int PLATFORM_FAT  = 0;
059    public static final int CRC_UNKNOWN = -1;
060    private static final int SHORT_MASK = 0xFFFF;
061    private static final int SHORT_SHIFT = 16;
062    private static final byte[] EMPTY = new byte[0];
063
064    /**
065     * Indicates how the name of this entry has been determined.
066     * @since 1.16
067     */
068    public enum NameSource {
069        /**
070         * The name has been read from the archive using the encoding
071         * of the archive specified when creating the {@link
072         * ZipArchiveInputStream} or {@link ZipFile} (defaults to the
073         * platform's default encoding).
074         */
075        NAME,
076        /**
077         * The name has been read from the archive and the archive
078         * specified the EFS flag which indicates the name has been
079         * encoded as UTF-8.
080         */
081        NAME_WITH_EFS_FLAG,
082        /**
083         * The name has been read from an {@link UnicodePathExtraField
084         * Unicode Extra Field}.
085         */
086        UNICODE_EXTRA_FIELD
087    }
088
089    /**
090     * Indicates how the comment of this entry has been determined.
091     * @since 1.16
092     */
093    public enum CommentSource {
094        /**
095         * The comment has been read from the archive using the encoding
096         * of the archive specified when creating the {@link
097         * ZipArchiveInputStream} or {@link ZipFile} (defaults to the
098         * platform's default encoding).
099         */
100        COMMENT,
101        /**
102         * The comment has been read from an {@link UnicodeCommentExtraField
103         * Unicode Extra Field}.
104         */
105        UNICODE_EXTRA_FIELD
106    }
107
108    /**
109     * The {@link java.util.zip.ZipEntry} base class only supports
110     * the compression methods STORED and DEFLATED. We override the
111     * field so that any compression methods can be used.
112     * <p>
113     * The default value -1 means that the method has not been specified.
114     *
115     * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
116     *        >COMPRESS-93</a>
117     */
118    private int method = ZipMethod.UNKNOWN_CODE;
119
120    /**
121     * The {@link java.util.zip.ZipEntry#setSize} method in the base
122     * class throws an IllegalArgumentException if the size is bigger
123     * than 2GB for Java versions &lt; 7 and even in Java 7+ if the
124     * implementation in java.util.zip doesn't support Zip64 itself
125     * (it is an optional feature).
126     *
127     * <p>We need to keep our own size information for Zip64 support.</p>
128     */
129    private long size = SIZE_UNKNOWN;
130
131    private int internalAttributes = 0;
132    private int versionRequired;
133    private int versionMadeBy;
134    private int platform = PLATFORM_FAT;
135    private int rawFlag;
136    private long externalAttributes = 0;
137    private int alignment = 0;
138    private ZipExtraField[] extraFields;
139    private UnparseableExtraFieldData unparseableExtra = null;
140    private String name = null;
141    private byte[] rawName = null;
142    private GeneralPurposeBit gpb = new GeneralPurposeBit();
143    private static final ZipExtraField[] noExtraFields = new ZipExtraField[0];
144    private long localHeaderOffset = OFFSET_UNKNOWN;
145    private long dataOffset = OFFSET_UNKNOWN;
146    private boolean isStreamContiguous = false;
147    private NameSource nameSource = NameSource.NAME;
148    private CommentSource commentSource = CommentSource.COMMENT;
149
150
151    /**
152     * Creates a new zip entry with the specified name.
153     *
154     * <p>Assumes the entry represents a directory if and only if the
155     * name ends with a forward slash "/".</p>
156     *
157     * @param name the name of the entry
158     */
159    public ZipArchiveEntry(final String name) {
160        super(name);
161        setName(name);
162    }
163
164    /**
165     * Creates a new zip entry with fields taken from the specified zip entry.
166     *
167     * <p>Assumes the entry represents a directory if and only if the
168     * name ends with a forward slash "/".</p>
169     *
170     * @param entry the entry to get fields from
171     * @throws ZipException on error
172     */
173    public ZipArchiveEntry(final java.util.zip.ZipEntry entry) throws ZipException {
174        super(entry);
175        setName(entry.getName());
176        final byte[] extra = entry.getExtra();
177        if (extra != null) {
178            setExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT));        } else {
179            // initializes extra data to an empty byte array
180            setExtra();
181        }
182        setMethod(entry.getMethod());
183        this.size = entry.getSize();
184    }
185
186    /**
187     * Creates a new zip entry with fields taken from the specified zip entry.
188     *
189     * <p>Assumes the entry represents a directory if and only if the
190     * name ends with a forward slash "/".</p>
191     *
192     * @param entry the entry to get fields from
193     * @throws ZipException on error
194     */
195    public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException {
196        this((java.util.zip.ZipEntry) entry);
197        setInternalAttributes(entry.getInternalAttributes());
198        setExternalAttributes(entry.getExternalAttributes());
199        setExtraFields(getAllExtraFieldsNoCopy());
200        setPlatform(entry.getPlatform());
201        final GeneralPurposeBit other = entry.getGeneralPurposeBit();
202        setGeneralPurposeBit(other == null ? null :
203                             (GeneralPurposeBit) other.clone());
204    }
205
206    /**
207     */
208    protected ZipArchiveEntry() {
209        this("");
210    }
211
212    /**
213     * Creates a new zip entry taking some information from the given
214     * file and using the provided name.
215     *
216     * <p>The name will be adjusted to end with a forward slash "/" if
217     * the file is a directory.  If the file is not a directory a
218     * potential trailing forward slash will be stripped from the
219     * entry name.</p>
220     * @param inputFile file to create the entry from
221     * @param entryName name of the entry
222     */
223    public ZipArchiveEntry(final File inputFile, final String entryName) {
224        this(inputFile.isDirectory() && !entryName.endsWith("/") ?
225             entryName + "/" : entryName);
226        if (inputFile.isFile()){
227            setSize(inputFile.length());
228        }
229        setTime(inputFile.lastModified());
230        // TODO are there any other fields we can set here?
231    }
232
233    /**
234     * Overwrite clone.
235     * @return a cloned copy of this ZipArchiveEntry
236     */
237    @Override
238    public Object clone() {
239        final ZipArchiveEntry e = (ZipArchiveEntry) super.clone();
240
241        e.setInternalAttributes(getInternalAttributes());
242        e.setExternalAttributes(getExternalAttributes());
243        e.setExtraFields(getAllExtraFieldsNoCopy());
244        return e;
245    }
246
247    /**
248     * Returns the compression method of this entry, or -1 if the
249     * compression method has not been specified.
250     *
251     * @return compression method
252     *
253     * @since 1.1
254     */
255    @Override
256    public int getMethod() {
257        return method;
258    }
259
260    /**
261     * Sets the compression method of this entry.
262     *
263     * @param method compression method
264     *
265     * @since 1.1
266     */
267    @Override
268    public void setMethod(final int method) {
269        if (method < 0) {
270            throw new IllegalArgumentException(
271                    "ZIP compression method can not be negative: " + method);
272        }
273        this.method = method;
274    }
275
276    /**
277     * Retrieves the internal file attributes.
278     *
279     * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill
280     * this field, you must use {@link ZipFile} if you want to read
281     * entries using this attribute.</p>
282     *
283     * @return the internal file attributes
284     */
285    public int getInternalAttributes() {
286        return internalAttributes;
287    }
288
289    /**
290     * Sets the internal file attributes.
291     * @param value an <code>int</code> value
292     */
293    public void setInternalAttributes(final int value) {
294        internalAttributes = value;
295    }
296
297    /**
298     * Retrieves the external file attributes.
299     *
300     * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill
301     * this field, you must use {@link ZipFile} if you want to read
302     * entries using this attribute.</p>
303     *
304     * @return the external file attributes
305     */
306    public long getExternalAttributes() {
307        return externalAttributes;
308    }
309
310    /**
311     * Sets the external file attributes.
312     * @param value an <code>long</code> value
313     */
314    public void setExternalAttributes(final long value) {
315        externalAttributes = value;
316    }
317
318    /**
319     * Sets Unix permissions in a way that is understood by Info-Zip's
320     * unzip command.
321     * @param mode an <code>int</code> value
322     */
323    public void setUnixMode(final int mode) {
324        // CheckStyle:MagicNumberCheck OFF - no point
325        setExternalAttributes((mode << SHORT_SHIFT)
326                              // MS-DOS read-only attribute
327                              | ((mode & 0200) == 0 ? 1 : 0)
328                              // MS-DOS directory flag
329                              | (isDirectory() ? 0x10 : 0));
330        // CheckStyle:MagicNumberCheck ON
331        platform = PLATFORM_UNIX;
332    }
333
334    /**
335     * Unix permission.
336     * @return the unix permissions
337     */
338    public int getUnixMode() {
339        return platform != PLATFORM_UNIX ? 0 :
340            (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
341    }
342
343    /**
344     * Returns true if this entry represents a unix symlink,
345     * in which case the entry's content contains the target path
346     * for the symlink.
347     *
348     * @since 1.5
349     * @return true if the entry represents a unix symlink, false otherwise.
350     */
351    public boolean isUnixSymlink() {
352        return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG;
353    }
354
355    /**
356     * Platform specification to put into the &quot;version made
357     * by&quot; part of the central file header.
358     *
359     * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
360     * has been called, in which case PLATFORM_UNIX will be returned.
361     */
362    public int getPlatform() {
363        return platform;
364    }
365
366    /**
367     * Set the platform (UNIX or FAT).
368     * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX
369     */
370    protected void setPlatform(final int platform) {
371        this.platform = platform;
372    }
373
374    /**
375     * Gets currently configured alignment.
376     *
377     * @return
378     *      alignment for this entry.
379     * @since 1.14
380     */
381    protected int getAlignment() {
382        return this.alignment;
383    }
384
385    /**
386     * Sets alignment for this entry.
387     *
388     * @param alignment
389     *      requested alignment, 0 for default.
390     * @since 1.14
391     */
392    public void setAlignment(int alignment) {
393        if ((alignment & (alignment - 1)) != 0 || alignment > 0xffff) {
394            throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than "
395                + 0xffff + " but is " + alignment);
396        }
397        this.alignment = alignment;
398    }
399
400    /**
401     * Replaces all currently attached extra fields with the new array.
402     * @param fields an array of extra fields
403     */
404    public void setExtraFields(final ZipExtraField[] fields) {
405        unparseableExtra = null;
406        final List<ZipExtraField> newFields = new ArrayList<>();
407        if (fields != null) {
408            for (final ZipExtraField field : fields) {
409                if (field instanceof UnparseableExtraFieldData) {
410                    unparseableExtra = (UnparseableExtraFieldData) field;
411                } else {
412                    newFields.add(field);
413                }
414            }
415        }
416        extraFields = newFields.toArray(noExtraFields);
417        setExtra();
418    }
419
420    /**
421     * Retrieves all extra fields that have been parsed successfully.
422     *
423     * <p><b>Note</b>: The set of extra fields may be incomplete when
424     * {@link ZipArchiveInputStream} has been used as some extra
425     * fields use the central directory to store additional
426     * information.</p>
427     *
428     * @return an array of the extra fields
429     */
430    public ZipExtraField[] getExtraFields() {
431        return getParseableExtraFields();
432    }
433
434    /**
435     * Retrieves extra fields.
436     * @param includeUnparseable whether to also return unparseable
437     * extra fields as {@link UnparseableExtraFieldData} if such data
438     * exists.
439     * @return an array of the extra fields
440     *
441     * @since 1.1
442     */
443    public ZipExtraField[] getExtraFields(final boolean includeUnparseable) {
444        return includeUnparseable ?
445                getAllExtraFields() :
446                getParseableExtraFields();
447    }
448
449    /**
450     * Retrieves extra fields.
451     * @param parsingBehavior controls parsing of extra fields.
452     * @return an array of the extra fields
453     *
454     * @throws ZipException if parsing fails, can not happen if {@code
455     * parsingBehavior} is {@link ExtraFieldParsingMode#BEST_EFFORT}.
456     *
457     * @since 1.19
458     */
459    public ZipExtraField[] getExtraFields(final ExtraFieldParsingBehavior parsingBehavior)
460        throws ZipException {
461        if (parsingBehavior == ExtraFieldParsingMode.BEST_EFFORT) {
462            return getExtraFields(true);
463        }
464        if (parsingBehavior == ExtraFieldParsingMode.ONLY_PARSEABLE_LENIENT) {
465            return getExtraFields(false);
466        }
467        byte[] local = getExtra();
468        List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(local, true,
469            parsingBehavior)));
470        byte[] central = getCentralDirectoryExtra();
471        List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(central, false,
472            parsingBehavior)));
473        List<ZipExtraField> merged = new ArrayList<>();
474        for (ZipExtraField l : localFields) {
475            ZipExtraField c = null;
476            if (l instanceof UnparseableExtraFieldData) {
477                c = findUnparseable(centralFields);
478            } else {
479                c = findMatching(l.getHeaderId(), centralFields);
480            }
481            if (c != null) {
482                byte[] cd = c.getCentralDirectoryData();
483                if (cd != null && cd.length > 0) {
484                    l.parseFromCentralDirectoryData(cd, 0, cd.length);
485                }
486                centralFields.remove(c);
487            }
488            merged.add(l);
489        }
490        merged.addAll(centralFields);
491        return merged.toArray(noExtraFields);
492    }
493
494    private ZipExtraField[] getParseableExtraFieldsNoCopy() {
495        if (extraFields == null) {
496            return noExtraFields;
497        }
498        return extraFields;
499    }
500
501    private ZipExtraField[] getParseableExtraFields() {
502        final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy();
503        return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields, parseableExtraFields.length)
504            : parseableExtraFields;
505    }
506
507    /**
508     * Get all extra fields, including unparseable ones.
509     * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method
510     */
511    private ZipExtraField[] getAllExtraFieldsNoCopy() {
512        if (extraFields == null) {
513            return getUnparseableOnly();
514        }
515        return unparseableExtra != null ? getMergedFields() : extraFields;
516    }
517
518    private ZipExtraField[] getMergedFields() {
519        final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
520        zipExtraFields[extraFields.length] = unparseableExtra;
521        return zipExtraFields;
522    }
523
524    private ZipExtraField[] getUnparseableOnly() {
525        return unparseableExtra == null ? noExtraFields : new ZipExtraField[] { unparseableExtra };
526    }
527
528    private ZipExtraField[] getAllExtraFields() {
529        final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy();
530        return (allExtraFieldsNoCopy == extraFields) ? copyOf(allExtraFieldsNoCopy, allExtraFieldsNoCopy.length)
531            : allExtraFieldsNoCopy;
532    }
533
534    private ZipExtraField findUnparseable(List<ZipExtraField> fs) {
535        for (ZipExtraField f : fs) {
536            if (f instanceof UnparseableExtraFieldData) {
537                return f;
538            }
539        }
540        return null;
541    }
542
543    private ZipExtraField findMatching(ZipShort headerId, List<ZipExtraField> fs) {
544        for (ZipExtraField f : fs) {
545            if (headerId.equals(f.getHeaderId())) {
546                return f;
547            }
548        }
549        return null;
550    }
551
552    /**
553     * Adds an extra field - replacing an already present extra field
554     * of the same type.
555     *
556     * <p>If no extra field of the same type exists, the field will be
557     * added as last field.</p>
558     * @param ze an extra field
559     */
560    public void addExtraField(final ZipExtraField ze) {
561        if (ze instanceof UnparseableExtraFieldData) {
562            unparseableExtra = (UnparseableExtraFieldData) ze;
563        } else {
564            if (extraFields == null) {
565                extraFields = new ZipExtraField[]{ ze };
566            } else {
567                if (getExtraField(ze.getHeaderId()) != null) {
568                    removeExtraField(ze.getHeaderId());
569                }
570                final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
571                zipExtraFields[zipExtraFields.length - 1] = ze;
572                extraFields = zipExtraFields;
573            }
574        }
575        setExtra();
576    }
577
578    /**
579     * Adds an extra field - replacing an already present extra field
580     * of the same type.
581     *
582     * <p>The new extra field will be the first one.</p>
583     * @param ze an extra field
584     */
585    public void addAsFirstExtraField(final ZipExtraField ze) {
586        if (ze instanceof UnparseableExtraFieldData) {
587            unparseableExtra = (UnparseableExtraFieldData) ze;
588        } else {
589            if (getExtraField(ze.getHeaderId()) != null) {
590                removeExtraField(ze.getHeaderId());
591            }
592            final ZipExtraField[] copy = extraFields;
593            final int newLen = extraFields != null ? extraFields.length + 1 : 1;
594            extraFields = new ZipExtraField[newLen];
595            extraFields[0] = ze;
596            if (copy != null){
597                System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1);
598            }
599        }
600        setExtra();
601    }
602
603    /**
604     * Remove an extra field.
605     * @param type the type of extra field to remove
606     */
607    public void removeExtraField(final ZipShort type) {
608        if (extraFields == null) {
609            throw new java.util.NoSuchElementException();
610        }
611
612        final List<ZipExtraField> newResult = new ArrayList<>();
613        for (final ZipExtraField extraField : extraFields) {
614            if (!type.equals(extraField.getHeaderId())) {
615                newResult.add(extraField);
616            }
617        }
618        if (extraFields.length == newResult.size()) {
619            throw new java.util.NoSuchElementException();
620        }
621        extraFields = newResult.toArray(noExtraFields);
622        setExtra();
623    }
624
625    /**
626     * Removes unparseable extra field data.
627     *
628     * @since 1.1
629     */
630    public void removeUnparseableExtraFieldData() {
631        if (unparseableExtra == null) {
632            throw new java.util.NoSuchElementException();
633        }
634        unparseableExtra = null;
635        setExtra();
636    }
637
638    /**
639     * Looks up an extra field by its header id.
640     *
641     * @param type the header id
642     * @return null if no such field exists.
643     */
644    public ZipExtraField getExtraField(final ZipShort type) {
645        if (extraFields != null) {
646            for (final ZipExtraField extraField : extraFields) {
647                if (type.equals(extraField.getHeaderId())) {
648                    return extraField;
649                }
650            }
651        }
652        return null;
653    }
654
655    /**
656     * Looks up extra field data that couldn't be parsed correctly.
657     *
658     * @return null if no such field exists.
659     *
660     * @since 1.1
661     */
662    public UnparseableExtraFieldData getUnparseableExtraFieldData() {
663        return unparseableExtra;
664    }
665
666    /**
667     * Parses the given bytes as extra field data and consumes any
668     * unparseable data as an {@link UnparseableExtraFieldData}
669     * instance.
670     * @param extra an array of bytes to be parsed into extra fields
671     * @throws RuntimeException if the bytes cannot be parsed
672     * @throws RuntimeException on error
673     */
674    @Override
675    public void setExtra(final byte[] extra) throws RuntimeException {
676        try {
677            final ZipExtraField[] local = ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT);
678            mergeExtraFields(local, true);
679        } catch (final ZipException e) {
680            // actually this is not possible as of Commons Compress 1.1
681            throw new RuntimeException("Error parsing extra fields for entry: " //NOSONAR
682                                       + getName() + " - " + e.getMessage(), e);
683        }
684    }
685
686    /**
687     * Unfortunately {@link java.util.zip.ZipOutputStream
688     * java.util.zip.ZipOutputStream} seems to access the extra data
689     * directly, so overriding getExtra doesn't help - we need to
690     * modify super's data directly.
691     */
692    protected void setExtra() {
693        super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy()));
694    }
695
696    /**
697     * Sets the central directory part of extra fields.
698     * @param b an array of bytes to be parsed into extra fields
699     */
700    public void setCentralDirectoryExtra(final byte[] b) {
701        try {
702            final ZipExtraField[] central = ExtraFieldUtils.parse(b, false, ExtraFieldParsingMode.BEST_EFFORT);
703            mergeExtraFields(central, false);
704        } catch (final ZipException e) {
705            // actually this is not possible as of Commons Compress 1.19
706            throw new RuntimeException(e.getMessage(), e); //NOSONAR
707        }
708    }
709
710    /**
711     * Retrieves the extra data for the local file data.
712     * @return the extra data for local file
713     */
714    public byte[] getLocalFileDataExtra() {
715        final byte[] extra = getExtra();
716        return extra != null ? extra : EMPTY;
717    }
718
719    /**
720     * Retrieves the extra data for the central directory.
721     * @return the central directory extra data
722     */
723    public byte[] getCentralDirectoryExtra() {
724        return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy());
725    }
726
727    /**
728     * Get the name of the entry.
729     *
730     * <p>This method returns the raw name as it is stored inside of the archive.</p>
731     *
732     * @return the entry name
733     */
734    @Override
735    public String getName() {
736        return name == null ? super.getName() : name;
737    }
738
739    /**
740     * Is this entry a directory?
741     * @return true if the entry is a directory
742     */
743    @Override
744    public boolean isDirectory() {
745        final String n = getName();
746        return n != null && n.endsWith("/");
747    }
748
749    /**
750     * Set the name of the entry.
751     * @param name the name to use
752     */
753    protected void setName(String name) {
754        if (name != null && getPlatform() == PLATFORM_FAT
755            && !name.contains("/")) {
756            name = name.replace('\\', '/');
757        }
758        this.name = name;
759    }
760
761    /**
762     * Gets the uncompressed size of the entry data.
763     *
764     * <p><b>Note</b>: {@link ZipArchiveInputStream} may create
765     * entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long
766     * as the entry hasn't been read completely.</p>
767     *
768     * @return the entry size
769     */
770    @Override
771    public long getSize() {
772        return size;
773    }
774
775    /**
776     * Sets the uncompressed size of the entry data.
777     * @param size the uncompressed size in bytes
778     * @throws IllegalArgumentException if the specified size is less
779     *            than 0
780     */
781    @Override
782    public void setSize(final long size) {
783        if (size < 0) {
784            throw new IllegalArgumentException("Invalid entry size");
785        }
786        this.size = size;
787    }
788
789    /**
790     * Sets the name using the raw bytes and the string created from
791     * it by guessing or using the configured encoding.
792     * @param name the name to use created from the raw bytes using
793     * the guessed or configured encoding
794     * @param rawName the bytes originally read as name from the
795     * archive
796     * @since 1.2
797     */
798    protected void setName(final String name, final byte[] rawName) {
799        setName(name);
800        this.rawName = rawName;
801    }
802
803    /**
804     * Returns the raw bytes that made up the name before it has been
805     * converted using the configured or guessed encoding.
806     *
807     * <p>This method will return null if this instance has not been
808     * read from an archive.</p>
809     *
810     * @return the raw name bytes
811     * @since 1.2
812     */
813    public byte[] getRawName() {
814        if (rawName != null) {
815            return copyOf(rawName, rawName.length);
816        }
817        return null;
818    }
819
820    protected long getLocalHeaderOffset() {
821        return this.localHeaderOffset;
822    }
823
824    protected void setLocalHeaderOffset(long localHeaderOffset) {
825        this.localHeaderOffset = localHeaderOffset;
826    }
827
828    @Override
829    public long getDataOffset() {
830        return dataOffset;
831    }
832
833    /**
834     * Sets the data offset.
835     *
836     * @param dataOffset
837     *      new value of data offset.
838     */
839    protected void setDataOffset(long dataOffset) {
840        this.dataOffset = dataOffset;
841    }
842
843    @Override
844    public boolean isStreamContiguous() {
845        return isStreamContiguous;
846    }
847
848    protected void setStreamContiguous(boolean isStreamContiguous) {
849        this.isStreamContiguous = isStreamContiguous;
850    }
851
852    /**
853     * Get the hashCode of the entry.
854     * This uses the name as the hashcode.
855     * @return a hashcode.
856     */
857    @Override
858    public int hashCode() {
859        // this method has severe consequences on performance. We cannot rely
860        // on the super.hashCode() method since super.getName() always return
861        // the empty string in the current implemention (there's no setter)
862        // so it is basically draining the performance of a hashmap lookup
863        final String n = getName();
864        return (n == null ? "" : n).hashCode();
865    }
866
867    /**
868     * The "general purpose bit" field.
869     * @return the general purpose bit
870     * @since 1.1
871     */
872    public GeneralPurposeBit getGeneralPurposeBit() {
873        return gpb;
874    }
875
876    /**
877     * The "general purpose bit" field.
878     * @param b the general purpose bit
879     * @since 1.1
880     */
881    public void setGeneralPurposeBit(final GeneralPurposeBit b) {
882        gpb = b;
883    }
884
885    /**
886     * If there are no extra fields, use the given fields as new extra
887     * data - otherwise merge the fields assuming the existing fields
888     * and the new fields stem from different locations inside the
889     * archive.
890     * @param f the extra fields to merge
891     * @param local whether the new fields originate from local data
892     */
893    private void mergeExtraFields(final ZipExtraField[] f, final boolean local) {
894        if (extraFields == null) {
895            setExtraFields(f);
896        } else {
897            for (final ZipExtraField element : f) {
898                ZipExtraField existing;
899                if (element instanceof UnparseableExtraFieldData) {
900                    existing = unparseableExtra;
901                } else {
902                    existing = getExtraField(element.getHeaderId());
903                }
904                if (existing == null) {
905                    addExtraField(element);
906                } else {
907                    final byte[] b = local ? element.getLocalFileDataData()
908                        : element.getCentralDirectoryData();
909                    try {
910                        if (local) {
911                            existing.parseFromLocalFileData(b, 0, b.length);
912                        } else {
913                            existing.parseFromCentralDirectoryData(b, 0, b.length);
914                        }
915                    } catch (ZipException ex) {
916                        // emulate ExtraFieldParsingMode.fillAndMakeUnrecognizedOnError
917                        final UnrecognizedExtraField u = new UnrecognizedExtraField();
918                        u.setHeaderId(existing.getHeaderId());
919                        if (local) {
920                            u.setLocalFileDataData(b);
921                            u.setCentralDirectoryData(existing.getCentralDirectoryData());
922                        } else {
923                            u.setLocalFileDataData(existing.getLocalFileDataData());
924                            u.setCentralDirectoryData(b);
925                        }
926                        removeExtraField(existing.getHeaderId());
927                        addExtraField(u);
928                    }
929                }
930            }
931            setExtra();
932        }
933    }
934
935    /**
936     * Wraps {@link java.util.zip.ZipEntry#getTime} with a {@link Date} as the
937     * entry's last modified date.
938     *
939     * <p>Changes to the implementation of {@link java.util.zip.ZipEntry#getTime}
940     * leak through and the returned value may depend on your local
941     * time zone as well as your version of Java.</p>
942     */
943    @Override
944    public Date getLastModifiedDate() {
945        return new Date(getTime());
946    }
947
948    /* (non-Javadoc)
949     * @see java.lang.Object#equals(java.lang.Object)
950     */
951    @Override
952    public boolean equals(final Object obj) {
953        if (this == obj) {
954            return true;
955        }
956        if (obj == null || getClass() != obj.getClass()) {
957            return false;
958        }
959        final ZipArchiveEntry other = (ZipArchiveEntry) obj;
960        final String myName = getName();
961        final String otherName = other.getName();
962        if (myName == null) {
963            if (otherName != null) {
964                return false;
965            }
966        } else if (!myName.equals(otherName)) {
967            return false;
968        }
969        String myComment = getComment();
970        String otherComment = other.getComment();
971        if (myComment == null) {
972            myComment = "";
973        }
974        if (otherComment == null) {
975            otherComment = "";
976        }
977        return getTime() == other.getTime()
978            && myComment.equals(otherComment)
979            && getInternalAttributes() == other.getInternalAttributes()
980            && getPlatform() == other.getPlatform()
981            && getExternalAttributes() == other.getExternalAttributes()
982            && getMethod() == other.getMethod()
983            && getSize() == other.getSize()
984            && getCrc() == other.getCrc()
985            && getCompressedSize() == other.getCompressedSize()
986            && Arrays.equals(getCentralDirectoryExtra(),
987                             other.getCentralDirectoryExtra())
988            && Arrays.equals(getLocalFileDataExtra(),
989                             other.getLocalFileDataExtra())
990            && localHeaderOffset == other.localHeaderOffset
991            && dataOffset == other.dataOffset
992            && gpb.equals(other.gpb);
993    }
994
995    /**
996     * Sets the "version made by" field.
997     * @param versionMadeBy "version made by" field
998     * @since 1.11
999     */
1000    public void setVersionMadeBy(final int versionMadeBy) {
1001        this.versionMadeBy = versionMadeBy;
1002    }
1003
1004    /**
1005     * Sets the "version required to expand" field.
1006     * @param versionRequired "version required to expand" field
1007     * @since 1.11
1008     */
1009    public void setVersionRequired(final int versionRequired) {
1010        this.versionRequired = versionRequired;
1011    }
1012
1013    /**
1014     * The "version required to expand" field.
1015     * @return "version required to expand" field
1016     * @since 1.11
1017     */
1018    public int getVersionRequired() {
1019        return versionRequired;
1020    }
1021
1022    /**
1023     * The "version made by" field.
1024     * @return "version made by" field
1025     * @since 1.11
1026     */
1027    public int getVersionMadeBy() {
1028        return versionMadeBy;
1029    }
1030
1031    /**
1032     * The content of the flags field.
1033     * @return content of the flags field
1034     * @since 1.11
1035     */
1036    public int getRawFlag() {
1037        return rawFlag;
1038    }
1039
1040    /**
1041     * Sets the content of the flags field.
1042     * @param rawFlag content of the flags field
1043     * @since 1.11
1044     */
1045    public void setRawFlag(final int rawFlag) {
1046        this.rawFlag = rawFlag;
1047    }
1048
1049    /**
1050     * The source of the name field value.
1051     * @return source of the name field value
1052     * @since 1.16
1053     */
1054    public NameSource getNameSource() {
1055        return nameSource;
1056    }
1057
1058    /**
1059     * Sets the source of the name field value.
1060     * @param nameSource source of the name field value
1061     * @since 1.16
1062     */
1063    public void setNameSource(NameSource nameSource) {
1064        this.nameSource = nameSource;
1065    }
1066
1067    /**
1068     * The source of the comment field value.
1069     * @return source of the comment field value
1070     * @since 1.16
1071     */
1072    public CommentSource getCommentSource() {
1073        return commentSource;
1074    }
1075
1076    /**
1077     * Sets the source of the comment field value.
1078     * @param commentSource source of the comment field value
1079     * @since 1.16
1080     */
1081    public void setCommentSource(CommentSource commentSource) {
1082        this.commentSource = commentSource;
1083    }
1084
1085
1086    /**
1087     * How to try to parse the extra fields.
1088     *
1089     * <p>Configures the bahvior for:</p>
1090     * <ul>
1091     *   <li>What shall happen if the extra field content doesn't
1092     *   follow the recommended pattern of two-byte id followed by a
1093     *   two-byte length?</li>
1094     *  <li>What shall happen if an extra field is generally supported
1095     *  by Commons Compress but its content cannot be parsed
1096     *  correctly? This may for example happen if the archive is
1097     *  corrupt, it triggers a bug in Commons Compress or the extra
1098     *  field uses a version not (yet) supported by Commons
1099     *  Compress.</li>
1100     * </ul>
1101     *
1102     * @since 1.19
1103     */
1104    public enum ExtraFieldParsingMode implements ExtraFieldParsingBehavior {
1105        /**
1106         * Try to parse as many extra fields as possible and wrap
1107         * unknown extra fields as well as supported extra fields that
1108         * cannot be parsed in {@link UnrecognizedExtraField}.
1109         *
1110         * <p>Wrap extra data that doesn't follow the recommended
1111         * pattern in an {@link UnparseableExtraFieldData}
1112         * instance.</p>
1113         *
1114         * <p>This is the default behavior starting with Commons Compress 1.19.</p>
1115         */
1116        BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ) {
1117            @Override
1118            public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local) {
1119                return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
1120            }
1121        },
1122        /**
1123         * Try to parse as many extra fields as possible and wrap
1124         * unknown extra fields in {@link UnrecognizedExtraField}.
1125         *
1126         * <p>Wrap extra data that doesn't follow the recommended
1127         * pattern in an {@link UnparseableExtraFieldData}
1128         * instance.</p>
1129         *
1130         * <p>Throw an exception if an extra field that is generally
1131         * supported cannot be parsed.</p>
1132         *
1133         * <p>This used to be the default behavior prior to Commons
1134         * Compress 1.19.</p>
1135         */
1136        STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ),
1137        /**
1138         * Try to parse as many extra fields as possible and wrap
1139         * unknown extra fields as well as supported extra fields that
1140         * cannot be parsed in {@link UnrecognizedExtraField}.
1141         *
1142         * <p>Ignore extra data that doesn't follow the recommended
1143         * pattern.</p>
1144         */
1145        ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP) {
1146            @Override
1147            public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local) {
1148                return fillAndMakeUnrecognizedOnError(field, data, off, len, local);
1149            }
1150        },
1151        /**
1152         * Try to parse as many extra fields as possible and wrap
1153         * unknown extra fields in {@link UnrecognizedExtraField}.
1154         *
1155         * <p>Ignore extra data that doesn't follow the recommended
1156         * pattern.</p>
1157         *
1158         * <p>Throw an exception if an extra field that is generally
1159         * supported cannot be parsed.</p>
1160         */
1161        ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP),
1162        /**
1163         * Throw an exception if any of the recognized extra fields
1164         * cannot be parsed or any extra field violates the
1165         * recommended pattern.
1166         */
1167        DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW);
1168
1169        private final ExtraFieldUtils.UnparseableExtraField onUnparseableData;
1170
1171        private ExtraFieldParsingMode(ExtraFieldUtils.UnparseableExtraField onUnparseableData) {
1172            this.onUnparseableData = onUnparseableData;
1173        }
1174
1175        @Override
1176        public ZipExtraField onUnparseableExtraField(byte[] data, int off, int len, boolean local,
1177            int claimedLength) throws ZipException {
1178            return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength);
1179        }
1180
1181        @Override
1182        public ZipExtraField createExtraField(final ZipShort headerId)
1183            throws ZipException, InstantiationException, IllegalAccessException {
1184            return ExtraFieldUtils.createExtraField(headerId);
1185        }
1186
1187        @Override
1188        public ZipExtraField fill(ZipExtraField field, byte[] data, int off, int len, boolean local)
1189            throws ZipException {
1190            return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
1191        }
1192
1193        private static ZipExtraField fillAndMakeUnrecognizedOnError(ZipExtraField field, byte[] data, int off,
1194            int len, boolean local) {
1195            try {
1196                return ExtraFieldUtils.fillExtraField(field, data, off, len, local);
1197            } catch (ZipException ex) {
1198                final UnrecognizedExtraField u = new UnrecognizedExtraField();
1199                u.setHeaderId(field.getHeaderId());
1200                if (local) {
1201                    u.setLocalFileDataData(Arrays.copyOfRange(data, off, off + len));
1202                } else {
1203                    u.setCentralDirectoryData(Arrays.copyOfRange(data, off, off + len));
1204                }
1205                return u;
1206            }
1207        }
1208    }
1209}