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