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