    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}