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