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