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