    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 *
    029 * @NotThreadSafe
    030 */
    031public abstract class AbstractUnicodeExtraField implements ZipExtraField {
    032    private long nameCRC32;
    033    private byte[] unicodeName;
    034    private byte[] data;
    035
    036    /**
    037     * Constructs a new instance.
    038     */
    039    protected AbstractUnicodeExtraField() {
    040    }
    041
    042    /**
    043     * Assemble as unicode extension from the name/comment and 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 file.
    047     */
    048    protected AbstractUnicodeExtraField(final String text, final byte[] bytes) {
    049        this(text, bytes, 0, bytes.length);
    050    }
    051
    052    /**
    053     * Assemble as unicode extension from the name/comment and 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 file.
    057     * @param off   The offset of the encoded file name or comment in {@code bytes}.
    058     * @param len   The length of the encoded file name or comment in {@code bytes}.
    059     */
    060    protected AbstractUnicodeExtraField(final String text, final byte[] bytes, final int off, final int len) {
    061        final CRC32 crc32 = new CRC32();
    062        crc32.update(bytes, off, len);
    063        nameCRC32 = crc32.getValue();
    064
    065        unicodeName = text.getBytes(UTF_8);
    066    }
    067
    068    private void assembleData() {
    069        if (unicodeName == null) {
    070            return;
    071        }
    072
    073        data = new byte[5 + unicodeName.length];
    074        // version 1
    075        data[0] = 0x01;
    076        System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4);
    077        System.arraycopy(unicodeName, 0, data, 5, unicodeName.length);
    078    }
    079
    080    @Override
    081    public byte[] getCentralDirectoryData() {
    082        if (data == null) {
    083            this.assembleData();
    084        }
    085        byte[] b = null;
    086        if (data != null) {
    087            b = Arrays.copyOf(data, data.length);
    088        }
    089        return b;
    090    }
    091
    092    @Override
    093    public ZipShort getCentralDirectoryLength() {
    094        if (data == null) {
    095            assembleData();
    096        }
    097        return new ZipShort(data != null ? data.length : 0);
    098    }
    099
    100    @Override
    101    public byte[] getLocalFileDataData() {
    102        return getCentralDirectoryData();
    103    }
    104
    105    @Override
    106    public ZipShort getLocalFileDataLength() {
    107        return getCentralDirectoryLength();
    108    }
    109
    110    /**
    111     * Gets the CRC32 checksum of the file name or comment as encoded in the central directory of the ZIP file.
    112     *
    113     * @return The CRC32 checksum of the file name or comment as encoded in the central directory of the ZIP file.
    114     */
    115    public long getNameCRC32() {
    116        return nameCRC32;
    117    }
    118
    119    /**
    120     * Gets The UTF-8 encoded name.
    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 same data in central directory and local file data.
    130     */
    131    @Override
    132    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
    133        parseFromLocalFileData(buffer, offset, length);
    134    }
    135
    136    @Override
    137    public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) throws ZipException {
    138
    139        if (length < 5) {
    140            throw new ZipException("UniCode path extra data must have at least 5 bytes.");
    141        }
    142
    143        final int version = buffer[offset];
    144
    145        if (version != 0x01) {
    146            throw new ZipException("Unsupported version [" + version + "] for UniCode path extra data.");
    147        }
    148
    149        nameCRC32 = ZipLong.getValue(buffer, offset + 1);
    150        unicodeName = new byte[length - 5];
    151        System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5);
    152        data = null;
    153    }
    154
    155    /**
    156     * Gets The CRC32 checksum of the file name as encoded in the central directory of the ZIP file to set.
    157     *
    158     * @param nameCRC32 The CRC32 checksum of the file name as encoded in the central directory of the ZIP file to set.
    159     */
    160    public void setNameCRC32(final long nameCRC32) {
    161        this.nameCRC32 = nameCRC32;
    162        data = null;
    163    }
    164
    165    /**
    166     * Gets the UTF-8 encoded name to set.
    167     *
    168     * @param unicodeName The UTF-8 encoded name to set.
    169     */
    170    public void setUnicodeName(final byte[] unicodeName) {
    171        if (unicodeName != null) {
    172            this.unicodeName = Arrays.copyOf(unicodeName, unicodeName.length);
    173        } else {
    174            this.unicodeName = null;
    175        }
    176        data = null;
    177    }
    178}