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