    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     */
    018    package org.apache.commons.compress.archivers.zip;
    019
    020    import java.io.File;
    021    import java.util.LinkedHashMap;
    022    import java.util.zip.ZipException;
    023    import org.apache.commons.compress.archivers.ArchiveEntry;
    024
    025    /**
    026     * Extension that adds better handling of extra fields and provides
    027     * access to the internal and external file attributes.
    028     *
    029     * @NotThreadSafe
    030     */
    031    public class ZipArchiveEntry extends java.util.zip.ZipEntry
    032        implements ArchiveEntry, Cloneable {
    033
    034        public static final int PLATFORM_UNIX = 3;
    035        public static final int PLATFORM_FAT  = 0;
    036        private static final int SHORT_MASK = 0xFFFF;
    037        private static final int SHORT_SHIFT = 16;
    038
    039        private int internalAttributes = 0;
    040        private int platform = PLATFORM_FAT;
    041        private long externalAttributes = 0;
    042        private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null;
    043        private String name = null;
    044
    045        /**
    046         * Creates a new zip entry with the specified name.
    047         * @param name the name of the entry
    048         */
    049        public ZipArchiveEntry(String name) {
    050            super(name);
    051        }
    052
    053        /**
    054         * Creates a new zip entry with fields taken from the specified zip entry.
    055         * @param entry the entry to get fields from
    056         * @throws ZipException on error
    057         */
    058        public ZipArchiveEntry(java.util.zip.ZipEntry entry) throws ZipException {
    059            super(entry);
    060            setName(entry.getName());
    061            byte[] extra = entry.getExtra();
    062            if (extra != null) {
    063                setExtraFields(ExtraFieldUtils.parse(extra));
    064            } else {
    065                // initializes extra data to an empty byte array
    066                setExtra();
    067            }
    068        }
    069
    070        /**
    071         * Creates a new zip entry with fields taken from the specified zip entry.
    072         * @param entry the entry to get fields from
    073         * @throws ZipException on error
    074         */
    075        public ZipArchiveEntry(ZipArchiveEntry entry) throws ZipException {
    076            this((java.util.zip.ZipEntry) entry);
    077            setInternalAttributes(entry.getInternalAttributes());
    078            setExternalAttributes(entry.getExternalAttributes());
    079            setExtraFields(entry.getExtraFields());
    080        }
    081
    082        /**
    083         */
    084        protected ZipArchiveEntry() {
    085            super("");
    086        }
    087
    088        public ZipArchiveEntry(File inputFile, String entryName) {
    089            this(entryName);
    090            if (inputFile.isFile()){
    091                setSize(inputFile.length());
    092            }
    093            setTime(inputFile.lastModified());
    094            // TODO are there any other fields we can set here?
    095        }
    096
    097        /**
    098         * Overwrite clone.
    099         * @return a cloned copy of this ZipArchiveEntry
    100         */
    101        public Object clone() {
    102            ZipArchiveEntry e = (ZipArchiveEntry) super.clone();
    103
    104            e.extraFields = extraFields != null ? (LinkedHashMap) extraFields.clone() : null;
    105            e.setInternalAttributes(getInternalAttributes());
    106            e.setExternalAttributes(getExternalAttributes());
    107            e.setExtraFields(getExtraFields());
    108            return e;
    109        }
    110
    111        /**
    112         * Retrieves the internal file attributes.
    113         *
    114         * @return the internal file attributes
    115         */
    116        public int getInternalAttributes() {
    117            return internalAttributes;
    118        }
    119
    120        /**
    121         * Sets the internal file attributes.
    122         * @param value an <code>int</code> value
    123         */
    124        public void setInternalAttributes(int value) {
    125            internalAttributes = value;
    126        }
    127
    128        /**
    129         * Retrieves the external file attributes.
    130         * @return the external file attributes
    131         */
    132        public long getExternalAttributes() {
    133            return externalAttributes;
    134        }
    135
    136        /**
    137         * Sets the external file attributes.
    138         * @param value an <code>long</code> value
    139         */
    140        public void setExternalAttributes(long value) {
    141            externalAttributes = value;
    142        }
    143
    144        /**
    145         * Sets Unix permissions in a way that is understood by Info-Zip's
    146         * unzip command.
    147         * @param mode an <code>int</code> value
    148         */
    149        public void setUnixMode(int mode) {
    150            // CheckStyle:MagicNumberCheck OFF - no point
    151            setExternalAttributes((mode << SHORT_SHIFT)
    152                                  // MS-DOS read-only attribute
    153                                  | ((mode & 0200) == 0 ? 1 : 0)
    154                                  // MS-DOS directory flag
    155                                  | (isDirectory() ? 0x10 : 0));
    156            // CheckStyle:MagicNumberCheck ON
    157            platform = PLATFORM_UNIX;
    158        }
    159
    160        /**
    161         * Unix permission.
    162         * @return the unix permissions
    163         */
    164        public int getUnixMode() {
    165            return platform != PLATFORM_UNIX ? 0 :
    166                (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
    167        }
    168
    169        /**
    170         * Platform specification to put into the &quot;version made
    171         * by&quot; part of the central file header.
    172         *
    173         * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
    174         * has been called, in which case PLATORM_UNIX will be returned.
    175         */
    176        public int getPlatform() {
    177            return platform;
    178        }
    179
    180        /**
    181         * Set the platform (UNIX or FAT).
    182         * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX
    183         */
    184        protected void setPlatform(int platform) {
    185            this.platform = platform;
    186        }
    187
    188        /**
    189         * Replaces all currently attached extra fields with the new array.
    190         * @param fields an array of extra fields
    191         */
    192        public void setExtraFields(ZipExtraField[] fields) {
    193            extraFields = new LinkedHashMap();
    194            for (int i = 0; i < fields.length; i++) {
    195                extraFields.put(fields[i].getHeaderId(), fields[i]);
    196            }
    197            setExtra();
    198        }
    199
    200        /**
    201         * Retrieves extra fields.
    202         * @return an array of the extra fields
    203         */
    204        public ZipExtraField[] getExtraFields() {
    205            if (extraFields == null) {
    206                return new ZipExtraField[0];
    207            }
    208            ZipExtraField[] result = new ZipExtraField[extraFields.size()];
    209            return (ZipExtraField[]) extraFields.values().toArray(result);
    210        }
    211
    212        /**
    213         * Adds an extra fields - replacing an already present extra field
    214         * of the same type.
    215         *
    216         * <p>If no extra field of the same type exists, the field will be
    217         * added as last field.</p>
    218         * @param ze an extra field
    219         */
    220        public void addExtraField(ZipExtraField ze) {
    221            if (extraFields == null) {
    222                extraFields = new LinkedHashMap();
    223            }
    224            extraFields.put(ze.getHeaderId(), ze);
    225            setExtra();
    226        }
    227
    228        /**
    229         * Adds an extra fields - replacing an already present extra field
    230         * of the same type.
    231         *
    232         * <p>The new extra field will be the first one.</p>
    233         * @param ze an extra field
    234         */
    235        public void addAsFirstExtraField(ZipExtraField ze) {
    236            LinkedHashMap copy = extraFields;
    237            extraFields = new LinkedHashMap();
    238            extraFields.put(ze.getHeaderId(), ze);
    239            if (copy != null) {
    240                copy.remove(ze.getHeaderId());
    241                extraFields.putAll(copy);
    242            }
    243            setExtra();
    244        }
    245
    246        /**
    247         * Remove an extra fields.
    248         * @param type the type of extra field to remove
    249         */
    250        public void removeExtraField(ZipShort type) {
    251            if (extraFields == null) {
    252                throw new java.util.NoSuchElementException();
    253            }
    254            if (extraFields.remove(type) == null) {
    255                throw new java.util.NoSuchElementException();
    256            }
    257            setExtra();
    258        }
    259
    260        /**
    261         * Looks up an extra field by its header id.
    262         *
    263         * @return null if no such field exists.
    264         */
    265        public ZipExtraField getExtraField(ZipShort type) {
    266            if (extraFields != null) {
    267                return (ZipExtraField) extraFields.get(type);
    268            }
    269            return null;
    270        }
    271
    272        /**
    273         * Throws an Exception if extra data cannot be parsed into extra fields.
    274         * @param extra an array of bytes to be parsed into extra fields
    275         * @throws RuntimeException if the bytes cannot be parsed
    276         * @throws RuntimeException on error
    277         */
    278        public void setExtra(byte[] extra) throws RuntimeException {
    279            try {
    280                ZipExtraField[] local = ExtraFieldUtils.parse(extra, true);
    281                mergeExtraFields(local, true);
    282            } catch (ZipException e) {
    283                throw new RuntimeException(e.getMessage(), e);
    284            }
    285        }
    286
    287        /**
    288         * Unfortunately {@link java.util.zip.ZipOutputStream
    289         * java.util.zip.ZipOutputStream} seems to access the extra data
    290         * directly, so overriding getExtra doesn't help - we need to
    291         * modify super's data directly.
    292         */
    293        protected void setExtra() {
    294            super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields()));
    295        }
    296
    297        /**
    298         * Sets the central directory part of extra fields.
    299         */
    300        public void setCentralDirectoryExtra(byte[] b) {
    301            try {
    302                ZipExtraField[] central = ExtraFieldUtils.parse(b, false);
    303                mergeExtraFields(central, false);
    304            } catch (ZipException e) {
    305                throw new RuntimeException(e.getMessage(), e);
    306            }
    307        }
    308
    309        /**
    310         * Retrieves the extra data for the local file data.
    311         * @return the extra data for local file
    312         */
    313        public byte[] getLocalFileDataExtra() {
    314            byte[] extra = getExtra();
    315            return extra != null ? extra : new byte[0];
    316        }
    317
    318        /**
    319         * Retrieves the extra data for the central directory.
    320         * @return the central directory extra data
    321         */
    322        public byte[] getCentralDirectoryExtra() {
    323            return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields());
    324        }
    325
    326        /**
    327         * Get the name of the entry.
    328         * @return the entry name
    329         */
    330        public String getName() {
    331            return name == null ? super.getName() : name;
    332        }
    333
    334        /**
    335         * Is this entry a directory?
    336         * @return true if the entry is a directory
    337         */
    338        public boolean isDirectory() {
    339            return getName().endsWith("/");
    340        }
    341
    342        /**
    343         * Set the name of the entry.
    344         * @param name the name to use
    345         */
    346        protected void setName(String name) {
    347            this.name = name;
    348        }
    349
    350        /**
    351         * Get the hashCode of the entry.
    352         * This uses the name as the hashcode.
    353         * @return a hashcode.
    354         */
    355        public int hashCode() {
    356            // this method has severe consequences on performance. We cannot rely
    357            // on the super.hashCode() method since super.getName() always return
    358            // the empty string in the current implemention (there's no setter)
    359            // so it is basically draining the performance of a hashmap lookup
    360            return getName().hashCode();
    361        }
    362
    363        /**
    364         * If there are no extra fields, use the given fields as new extra
    365         * data - otherwise merge the fields assuming the existing fields
    366         * and the new fields stem from different locations inside the
    367         * archive.
    368         * @param f the extra fields to merge
    369         * @param local whether the new fields originate from local data
    370         */
    371        private void mergeExtraFields(ZipExtraField[] f, boolean local)
    372            throws ZipException {
    373            if (extraFields == null) {
    374                setExtraFields(f);
    375            } else {
    376                for (int i = 0; i < f.length; i++) {
    377                    ZipExtraField existing = getExtraField(f[i].getHeaderId());
    378                    if (existing == null) {
    379                        addExtraField(f[i]);
    380                    } else {
    381                        if (local) {
    382                            byte[] b = f[i].getLocalFileDataData();
    383                            existing.parseFromLocalFileData(b, 0, b.length);
    384                        } else {
    385                            byte[] b = f[i].getCentralDirectoryData();
    386                            existing.parseFromCentralDirectoryData(b, 0, b.length);
    387                        }
    388                    }
    389                }
    390                setExtra();
    391            }
    392        }
    393
    394        /* (non-Javadoc)
    395         * @see java.lang.Object#equals(java.lang.Object)
    396         */
    397        public boolean equals(Object obj) {
    398            if (this == obj) {
    399                return true;
    400            }
    401            if (obj == null || getClass() != obj.getClass()) {
    402                return false;
    403            }
    404            ZipArchiveEntry other = (ZipArchiveEntry) obj;
    405            if (name == null) {
    406                if (other.name != null) {
    407                    return false;
    408                }
    409            } else if (!name.equals(other.name)) {
    410                return false;
    411            }
    412            return true;
    413        }
    414    }