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