1 using System; 2 using System.IO; 3 4 namespace ICSharpCode.SharpZipLib.Zip 5 { 6 /// <summary> 7 /// Defines known values for the <see cref="HostSystemID"/> property. 8 /// </summary> 9 public enum HostSystemID 10 { 11 /// <summary> 12 /// Host system = MSDOS 13 /// </summary> 14 Msdos = 0, 15 /// <summary> 16 /// Host system = Amiga 17 /// </summary> 18 Amiga = 1, 19 /// <summary> 20 /// Host system = Open VMS 21 /// </summary> 22 OpenVms = 2, 23 /// <summary> 24 /// Host system = Unix 25 /// </summary> 26 Unix = 3, 27 /// <summary> 28 /// Host system = VMCms 29 /// </summary> 30 VMCms = 4, 31 /// <summary> 32 /// Host system = Atari ST 33 /// </summary> 34 AtariST = 5, 35 /// <summary> 36 /// Host system = OS2 37 /// </summary> 38 OS2 = 6, 39 /// <summary> 40 /// Host system = Macintosh 41 /// </summary> 42 Macintosh = 7, 43 /// <summary> 44 /// Host system = ZSystem 45 /// </summary> 46 ZSystem = 8, 47 /// <summary> 48 /// Host system = Cpm 49 /// </summary> 50 Cpm = 9, 51 /// <summary> 52 /// Host system = Windows NT 53 /// </summary> 54 WindowsNT = 10, 55 /// <summary> 56 /// Host system = MVS 57 /// </summary> 58 MVS = 11, 59 /// <summary> 60 /// Host system = VSE 61 /// </summary> 62 Vse = 12, 63 /// <summary> 64 /// Host system = Acorn RISC 65 /// </summary> 66 AcornRisc = 13, 67 /// <summary> 68 /// Host system = VFAT 69 /// </summary> 70 Vfat = 14, 71 /// <summary> 72 /// Host system = Alternate MVS 73 /// </summary> 74 AlternateMvs = 15, 75 /// <summary> 76 /// Host system = BEOS 77 /// </summary> 78 BeOS = 16, 79 /// <summary> 80 /// Host system = Tandem 81 /// </summary> 82 Tandem = 17, 83 /// <summary> 84 /// Host system = OS400 85 /// </summary> 86 OS400 = 18, 87 /// <summary> 88 /// Host system = OSX 89 /// </summary> 90 OSX = 19, 91 /// <summary> 92 /// Host system = WinZIP AES 93 /// </summary> 94 WinZipAES = 99, 95 } 96 97 /// <summary> 98 /// This class represents an entry in a zip archive. This can be a file 99 /// or a directory 100 /// ZipFile and ZipInputStream will give you instances of this class as 101 /// information about the members in an archive. ZipOutputStream 102 /// uses an instance of this class when creating an entry in a Zip file. 103 /// <br/> 104 /// <br/>Author of the original java version : Jochen Hoenicke 105 /// </summary> 106 public class ZipEntry 107 { 108 [Flags] 109 enum Known : byte 110 { 111 None = 0, 112 Size = 0x01, 113 CompressedSize = 0x02, 114 Crc = 0x04, 115 Time = 0x08, 116 ExternalAttributes = 0x10, 117 } 118 119 #region Constructors 120 /// <summary> 121 /// Creates a zip entry with the given name. 122 /// </summary> 123 /// <param name="name"> 124 /// The name for this entry. Can include directory components. 125 /// The convention for names is 'unix' style paths with relative names only. 126 /// There are with no device names and path elements are separated by '/' characters. 127 /// </param> 128 /// <exception cref="ArgumentNullException"> 129 /// The name passed is null 130 /// </exception> ZipEntry(string name)131 public ZipEntry(string name) 132 : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) 133 { 134 } 135 136 /// <summary> 137 /// Creates a zip entry with the given name and version required to extract 138 /// </summary> 139 /// <param name="name"> 140 /// The name for this entry. Can include directory components. 141 /// The convention for names is 'unix' style paths with no device names and 142 /// path elements separated by '/' characters. This is not enforced see <see cref="CleanName(string)">CleanName</see> 143 /// on how to ensure names are valid if this is desired. 144 /// </param> 145 /// <param name="versionRequiredToExtract"> 146 /// The minimum 'feature version' required this entry 147 /// </param> 148 /// <exception cref="ArgumentNullException"> 149 /// The name passed is null 150 /// </exception> ZipEntry(string name, int versionRequiredToExtract)151 internal ZipEntry(string name, int versionRequiredToExtract) 152 : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, 153 CompressionMethod.Deflated) 154 { 155 } 156 157 /// <summary> 158 /// Initializes an entry with the given name and made by information 159 /// </summary> 160 /// <param name="name">Name for this entry</param> 161 /// <param name="madeByInfo">Version and HostSystem Information</param> 162 /// <param name="versionRequiredToExtract">Minimum required zip feature version required to extract this entry</param> 163 /// <param name="method">Compression method for this entry.</param> 164 /// <exception cref="ArgumentNullException"> 165 /// The name passed is null 166 /// </exception> 167 /// <exception cref="ArgumentOutOfRangeException"> 168 /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 169 /// </exception> 170 /// <remarks> 171 /// This constructor is used by the ZipFile class when reading from the central header 172 /// It is not generally useful, use the constructor specifying the name only. 173 /// </remarks> ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, CompressionMethod method)174 internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, 175 CompressionMethod method) 176 { 177 if (name == null) { 178 throw new ArgumentNullException(nameof(name)); 179 } 180 181 if (name.Length > 0xffff) { 182 throw new ArgumentException("Name is too long", nameof(name)); 183 } 184 185 if ((versionRequiredToExtract != 0) && (versionRequiredToExtract < 10)) { 186 throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract)); 187 } 188 189 this.DateTime = DateTime.Now; 190 this.name = CleanName(name); 191 this.versionMadeBy = (ushort)madeByInfo; 192 this.versionToExtract = (ushort)versionRequiredToExtract; 193 this.method = method; 194 } 195 196 /// <summary> 197 /// Creates a deep copy of the given zip entry. 198 /// </summary> 199 /// <param name="entry"> 200 /// The entry to copy. 201 /// </param> 202 [Obsolete("Use Clone instead")] ZipEntry(ZipEntry entry)203 public ZipEntry(ZipEntry entry) 204 { 205 if (entry == null) { 206 throw new ArgumentNullException(nameof(entry)); 207 } 208 209 known = entry.known; 210 name = entry.name; 211 size = entry.size; 212 compressedSize = entry.compressedSize; 213 crc = entry.crc; 214 dosTime = entry.dosTime; 215 method = entry.method; 216 comment = entry.comment; 217 versionToExtract = entry.versionToExtract; 218 versionMadeBy = entry.versionMadeBy; 219 externalFileAttributes = entry.externalFileAttributes; 220 flags = entry.flags; 221 222 zipFileIndex = entry.zipFileIndex; 223 offset = entry.offset; 224 225 forceZip64_ = entry.forceZip64_; 226 227 if (entry.extra != null) { 228 extra = new byte[entry.extra.Length]; 229 Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length); 230 } 231 } 232 233 #endregion 234 235 /// <summary> 236 /// Get a value indicating wether the entry has a CRC value available. 237 /// </summary> 238 public bool HasCrc { 239 get { 240 return (known & Known.Crc) != 0; 241 } 242 } 243 244 /// <summary> 245 /// Get/Set flag indicating if entry is encrypted. 246 /// A simple helper routine to aid interpretation of <see cref="Flags">flags</see> 247 /// </summary> 248 /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> 249 public bool IsCrypted { 250 get { 251 return (flags & 1) != 0; 252 } 253 set { 254 if (value) { 255 flags |= 1; 256 } else { 257 flags &= ~1; 258 } 259 } 260 } 261 262 /// <summary> 263 /// Get / set a flag indicating wether entry name and comment text are 264 /// encoded in <a href="http://www.unicode.org">unicode UTF8</a>. 265 /// </summary> 266 /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> 267 public bool IsUnicodeText { 268 get { 269 return (flags & (int)GeneralBitFlags.UnicodeText) != 0; 270 } 271 set { 272 if (value) { 273 flags |= (int)GeneralBitFlags.UnicodeText; 274 } else { 275 flags &= ~(int)GeneralBitFlags.UnicodeText; 276 } 277 } 278 } 279 280 /// <summary> 281 /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. 282 /// </summary> 283 internal byte CryptoCheckValue { 284 get { 285 return cryptoCheckValue_; 286 } 287 288 set { 289 cryptoCheckValue_ = value; 290 } 291 } 292 293 /// <summary> 294 /// Get/Set general purpose bit flag for entry 295 /// </summary> 296 /// <remarks> 297 /// General purpose bit flag<br/> 298 /// <br/> 299 /// Bit 0: If set, indicates the file is encrypted<br/> 300 /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating<br/> 301 /// Imploding:<br/> 302 /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used<br/> 303 /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise<br/> 304 /// <br/> 305 /// Deflating:<br/> 306 /// Bit 2 Bit 1<br/> 307 /// 0 0 Normal compression was used<br/> 308 /// 0 1 Maximum compression was used<br/> 309 /// 1 0 Fast compression was used<br/> 310 /// 1 1 Super fast compression was used<br/> 311 /// <br/> 312 /// Bit 3: If set, the fields crc-32, compressed size 313 /// and uncompressed size are were not able to be written during zip file creation 314 /// The correct values are held in a data descriptor immediately following the compressed data. <br/> 315 /// Bit 4: Reserved for use by PKZIP for enhanced deflating<br/> 316 /// Bit 5: If set indicates the file contains compressed patch data<br/> 317 /// Bit 6: If set indicates strong encryption was used.<br/> 318 /// Bit 7-10: Unused or reserved<br/> 319 /// Bit 11: If set the name and comments for this entry are in <a href="http://www.unicode.org">unicode</a>.<br/> 320 /// Bit 12-15: Unused or reserved<br/> 321 /// </remarks> 322 /// <seealso cref="IsUnicodeText"></seealso> 323 /// <seealso cref="IsCrypted"></seealso> 324 public int Flags { 325 get { 326 return flags; 327 } 328 set { 329 flags = value; 330 } 331 } 332 333 /// <summary> 334 /// Get/Set index of this entry in Zip file 335 /// </summary> 336 /// <remarks>This is only valid when the entry is part of a <see cref="ZipFile"></see></remarks> 337 public long ZipFileIndex { 338 get { 339 return zipFileIndex; 340 } 341 set { 342 zipFileIndex = value; 343 } 344 } 345 346 /// <summary> 347 /// Get/set offset for use in central header 348 /// </summary> 349 public long Offset { 350 get { 351 return offset; 352 } 353 set { 354 offset = value; 355 } 356 } 357 358 /// <summary> 359 /// Get/Set external file attributes as an integer. 360 /// The values of this are operating system dependant see 361 /// <see cref="HostSystem">HostSystem</see> for details 362 /// </summary> 363 public int ExternalFileAttributes { 364 get { 365 if ((known & Known.ExternalAttributes) == 0) { 366 return -1; 367 } else { 368 return externalFileAttributes; 369 } 370 } 371 372 set { 373 externalFileAttributes = value; 374 known |= Known.ExternalAttributes; 375 } 376 } 377 378 /// <summary> 379 /// Get the version made by for this entry or zero if unknown. 380 /// The value / 10 indicates the major version number, and 381 /// the value mod 10 is the minor version number 382 /// </summary> 383 public int VersionMadeBy { 384 get { 385 return (versionMadeBy & 0xff); 386 } 387 } 388 389 /// <summary> 390 /// Get a value indicating this entry is for a DOS/Windows system. 391 /// </summary> 392 public bool IsDOSEntry { 393 get { 394 return ((HostSystem == (int)HostSystemID.Msdos) || 395 (HostSystem == (int)HostSystemID.WindowsNT)); 396 } 397 } 398 399 /// <summary> 400 /// Test the external attributes for this <see cref="ZipEntry"/> to 401 /// see if the external attributes are Dos based (including WINNT and variants) 402 /// and match the values 403 /// </summary> 404 /// <param name="attributes">The attributes to test.</param> 405 /// <returns>Returns true if the external attributes are known to be DOS/Windows 406 /// based and have the same attributes set as the value passed.</returns> HasDosAttributes(int attributes)407 bool HasDosAttributes(int attributes) 408 { 409 bool result = false; 410 if ((known & Known.ExternalAttributes) != 0) { 411 result |= (((HostSystem == (int)HostSystemID.Msdos) || 412 (HostSystem == (int)HostSystemID.WindowsNT)) && 413 (ExternalFileAttributes & attributes) == attributes); 414 } 415 return result; 416 } 417 418 /// <summary> 419 /// Gets the compatability information for the <see cref="ExternalFileAttributes">external file attribute</see> 420 /// If the external file attributes are compatible with MS-DOS and can be read 421 /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value 422 /// will be non-zero and identify the host system on which the attributes are compatible. 423 /// </summary> 424 /// 425 /// <remarks> 426 /// The values for this as defined in the Zip File format and by others are shown below. The values are somewhat 427 /// misleading in some cases as they are not all used as shown. You should consult the relevant documentation 428 /// to obtain up to date and correct information. The modified appnote by the infozip group is 429 /// particularly helpful as it documents a lot of peculiarities. The document is however a little dated. 430 /// <list type="table"> 431 /// <item>0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)</item> 432 /// <item>1 - Amiga</item> 433 /// <item>2 - OpenVMS</item> 434 /// <item>3 - Unix</item> 435 /// <item>4 - VM/CMS</item> 436 /// <item>5 - Atari ST</item> 437 /// <item>6 - OS/2 HPFS</item> 438 /// <item>7 - Macintosh</item> 439 /// <item>8 - Z-System</item> 440 /// <item>9 - CP/M</item> 441 /// <item>10 - Windows NTFS</item> 442 /// <item>11 - MVS (OS/390 - Z/OS)</item> 443 /// <item>12 - VSE</item> 444 /// <item>13 - Acorn Risc</item> 445 /// <item>14 - VFAT</item> 446 /// <item>15 - Alternate MVS</item> 447 /// <item>16 - BeOS</item> 448 /// <item>17 - Tandem</item> 449 /// <item>18 - OS/400</item> 450 /// <item>19 - OS/X (Darwin)</item> 451 /// <item>99 - WinZip AES</item> 452 /// <item>remainder - unused</item> 453 /// </list> 454 /// </remarks> 455 public int HostSystem { 456 get { 457 return (versionMadeBy >> 8) & 0xff; 458 } 459 460 set { 461 versionMadeBy &= 0xff; 462 versionMadeBy |= (ushort)((value & 0xff) << 8); 463 } 464 } 465 466 /// <summary> 467 /// Get minimum Zip feature version required to extract this entry 468 /// </summary> 469 /// <remarks> 470 /// Minimum features are defined as:<br/> 471 /// 1.0 - Default value<br/> 472 /// 1.1 - File is a volume label<br/> 473 /// 2.0 - File is a folder/directory<br/> 474 /// 2.0 - File is compressed using Deflate compression<br/> 475 /// 2.0 - File is encrypted using traditional encryption<br/> 476 /// 2.1 - File is compressed using Deflate64<br/> 477 /// 2.5 - File is compressed using PKWARE DCL Implode<br/> 478 /// 2.7 - File is a patch data set<br/> 479 /// 4.5 - File uses Zip64 format extensions<br/> 480 /// 4.6 - File is compressed using BZIP2 compression<br/> 481 /// 5.0 - File is encrypted using DES<br/> 482 /// 5.0 - File is encrypted using 3DES<br/> 483 /// 5.0 - File is encrypted using original RC2 encryption<br/> 484 /// 5.0 - File is encrypted using RC4 encryption<br/> 485 /// 5.1 - File is encrypted using AES encryption<br/> 486 /// 5.1 - File is encrypted using corrected RC2 encryption<br/> 487 /// 5.1 - File is encrypted using corrected RC2-64 encryption<br/> 488 /// 6.1 - File is encrypted using non-OAEP key wrapping<br/> 489 /// 6.2 - Central directory encryption (not confirmed yet)<br/> 490 /// 6.3 - File is compressed using LZMA<br/> 491 /// 6.3 - File is compressed using PPMD+<br/> 492 /// 6.3 - File is encrypted using Blowfish<br/> 493 /// 6.3 - File is encrypted using Twofish<br/> 494 /// </remarks> 495 /// <seealso cref="CanDecompress"></seealso> 496 public int Version { 497 get { 498 // Return recorded version if known. 499 if (versionToExtract != 0) { 500 return versionToExtract & 0x00ff; // Only lower order byte. High order is O/S file system. 501 } else { 502 int result = 10; 503 if (AESKeySize > 0) { 504 result = ZipConstants.VERSION_AES; // Ver 5.1 = AES 505 } else if (CentralHeaderRequiresZip64) { 506 result = ZipConstants.VersionZip64; 507 } else if (CompressionMethod.Deflated == method) { 508 result = 20; 509 } else if (IsDirectory == true) { 510 result = 20; 511 } else if (IsCrypted == true) { 512 result = 20; 513 } else if (HasDosAttributes(0x08)) { 514 result = 11; 515 } 516 return result; 517 } 518 } 519 } 520 521 /// <summary> 522 /// Get a value indicating whether this entry can be decompressed by the library. 523 /// </summary> 524 /// <remarks>This is based on the <see cref="Version"></see> and 525 /// wether the <see cref="IsCompressionMethodSupported()">compression method</see> is supported.</remarks> 526 public bool CanDecompress { 527 get { 528 return (Version <= ZipConstants.VersionMadeBy) && 529 ((Version == 10) || 530 (Version == 11) || 531 (Version == 20) || 532 (Version == 45) || 533 (Version == 51)) && 534 IsCompressionMethodSupported(); 535 } 536 } 537 538 /// <summary> 539 /// Force this entry to be recorded using Zip64 extensions. 540 /// </summary> ForceZip64()541 public void ForceZip64() 542 { 543 forceZip64_ = true; 544 } 545 546 /// <summary> 547 /// Get a value indicating wether Zip64 extensions were forced. 548 /// </summary> 549 /// <returns>A <see cref="bool"/> value of true if Zip64 extensions have been forced on; false if not.</returns> IsZip64Forced()550 public bool IsZip64Forced() 551 { 552 return forceZip64_; 553 } 554 555 /// <summary> 556 /// Gets a value indicating if the entry requires Zip64 extensions 557 /// to store the full entry values. 558 /// </summary> 559 /// <value>A <see cref="bool"/> value of true if a local header requires Zip64 extensions; false if not.</value> 560 public bool LocalHeaderRequiresZip64 { 561 get { 562 bool result = forceZip64_; 563 564 if (!result) { 565 ulong trueCompressedSize = compressedSize; 566 567 if ((versionToExtract == 0) && IsCrypted) { 568 trueCompressedSize += ZipConstants.CryptoHeaderSize; 569 } 570 571 // TODO: A better estimation of the true limit based on compression overhead should be used 572 // to determine when an entry should use Zip64. 573 result = 574 ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) && 575 ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64)); 576 } 577 578 return result; 579 } 580 } 581 582 /// <summary> 583 /// Get a value indicating wether the central directory entry requires Zip64 extensions to be stored. 584 /// </summary> 585 public bool CentralHeaderRequiresZip64 { 586 get { 587 return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); 588 } 589 } 590 591 /// <summary> 592 /// Get/Set DosTime value. 593 /// </summary> 594 /// <remarks> 595 /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. 596 /// </remarks> 597 public long DosTime { 598 get { 599 if ((known & Known.Time) == 0) { 600 return 0; 601 } else { 602 return dosTime; 603 } 604 } 605 606 set { 607 unchecked { 608 dosTime = (uint)value; 609 } 610 611 known |= Known.Time; 612 } 613 } 614 615 /// <summary> 616 /// Gets/Sets the time of last modification of the entry. 617 /// </summary> 618 /// <remarks> 619 /// The <see cref="DosTime"></see> property is updated to match this as far as possible. 620 /// </remarks> 621 public DateTime DateTime 622 { 623 get 624 { 625 uint sec = Math.Min(59, 2 * (dosTime & 0x1f)); 626 uint min = Math.Min(59, (dosTime >> 5) & 0x3f); 627 uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f); 628 uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf))); 629 uint year = ((dosTime >> 25) & 0x7f) + 1980; 630 int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f))); 631 return new System.DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec); 632 } 633 634 set { 635 var year = (uint)value.Year; 636 var month = (uint)value.Month; 637 var day = (uint)value.Day; 638 var hour = (uint)value.Hour; 639 var minute = (uint)value.Minute; 640 var second = (uint)value.Second; 641 642 if (year < 1980) { 643 year = 1980; 644 month = 1; 645 day = 1; 646 hour = 0; 647 minute = 0; 648 second = 0; 649 } else if (year > 2107) { 650 year = 2107; 651 month = 12; 652 day = 31; 653 hour = 23; 654 minute = 59; 655 second = 59; 656 } 657 658 DosTime = ((year - 1980) & 0x7f) << 25 | 659 (month << 21) | 660 (day << 16) | 661 (hour << 11) | 662 (minute << 5) | 663 (second >> 1); 664 } 665 } 666 667 /// <summary> 668 /// Returns the entry name. 669 /// </summary> 670 /// <remarks> 671 /// The unix naming convention is followed. 672 /// Path components in the entry should always separated by forward slashes ('/'). 673 /// Dos device names like C: should also be removed. 674 /// See the <see cref="ZipNameTransform"/> class, or <see cref="CleanName(string)"/> 675 ///</remarks> 676 public string Name { 677 get { 678 return name; 679 } 680 } 681 682 /// <summary> 683 /// Gets/Sets the size of the uncompressed data. 684 /// </summary> 685 /// <returns> 686 /// The size or -1 if unknown. 687 /// </returns> 688 /// <remarks>Setting the size before adding an entry to an archive can help 689 /// avoid compatability problems with some archivers which dont understand Zip64 extensions.</remarks> 690 public long Size { 691 get { 692 return (known & Known.Size) != 0 ? (long)size : -1L; 693 } 694 set { 695 this.size = (ulong)value; 696 this.known |= Known.Size; 697 } 698 } 699 700 /// <summary> 701 /// Gets/Sets the size of the compressed data. 702 /// </summary> 703 /// <returns> 704 /// The compressed entry size or -1 if unknown. 705 /// </returns> 706 public long CompressedSize { 707 get { 708 return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; 709 } 710 set { 711 this.compressedSize = (ulong)value; 712 this.known |= Known.CompressedSize; 713 } 714 } 715 716 /// <summary> 717 /// Gets/Sets the crc of the uncompressed data. 718 /// </summary> 719 /// <exception cref="System.ArgumentOutOfRangeException"> 720 /// Crc is not in the range 0..0xffffffffL 721 /// </exception> 722 /// <returns> 723 /// The crc value or -1 if unknown. 724 /// </returns> 725 public long Crc { 726 get { 727 return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; 728 } 729 set { 730 if (((ulong)crc & 0xffffffff00000000L) != 0) { 731 throw new ArgumentOutOfRangeException(nameof(value)); 732 } 733 this.crc = (uint)value; 734 this.known |= Known.Crc; 735 } 736 } 737 738 /// <summary> 739 /// Gets/Sets the compression method. Only Deflated and Stored are supported. 740 /// </summary> 741 /// <returns> 742 /// The compression method for this entry 743 /// </returns> 744 /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Deflated"/> 745 /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Stored"/> 746 public CompressionMethod CompressionMethod { 747 get { 748 return method; 749 } 750 751 set { 752 if (!IsCompressionMethodSupported(value)) { 753 throw new NotSupportedException("Compression method not supported"); 754 } 755 this.method = value; 756 } 757 } 758 759 /// <summary> 760 /// Gets the compression method for outputting to the local or central header. 761 /// Returns same value as CompressionMethod except when AES encrypting, which 762 /// places 99 in the method and places the real method in the extra data. 763 /// </summary> 764 internal CompressionMethod CompressionMethodForHeader { 765 get { 766 return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; 767 } 768 } 769 770 /// <summary> 771 /// Gets/Sets the extra data. 772 /// </summary> 773 /// <exception cref="System.ArgumentOutOfRangeException"> 774 /// Extra data is longer than 64KB (0xffff) bytes. 775 /// </exception> 776 /// <returns> 777 /// Extra data or null if not set. 778 /// </returns> 779 public byte[] ExtraData { 780 781 get { 782 // TODO: This is slightly safer but less efficient. Think about wether it should change. 783 // return (byte[]) extra.Clone(); 784 return extra; 785 } 786 787 set { 788 if (value == null) { 789 extra = null; 790 } else { 791 if (value.Length > 0xffff) { 792 throw new System.ArgumentOutOfRangeException(nameof(value)); 793 } 794 795 extra = new byte[value.Length]; 796 Array.Copy(value, 0, extra, 0, value.Length); 797 } 798 } 799 } 800 801 802 /// <summary> 803 /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256). 804 /// When setting, only 0 (off), 128 or 256 is supported. 805 /// </summary> 806 public int AESKeySize { 807 get { 808 // the strength (1 or 3) is in the entry header 809 switch (_aesEncryptionStrength) { 810 case 0: 811 return 0; // Not AES 812 case 1: 813 return 128; 814 case 2: 815 return 192; // Not used by WinZip 816 case 3: 817 return 256; 818 default: 819 throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength); 820 } 821 } 822 set { 823 switch (value) { 824 case 0: 825 _aesEncryptionStrength = 0; 826 break; 827 case 128: 828 _aesEncryptionStrength = 1; 829 break; 830 case 256: 831 _aesEncryptionStrength = 3; 832 break; 833 default: 834 throw new ZipException("AESKeySize must be 0, 128 or 256: " + value); 835 } 836 } 837 } 838 839 /// <summary> 840 /// AES Encryption strength for storage in extra data in entry header. 841 /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. 842 /// </summary> 843 internal byte AESEncryptionStrength { 844 get { 845 return (byte)_aesEncryptionStrength; 846 } 847 } 848 849 /// <summary> 850 /// Returns the length of the salt, in bytes 851 /// </summary> 852 internal int AESSaltLen { 853 get { 854 // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. 855 return AESKeySize / 16; 856 } 857 } 858 859 /// <summary> 860 /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) 861 /// </summary> 862 internal int AESOverheadSize { 863 get { 864 // File format: 865 // Bytes Content 866 // Variable Salt value 867 // 2 Password verification value 868 // Variable Encrypted file data 869 // 10 Authentication code 870 return 12 + AESSaltLen; 871 } 872 } 873 874 /// <summary> 875 /// Process extra data fields updating the entry based on the contents. 876 /// </summary> 877 /// <param name="localHeader">True if the extra data fields should be handled 878 /// for a local header, rather than for a central header. 879 /// </param> ProcessExtraData(bool localHeader)880 internal void ProcessExtraData(bool localHeader) 881 { 882 var extraData = new ZipExtraData(this.extra); 883 884 if (extraData.Find(0x0001)) { 885 // Version required to extract is ignored here as some archivers dont set it correctly 886 // in theory it should be version 45 or higher 887 888 // The recorded size will change but remember that this is zip64. 889 forceZip64_ = true; 890 891 if (extraData.ValueLength < 4) { 892 throw new ZipException("Extra data extended Zip64 information length is invalid"); 893 } 894 895 // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between local header & central directory 896 // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT 897 // ... 898 // 4.4 Explanation of fields 899 // ... 900 // 4.4.8 compressed size: (4 bytes) 901 // 4.4.9 uncompressed size: (4 bytes) 902 // 903 // The size of the file compressed (4.4.8) and uncompressed, 904 // (4.4.9) respectively. When a decryption header is present it 905 // will be placed in front of the file data and the value of the 906 // compressed file size will include the bytes of the decryption 907 // header. If bit 3 of the general purpose bit flag is set, 908 // these fields are set to zero in the local header and the 909 // correct values are put in the data descriptor and 910 // in the central directory. If an archive is in ZIP64 format 911 // and the value in this field is 0xFFFFFFFF, the size will be 912 // in the corresponding 8 byte ZIP64 extended information 913 // extra field. When encrypting the central directory, if the 914 // local header is not in ZIP64 format and general purpose bit 915 // flag 13 is set indicating masking, the value stored for the 916 // uncompressed size in the Local Header will be zero. 917 // 918 // Othewise there is problem with minizip implementation 919 if (size == uint.MaxValue) { 920 size = (ulong)extraData.ReadLong(); 921 } 922 923 if (compressedSize == uint.MaxValue) { 924 compressedSize = (ulong)extraData.ReadLong(); 925 } 926 927 if (!localHeader && (offset == uint.MaxValue)) { 928 offset = extraData.ReadLong(); 929 } 930 931 // Disk number on which file starts is ignored 932 } else { 933 if ( 934 ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && 935 ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) 936 ) { 937 throw new ZipException("Zip64 Extended information required but is missing."); 938 } 939 } 940 941 DateTime = GetDateTime(extraData); 942 if (method == CompressionMethod.WinZipAES) { 943 ProcessAESExtraData(extraData); 944 } 945 } 946 GetDateTime(ZipExtraData extraData)947 private DateTime GetDateTime(ZipExtraData extraData) { 948 // Check for NT timestamp 949 // NOTE: Disable by default to match behavior of InfoZIP 950 #if RESPECT_NT_TIMESTAMP 951 NTTaggedData ntData = extraData.GetData<NTTaggedData>(); 952 if (ntData != null) 953 return ntData.LastModificationTime; 954 #endif 955 956 // Check for Unix timestamp 957 ExtendedUnixData unixData = extraData.GetData<ExtendedUnixData>(); 958 if (unixData != null && 959 // Only apply modification time, but require all other values to be present 960 // This is done to match InfoZIP's behaviour 961 ((unixData.Include & ExtendedUnixData.Flags.ModificationTime) != 0) && 962 ((unixData.Include & ExtendedUnixData.Flags.AccessTime) != 0) && 963 ((unixData.Include & ExtendedUnixData.Flags.CreateTime) != 0)) 964 return unixData.ModificationTime; 965 966 // Fall back to DOS time 967 uint sec = Math.Min(59, 2 * (dosTime & 0x1f)); 968 uint min = Math.Min(59, (dosTime >> 5) & 0x3f); 969 uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f); 970 uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf))); 971 uint year = ((dosTime >> 25) & 0x7f) + 1980; 972 int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f))); 973 return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc); 974 } 975 976 // For AES the method in the entry is 99, and the real compression method is in the extradata 977 // ProcessAESExtraData(ZipExtraData extraData)978 private void ProcessAESExtraData(ZipExtraData extraData) 979 { 980 981 if (extraData.Find(0x9901)) { 982 // Set version and flag for Zipfile.CreateAndInitDecryptionStream 983 versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter 984 // Set StrongEncryption flag for ZipFile.CreateAndInitDecryptionStream 985 Flags = Flags | (int)GeneralBitFlags.StrongEncryption; 986 // 987 // Unpack AES extra data field see http://www.winzip.com/aes_info.htm 988 int length = extraData.ValueLength; // Data size currently 7 989 if (length < 7) 990 throw new ZipException("AES Extra Data Length " + length + " invalid."); 991 int ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2) 992 int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" 993 int encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256 994 int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file 995 _aesVer = ver; 996 _aesEncryptionStrength = encrStrength; 997 method = (CompressionMethod)actualCompress; 998 } else 999 throw new ZipException("AES Extra Data missing"); 1000 } 1001 1002 /// <summary> 1003 /// Gets/Sets the entry comment. 1004 /// </summary> 1005 /// <exception cref="System.ArgumentOutOfRangeException"> 1006 /// If comment is longer than 0xffff. 1007 /// </exception> 1008 /// <returns> 1009 /// The comment or null if not set. 1010 /// </returns> 1011 /// <remarks> 1012 /// A comment is only available for entries when read via the <see cref="ZipFile"/> class. 1013 /// The <see cref="ZipInputStream"/> class doesnt have the comment data available. 1014 /// </remarks> 1015 public string Comment { 1016 get { 1017 return comment; 1018 } 1019 set { 1020 // This test is strictly incorrect as the length is in characters 1021 // while the storage limit is in bytes. 1022 // While the test is partially correct in that a comment of this length or greater 1023 // is definitely invalid, shorter comments may also have an invalid length 1024 // where there are multi-byte characters 1025 // The full test is not possible here however as the code page to apply conversions with 1026 // isnt available. 1027 if ((value != null) && (value.Length > 0xffff)) { 1028 throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); 1029 } 1030 1031 comment = value; 1032 } 1033 } 1034 1035 /// <summary> 1036 /// Gets a value indicating if the entry is a directory. 1037 /// however. 1038 /// </summary> 1039 /// <remarks> 1040 /// A directory is determined by an entry name with a trailing slash '/'. 1041 /// The external file attributes can also indicate an entry is for a directory. 1042 /// Currently only dos/windows attributes are tested in this manner. 1043 /// The trailing slash convention should always be followed. 1044 /// </remarks> 1045 public bool IsDirectory { 1046 get { 1047 int nameLength = name.Length; 1048 bool result = 1049 ((nameLength > 0) && 1050 ((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) || 1051 HasDosAttributes(16) 1052 ; 1053 return result; 1054 } 1055 } 1056 1057 /// <summary> 1058 /// Get a value of true if the entry appears to be a file; false otherwise 1059 /// </summary> 1060 /// <remarks> 1061 /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. 1062 /// For linux and others the result may be incorrect. 1063 /// </remarks> 1064 public bool IsFile { 1065 get { 1066 return !IsDirectory && !HasDosAttributes(8); 1067 } 1068 } 1069 1070 /// <summary> 1071 /// Test entry to see if data can be extracted. 1072 /// </summary> 1073 /// <returns>Returns true if data can be extracted for this entry; false otherwise.</returns> IsCompressionMethodSupported()1074 public bool IsCompressionMethodSupported() 1075 { 1076 return IsCompressionMethodSupported(CompressionMethod); 1077 } 1078 1079 #region ICloneable Members 1080 /// <summary> 1081 /// Creates a copy of this zip entry. 1082 /// </summary> 1083 /// <returns>An <see cref="Object"/> that is a copy of the current instance.</returns> Clone()1084 public object Clone() 1085 { 1086 var result = (ZipEntry)this.MemberwiseClone(); 1087 1088 // Ensure extra data is unique if it exists. 1089 if (extra != null) { 1090 result.extra = new byte[extra.Length]; 1091 Array.Copy(extra, 0, result.extra, 0, extra.Length); 1092 } 1093 1094 return result; 1095 } 1096 1097 #endregion 1098 1099 /// <summary> 1100 /// Gets a string representation of this ZipEntry. 1101 /// </summary> 1102 /// <returns>A readable textual representation of this <see cref="ZipEntry"/></returns> ToString()1103 public override string ToString() 1104 { 1105 return name; 1106 } 1107 1108 /// <summary> 1109 /// Test a <see cref="CompressionMethod">compression method</see> to see if this library 1110 /// supports extracting data compressed with that method 1111 /// </summary> 1112 /// <param name="method">The compression method to test.</param> 1113 /// <returns>Returns true if the compression method is supported; false otherwise</returns> IsCompressionMethodSupported(CompressionMethod method)1114 public static bool IsCompressionMethodSupported(CompressionMethod method) 1115 { 1116 return 1117 (method == CompressionMethod.Deflated) || 1118 (method == CompressionMethod.Stored); 1119 } 1120 1121 /// <summary> 1122 /// Cleans a name making it conform to Zip file conventions. 1123 /// Devices names ('c:\') and UNC share names ('\\server\share') are removed 1124 /// and forward slashes ('\') are converted to back slashes ('/'). 1125 /// Names are made relative by trimming leading slashes which is compatible 1126 /// with the ZIP naming convention. 1127 /// </summary> 1128 /// <param name="name">The name to clean</param> 1129 /// <returns>The 'cleaned' name.</returns> 1130 /// <remarks> 1131 /// The <seealso cref="ZipNameTransform">Zip name transform</seealso> class is more flexible. 1132 /// </remarks> CleanName(string name)1133 public static string CleanName(string name) 1134 { 1135 if (name == null) { 1136 return string.Empty; 1137 } 1138 1139 if (Path.IsPathRooted(name)) { 1140 // NOTE: 1141 // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt 1142 name = name.Substring(Path.GetPathRoot(name).Length); 1143 } 1144 1145 name = name.Replace(@"\", "/"); 1146 1147 while ((name.Length > 0) && (name[0] == '/')) { 1148 name = name.Remove(0, 1); 1149 } 1150 return name; 1151 } 1152 1153 #region Instance Fields 1154 Known known; 1155 int externalFileAttributes = -1; // contains external attributes (O/S dependant) 1156 1157 ushort versionMadeBy; // Contains host system and version information 1158 // only relevant for central header entries 1159 1160 string name; 1161 ulong size; 1162 ulong compressedSize; 1163 ushort versionToExtract; // Version required to extract (library handles <= 2.0) 1164 uint crc; 1165 uint dosTime; 1166 1167 CompressionMethod method = CompressionMethod.Deflated; 1168 byte[] extra; 1169 string comment; 1170 1171 int flags; // general purpose bit flags 1172 1173 long zipFileIndex = -1; // used by ZipFile 1174 long offset; // used by ZipFile and ZipOutputStream 1175 1176 bool forceZip64_; 1177 byte cryptoCheckValue_; 1178 int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. 1179 int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 1180 #endregion 1181 } 1182 } 1183