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 */ 019 package org.apache.commons.compress.archivers.zip; 020 021 import java.util.zip.ZipException; 022 023 /** 024 * Holds size and other extended information for entries that use Zip64 025 * features. 026 * 027 * <p>From {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's APPNOTE.TXT} 028 * <pre> 029 * Zip64 Extended Information Extra Field (0x0001): 030 * 031 * The following is the layout of the zip64 extended 032 * information "extra" block. If one of the size or 033 * offset fields in the Local or Central directory 034 * record is too small to hold the required data, 035 * a Zip64 extended information record is created. 036 * The order of the fields in the zip64 extended 037 * information record is fixed, but the fields will 038 * only appear if the corresponding Local or Central 039 * directory record field is set to 0xFFFF or 0xFFFFFFFF. 040 * 041 * Note: all fields stored in Intel low-byte/high-byte order. 042 * 043 * Value Size Description 044 * ----- ---- ----------- 045 * (ZIP64) 0x0001 2 bytes Tag for this "extra" block type 046 * Size 2 bytes Size of this "extra" block 047 * Original 048 * Size 8 bytes Original uncompressed file size 049 * Compressed 050 * Size 8 bytes Size of compressed data 051 * Relative Header 052 * Offset 8 bytes Offset of local header record 053 * Disk Start 054 * Number 4 bytes Number of the disk on which 055 * this file starts 056 * 057 * This entry in the Local header must include BOTH original 058 * and compressed file size fields. If encrypting the 059 * central directory and bit 13 of the general purpose bit 060 * flag is set indicating masking, the value stored in the 061 * Local Header for the original file size will be zero. 062 * </pre></p> 063 * 064 * <p>Currently Commons Compress doesn't support encrypting the 065 * central directory so the not about masking doesn't apply.</p> 066 * 067 * <p>The implementation relies on data being read from the local file 068 * header and assumes that both size values are always present.</p> 069 * 070 * @since Apache Commons Compress 1.2 071 * @NotThreadSafe 072 */ 073 public class Zip64ExtendedInformationExtraField implements ZipExtraField { 074 // TODO: the LFH should probably not contain relativeHeaderOffset 075 // and diskStart but then ZipArchivePOutputStream won't write it to 076 // the CD either - need to test interop with other implementations 077 // to see whether they do have a problem with the extraneous 078 // information inside the LFH 079 080 private static final ZipShort HEADER_ID = new ZipShort(0x0001); 081 082 private static final int WORD = 4, DWORD = 8; 083 084 private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; 085 private ZipLong diskStart; 086 087 /** 088 * This constructor should only be used by the code that reads 089 * archives inside of Commons Compress. 090 */ 091 public Zip64ExtendedInformationExtraField() { } 092 093 /** 094 * Creates an extra field based on the original and compressed size. 095 * 096 * @param size the entry's original size 097 * @param compressedSize the entry's compressed size 098 * 099 * @throws IllegalArgumentException if size or compressedSize is null 100 */ 101 public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, 102 ZipEightByteInteger compressedSize) { 103 this(size, compressedSize, null, null); 104 } 105 106 /** 107 * Creates an extra field based on all four possible values. 108 * 109 * @param size the entry's original size 110 * @param compressedSize the entry's compressed size 111 * 112 * @throws IllegalArgumentException if size or compressedSize is null 113 */ 114 public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, 115 ZipEightByteInteger compressedSize, 116 ZipEightByteInteger relativeHeaderOffset, 117 ZipLong diskStart) { 118 if (size == null) { 119 throw new IllegalArgumentException("size must not be null"); 120 } 121 if (compressedSize == null) { 122 throw new IllegalArgumentException("compressedSize must not be null"); 123 } 124 this.size = size; 125 this.compressedSize = compressedSize; 126 this.relativeHeaderOffset = relativeHeaderOffset; 127 this.diskStart = diskStart; 128 } 129 130 /** {@inheritDoc} */ 131 public ZipShort getHeaderId() { 132 return HEADER_ID; 133 } 134 135 /** {@inheritDoc} */ 136 public ZipShort getLocalFileDataLength() { 137 return getCentralDirectoryLength(); 138 } 139 140 /** {@inheritDoc} */ 141 public ZipShort getCentralDirectoryLength() { 142 return new ZipShort(2 * DWORD // both size fields 143 + (relativeHeaderOffset != null ? DWORD : 0) 144 + (diskStart != null ? WORD : 0)); 145 } 146 147 /** {@inheritDoc} */ 148 public byte[] getLocalFileDataData() { 149 return getCentralDirectoryData(); 150 } 151 152 /** {@inheritDoc} */ 153 public byte[] getCentralDirectoryData() { 154 byte[] data = new byte[getCentralDirectoryLength().getValue()]; 155 addSizes(data); 156 int off = 2 * DWORD; 157 if (relativeHeaderOffset != null) { 158 System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD); 159 off += DWORD; 160 } 161 if (diskStart != null) { 162 System.arraycopy(diskStart.getBytes(), 0, data, off, WORD); 163 off += WORD; 164 } 165 return data; 166 } 167 168 /** {@inheritDoc} */ 169 public void parseFromLocalFileData(byte[] buffer, int offset, int length) 170 throws ZipException { 171 if (length < 2 * DWORD) { 172 throw new ZipException("Zip64 extended information must contain" 173 + " both size values in the local file" 174 + " header."); 175 } 176 size = new ZipEightByteInteger(buffer, offset); 177 offset += DWORD; 178 compressedSize = new ZipEightByteInteger(buffer, offset); 179 offset += DWORD; 180 int remaining = length - 2 * DWORD; 181 if (remaining >= DWORD) { 182 relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); 183 offset += DWORD; 184 remaining -= DWORD; 185 } 186 if (remaining >= WORD) { 187 diskStart = new ZipLong(buffer, offset); 188 offset += WORD; 189 remaining -= WORD; 190 } 191 } 192 193 /** {@inheritDoc} */ 194 public void parseFromCentralDirectoryData(byte[] buffer, int offset, 195 int length) 196 throws ZipException { 197 // if there is no size information in here, we are screwed and 198 // can only hope things will get resolved by LFH data later 199 // But there are some cases that can be detected 200 // * all data is there 201 // * length % 8 == 4 -> at least we can identify the diskStart field 202 if (length >= 3 * DWORD + WORD) { 203 parseFromLocalFileData(buffer, offset, length); 204 } else if (length % DWORD == WORD) { 205 diskStart = new ZipLong(buffer, offset + length - WORD); 206 } 207 } 208 209 /** 210 * The uncompressed size stored in this extra field. 211 */ 212 public ZipEightByteInteger getSize() { 213 return size; 214 } 215 216 /** 217 * The compressed size stored in this extra field. 218 */ 219 public ZipEightByteInteger getCompressedSize() { 220 return compressedSize; 221 } 222 223 /** 224 * The relative header offset stored in this extra field. 225 */ 226 public ZipEightByteInteger getRelativeHeaderOffset() { 227 return relativeHeaderOffset; 228 } 229 230 /** 231 * The disk start number stored in this extra field. 232 */ 233 public ZipLong getDiskStartNumber() { 234 return diskStart; 235 } 236 237 private void addSizes(byte[] data) { 238 System.arraycopy(size.getBytes(), 0, data, 0, DWORD); 239 System.arraycopy(compressedSize.getBytes(), 0, data, DWORD, DWORD); 240 } 241 }