    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}