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