    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
    019    package org.apache.commons.compress.archivers.zip;
    020
    021    import java.io.UnsupportedEncodingException;
    022    import java.util.zip.CRC32;
    023    import java.util.zip.ZipException;
    024
    025    /**
    026     * A common base class for Unicode extra information extra fields.
    027     * @NotThreadSafe
    028     */
    029    public abstract class AbstractUnicodeExtraField implements ZipExtraField {
    030        private long nameCRC32;
    031        private byte[] unicodeName;
    032        private byte[] data;
    033
    034        protected AbstractUnicodeExtraField() {
    035        }
    036
    037        /**
    038         * Assemble as unicode extension from the name/comment and
    039         * encoding of the orginal zip entry.
    040         *
    041         * @param text The file name or comment.
    042         * @param bytes The encoded of the filename or comment in the zip
    043         * file.
    044         * @param off The offset of the encoded filename or comment in
    045         * <code>bytes</code>.
    046         * @param len The length of the encoded filename or commentin
    047         * <code>bytes</code>.
    048         */
    049        protected AbstractUnicodeExtraField(String text, byte[] bytes, int off, int len) {
    050            CRC32 crc32 = new CRC32();
    051            crc32.update(bytes, off, len);
    052            nameCRC32 = crc32.getValue();
    053
    054            try {
    055                unicodeName = text.getBytes("UTF-8");
    056            } catch (UnsupportedEncodingException e) {
    057                throw new RuntimeException("FATAL: UTF-8 encoding not supported.", e);
    058            }
    059        }
    060
    061        /**
    062         * Assemble as unicode extension from the name/comment and
    063         * encoding of the orginal zip entry.
    064         *
    065         * @param text The file name or comment.
    066         * @param bytes The encoded of the filename or comment in the zip
    067         * file.
    068         */
    069        protected AbstractUnicodeExtraField(String text, byte[] bytes) {
    070            this(text, bytes, 0, bytes.length);
    071        }
    072
    073        private void assembleData() {
    074            if (unicodeName == null) {
    075                return;
    076            }
    077
    078            data = new byte[5 + unicodeName.length];
    079            // version 1
    080            data[0] = 0x01;
    081            System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4);
    082            System.arraycopy(unicodeName, 0, data, 5, unicodeName.length);
    083        }
    084
    085        /**
    086         * @return The CRC32 checksum of the filename or comment as
    087         *         encoded in the central directory of the zip file.
    088         */
    089        public long getNameCRC32() {
    090            return nameCRC32;
    091        }
    092
    093        /**
    094         * @param nameCRC32 The CRC32 checksum of the filename as encoded
    095         *         in the central directory of the zip file to set.
    096         */
    097        public void setNameCRC32(long nameCRC32) {
    098            this.nameCRC32 = nameCRC32;
    099            data = null;
    100        }
    101
    102        /**
    103         * @return The utf-8 encoded name.
    104         */
    105        public byte[] getUnicodeName() {
    106            byte[] b = null;
    107            if (unicodeName != null) {
    108                b = new byte[unicodeName.length];
    109                System.arraycopy(unicodeName, 0, b, 0, b.length);
    110            }
    111            return b;
    112        }
    113
    114        /**
    115         * @param unicodeName The utf-8 encoded name to set.
    116         */
    117        public void setUnicodeName(byte[] unicodeName) {
    118            if (unicodeName != null) {
    119                this.unicodeName = new byte[unicodeName.length];
    120                System.arraycopy(unicodeName, 0, this.unicodeName, 0,
    121                                 unicodeName.length);
    122            } else {
    123                this.unicodeName = null;
    124            }
    125            data = null;
    126        }
    127
    128        /** {@inheritDoc} */
    129        public byte[] getCentralDirectoryData() {
    130            if (data == null) {
    131                this.assembleData();
    132            }
    133            byte[] b = null;
    134            if (data != null) {
    135                b = new byte[data.length];
    136                System.arraycopy(data, 0, b, 0, b.length);
    137            }
    138            return b;
    139        }
    140
    141        /** {@inheritDoc} */
    142        public ZipShort getCentralDirectoryLength() {
    143            if (data == null) {
    144                assembleData();
    145            }
    146            return new ZipShort(data.length);
    147        }
    148
    149        /** {@inheritDoc} */
    150        public byte[] getLocalFileDataData() {
    151            return getCentralDirectoryData();
    152        }
    153
    154        /** {@inheritDoc} */
    155        public ZipShort getLocalFileDataLength() {
    156            return getCentralDirectoryLength();
    157        }
    158
    159        /** {@inheritDoc} */
    160        public void parseFromLocalFileData(byte[] buffer, int offset, int length)
    161            throws ZipException {
    162
    163            if (length < 5) {
    164                throw new ZipException("UniCode path extra data must have at least 5 bytes.");
    165            }
    166
    167            int version = buffer[offset];
    168
    169            if (version != 0x01) {
    170                throw new ZipException("Unsupported version [" + version
    171                                       + "] for UniCode path extra data.");
    172            }
    173
    174            nameCRC32 = ZipLong.getValue(buffer, offset + 1);
    175            unicodeName = new byte[length - 5];
    176            System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5);
    177            data = null;
    178        }
    179
    180        /**
    181         * Doesn't do anything special since this class always uses the
    182         * same data in central directory and local file data.
    183         */
    184        public void parseFromCentralDirectoryData(byte[] buffer, int offset,
    185                                                  int length)
    186            throws ZipException {
    187            parseFromLocalFileData(buffer, offset, length);
    188        }
    189    }