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