Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 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 | |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 23 | import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; |
| 24 | import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; |
| 25 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 26 | /** |
| 27 | * Holds size and other extended information for entries that use Zip64 |
| 28 | * features. |
| 29 | * |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 30 | * <p>Currently Commons Compress doesn't support encrypting the |
Stefan Bodewig | 45e51c2 | 2013-12-22 07:03:43 +0000 | [diff] [blame^] | 31 | * central directory so the note in APPNOTE.TXT about masking doesn't |
| 32 | * apply.</p> |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 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 | * |
Stefan Bodewig | 45e51c2 | 2013-12-22 07:03:43 +0000 | [diff] [blame^] | 37 | * @see <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">PKWARE's |
| 38 | * APPNOTE.TXT, section 4.5.3</a> |
| 39 | * |
Gary D. Gregory | 2bd0dd4 | 2012-04-01 13:02:39 +0000 | [diff] [blame] | 40 | * @since 1.2 |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 41 | * @NotThreadSafe |
| 42 | */ |
| 43 | public class Zip64ExtendedInformationExtraField implements ZipExtraField { |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 44 | |
Stefan Bodewig | 6c5f04b | 2011-07-25 20:14:59 +0000 | [diff] [blame] | 45 | static final ZipShort HEADER_ID = new ZipShort(0x0001); |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 46 | |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 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."; |
Stefan Bodewig | a2f978e | 2013-01-05 19:28:42 +0000 | [diff] [blame] | 50 | private static final byte[] EMPTY = new byte[0]; |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 51 | |
| 52 | private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; |
| 53 | private ZipLong diskStart; |
| 54 | |
| 55 | /** |
Stefan Bodewig | 8044739 | 2011-08-04 05:25:28 +0000 | [diff] [blame] | 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 | * |
Gary D. Gregory | 2bd0dd4 | 2012-04-01 13:02:39 +0000 | [diff] [blame] | 63 | * @since 1.3 |
Stefan Bodewig | 8044739 | 2011-08-04 05:25:28 +0000 | [diff] [blame] | 64 | */ |
| 65 | private byte[] rawCentralDirectoryData; |
| 66 | |
| 67 | /** |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 68 | * This constructor should only be used by the code that reads |
| 69 | * archives inside of Commons Compress. |
| 70 | */ |
| 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 | */ |
| 81 | public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, |
| 82 | 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 | * |
| 92 | * @throws IllegalArgumentException if size or compressedSize is null |
| 93 | */ |
| 94 | public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, |
| 95 | ZipEightByteInteger compressedSize, |
| 96 | ZipEightByteInteger relativeHeaderOffset, |
| 97 | ZipLong diskStart) { |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 98 | this.size = size; |
| 99 | this.compressedSize = compressedSize; |
| 100 | this.relativeHeaderOffset = relativeHeaderOffset; |
| 101 | this.diskStart = diskStart; |
| 102 | } |
| 103 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 104 | public ZipShort getHeaderId() { |
| 105 | return HEADER_ID; |
| 106 | } |
| 107 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 108 | public ZipShort getLocalFileDataLength() { |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 109 | return new ZipShort(size != null ? 2 * DWORD : 0); |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 110 | } |
| 111 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 112 | public ZipShort getCentralDirectoryLength() { |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 113 | return new ZipShort((size != null ? DWORD : 0) |
| 114 | + (compressedSize != null ? DWORD : 0) |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 115 | + (relativeHeaderOffset != null ? DWORD : 0) |
| 116 | + (diskStart != null ? WORD : 0)); |
| 117 | } |
| 118 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 119 | public byte[] getLocalFileDataData() { |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 120 | if (size != null || compressedSize != null) { |
| 121 | if (size == null || compressedSize == null) { |
| 122 | throw new IllegalArgumentException(LFH_MUST_HAVE_BOTH_SIZES_MSG); |
| 123 | } |
| 124 | byte[] data = new byte[2 * DWORD]; |
| 125 | addSizes(data); |
| 126 | return data; |
| 127 | } |
Stefan Bodewig | a2f978e | 2013-01-05 19:28:42 +0000 | [diff] [blame] | 128 | return EMPTY; |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 129 | } |
| 130 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 131 | public byte[] getCentralDirectoryData() { |
| 132 | byte[] data = new byte[getCentralDirectoryLength().getValue()]; |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 133 | int off = addSizes(data); |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 134 | if (relativeHeaderOffset != null) { |
| 135 | System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD); |
| 136 | off += DWORD; |
| 137 | } |
| 138 | if (diskStart != null) { |
| 139 | System.arraycopy(diskStart.getBytes(), 0, data, off, WORD); |
| 140 | off += WORD; |
| 141 | } |
| 142 | return data; |
| 143 | } |
| 144 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 145 | public void parseFromLocalFileData(byte[] buffer, int offset, int length) |
| 146 | throws ZipException { |
Stefan Bodewig | 46701aa | 2011-08-04 11:26:48 +0000 | [diff] [blame] | 147 | if (length == 0) { |
| 148 | // no local file data at all, may happen if an archive |
| 149 | // only holds a ZIP64 extended information extra field |
| 150 | // inside the central directory but not inside the local |
| 151 | // file header |
| 152 | return; |
| 153 | } |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 154 | if (length < 2 * DWORD) { |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 155 | throw new ZipException(LFH_MUST_HAVE_BOTH_SIZES_MSG); |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 156 | } |
| 157 | size = new ZipEightByteInteger(buffer, offset); |
| 158 | offset += DWORD; |
| 159 | compressedSize = new ZipEightByteInteger(buffer, offset); |
| 160 | offset += DWORD; |
| 161 | int remaining = length - 2 * DWORD; |
| 162 | if (remaining >= DWORD) { |
| 163 | relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); |
| 164 | offset += DWORD; |
| 165 | remaining -= DWORD; |
| 166 | } |
| 167 | if (remaining >= WORD) { |
| 168 | diskStart = new ZipLong(buffer, offset); |
| 169 | offset += WORD; |
| 170 | remaining -= WORD; |
| 171 | } |
| 172 | } |
| 173 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 174 | public void parseFromCentralDirectoryData(byte[] buffer, int offset, |
| 175 | int length) |
| 176 | throws ZipException { |
Stefan Bodewig | 8044739 | 2011-08-04 05:25:28 +0000 | [diff] [blame] | 177 | // store for processing in reparseCentralDirectoryData |
| 178 | rawCentralDirectoryData = new byte[length]; |
| 179 | System.arraycopy(buffer, offset, rawCentralDirectoryData, 0, length); |
| 180 | |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 181 | // if there is no size information in here, we are screwed and |
| 182 | // can only hope things will get resolved by LFH data later |
| 183 | // But there are some cases that can be detected |
| 184 | // * all data is there |
Stefan Bodewig | a058133 | 2011-07-29 15:05:52 +0000 | [diff] [blame] | 185 | // * length == 24 -> both sizes and offset |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 186 | // * length % 8 == 4 -> at least we can identify the diskStart field |
| 187 | if (length >= 3 * DWORD + WORD) { |
| 188 | parseFromLocalFileData(buffer, offset, length); |
Stefan Bodewig | a058133 | 2011-07-29 15:05:52 +0000 | [diff] [blame] | 189 | } else if (length == 3 * DWORD) { |
| 190 | size = new ZipEightByteInteger(buffer, offset); |
| 191 | offset += DWORD; |
| 192 | compressedSize = new ZipEightByteInteger(buffer, offset); |
| 193 | offset += DWORD; |
| 194 | relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 195 | } else if (length % DWORD == WORD) { |
| 196 | diskStart = new ZipLong(buffer, offset + length - WORD); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /** |
Stefan Bodewig | 8044739 | 2011-08-04 05:25:28 +0000 | [diff] [blame] | 201 | * Parses the raw bytes read from the central directory extra |
| 202 | * field with knowledge which fields are expected to be there. |
| 203 | * |
| 204 | * <p>All four fields inside the zip64 extended information extra |
Stefan Bodewig | e860d2f | 2013-05-26 17:36:35 +0000 | [diff] [blame] | 205 | * field are optional and must only be present if their corresponding |
Stefan Bodewig | 8044739 | 2011-08-04 05:25:28 +0000 | [diff] [blame] | 206 | * entry inside the central directory contains the correct magic |
| 207 | * value.</p> |
| 208 | */ |
| 209 | public void reparseCentralDirectoryData(boolean hasUncompressedSize, |
| 210 | boolean hasCompressedSize, |
| 211 | boolean hasRelativeHeaderOffset, |
| 212 | boolean hasDiskStart) |
| 213 | throws ZipException { |
| 214 | if (rawCentralDirectoryData != null) { |
| 215 | int expectedLength = (hasUncompressedSize ? DWORD : 0) |
| 216 | + (hasCompressedSize ? DWORD : 0) |
| 217 | + (hasRelativeHeaderOffset ? DWORD : 0) |
| 218 | + (hasDiskStart ? WORD : 0); |
Stefan Bodewig | e860d2f | 2013-05-26 17:36:35 +0000 | [diff] [blame] | 219 | if (rawCentralDirectoryData.length < expectedLength) { |
Stefan Bodewig | 8044739 | 2011-08-04 05:25:28 +0000 | [diff] [blame] | 220 | throw new ZipException("central directory zip64 extended" |
| 221 | + " information extra field's length" |
| 222 | + " doesn't match central directory" |
| 223 | + " data. Expected length " |
| 224 | + expectedLength + " but is " |
| 225 | + rawCentralDirectoryData.length); |
| 226 | } |
| 227 | int offset = 0; |
| 228 | if (hasUncompressedSize) { |
| 229 | size = new ZipEightByteInteger(rawCentralDirectoryData, offset); |
| 230 | offset += DWORD; |
| 231 | } |
| 232 | if (hasCompressedSize) { |
| 233 | compressedSize = new ZipEightByteInteger(rawCentralDirectoryData, |
| 234 | offset); |
| 235 | offset += DWORD; |
| 236 | } |
| 237 | if (hasRelativeHeaderOffset) { |
| 238 | relativeHeaderOffset = |
| 239 | new ZipEightByteInteger(rawCentralDirectoryData, offset); |
| 240 | offset += DWORD; |
| 241 | } |
| 242 | if (hasDiskStart) { |
| 243 | diskStart = new ZipLong(rawCentralDirectoryData, offset); |
| 244 | offset += WORD; |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | /** |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 250 | * The uncompressed size stored in this extra field. |
| 251 | */ |
| 252 | public ZipEightByteInteger getSize() { |
| 253 | return size; |
| 254 | } |
| 255 | |
| 256 | /** |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 257 | * The uncompressed size stored in this extra field. |
| 258 | */ |
| 259 | public void setSize(ZipEightByteInteger size) { |
| 260 | this.size = size; |
| 261 | } |
| 262 | |
| 263 | /** |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 264 | * The compressed size stored in this extra field. |
| 265 | */ |
| 266 | public ZipEightByteInteger getCompressedSize() { |
| 267 | return compressedSize; |
| 268 | } |
| 269 | |
| 270 | /** |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 271 | * The uncompressed size stored in this extra field. |
| 272 | */ |
| 273 | public void setCompressedSize(ZipEightByteInteger compressedSize) { |
| 274 | this.compressedSize = compressedSize; |
| 275 | } |
| 276 | |
| 277 | /** |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 278 | * The relative header offset stored in this extra field. |
| 279 | */ |
| 280 | public ZipEightByteInteger getRelativeHeaderOffset() { |
| 281 | return relativeHeaderOffset; |
| 282 | } |
| 283 | |
| 284 | /** |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 285 | * The relative header offset stored in this extra field. |
| 286 | */ |
| 287 | public void setRelativeHeaderOffset(ZipEightByteInteger rho) { |
| 288 | relativeHeaderOffset = rho; |
| 289 | } |
| 290 | |
| 291 | /** |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 292 | * The disk start number stored in this extra field. |
| 293 | */ |
| 294 | public ZipLong getDiskStartNumber() { |
| 295 | return diskStart; |
| 296 | } |
| 297 | |
Stefan Bodewig | 43e546e | 2011-07-29 14:41:25 +0000 | [diff] [blame] | 298 | /** |
| 299 | * The disk start number stored in this extra field. |
| 300 | */ |
| 301 | public void setDiskStartNumber(ZipLong ds) { |
| 302 | diskStart = ds; |
| 303 | } |
| 304 | |
| 305 | private int addSizes(byte[] data) { |
| 306 | int off = 0; |
| 307 | if (size != null) { |
| 308 | System.arraycopy(size.getBytes(), 0, data, 0, DWORD); |
| 309 | off += DWORD; |
| 310 | } |
| 311 | if (compressedSize != null) { |
| 312 | System.arraycopy(compressedSize.getBytes(), 0, data, off, DWORD); |
| 313 | off += DWORD; |
| 314 | } |
| 315 | return off; |
Stefan Bodewig | 60e2dec | 2011-07-21 12:11:06 +0000 | [diff] [blame] | 316 | } |
Stefan Bodewig | a2f978e | 2013-01-05 19:28:42 +0000 | [diff] [blame] | 317 | } |