Zip64ExtendedInformationExtraField.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  * http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.compress.archivers.zip;

  20. import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
  21. import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;

  22. import java.util.zip.ZipException;

  23. import org.apache.commons.compress.utils.ByteUtils;

  24. /**
  25.  * Holds size and other extended information for entries that use Zip64 features.
  26.  *
  27.  * <p>
  28.  * Currently Commons Compress doesn't support encrypting the central directory so the note in APPNOTE.TXT about masking doesn't apply.
  29.  * </p>
  30.  *
  31.  * <p>
  32.  * The implementation relies on data being read from the local file header and assumes that both size values are always present.
  33.  * </p>
  34.  *
  35.  * @see <a href="https://www.pkware.com/documents/casestudies/APPNOTE.TXT">PKWARE APPNOTE.TXT, section 4.5.3</a>
  36.  *
  37.  * @since 1.2
  38.  * @NotThreadSafe
  39.  */
  40. public class Zip64ExtendedInformationExtraField implements ZipExtraField {

  41.     static final ZipShort HEADER_ID = new ZipShort(0x0001);

  42.     private static final String LFH_MUST_HAVE_BOTH_SIZES_MSG = "Zip64 extended information must contain" + " both size values in the local file header.";
  43.     private ZipEightByteInteger size, compressedSize, relativeHeaderOffset;
  44.     private ZipLong diskStart;

  45.     /**
  46.      * Stored in {@link #parseFromCentralDirectoryData parseFromCentralDirectoryData} so it can be reused when ZipFile calls {@link #reparseCentralDirectoryData
  47.      * reparseCentralDirectoryData}.
  48.      *
  49.      * <p>
  50.      * Not used for anything else
  51.      * </p>
  52.      *
  53.      * @since 1.3
  54.      */
  55.     private byte[] rawCentralDirectoryData;

  56.     /**
  57.      * This constructor should only be used by the code that reads archives inside of Commons Compress.
  58.      */
  59.     public Zip64ExtendedInformationExtraField() {
  60.     }

  61.     /**
  62.      * Creates an extra field based on the original and compressed size.
  63.      *
  64.      * @param size           the entry's original size
  65.      * @param compressedSize the entry's compressed size
  66.      *
  67.      * @throws IllegalArgumentException if size or compressedSize is null
  68.      */
  69.     public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, final ZipEightByteInteger compressedSize) {
  70.         this(size, compressedSize, null, null);
  71.     }

  72.     /**
  73.      * Creates an extra field based on all four possible values.
  74.      *
  75.      * @param size                 the entry's original size
  76.      * @param compressedSize       the entry's compressed size
  77.      * @param relativeHeaderOffset the entry's offset
  78.      * @param diskStart            the disk start
  79.      *
  80.      * @throws IllegalArgumentException if size or compressedSize is null
  81.      */
  82.     public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, final ZipEightByteInteger compressedSize,
  83.             final ZipEightByteInteger relativeHeaderOffset, final ZipLong diskStart) {
  84.         this.size = size;
  85.         this.compressedSize = compressedSize;
  86.         this.relativeHeaderOffset = relativeHeaderOffset;
  87.         this.diskStart = diskStart;
  88.     }

  89.     private int addSizes(final byte[] data) {
  90.         int off = 0;
  91.         if (size != null) {
  92.             System.arraycopy(size.getBytes(), 0, data, 0, DWORD);
  93.             off += DWORD;
  94.         }
  95.         if (compressedSize != null) {
  96.             System.arraycopy(compressedSize.getBytes(), 0, data, off, DWORD);
  97.             off += DWORD;
  98.         }
  99.         return off;
  100.     }

  101.     @Override
  102.     public byte[] getCentralDirectoryData() {
  103.         final byte[] data = new byte[getCentralDirectoryLength().getValue()];
  104.         int off = addSizes(data);
  105.         if (relativeHeaderOffset != null) {
  106.             System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD);
  107.             off += DWORD;
  108.         }
  109.         if (diskStart != null) {
  110.             System.arraycopy(diskStart.getBytes(), 0, data, off, WORD);
  111.             off += WORD; // NOSONAR - assignment as documentation
  112.         }
  113.         return data;
  114.     }

  115.     @Override
  116.     public ZipShort getCentralDirectoryLength() {
  117.         return new ZipShort((size != null ? DWORD : 0) + (compressedSize != null ? DWORD : 0) + (relativeHeaderOffset != null ? DWORD : 0)
  118.                 + (diskStart != null ? WORD : 0));
  119.     }

  120.     /**
  121.      * The compressed size stored in this extra field.
  122.      *
  123.      * @return The compressed size stored in this extra field.
  124.      */
  125.     public ZipEightByteInteger getCompressedSize() {
  126.         return compressedSize;
  127.     }

  128.     /**
  129.      * The disk start number stored in this extra field.
  130.      *
  131.      * @return The disk start number stored in this extra field.
  132.      */
  133.     public ZipLong getDiskStartNumber() {
  134.         return diskStart;
  135.     }

  136.     @Override
  137.     public ZipShort getHeaderId() {
  138.         return HEADER_ID;
  139.     }

  140.     @Override
  141.     public byte[] getLocalFileDataData() {
  142.         if (size != null || compressedSize != null) {
  143.             if (size == null || compressedSize == null) {
  144.                 throw new IllegalArgumentException(LFH_MUST_HAVE_BOTH_SIZES_MSG);
  145.             }
  146.             final byte[] data = new byte[2 * DWORD];
  147.             addSizes(data);
  148.             return data;
  149.         }
  150.         return ByteUtils.EMPTY_BYTE_ARRAY;
  151.     }

  152.     @Override
  153.     public ZipShort getLocalFileDataLength() {
  154.         return new ZipShort(size != null ? 2 * DWORD : 0);
  155.     }

  156.     /**
  157.      * The relative header offset stored in this extra field.
  158.      *
  159.      * @return The relative header offset stored in this extra field.
  160.      */
  161.     public ZipEightByteInteger getRelativeHeaderOffset() {
  162.         return relativeHeaderOffset;
  163.     }

  164.     /**
  165.      * The uncompressed size stored in this extra field.
  166.      *
  167.      * @return The uncompressed size stored in this extra field.
  168.      */
  169.     public ZipEightByteInteger getSize() {
  170.         return size;
  171.     }

  172.     @Override
  173.     public void parseFromCentralDirectoryData(final byte[] buffer, int offset, final int length) throws ZipException {
  174.         // store for processing in reparseCentralDirectoryData
  175.         rawCentralDirectoryData = new byte[length];
  176.         System.arraycopy(buffer, offset, rawCentralDirectoryData, 0, length);

  177.         // if there is no size information in here, we are screwed and
  178.         // can only hope things will get resolved by LFH data later
  179.         // But there are some cases that can be detected
  180.         // * all data is there
  181.         // * length == 24 -> both sizes and offset
  182.         // * length % 8 == 4 -> at least we can identify the diskStart field
  183.         if (length >= 3 * DWORD + WORD) {
  184.             parseFromLocalFileData(buffer, offset, length);
  185.         } else if (length == 3 * DWORD) {
  186.             size = new ZipEightByteInteger(buffer, offset);
  187.             offset += DWORD;
  188.             compressedSize = new ZipEightByteInteger(buffer, offset);
  189.             offset += DWORD;
  190.             relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
  191.         } else if (length % DWORD == WORD) {
  192.             diskStart = new ZipLong(buffer, offset + length - WORD);
  193.         }
  194.     }

  195.     @Override
  196.     public void parseFromLocalFileData(final byte[] buffer, int offset, final int length) throws ZipException {
  197.         if (length == 0) {
  198.             // no local file data at all, may happen if an archive
  199.             // only holds a ZIP64 extended information extra field
  200.             // inside the central directory but not inside the local
  201.             // file header
  202.             return;
  203.         }
  204.         if (length < 2 * DWORD) {
  205.             throw new ZipException(LFH_MUST_HAVE_BOTH_SIZES_MSG);
  206.         }
  207.         size = new ZipEightByteInteger(buffer, offset);
  208.         offset += DWORD;
  209.         compressedSize = new ZipEightByteInteger(buffer, offset);
  210.         offset += DWORD;
  211.         int remaining = length - 2 * DWORD;
  212.         if (remaining >= DWORD) {
  213.             relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
  214.             offset += DWORD;
  215.             remaining -= DWORD;
  216.         }
  217.         if (remaining >= WORD) {
  218.             diskStart = new ZipLong(buffer, offset);
  219.             offset += WORD; // NOSONAR - assignment as documentation
  220.             remaining -= WORD; // NOSONAR - assignment as documentation
  221.         }
  222.     }

  223.     /**
  224.      * Parses the raw bytes read from the central directory extra field with knowledge which fields are expected to be there.
  225.      *
  226.      * <p>
  227.      * All four fields inside the zip64 extended information extra field are optional and must only be present if their corresponding entry inside the central
  228.      * directory contains the correct magic value.
  229.      * </p>
  230.      *
  231.      * @param hasUncompressedSize     flag to read from central directory
  232.      * @param hasCompressedSize       flag to read from central directory
  233.      * @param hasRelativeHeaderOffset flag to read from central directory
  234.      * @param hasDiskStart            flag to read from central directory
  235.      * @throws ZipException on error
  236.      */
  237.     public void reparseCentralDirectoryData(final boolean hasUncompressedSize, final boolean hasCompressedSize, final boolean hasRelativeHeaderOffset,
  238.             final boolean hasDiskStart) throws ZipException {
  239.         if (rawCentralDirectoryData != null) {
  240.             final int expectedLength = (hasUncompressedSize ? DWORD : 0) + (hasCompressedSize ? DWORD : 0) + (hasRelativeHeaderOffset ? DWORD : 0)
  241.                     + (hasDiskStart ? WORD : 0);
  242.             if (rawCentralDirectoryData.length < expectedLength) {
  243.                 throw new ZipException("Central directory zip64 extended" + " information extra field's length" + " doesn't match central directory"
  244.                         + " data.  Expected length " + expectedLength + " but is " + rawCentralDirectoryData.length);
  245.             }
  246.             int offset = 0;
  247.             if (hasUncompressedSize) {
  248.                 size = new ZipEightByteInteger(rawCentralDirectoryData, offset);
  249.                 offset += DWORD;
  250.             }
  251.             if (hasCompressedSize) {
  252.                 compressedSize = new ZipEightByteInteger(rawCentralDirectoryData, offset);
  253.                 offset += DWORD;
  254.             }
  255.             if (hasRelativeHeaderOffset) {
  256.                 relativeHeaderOffset = new ZipEightByteInteger(rawCentralDirectoryData, offset);
  257.                 offset += DWORD;
  258.             }
  259.             if (hasDiskStart) {
  260.                 diskStart = new ZipLong(rawCentralDirectoryData, offset);
  261.                 offset += WORD; // NOSONAR - assignment as documentation
  262.             }
  263.         }
  264.     }

  265.     /**
  266.      * The uncompressed size stored in this extra field.
  267.      *
  268.      * @param compressedSize The uncompressed size stored in this extra field.
  269.      */
  270.     public void setCompressedSize(final ZipEightByteInteger compressedSize) {
  271.         this.compressedSize = compressedSize;
  272.     }

  273.     /**
  274.      * The disk start number stored in this extra field.
  275.      *
  276.      * @param ds The disk start number stored in this extra field.
  277.      */
  278.     public void setDiskStartNumber(final ZipLong ds) {
  279.         diskStart = ds;
  280.     }

  281.     /**
  282.      * The relative header offset stored in this extra field.
  283.      *
  284.      * @param rho The relative header offset stored in this extra field.
  285.      */
  286.     public void setRelativeHeaderOffset(final ZipEightByteInteger rho) {
  287.         relativeHeaderOffset = rho;
  288.     }

  289.     /**
  290.      * The uncompressed size stored in this extra field.
  291.      *
  292.      * @param size The uncompressed size stored in this extra field.
  293.      */
  294.     public void setSize(final ZipEightByteInteger size) {
  295.         this.size = size;
  296.     }
  297. }