001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.commons.compress.archivers.zip;
021
022import static java.nio.charset.StandardCharsets.UTF_8;
023
024import java.util.Arrays;
025import java.util.zip.CRC32;
026import java.util.zip.ZipException;
027
028/**
029 * A common base class for Unicode extra information extra fields.
030 *
031 * @NotThreadSafe
032 */
033public abstract class AbstractUnicodeExtraField implements ZipExtraField {
034    private long nameCRC32;
035    private byte[] unicodeName;
036    private byte[] data;
037
038    /**
039     * Constructs a new instance.
040     */
041    protected AbstractUnicodeExtraField() {
042    }
043
044    /**
045     * Assemble as unicode extension from the name/comment and encoding of the original ZIP entry.
046     *
047     * @param text  The file name or comment.
048     * @param bytes The encoded of the file name or comment in the ZIP file.
049     */
050    protected AbstractUnicodeExtraField(final String text, final byte[] bytes) {
051        this(text, bytes, 0, bytes.length);
052    }
053
054    /**
055     * Assemble as unicode extension from the name/comment and encoding of the original ZIP entry.
056     *
057     * @param text  The file name or comment.
058     * @param bytes The encoded of the file name or comment in the ZIP file.
059     * @param off   The offset of the encoded file name or comment in {@code bytes}.
060     * @param len   The length of the encoded file name or comment in {@code bytes}.
061     */
062    protected AbstractUnicodeExtraField(final String text, final byte[] bytes, final int off, final int len) {
063        final CRC32 crc32 = new CRC32();
064        crc32.update(bytes, off, len);
065        nameCRC32 = crc32.getValue();
066
067        unicodeName = text.getBytes(UTF_8);
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    @Override
083    public byte[] getCentralDirectoryData() {
084        if (data == null) {
085            assembleData();
086        }
087        byte[] b = null;
088        if (data != null) {
089            b = Arrays.copyOf(data, data.length);
090        }
091        return b;
092    }
093
094    @Override
095    public ZipShort getCentralDirectoryLength() {
096        if (data == null) {
097            assembleData();
098        }
099        return ZipShort.lengthOf(data);
100    }
101
102    @Override
103    public byte[] getLocalFileDataData() {
104        return getCentralDirectoryData();
105    }
106
107    @Override
108    public ZipShort getLocalFileDataLength() {
109        return getCentralDirectoryLength();
110    }
111
112    /**
113     * Gets the CRC32 checksum of the file name or comment as encoded in the central directory of the ZIP file.
114     *
115     * @return The CRC32 checksum of the file name or comment as encoded in the central directory of the ZIP file.
116     */
117    public long getNameCRC32() {
118        return nameCRC32;
119    }
120
121    /**
122     * Gets The UTF-8 encoded name.
123     *
124     * @return The UTF-8 encoded name.
125     */
126    public byte[] getUnicodeName() {
127        return unicodeName != null ? Arrays.copyOf(unicodeName, unicodeName.length) : null;
128    }
129
130    /**
131     * Doesn't do anything special since this class always uses the same data in central directory and local file data.
132     */
133    @Override
134    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
135        parseFromLocalFileData(buffer, offset, length);
136    }
137
138    @Override
139    public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) throws ZipException {
140
141        if (length < 5) {
142            throw new ZipException("UniCode path extra data must have at least 5 bytes.");
143        }
144
145        final int version = buffer[offset];
146
147        if (version != 0x01) {
148            throw new ZipException("Unsupported version [" + version + "] for UniCode path extra data.");
149        }
150
151        nameCRC32 = ZipLong.getValue(buffer, offset + 1);
152        unicodeName = new byte[length - 5];
153        System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5);
154        data = null;
155    }
156
157    /**
158     * Gets The CRC32 checksum of the file name as encoded in the central directory of the ZIP file to set.
159     *
160     * @param nameCRC32 The CRC32 checksum of the file name as encoded in the central directory of the ZIP file to set.
161     */
162    public void setNameCRC32(final long nameCRC32) {
163        this.nameCRC32 = nameCRC32;
164        data = null;
165    }
166
167    /**
168     * Gets the UTF-8 encoded name to set.
169     *
170     * @param unicodeName The UTF-8 encoded name to set.
171     */
172    public void setUnicodeName(final byte[] unicodeName) {
173        if (unicodeName != null) {
174            this.unicodeName = Arrays.copyOf(unicodeName, unicodeName.length);
175        } else {
176            this.unicodeName = null;
177        }
178        data = null;
179    }
180}