/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.commons.compress.archivers.zip; import java.util.zip.ZipException; /** * Holds size and other extended information for entries that use Zip64 * features. * * <p>From {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's APPNOTE.TXT} * <pre> * Zip64 Extended Information Extra Field (0x0001): * * The following is the layout of the zip64 extended * information "extra" block. If one of the size or * offset fields in the Local or Central directory * record is too small to hold the required data, * a Zip64 extended information record is created. * The order of the fields in the zip64 extended * information record is fixed, but the fields will * only appear if the corresponding Local or Central * directory record field is set to 0xFFFF or 0xFFFFFFFF. * * Note: all fields stored in Intel low-byte/high-byte order. * * Value Size Description * ----- ---- ----------- * (ZIP64) 0x0001 2 bytes Tag for this "extra" block type * Size 2 bytes Size of this "extra" block * Original * Size 8 bytes Original uncompressed file size * Compressed * Size 8 bytes Size of compressed data * Relative Header * Offset 8 bytes Offset of local header record * Disk Start * Number 4 bytes Number of the disk on which * this file starts * * This entry in the Local header must include BOTH original * and compressed file size fields. If encrypting the * central directory and bit 13 of the general purpose bit * flag is set indicating masking, the value stored in the * Local Header for the original file size will be zero. * </pre></p> * * <p>Currently Commons Compress doesn't support encrypting the * central directory so the not about masking doesn't apply.</p> * * <p>The implementation relies on data being read from the local file * header and assumes that both size values are always present.</p> * * @since Apache Commons Compress 1.2 * @NotThreadSafe */ public class Zip64ExtendedInformationExtraField implements ZipExtraField { // TODO: the LFH should probably not contain relativeHeaderOffset // and diskStart but then ZipArchivePOutputStream won't write it to // the CD either - need to test interop with other implementations // to see whether they do have a problem with the extraneous // information inside the LFH private static final ZipShort HEADER_ID = new ZipShort(0x0001); private static final int WORD = 4, DWORD = 8; private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; private ZipLong diskStart; /** * This constructor should only be used by the code that reads * archives inside of Commons Compress. */ public Zip64ExtendedInformationExtraField() { } /** * Creates an extra field based on the original and compressed size. * * @param size the entry's original size * @param compressedSize the entry's compressed size * * @throws IllegalArgumentException if size or compressedSize is null */ public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, ZipEightByteInteger compressedSize) { this(size, compressedSize, null, null); } /** * Creates an extra field based on all four possible values. * * @param size the entry's original size * @param compressedSize the entry's compressed size * * @throws IllegalArgumentException if size or compressedSize is null */ public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, ZipEightByteInteger compressedSize, ZipEightByteInteger relativeHeaderOffset, ZipLong diskStart) { if (size == null) { throw new IllegalArgumentException("size must not be null"); } if (compressedSize == null) { throw new IllegalArgumentException("compressedSize must not be null"); } this.size = size; this.compressedSize = compressedSize; this.relativeHeaderOffset = relativeHeaderOffset; this.diskStart = diskStart; } /** {@inheritDoc} */ public ZipShort getHeaderId() { return HEADER_ID; } /** {@inheritDoc} */ public ZipShort getLocalFileDataLength() { return getCentralDirectoryLength(); } /** {@inheritDoc} */ public ZipShort getCentralDirectoryLength() { return new ZipShort(2 * DWORD // both size fields + (relativeHeaderOffset != null ? DWORD : 0) + (diskStart != null ? WORD : 0)); } /** {@inheritDoc} */ public byte[] getLocalFileDataData() { return getCentralDirectoryData(); } /** {@inheritDoc} */ public byte[] getCentralDirectoryData() { byte[] data = new byte[getCentralDirectoryLength().getValue()]; addSizes(data); int off = 2 * DWORD; if (relativeHeaderOffset != null) { System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD); off += DWORD; } if (diskStart != null) { System.arraycopy(diskStart.getBytes(), 0, data, off, WORD); off += WORD; } return data; } /** {@inheritDoc} */ public void parseFromLocalFileData(byte[] buffer, int offset, int length) throws ZipException { if (length < 2 * DWORD) { throw new ZipException("Zip64 extended information must contain" + " both size values in the local file" + " header."); } size = new ZipEightByteInteger(buffer, offset); offset += DWORD; compressedSize = new ZipEightByteInteger(buffer, offset); offset += DWORD; int remaining = length - 2 * DWORD; if (remaining >= DWORD) { relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); offset += DWORD; remaining -= DWORD; } if (remaining >= WORD) { diskStart = new ZipLong(buffer, offset); offset += WORD; remaining -= WORD; } } /** {@inheritDoc} */ public void parseFromCentralDirectoryData(byte[] buffer, int offset, int length) throws ZipException { // if there is no size information in here, we are screwed and // can only hope things will get resolved by LFH data later // But there are some cases that can be detected // * all data is there // * length % 8 == 4 -> at least we can identify the diskStart field if (length >= 3 * DWORD + WORD) { parseFromLocalFileData(buffer, offset, length); } else if (length % DWORD == WORD) { diskStart = new ZipLong(buffer, offset + length - WORD); } } /** * The uncompressed size stored in this extra field. */ public ZipEightByteInteger getSize() { return size; } /** * The compressed size stored in this extra field. */ public ZipEightByteInteger getCompressedSize() { return compressedSize; } /** * The relative header offset stored in this extra field. */ public ZipEightByteInteger getRelativeHeaderOffset() { return relativeHeaderOffset; } /** * The disk start number stored in this extra field. */ public ZipLong getDiskStartNumber() { return diskStart; } private void addSizes(byte[] data) { System.arraycopy(size.getBytes(), 0, data, 0, DWORD); System.arraycopy(compressedSize.getBytes(), 0, data, DWORD, DWORD); } }