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<>();
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<>();
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: " //NOSONAR
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); //NOSONAR
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}