ZipEntry.cs 36 KB

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