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