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