You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1252 lines
37 KiB

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