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
019package org.apache.commons.compress.archivers.zip;
020
021import java.io.UnsupportedEncodingException;
022import java.util.zip.CRC32;
023import java.util.zip.ZipException;
024
025import org.apache.commons.compress.utils.CharsetNames;
026
027/**
028 * A common base class for Unicode extra information extra fields.
029 * @NotThreadSafe
030 */
031public 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(final String text, final byte[] bytes, final int off, final int len) {
052        final 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 (final UnsupportedEncodingException e) {
059            throw new RuntimeException("FATAL: UTF-8 encoding not supported.", e); //NOSONAR
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(final String text, final 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(final 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(final 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    @Override
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    @Override
144    public ZipShort getCentralDirectoryLength() {
145        if (data == null) {
146            assembleData();
147        }
148        return new ZipShort(data != null ? data.length : 0);
149    }
150
151    @Override
152    public byte[] getLocalFileDataData() {
153        return getCentralDirectoryData();
154    }
155
156    @Override
157    public ZipShort getLocalFileDataLength() {
158        return getCentralDirectoryLength();
159    }
160
161    @Override
162    public void parseFromLocalFileData(final byte[] buffer, final int offset, final 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        final 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    @Override
187    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset,
188                                              final int length)
189        throws ZipException {
190        parseFromLocalFileData(buffer, offset, length);
191    }
192}