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