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