1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.commons.compress.archivers.zip; 20 21 import java.util.zip.ZipException; 22 23 import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; 24 import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 25 26 /** 27 * Holds size and other extended information for entries that use Zip64 28 * features. 29 * 30 * <p>Currently Commons Compress doesn't support encrypting the 31 * central directory so the note in APPNOTE.TXT about masking doesn't 32 * apply.</p> 33 * 34 * <p>The implementation relies on data being read from the local file 35 * header and assumes that both size values are always present.</p> 36 * 37 * @see <a href="https://www.pkware.com/documents/casestudies/APPNOTE.TXT">PKWARE 38 * APPNOTE.TXT, section 4.5.3</a> 39 * 40 * @since 1.2 41 * @NotThreadSafe 42 */ 43 public class Zip64ExtendedInformationExtraField implements ZipExtraField { 44 45 static final ZipShort HEADER_ID = new ZipShort(0x0001); 46 47 private static final String LFH_MUST_HAVE_BOTH_SIZES_MSG = 48 "Zip64 extended information must contain" 49 + " both size values in the local file header."; 50 private static final byte[] EMPTY = new byte[0]; 51 52 private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; 53 private ZipLong diskStart; 54 55 /** 56 * Stored in {@link #parseFromCentralDirectoryData 57 * parseFromCentralDirectoryData} so it can be reused when ZipFile 58 * calls {@link #reparseCentralDirectoryData 59 * reparseCentralDirectoryData}. 60 * 61 * <p>Not used for anything else</p> 62 * 63 * @since 1.3 64 */ 65 private byte[] rawCentralDirectoryData; 66 67 /** 68 * This constructor should only be used by the code that reads 69 * archives inside of Commons Compress. 70 */ Zip64ExtendedInformationExtraField()71 public Zip64ExtendedInformationExtraField() { } 72 73 /** 74 * Creates an extra field based on the original and compressed size. 75 * 76 * @param size the entry's original size 77 * @param compressedSize the entry's compressed size 78 * 79 * @throws IllegalArgumentException if size or compressedSize is null 80 */ Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, final ZipEightByteInteger compressedSize)81 public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, 82 final ZipEightByteInteger compressedSize) { 83 this(size, compressedSize, null, null); 84 } 85 86 /** 87 * Creates an extra field based on all four possible values. 88 * 89 * @param size the entry's original size 90 * @param compressedSize the entry's compressed size 91 * @param relativeHeaderOffset the entry's offset 92 * @param diskStart the disk start 93 * 94 * @throws IllegalArgumentException if size or compressedSize is null 95 */ Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, final ZipEightByteInteger compressedSize, final ZipEightByteInteger relativeHeaderOffset, final ZipLong diskStart)96 public Zip64ExtendedInformationExtraField(final ZipEightByteInteger size, 97 final ZipEightByteInteger compressedSize, 98 final ZipEightByteInteger relativeHeaderOffset, 99 final ZipLong diskStart) { 100 this.size = size; 101 this.compressedSize = compressedSize; 102 this.relativeHeaderOffset = relativeHeaderOffset; 103 this.diskStart = diskStart; 104 } 105 106 @Override getHeaderId()107 public ZipShort getHeaderId() { 108 return HEADER_ID; 109 } 110 111 @Override getLocalFileDataLength()112 public ZipShort getLocalFileDataLength() { 113 return new ZipShort(size != null ? 2 * DWORD : 0); 114 } 115 116 @Override getCentralDirectoryLength()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 getLocalFileDataData()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 getCentralDirectoryData()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; // NOSONAR - assignment as documentation 148 } 149 return data; 150 } 151 152 @Override parseFromLocalFileData(final byte[] buffer, int offset, final int length)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; // NOSONAR - assignment as documentation 178 remaining -= WORD; // NOSONAR - assignment as documentation 179 } 180 } 181 182 @Override parseFromCentralDirectoryData(final byte[] buffer, int offset, final int length)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 */ reparseCentralDirectoryData(final boolean hasUncompressedSize, final boolean hasCompressedSize, final boolean hasRelativeHeaderOffset, final boolean hasDiskStart)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; // NOSONAR - assignment as documentation 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 */ getSize()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 */ setSize(final ZipEightByteInteger size)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 */ getCompressedSize()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 */ setCompressedSize(final ZipEightByteInteger compressedSize)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 */ getRelativeHeaderOffset()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 */ setRelativeHeaderOffset(final ZipEightByteInteger rho)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 */ getDiskStartNumber()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 */ setDiskStartNumber(final ZipLong ds)324 public void setDiskStartNumber(final ZipLong ds) { 325 diskStart = ds; 326 } 327 addSizes(final byte[] data)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 } 341