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