    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 * http://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 */
    019package org.apache.commons.compress.archivers.zip;
    020
    021import java.util.zip.ZipException;
    022
    023import org.apache.commons.compress.utils.ByteUtils;
    024
    025import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
    026import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
    027
    028/**
    029 * Holds size and other extended information for entries that use Zip64
    030 * features.
    031 *
    032 * <p>Currently Commons Compress doesn't support encrypting the
    033 * central directory so the note in APPNOTE.TXT about masking doesn't
    034 * apply.</p>
    035 *
    036 * <p>The implementation relies on data being read from the local file
    037 * header and assumes that both size values are always present.</p>
    038 *
    039 * @see <a href="https://www.pkware.com/documents/casestudies/APPNOTE.TXT">PKWARE
    040 * APPNOTE.TXT, section 4.5.3</a>
    041 *
    042 * @since 1.2
    043 * @NotThreadSafe
    044 */
    045public class Zip64ExtendedInformationExtraField implements ZipExtraField {
    046
    047    static final ZipShort HEADER_ID = new ZipShort(0x0001);
    048
    049    private static final String LFH_MUST_HAVE_BOTH_SIZES_MSG =
    050        "Zip64 extended information must contain"
    051        + " both size values in the local file header.";
    052    private ZipEightByteInteger size, compressedSize, relativeHeaderOffset;
    053    private ZipLong diskStart;
    054
    055    /**
    056     * Stored in {@link #parseFromCentralDirectoryData
    057     * parseFromCentralDirectoryData} so it can be reused when ZipFile
    058     * calls {@link #reparseCentralDirectoryData
    059     * reparseCentralDirectoryData}.
    060     *
    061     * <p>Not used for anything else</p>
    062     *
    063     * @since 1.3
    064     */
    065    private byte[] rawCentralDirectoryData;
    066
    067    /**
    068     * This constructor should only be used by the code that reads
    069     * archives inside of Commons Compress.
    070     */
    071    public Zip64ExtendedInformationExtraField() { }
    072
    073    /**
    074     * Creates an extra field based on the original and compressed size.
    075     *
    076     * @param size the entry's original size
    077     * @param compressedSize the entry's compressed size
    078     *
    079     * @throws IllegalArgumentException if size or compressedSize is null
    080     */
    081    public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size,
    082                                              final ZipEightByteInteger compressedSize) {
    083        this(size, compressedSize, null, null);
    084    }
    085
    086    /**
    087     * Creates an extra field based on all four possible values.
    088     *
    089     * @param size the entry's original size
    090     * @param compressedSize the entry's compressed size
    091     * @param relativeHeaderOffset the entry's offset
    092     * @param diskStart the disk start
    093     *
    094     * @throws IllegalArgumentException if size or compressedSize is null
    095     */
    096    public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size,
    097                                              final ZipEightByteInteger compressedSize,
    098                                              final ZipEightByteInteger relativeHeaderOffset,
    099                                              final ZipLong diskStart) {
    100        this.size = size;
    101        this.compressedSize = compressedSize;
    102        this.relativeHeaderOffset = relativeHeaderOffset;
    103        this.diskStart = diskStart;
    104    }
    105
    106    @Override
    107    public ZipShort getHeaderId() {
    108        return HEADER_ID;
    109    }
    110
    111    @Override
    112    public ZipShort getLocalFileDataLength() {
    113        return new ZipShort(size != null ? 2 * DWORD : 0);
    114    }
    115
    116    @Override
    117    public ZipShort getCentralDirectoryLength() {
    118        return new ZipShort((size != null ? DWORD : 0)
    119                            + (compressedSize != null ? DWORD : 0)
    120                            + (relativeHeaderOffset != null ? DWORD : 0)
    121                            + (diskStart != null ? WORD : 0));
    122    }
    123
    124    @Override
    125    public byte[] getLocalFileDataData() {
    126        if (size != null || compressedSize != null) {
    127            if (size == null || compressedSize == null) {
    128                throw new IllegalArgumentException(LFH_MUST_HAVE_BOTH_SIZES_MSG);
    129            }
    130            final byte[] data = new byte[2 * DWORD];
    131            addSizes(data);
    132            return data;
    133        }
    134        return ByteUtils.EMPTY_BYTE_ARRAY;
    135    }
    136
    137    @Override
    138    public byte[] getCentralDirectoryData() {
    139        final byte[] data = new byte[getCentralDirectoryLength().getValue()];
    140        int off = addSizes(data);
    141        if (relativeHeaderOffset != null) {
    142            System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD);
    143            off += DWORD;
    144        }
    145        if (diskStart != null) {
    146            System.arraycopy(diskStart.getBytes(), 0, data, off, WORD);
    147            off += WORD; // NOSONAR - assignment as documentation
    148        }
    149        return data;
    150    }
    151
    152    @Override
    153    public void parseFromLocalFileData(final byte[] buffer, int offset, final int length)
    154        throws ZipException {
    155        if (length == 0) {
    156            // no local file data at all, may happen if an archive
    157            // only holds a ZIP64 extended information extra field
    158            // inside the central directory but not inside the local
    159            // file header
    160            return;
    161        }
    162        if (length < 2 * DWORD) {
    163            throw new ZipException(LFH_MUST_HAVE_BOTH_SIZES_MSG);
    164        }
    165        size = new ZipEightByteInteger(buffer, offset);
    166        offset += DWORD;
    167        compressedSize = new ZipEightByteInteger(buffer, offset);
    168        offset += DWORD;
    169        int remaining = length - 2 * DWORD;
    170        if (remaining >= DWORD) {
    171            relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
    172            offset += DWORD;
    173            remaining -= DWORD;
    174        }
    175        if (remaining >= WORD) {
    176            diskStart = new ZipLong(buffer, offset);
    177            offset += WORD; // NOSONAR - assignment as documentation
    178            remaining -= WORD; // NOSONAR - assignment as documentation
    179        }
    180    }
    181
    182    @Override
    183    public void parseFromCentralDirectoryData(final byte[] buffer, int offset,
    184                                              final int length)
    185        throws ZipException {
    186        // store for processing in reparseCentralDirectoryData
    187        rawCentralDirectoryData = new byte[length];
    188        System.arraycopy(buffer, offset, rawCentralDirectoryData, 0, length);
    189
    190        // if there is no size information in here, we are screwed and
    191        // can only hope things will get resolved by LFH data later
    192        // But there are some cases that can be detected
    193        // * all data is there
    194        // * length == 24 -> both sizes and offset
    195        // * length % 8 == 4 -> at least we can identify the diskStart field
    196        if (length >= 3 * DWORD + WORD) {
    197            parseFromLocalFileData(buffer, offset, length);
    198        } else if (length == 3 * DWORD) {
    199            size = new ZipEightByteInteger(buffer, offset);
    200            offset += DWORD;
    201            compressedSize = new ZipEightByteInteger(buffer, offset);
    202            offset += DWORD;
    203            relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
    204        } else if (length % DWORD == WORD) {
    205            diskStart = new ZipLong(buffer, offset + length - WORD);
    206        }
    207    }
    208
    209    /**
    210     * Parses the raw bytes read from the central directory extra
    211     * field with knowledge which fields are expected to be there.
    212     *
    213     * <p>All four fields inside the zip64 extended information extra
    214     * field are optional and must only be present if their corresponding
    215     * entry inside the central directory contains the correct magic
    216     * value.</p>
    217     *
    218     * @param hasUncompressedSize flag to read from central directory
    219     * @param hasCompressedSize flag to read from central directory
    220     * @param hasRelativeHeaderOffset flag to read from central directory
    221     * @param hasDiskStart flag to read from central directory
    222     * @throws ZipException on error
    223     */
    224    public void reparseCentralDirectoryData(final boolean hasUncompressedSize,
    225                                            final boolean hasCompressedSize,
    226                                            final boolean hasRelativeHeaderOffset,
    227                                            final boolean hasDiskStart)
    228        throws ZipException {
    229        if (rawCentralDirectoryData != null) {
    230            final int expectedLength = (hasUncompressedSize ? DWORD : 0)
    231                + (hasCompressedSize ? DWORD : 0)
    232                + (hasRelativeHeaderOffset ? DWORD : 0)
    233                + (hasDiskStart ? WORD : 0);
    234            if (rawCentralDirectoryData.length < expectedLength) {
    235                throw new ZipException("Central directory zip64 extended"
    236                                       + " information extra field's length"
    237                                       + " doesn't match central directory"
    238                                       + " data.  Expected length "
    239                                       + expectedLength + " but is "
    240                                       + rawCentralDirectoryData.length);
    241            }
    242            int offset = 0;
    243            if (hasUncompressedSize) {
    244                size = new ZipEightByteInteger(rawCentralDirectoryData, offset);
    245                offset += DWORD;
    246            }
    247            if (hasCompressedSize) {
    248                compressedSize = new ZipEightByteInteger(rawCentralDirectoryData,
    249                                                         offset);
    250                offset += DWORD;
    251            }
    252            if (hasRelativeHeaderOffset) {
    253                relativeHeaderOffset =
    254                    new ZipEightByteInteger(rawCentralDirectoryData, offset);
    255                offset += DWORD;
    256            }
    257            if (hasDiskStart) {
    258                diskStart = new ZipLong(rawCentralDirectoryData, offset);
    259                offset += WORD; // NOSONAR - assignment as documentation
    260            }
    261        }
    262    }
    263
    264    /**
    265     * The uncompressed size stored in this extra field.
    266     * @return The uncompressed size stored in this extra field.
    267     */
    268    public ZipEightByteInteger getSize() {
    269        return size;
    270    }
    271
    272    /**
    273     * The uncompressed size stored in this extra field.
    274     * @param size The uncompressed size stored in this extra field.
    275     */
    276    public void setSize(final ZipEightByteInteger size) {
    277        this.size = size;
    278    }
    279
    280    /**
    281     * The compressed size stored in this extra field.
    282     * @return The compressed size stored in this extra field.
    283     */
    284    public ZipEightByteInteger getCompressedSize() {
    285        return compressedSize;
    286    }
    287
    288    /**
    289     * The uncompressed size stored in this extra field.
    290     * @param compressedSize The uncompressed size stored in this extra field.
    291     */
    292    public void setCompressedSize(final ZipEightByteInteger compressedSize) {
    293        this.compressedSize = compressedSize;
    294    }
    295
    296    /**
    297     * The relative header offset stored in this extra field.
    298     * @return The relative header offset stored in this extra field.
    299     */
    300    public ZipEightByteInteger getRelativeHeaderOffset() {
    301        return relativeHeaderOffset;
    302    }
    303
    304    /**
    305     * The relative header offset stored in this extra field.
    306     * @param rho The relative header offset stored in this extra field.
    307     */
    308    public void setRelativeHeaderOffset(final ZipEightByteInteger rho) {
    309        relativeHeaderOffset = rho;
    310    }
    311
    312    /**
    313     * The disk start number stored in this extra field.
    314     * @return The disk start number stored in this extra field.
    315     */
    316    public ZipLong getDiskStartNumber() {
    317        return diskStart;
    318    }
    319
    320    /**
    321     * The disk start number stored in this extra field.
    322     * @param ds The disk start number stored in this extra field.
    323     */
    324    public void setDiskStartNumber(final ZipLong ds) {
    325        diskStart = ds;
    326    }
    327
    328    private int addSizes(final byte[] data) {
    329        int off = 0;
    330        if (size != null) {
    331            System.arraycopy(size.getBytes(), 0, data, 0, DWORD);
    332            off += DWORD;
    333        }
    334        if (compressedSize != null) {
    335            System.arraycopy(compressedSize.getBytes(), 0, data, off, DWORD);
    336            off += DWORD;
    337        }
    338        return off;
    339    }
    340}