    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;
    021
    022import java.io.File;
    023import java.util.ArrayList;
    024import java.util.Arrays;
    025import java.util.Date;
    026import java.util.List;
    027import java.util.zip.ZipException;
    028
    029/**
    030 * Extension that adds better handling of extra fields and provides
    031 * access to the internal and external file attributes.
    032 *
    033 * <p>The extra data is expected to follow the recommendation of
    034 * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:</p>
    035 * <ul>
    036 *   <li>the extra byte array consists of a sequence of extra fields</li>
    037 *   <li>each extra fields starts by a two byte header id followed by
    038 *   a two byte sequence holding the length of the remainder of
    039 *   data.</li>
    040 * </ul>
    041 *
    042 * <p>Any extra data that cannot be parsed by the rules above will be
    043 * consumed as "unparseable" extra data and treated differently by the
    044 * methods of this class.  Versions prior to Apache Commons Compress
    045 * 1.1 would have thrown an exception if any attempt was made to read
    046 * or write extra data not conforming to the recommendation.</p>
    047 *
    048 * @NotThreadSafe
    049 */
    050public class ZipArchiveEntry extends java.util.zip.ZipEntry
    051    implements ArchiveEntry {
    052
    053    public static final int PLATFORM_UNIX = 3;
    054    public static final int PLATFORM_FAT  = 0;
    055    public static final int CRC_UNKNOWN = -1;
    056    private static final int SHORT_MASK = 0xFFFF;
    057    private static final int SHORT_SHIFT = 16;
    058    private static final byte[] EMPTY = new byte[0];
    059
    060    /**
    061     * The {@link java.util.zip.ZipEntry} base class only supports
    062     * the compression methods STORED and DEFLATED. We override the
    063     * field so that any compression methods can be used.
    064     * <p>
    065     * The default value -1 means that the method has not been specified.
    066     *
    067     * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
    068     *        >COMPRESS-93</a>
    069     */
    070    private int method = ZipMethod.UNKNOWN_CODE;
    071
    072    /**
    073     * The {@link java.util.zip.ZipEntry#setSize} method in the base
    074     * class throws an IllegalArgumentException if the size is bigger
    075     * than 2GB for Java versions < 7.  Need to keep our own size
    076     * information for Zip64 support.
    077     */
    078    private long size = SIZE_UNKNOWN;
    079
    080    private int internalAttributes = 0;
    081    private int versionRequired;
    082    private int versionMadeBy;
    083    private int platform = PLATFORM_FAT;
    084    private int rawFlag;
    085    private long externalAttributes = 0;
    086    private ZipExtraField[] extraFields;
    087    private UnparseableExtraFieldData unparseableExtra = null;
    088    private String name = null;
    089    private byte[] rawName = null;
    090    private GeneralPurposeBit gpb = new GeneralPurposeBit();
    091    private static final ZipExtraField[] noExtraFields = new ZipExtraField[0];
    092
    093    /**
    094     * Creates a new zip entry with the specified name.
    095     *
    096     * <p>Assumes the entry represents a directory if and only if the
    097     * name ends with a forward slash "/".</p>
    098     *
    099     * @param name the name of the entry
    100     */
    101    public ZipArchiveEntry(final String name) {
    102        super(name);
    103        setName(name);
    104    }
    105
    106    /**
    107     * Creates a new zip entry with fields taken from the specified zip entry.
    108     *
    109     * <p>Assumes the entry represents a directory if and only if the
    110     * name ends with a forward slash "/".</p>
    111     *
    112     * @param entry the entry to get fields from
    113     * @throws ZipException on error
    114     */
    115    public ZipArchiveEntry(final java.util.zip.ZipEntry entry) throws ZipException {
    116        super(entry);
    117        setName(entry.getName());
    118        final byte[] extra = entry.getExtra();
    119        if (extra != null) {
    120            setExtraFields(ExtraFieldUtils.parse(extra, true,
    121                                                 ExtraFieldUtils
    122                                                 .UnparseableExtraField.READ));
    123        } else {
    124            // initializes extra data to an empty byte array
    125            setExtra();
    126        }
    127        setMethod(entry.getMethod());
    128        this.size = entry.getSize();
    129    }
    130
    131    /**
    132     * Creates a new zip entry with fields taken from the specified zip entry.
    133     *
    134     * <p>Assumes the entry represents a directory if and only if the
    135     * name ends with a forward slash "/".</p>
    136     *
    137     * @param entry the entry to get fields from
    138     * @throws ZipException on error
    139     */
    140    public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException {
    141        this((java.util.zip.ZipEntry) entry);
    142        setInternalAttributes(entry.getInternalAttributes());
    143        setExternalAttributes(entry.getExternalAttributes());
    144        setExtraFields(getAllExtraFieldsNoCopy());
    145        setPlatform(entry.getPlatform());
    146        final GeneralPurposeBit other = entry.getGeneralPurposeBit();
    147        setGeneralPurposeBit(other == null ? null :
    148                             (GeneralPurposeBit) other.clone());
    149    }
    150
    151    /**
    152     */
    153    protected ZipArchiveEntry() {
    154        this("");
    155    }
    156
    157    /**
    158     * Creates a new zip entry taking some information from the given
    159     * file and using the provided name.
    160     *
    161     * <p>The name will be adjusted to end with a forward slash "/" if
    162     * the file is a directory.  If the file is not a directory a
    163     * potential trailing forward slash will be stripped from the
    164     * entry name.</p>
    165     * @param inputFile file to create the entry from
    166     * @param entryName name of the entry
    167     */
    168    public ZipArchiveEntry(final File inputFile, final String entryName) {
    169        this(inputFile.isDirectory() && !entryName.endsWith("/") ?
    170             entryName + "/" : entryName);
    171        if (inputFile.isFile()){
    172            setSize(inputFile.length());
    173        }
    174        setTime(inputFile.lastModified());
    175        // TODO are there any other fields we can set here?
    176    }
    177
    178    /**
    179     * Overwrite clone.
    180     * @return a cloned copy of this ZipArchiveEntry
    181     */
    182    @Override
    183    public Object clone() {
    184        final ZipArchiveEntry e = (ZipArchiveEntry) super.clone();
    185
    186        e.setInternalAttributes(getInternalAttributes());
    187        e.setExternalAttributes(getExternalAttributes());
    188        e.setExtraFields(getAllExtraFieldsNoCopy());
    189        return e;
    190    }
    191
    192    /**
    193     * Returns the compression method of this entry, or -1 if the
    194     * compression method has not been specified.
    195     *
    196     * @return compression method
    197     *
    198     * @since 1.1
    199     */
    200    @Override
    201    public int getMethod() {
    202        return method;
    203    }
    204
    205    /**
    206     * Sets the compression method of this entry.
    207     *
    208     * @param method compression method
    209     *
    210     * @since 1.1
    211     */
    212    @Override
    213    public void setMethod(final int method) {
    214        if (method < 0) {
    215            throw new IllegalArgumentException(
    216                    "ZIP compression method can not be negative: " + method);
    217        }
    218        this.method = method;
    219    }
    220
    221    /**
    222     * Retrieves the internal file attributes.
    223     *
    224     * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill
    225     * this field, you must use {@link ZipFile} if you want to read
    226     * entries using this attribute.</p>
    227     *
    228     * @return the internal file attributes
    229     */
    230    public int getInternalAttributes() {
    231        return internalAttributes;
    232    }
    233
    234    /**
    235     * Sets the internal file attributes.
    236     * @param value an <code>int</code> value
    237     */
    238    public void setInternalAttributes(final int value) {
    239        internalAttributes = value;
    240    }
    241
    242    /**
    243     * Retrieves the external file attributes.
    244     *
    245     * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill
    246     * this field, you must use {@link ZipFile} if you want to read
    247     * entries using this attribute.</p>
    248     *
    249     * @return the external file attributes
    250     */
    251    public long getExternalAttributes() {
    252        return externalAttributes;
    253    }
    254
    255    /**
    256     * Sets the external file attributes.
    257     * @param value an <code>long</code> value
    258     */
    259    public void setExternalAttributes(final long value) {
    260        externalAttributes = value;
    261    }
    262
    263    /**
    264     * Sets Unix permissions in a way that is understood by Info-Zip's
    265     * unzip command.
    266     * @param mode an <code>int</code> value
    267     */
    268    public void setUnixMode(final int mode) {
    269        // CheckStyle:MagicNumberCheck OFF - no point
    270        setExternalAttributes((mode << SHORT_SHIFT)
    271                              // MS-DOS read-only attribute
    272                              | ((mode & 0200) == 0 ? 1 : 0)
    273                              // MS-DOS directory flag
    274                              | (isDirectory() ? 0x10 : 0));
    275        // CheckStyle:MagicNumberCheck ON
    276        platform = PLATFORM_UNIX;
    277    }
    278
    279    /**
    280     * Unix permission.
    281     * @return the unix permissions
    282     */
    283    public int getUnixMode() {
    284        return platform != PLATFORM_UNIX ? 0 :
    285            (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
    286    }
    287
    288    /**
    289     * Returns true if this entry represents a unix symlink,
    290     * in which case the entry's content contains the target path
    291     * for the symlink.
    292     *
    293     * @since 1.5
    294     * @return true if the entry represents a unix symlink, false otherwise.
    295     */
    296    public boolean isUnixSymlink() {
    297        return (getUnixMode() & UnixStat.LINK_FLAG) == UnixStat.LINK_FLAG;
    298    }
    299
    300    /**
    301     * Platform specification to put into the &quot;version made
    302     * by&quot; part of the central file header.
    303     *
    304     * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
    305     * has been called, in which case PLATFORM_UNIX will be returned.
    306     */
    307    public int getPlatform() {
    308        return platform;
    309    }
    310
    311    /**
    312     * Set the platform (UNIX or FAT).
    313     * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX
    314     */
    315    protected void setPlatform(final int platform) {
    316        this.platform = platform;
    317    }
    318
    319    /**
    320     * Replaces all currently attached extra fields with the new array.
    321     * @param fields an array of extra fields
    322     */
    323    public void setExtraFields(final ZipExtraField[] fields) {
    324        final List<ZipExtraField> newFields = new ArrayList<ZipExtraField>();
    325        for (final ZipExtraField field : fields) {
    326            if (field instanceof UnparseableExtraFieldData) {
    327                unparseableExtra = (UnparseableExtraFieldData) field;
    328            } else {
    329                newFields.add( field);
    330            }
    331        }
    332        extraFields = newFields.toArray(new ZipExtraField[newFields.size()]);
    333        setExtra();
    334    }
    335
    336    /**
    337     * Retrieves all extra fields that have been parsed successfully.
    338     *
    339     * <p><b>Note</b>: The set of extra fields may be incomplete when
    340     * {@link ZipArchiveInputStream} has been used as some extra
    341     * fields use the central directory to store additional
    342     * information.</p>
    343     *
    344     * @return an array of the extra fields
    345     */
    346    public ZipExtraField[] getExtraFields() {
    347        return getParseableExtraFields();
    348    }
    349
    350    /**
    351     * Retrieves extra fields.
    352     * @param includeUnparseable whether to also return unparseable
    353     * extra fields as {@link UnparseableExtraFieldData} if such data
    354     * exists.
    355     * @return an array of the extra fields
    356     *
    357     * @since 1.1
    358     */
    359    public ZipExtraField[] getExtraFields(final boolean includeUnparseable) {
    360        return includeUnparseable ?
    361                getAllExtraFields() :
    362                getParseableExtraFields();
    363    }
    364
    365    private ZipExtraField[] getParseableExtraFieldsNoCopy() {
    366        if (extraFields == null) {
    367            return noExtraFields;
    368        }
    369        return extraFields;
    370    }
    371
    372    private ZipExtraField[] getParseableExtraFields() {
    373        final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy();
    374        return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields) : parseableExtraFields;
    375    }
    376
    377    /**
    378     * Get all extra fields, including unparseable ones.
    379     * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method
    380     */
    381    private ZipExtraField[] getAllExtraFieldsNoCopy() {
    382        if (extraFields == null) {
    383            return getUnparseableOnly();
    384        }
    385        return unparseableExtra != null ? getMergedFields() : extraFields;
    386    }
    387
    388    private ZipExtraField[] copyOf(final ZipExtraField[] src){
    389        return copyOf(src, src.length);
    390    }
    391
    392    private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) {
    393        final ZipExtraField[] cpy = new ZipExtraField[length];
    394        System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length));
    395        return cpy;
    396    }
    397
    398    private ZipExtraField[] getMergedFields() {
    399        final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
    400        zipExtraFields[extraFields.length] = unparseableExtra;
    401        return zipExtraFields;
    402    }
    403
    404    private ZipExtraField[] getUnparseableOnly() {
    405        return unparseableExtra == null ? noExtraFields : new ZipExtraField[] { unparseableExtra };
    406    }
    407
    408    private ZipExtraField[] getAllExtraFields() {
    409        final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy();
    410        return (allExtraFieldsNoCopy == extraFields) ? copyOf( allExtraFieldsNoCopy) : allExtraFieldsNoCopy;
    411    }
    412    /**
    413     * Adds an extra field - replacing an already present extra field
    414     * of the same type.
    415     *
    416     * <p>If no extra field of the same type exists, the field will be
    417     * added as last field.</p>
    418     * @param ze an extra field
    419     */
    420    public void addExtraField(final ZipExtraField ze) {
    421        if (ze instanceof UnparseableExtraFieldData) {
    422            unparseableExtra = (UnparseableExtraFieldData) ze;
    423        } else {
    424            if (extraFields == null) {
    425                extraFields = new ZipExtraField[]{ ze};
    426            } else {
    427                if (getExtraField(ze.getHeaderId())!= null){
    428                    removeExtraField(ze.getHeaderId());
    429                }
    430                final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1);
    431                zipExtraFields[zipExtraFields.length -1] = ze;
    432                extraFields = zipExtraFields;
    433            }
    434        }
    435        setExtra();
    436    }
    437
    438    /**
    439     * Adds an extra field - replacing an already present extra field
    440     * of the same type.
    441     *
    442     * <p>The new extra field will be the first one.</p>
    443     * @param ze an extra field
    444     */
    445    public void addAsFirstExtraField(final ZipExtraField ze) {
    446        if (ze instanceof UnparseableExtraFieldData) {
    447            unparseableExtra = (UnparseableExtraFieldData) ze;
    448        } else {
    449            if (getExtraField(ze.getHeaderId()) != null){
    450                removeExtraField(ze.getHeaderId());
    451            }
    452            final ZipExtraField[] copy = extraFields;
    453            final int newLen = extraFields != null ? extraFields.length + 1: 1;
    454            extraFields = new ZipExtraField[newLen];
    455            extraFields[0] = ze;
    456            if (copy != null){
    457                System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1);
    458            }
    459        }
    460        setExtra();
    461    }
    462
    463    /**
    464     * Remove an extra field.
    465     * @param type the type of extra field to remove
    466     */
    467    public void removeExtraField(final ZipShort type) {
    468        if (extraFields == null) {
    469            throw new java.util.NoSuchElementException();
    470        }
    471
    472        final List<ZipExtraField> newResult = new ArrayList<ZipExtraField>();
    473        for (final ZipExtraField extraField : extraFields) {
    474            if (!type.equals(extraField.getHeaderId())){
    475                newResult.add( extraField);
    476            }
    477        }
    478        if (extraFields.length == newResult.size()) {
    479            throw new java.util.NoSuchElementException();
    480        }
    481        extraFields = newResult.toArray(new ZipExtraField[newResult.size()]);
    482        setExtra();
    483    }
    484
    485    /**
    486     * Removes unparseable extra field data.
    487     *
    488     * @since 1.1
    489     */
    490    public void removeUnparseableExtraFieldData() {
    491        if (unparseableExtra == null) {
    492            throw new java.util.NoSuchElementException();
    493        }
    494        unparseableExtra = null;
    495        setExtra();
    496    }
    497
    498    /**
    499     * Looks up an extra field by its header id.
    500     *
    501     * @param type the header id
    502     * @return null if no such field exists.
    503     */
    504    public ZipExtraField getExtraField(final ZipShort type) {
    505        if (extraFields != null) {
    506            for (final ZipExtraField extraField : extraFields) {
    507                if (type.equals(extraField.getHeaderId())) {
    508                    return extraField;
    509                }
    510            }
    511        }
    512        return null;
    513    }
    514
    515    /**
    516     * Looks up extra field data that couldn't be parsed correctly.
    517     *
    518     * @return null if no such field exists.
    519     *
    520     * @since 1.1
    521     */
    522    public UnparseableExtraFieldData getUnparseableExtraFieldData() {
    523        return unparseableExtra;
    524    }
    525
    526    /**
    527     * Parses the given bytes as extra field data and consumes any
    528     * unparseable data as an {@link UnparseableExtraFieldData}
    529     * instance.
    530     * @param extra an array of bytes to be parsed into extra fields
    531     * @throws RuntimeException if the bytes cannot be parsed
    532     * @throws RuntimeException on error
    533     */
    534    @Override
    535    public void setExtra(final byte[] extra) throws RuntimeException {
    536        try {
    537            final ZipExtraField[] local =
    538                ExtraFieldUtils.parse(extra, true,
    539                                      ExtraFieldUtils.UnparseableExtraField.READ);
    540            mergeExtraFields(local, true);
    541        } catch (final ZipException e) {
    542            // actually this is not possible as of Commons Compress 1.1
    543            throw new RuntimeException("Error parsing extra fields for entry: "
    544                                       + getName() + " - " + e.getMessage(), e);
    545        }
    546    }
    547
    548    /**
    549     * Unfortunately {@link java.util.zip.ZipOutputStream
    550     * java.util.zip.ZipOutputStream} seems to access the extra data
    551     * directly, so overriding getExtra doesn't help - we need to
    552     * modify super's data directly.
    553     */
    554    protected void setExtra() {
    555        super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy()));
    556    }
    557
    558    /**
    559     * Sets the central directory part of extra fields.
    560     * @param b an array of bytes to be parsed into extra fields
    561     */
    562    public void setCentralDirectoryExtra(final byte[] b) {
    563        try {
    564            final ZipExtraField[] central =
    565                ExtraFieldUtils.parse(b, false,
    566                                      ExtraFieldUtils.UnparseableExtraField.READ);
    567            mergeExtraFields(central, false);
    568        } catch (final ZipException e) {
    569            throw new RuntimeException(e.getMessage(), e);
    570        }
    571    }
    572
    573    /**
    574     * Retrieves the extra data for the local file data.
    575     * @return the extra data for local file
    576     */
    577    public byte[] getLocalFileDataExtra() {
    578        final byte[] extra = getExtra();
    579        return extra != null ? extra : EMPTY;
    580    }
    581
    582    /**
    583     * Retrieves the extra data for the central directory.
    584     * @return the central directory extra data
    585     */
    586    public byte[] getCentralDirectoryExtra() {
    587        return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy());
    588    }
    589
    590    /**
    591     * Get the name of the entry.
    592     * @return the entry name
    593     */
    594    @Override
    595    public String getName() {
    596        return name == null ? super.getName() : name;
    597    }
    598
    599    /**
    600     * Is this entry a directory?
    601     * @return true if the entry is a directory
    602     */
    603    @Override
    604    public boolean isDirectory() {
    605        return getName().endsWith("/");
    606    }
    607
    608    /**
    609     * Set the name of the entry.
    610     * @param name the name to use
    611     */
    612    protected void setName(String name) {
    613        if (name != null && getPlatform() == PLATFORM_FAT
    614            && !name.contains("/")) {
    615            name = name.replace('\\', '/');
    616        }
    617        this.name = name;
    618    }
    619
    620    /**
    621     * Gets the uncompressed size of the entry data.
    622     *
    623     * <p><b>Note</b>: {@link ZipArchiveInputStream} may create
    624     * entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long
    625     * as the entry hasn't been read completely.</p>
    626     *
    627     * @return the entry size
    628     */
    629    @Override
    630    public long getSize() {
    631        return size;
    632    }
    633
    634    /**
    635     * Sets the uncompressed size of the entry data.
    636     * @param size the uncompressed size in bytes
    637     * @throws IllegalArgumentException if the specified size is less
    638     *            than 0
    639     */
    640    @Override
    641    public void setSize(final long size) {
    642        if (size < 0) {
    643            throw new IllegalArgumentException("invalid entry size");
    644        }
    645        this.size = size;
    646    }
    647
    648    /**
    649     * Sets the name using the raw bytes and the string created from
    650     * it by guessing or using the configured encoding.
    651     * @param name the name to use created from the raw bytes using
    652     * the guessed or configured encoding
    653     * @param rawName the bytes originally read as name from the
    654     * archive
    655     * @since 1.2
    656     */
    657    protected void setName(final String name, final byte[] rawName) {
    658        setName(name);
    659        this.rawName = rawName;
    660    }
    661
    662    /**
    663     * Returns the raw bytes that made up the name before it has been
    664     * converted using the configured or guessed encoding.
    665     *
    666     * <p>This method will return null if this instance has not been
    667     * read from an archive.</p>
    668     *
    669     * @return the raw name bytes
    670     * @since 1.2
    671     */
    672    public byte[] getRawName() {
    673        if (rawName != null) {
    674            final byte[] b = new byte[rawName.length];
    675            System.arraycopy(rawName, 0, b, 0, rawName.length);
    676            return b;
    677        }
    678        return null;
    679    }
    680
    681    /**
    682     * Get the hashCode of the entry.
    683     * This uses the name as the hashcode.
    684     * @return a hashcode.
    685     */
    686    @Override
    687    public int hashCode() {
    688        // this method has severe consequences on performance. We cannot rely
    689        // on the super.hashCode() method since super.getName() always return
    690        // the empty string in the current implemention (there's no setter)
    691        // so it is basically draining the performance of a hashmap lookup
    692        return getName().hashCode();
    693    }
    694
    695    /**
    696     * The "general purpose bit" field.
    697     * @return the general purpose bit
    698     * @since 1.1
    699     */
    700    public GeneralPurposeBit getGeneralPurposeBit() {
    701        return gpb;
    702    }
    703
    704    /**
    705     * The "general purpose bit" field.
    706     * @param b the general purpose bit
    707     * @since 1.1
    708     */
    709    public void setGeneralPurposeBit(final GeneralPurposeBit b) {
    710        gpb = b;
    711    }
    712
    713    /**
    714     * If there are no extra fields, use the given fields as new extra
    715     * data - otherwise merge the fields assuming the existing fields
    716     * and the new fields stem from different locations inside the
    717     * archive.
    718     * @param f the extra fields to merge
    719     * @param local whether the new fields originate from local data
    720     */
    721    private void mergeExtraFields(final ZipExtraField[] f, final boolean local)
    722        throws ZipException {
    723        if (extraFields == null) {
    724            setExtraFields(f);
    725        } else {
    726            for (final ZipExtraField element : f) {
    727                ZipExtraField existing;
    728                if (element instanceof UnparseableExtraFieldData) {
    729                    existing = unparseableExtra;
    730                } else {
    731                    existing = getExtraField(element.getHeaderId());
    732                }
    733                if (existing == null) {
    734                    addExtraField(element);
    735                } else {
    736                    if (local) {
    737                        final byte[] b = element.getLocalFileDataData();
    738                        existing.parseFromLocalFileData(b, 0, b.length);
    739                    } else {
    740                        final byte[] b = element.getCentralDirectoryData();
    741                        existing.parseFromCentralDirectoryData(b, 0, b.length);
    742                    }
    743                }
    744            }
    745            setExtra();
    746        }
    747    }
    748
    749    /**
    750     * Wraps {@link java.util.zip.ZipEntry#getTime} with a {@link Date} as the
    751     * entry's last modified date.
    752     *
    753     * <p>Changes to the implementation of {@link java.util.zip.ZipEntry#getTime}
    754     * leak through and the returned value may depend on your local
    755     * time zone as well as your version of Java.</p>
    756     */
    757    @Override
    758    public Date getLastModifiedDate() {
    759        return new Date(getTime());
    760    }
    761
    762    /* (non-Javadoc)
    763     * @see java.lang.Object#equals(java.lang.Object)
    764     */
    765    @Override
    766    public boolean equals(final Object obj) {
    767        if (this == obj) {
    768            return true;
    769        }
    770        if (obj == null || getClass() != obj.getClass()) {
    771            return false;
    772        }
    773        final ZipArchiveEntry other = (ZipArchiveEntry) obj;
    774        final String myName = getName();
    775        final String otherName = other.getName();
    776        if (myName == null) {
    777            if (otherName != null) {
    778                return false;
    779            }
    780        } else if (!myName.equals(otherName)) {
    781            return false;
    782        }
    783        String myComment = getComment();
    784        String otherComment = other.getComment();
    785        if (myComment == null) {
    786            myComment = "";
    787        }
    788        if (otherComment == null) {
    789            otherComment = "";
    790        }
    791        return getTime() == other.getTime()
    792            && myComment.equals(otherComment)
    793            && getInternalAttributes() == other.getInternalAttributes()
    794            && getPlatform() == other.getPlatform()
    795            && getExternalAttributes() == other.getExternalAttributes()
    796            && getMethod() == other.getMethod()
    797            && getSize() == other.getSize()
    798            && getCrc() == other.getCrc()
    799            && getCompressedSize() == other.getCompressedSize()
    800            && Arrays.equals(getCentralDirectoryExtra(),
    801                             other.getCentralDirectoryExtra())
    802            && Arrays.equals(getLocalFileDataExtra(),
    803                             other.getLocalFileDataExtra())
    804            && gpb.equals(other.gpb);
    805    }
    806
    807    /**
    808     * Sets the "version made by" field.
    809     * @param versionMadeBy "version made by" field
    810     * @since 1.11
    811     */
    812    public void setVersionMadeBy(final int versionMadeBy) {
    813        this.versionMadeBy = versionMadeBy;
    814    }
    815
    816    /**
    817     * Sets the "version required to expand" field.
    818     * @param versionRequired "version required to expand" field
    819     * @since 1.11
    820     */
    821    public void setVersionRequired(final int versionRequired) {
    822        this.versionRequired = versionRequired;
    823    }
    824
    825    /**
    826     * The "version required to expand" field.
    827     * @return "version required to expand" field
    828     * @since 1.11
    829     */
    830    public int getVersionRequired() {
    831        return versionRequired;
    832    }
    833
    834    /**
    835     * The "version made by" field.
    836     * @return "version made by" field
    837     * @since 1.11
    838     */
    839    public int getVersionMadeBy() {
    840        return versionMadeBy;
    841    }
    842
    843    /**
    844     * The content of the flags field.
    845     * @return content of the flags field
    846     * @since 1.11
    847     */
    848    public int getRawFlag() {
    849        return rawFlag;
    850    }
    851
    852    /**
    853     * Sets the content of the flags field.
    854     * @param rawFlag content of the flags field
    855     * @since 1.11
    856     */
    857    public void setRawFlag(final int rawFlag) {
    858        this.rawFlag = rawFlag;
    859    }
    860}