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