1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.util.zip; 19 20import java.io.EOFException; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.RandomAccessFile; 24import java.io.UnsupportedEncodingException; 25import java.util.Calendar; 26import java.util.Date; 27import java.util.GregorianCalendar; 28 29/** 30 * An instance of {@code ZipEntry} represents an entry within a <i>ZIP-archive</i>. 31 * An entry has attributes such as name (= path) or the size of its data. While 32 * an entry identifies data stored in an archive, it does not hold the data 33 * itself. For example when reading a <i>ZIP-file</i> you will first retrieve 34 * all its entries in a collection and then read the data for a specific entry 35 * through an input stream. 36 * 37 * @see ZipFile 38 * @see ZipOutputStream 39 */ 40public class ZipEntry implements ZipConstants, Cloneable { 41 String name, comment; 42 43 long compressedSize = -1, crc = -1, size = -1; 44 45 int compressionMethod = -1, time = -1, modDate = -1; 46 47 byte[] extra; 48 49 int nameLen = -1; 50 long mLocalHeaderRelOffset = -1; 51 52 /** 53 * Zip entry state: Deflated. 54 */ 55 public static final int DEFLATED = 8; 56 57 /** 58 * Zip entry state: Stored. 59 */ 60 public static final int STORED = 0; 61 62 /** 63 * Constructs a new {@code ZipEntry} with the specified name. 64 * 65 * @param name 66 * the name of the ZIP entry. 67 * @throws IllegalArgumentException 68 * if the name length is outside the range (> 0xFFFF). 69 */ 70 public ZipEntry(String name) { 71 if (name == null) { 72 throw new NullPointerException(); 73 } 74 if (name.length() > 0xFFFF) { 75 throw new IllegalArgumentException(); 76 } 77 this.name = name; 78 } 79 80 /** 81 * Gets the comment for this {@code ZipEntry}. 82 * 83 * @return the comment for this {@code ZipEntry}, or {@code null} if there 84 * is no comment. If we're reading an archive with 85 * {@code ZipInputStream} the comment is not available. 86 */ 87 public String getComment() { 88 return comment; 89 } 90 91 /** 92 * Gets the compressed size of this {@code ZipEntry}. 93 * 94 * @return the compressed size, or -1 if the compressed size has not been 95 * set. 96 */ 97 public long getCompressedSize() { 98 return compressedSize; 99 } 100 101 /** 102 * Gets the checksum for this {@code ZipEntry}. 103 * 104 * @return the checksum, or -1 if the checksum has not been set. 105 */ 106 public long getCrc() { 107 return crc; 108 } 109 110 /** 111 * Gets the extra information for this {@code ZipEntry}. 112 * 113 * @return a byte array containing the extra information, or {@code null} if 114 * there is none. 115 */ 116 public byte[] getExtra() { 117 return extra; 118 } 119 120 /** 121 * Gets the compression method for this {@code ZipEntry}. 122 * 123 * @return the compression method, either {@code DEFLATED}, {@code STORED} 124 * or -1 if the compression method has not been set. 125 */ 126 public int getMethod() { 127 return compressionMethod; 128 } 129 130 /** 131 * Gets the name of this {@code ZipEntry}. 132 * 133 * @return the entry name. 134 */ 135 public String getName() { 136 return name; 137 } 138 139 /** 140 * Gets the uncompressed size of this {@code ZipEntry}. 141 * 142 * @return the uncompressed size, or {@code -1} if the size has not been 143 * set. 144 */ 145 public long getSize() { 146 return size; 147 } 148 149 /** 150 * Gets the last modification time of this {@code ZipEntry}. 151 * 152 * @return the last modification time as the number of milliseconds since 153 * Jan. 1, 1970. 154 */ 155 public long getTime() { 156 if (time != -1) { 157 GregorianCalendar cal = new GregorianCalendar(); 158 cal.set(Calendar.MILLISECOND, 0); 159 cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1, 160 modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f, 161 (time & 0x1f) << 1); 162 return cal.getTime().getTime(); 163 } 164 return -1; 165 } 166 167 /** 168 * Determine whether or not this {@code ZipEntry} is a directory. 169 * 170 * @return {@code true} when this {@code ZipEntry} is a directory, {@code 171 * false} otherwise. 172 */ 173 public boolean isDirectory() { 174 return name.charAt(name.length() - 1) == '/'; 175 } 176 177 /** 178 * Sets the comment for this {@code ZipEntry}. 179 * 180 * @param string 181 * the comment for this entry. 182 */ 183 public void setComment(String string) { 184 if (string == null || string.length() <= 0xFFFF) { 185 comment = string; 186 } else { 187 throw new IllegalArgumentException(); 188 } 189 } 190 191 /** 192 * Sets the compressed size for this {@code ZipEntry}. 193 * 194 * @param value 195 * the compressed size (in bytes). 196 */ 197 public void setCompressedSize(long value) { 198 compressedSize = value; 199 } 200 201 /** 202 * Sets the checksum for this {@code ZipEntry}. 203 * 204 * @param value 205 * the checksum for this entry. 206 * @throws IllegalArgumentException 207 * if {@code value} is < 0 or > 0xFFFFFFFFL. 208 */ 209 public void setCrc(long value) { 210 if (value >= 0 && value <= 0xFFFFFFFFL) { 211 crc = value; 212 } else { 213 throw new IllegalArgumentException(); 214 } 215 } 216 217 /** 218 * Sets the extra information for this {@code ZipEntry}. 219 * 220 * @param data 221 * a byte array containing the extra information. 222 * @throws IllegalArgumentException 223 * when the length of data is greater than 0xFFFF bytes. 224 */ 225 public void setExtra(byte[] data) { 226 if (data == null || data.length <= 0xFFFF) { 227 extra = data; 228 } else { 229 throw new IllegalArgumentException(); 230 } 231 } 232 233 /** 234 * Sets the compression method for this {@code ZipEntry}. 235 * 236 * @param value 237 * the compression method, either {@code DEFLATED} or {@code 238 * STORED}. 239 * @throws IllegalArgumentException 240 * when value is not {@code DEFLATED} or {@code STORED}. 241 */ 242 public void setMethod(int value) { 243 if (value != STORED && value != DEFLATED) { 244 throw new IllegalArgumentException(); 245 } 246 compressionMethod = value; 247 } 248 249 /** 250 * Sets the uncompressed size of this {@code ZipEntry}. 251 * 252 * @param value 253 * the uncompressed size for this entry. 254 * @throws IllegalArgumentException 255 * if {@code value} < 0 or {@code value} > 0xFFFFFFFFL. 256 */ 257 public void setSize(long value) { 258 if (value >= 0 && value <= 0xFFFFFFFFL) { 259 size = value; 260 } else { 261 throw new IllegalArgumentException(); 262 } 263 } 264 265 /** 266 * Sets the modification time of this {@code ZipEntry}. 267 * 268 * @param value 269 * the modification time as the number of milliseconds since Jan. 270 * 1, 1970. 271 */ 272 public void setTime(long value) { 273 GregorianCalendar cal = new GregorianCalendar(); 274 cal.setTime(new Date(value)); 275 int year = cal.get(Calendar.YEAR); 276 if (year < 1980) { 277 modDate = 0x21; 278 time = 0; 279 } else { 280 modDate = cal.get(Calendar.DATE); 281 modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate; 282 modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate; 283 time = cal.get(Calendar.SECOND) >> 1; 284 time = (cal.get(Calendar.MINUTE) << 5) | time; 285 time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time; 286 } 287 } 288 289 /** 290 * Returns the string representation of this {@code ZipEntry}. 291 * 292 * @return the string representation of this {@code ZipEntry}. 293 */ 294 @Override 295 public String toString() { 296 return name; 297 } 298 299 /** 300 * Constructs a new {@code ZipEntry} using the values obtained from {@code 301 * ze}. 302 * 303 * @param ze 304 * the {@code ZipEntry} from which to obtain values. 305 */ 306 public ZipEntry(ZipEntry ze) { 307 name = ze.name; 308 comment = ze.comment; 309 time = ze.time; 310 size = ze.size; 311 compressedSize = ze.compressedSize; 312 crc = ze.crc; 313 compressionMethod = ze.compressionMethod; 314 modDate = ze.modDate; 315 extra = ze.extra; 316 nameLen = ze.nameLen; 317 mLocalHeaderRelOffset = ze.mLocalHeaderRelOffset; 318 } 319 320 /** 321 * Returns a shallow copy of this entry. 322 * 323 * @return a copy of this entry. 324 */ 325 @Override 326 public Object clone() { 327 return new ZipEntry(this); 328 } 329 330 /** 331 * Returns the hash code for this {@code ZipEntry}. 332 * 333 * @return the hash code of the entry. 334 */ 335 @Override 336 public int hashCode() { 337 return name.hashCode(); 338 } 339 340 /* 341 * Internal constructor. Creates a new ZipEntry by reading the 342 * Central Directory Entry from "in", which must be positioned at 343 * the CDE signature. 344 * 345 * On exit, "in" will be positioned at the start of the next entry. 346 */ 347 ZipEntry(LittleEndianReader ler, InputStream in) throws IOException { 348 349 /* 350 * We're seeing performance issues when we call readShortLE and 351 * readIntLE, so we're going to read the entire header at once 352 * and then parse the results out without using any function calls. 353 * Uglier, but should be much faster. 354 * 355 * Note that some lines look a bit different, because the corresponding 356 * fields or locals are long and so we need to do & 0xffffffffl to avoid 357 * problems induced by sign extension. 358 */ 359 360 byte[] hdrBuf = ler.hdrBuf; 361 myReadFully(in, hdrBuf); 362 363 long sig = (hdrBuf[0] & 0xff) | ((hdrBuf[1] & 0xff) << 8) | 364 ((hdrBuf[2] & 0xff) << 16) | ((hdrBuf[3] << 24) & 0xffffffffL); 365 if (sig != CENSIG) { 366 throw new ZipException("Central Directory Entry not found"); 367 } 368 369 compressionMethod = (hdrBuf[10] & 0xff) | ((hdrBuf[11] & 0xff) << 8); 370 time = (hdrBuf[12] & 0xff) | ((hdrBuf[13] & 0xff) << 8); 371 modDate = (hdrBuf[14] & 0xff) | ((hdrBuf[15] & 0xff) << 8); 372 crc = (hdrBuf[16] & 0xff) | ((hdrBuf[17] & 0xff) << 8) 373 | ((hdrBuf[18] & 0xff) << 16) 374 | ((hdrBuf[19] << 24) & 0xffffffffL); 375 compressedSize = (hdrBuf[20] & 0xff) | ((hdrBuf[21] & 0xff) << 8) 376 | ((hdrBuf[22] & 0xff) << 16) 377 | ((hdrBuf[23] << 24) & 0xffffffffL); 378 size = (hdrBuf[24] & 0xff) | ((hdrBuf[25] & 0xff) << 8) 379 | ((hdrBuf[26] & 0xff) << 16) 380 | ((hdrBuf[27] << 24) & 0xffffffffL); 381 nameLen = (hdrBuf[28] & 0xff) | ((hdrBuf[29] & 0xff) << 8); 382 int extraLen = (hdrBuf[30] & 0xff) | ((hdrBuf[31] & 0xff) << 8); 383 int commentLen = (hdrBuf[32] & 0xff) | ((hdrBuf[33] & 0xff) << 8); 384 mLocalHeaderRelOffset = (hdrBuf[42] & 0xff) | ((hdrBuf[43] & 0xff) << 8) 385 | ((hdrBuf[44] & 0xff) << 16) 386 | ((hdrBuf[45] << 24) & 0xffffffffL); 387 388 byte[] nameBytes = new byte[nameLen]; 389 myReadFully(in, nameBytes); 390 391 byte[] commentBytes = null; 392 if (commentLen > 0) { 393 commentBytes = new byte[commentLen]; 394 myReadFully(in, commentBytes); 395 } 396 397 if (extraLen > 0) { 398 extra = new byte[extraLen]; 399 myReadFully(in, extra); 400 } 401 402 try { 403 /* 404 * The actual character set is "IBM Code Page 437". As of 405 * Sep 2006, the Zip spec (APPNOTE.TXT) supports UTF-8. When 406 * bit 11 of the GP flags field is set, the file name and 407 * comment fields are UTF-8. 408 * 409 * TODO: add correct UTF-8 support. 410 */ 411 name = new String(nameBytes, "ISO-8859-1"); 412 if (commentBytes != null) { 413 comment = new String(commentBytes, "ISO-8859-1"); 414 } else { 415 comment = null; 416 } 417 } catch (UnsupportedEncodingException uee) { 418 throw new InternalError(uee.getMessage()); 419 } 420 } 421 422 private void myReadFully(InputStream in, byte[] b) throws IOException { 423 int len = b.length; 424 int off = 0; 425 426 while (len > 0) { 427 int count = in.read(b, off, len); 428 if (count <= 0) { 429 throw new EOFException(); 430 } 431 off += count; 432 len -= count; 433 } 434 } 435 436 /* 437 * Read a four-byte int in little-endian order. 438 */ 439 static long readIntLE(RandomAccessFile raf) throws IOException { 440 int b0 = raf.read(); 441 int b1 = raf.read(); 442 int b2 = raf.read(); 443 int b3 = raf.read(); 444 445 if (b3 < 0) { 446 throw new EOFException("in ZipEntry.readIntLE(RandomAccessFile)"); 447 } 448 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); // ATTENTION: DOES SIGN EXTENSION: IS THIS WANTED? 449 } 450 451 static class LittleEndianReader { 452 private byte[] b = new byte[4]; 453 byte[] hdrBuf = new byte[CENHDR]; 454 455 /* 456 * Read a two-byte short in little-endian order. 457 */ 458 int readShortLE(InputStream in) throws IOException { 459 if (in.read(b, 0, 2) == 2) { 460 return (b[0] & 0XFF) | ((b[1] & 0XFF) << 8); 461 } else { 462 throw new EOFException("in ZipEntry.readShortLE(InputStream)"); 463 } 464 } 465 466 /* 467 * Read a four-byte int in little-endian order. 468 */ 469 long readIntLE(InputStream in) throws IOException { 470 if (in.read(b, 0, 4) == 4) { 471 return ( ((b[0] & 0XFF)) 472 | ((b[1] & 0XFF) << 8) 473 | ((b[2] & 0XFF) << 16) 474 | ((b[3] & 0XFF) << 24)) 475 & 0XFFFFFFFFL; // Here for sure NO sign extension is wanted. 476 } else { 477 throw new EOFException("in ZipEntry.readIntLE(InputStream)"); 478 } 479 } 480 } 481} 482