PageRenderTime 34ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/dotnet/common-windsor-net/windsor-commons-compression/DotNetZip/Library/ZipEntry.cs

http://opennode2.googlecode.com/
C# | 3960 lines | 2441 code | 327 blank | 1192 comment | 246 complexity | 0490151077d9c42f7c2318602c17223c MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0, Apache-2.0, LGPL-2.1, CC-BY-SA-3.0, MPL-2.0-no-copyleft-exception
  1. #region License
  2. /*
  3. Copyright (c) 2009, The Environmental Council of the States (ECOS)
  4. All rights reserved.
  5. Redistribution and use in source and binary forms, with or without
  6. modification, are permitted provided that the following conditions
  7. are met:
  8. * Redistributions of source code must retain the above copyright
  9. notice, this list of conditions and the following disclaimer.
  10. * Redistributions in binary form must reproduce the above copyright
  11. notice, this list of conditions and the following disclaimer in the
  12. documentation and/or other materials provided with the distribution.
  13. * Neither the name of the ECOS nor the names of its contributors may
  14. be used to endorse or promote products derived from this software
  15. without specific prior written permission.
  16. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  17. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  18. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  19. FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  20. COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  21. INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  22. BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  24. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  25. LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  26. ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. POSSIBILITY OF SUCH DAMAGE.
  28. */
  29. #endregion
  30. #define OPTIMIZE_WI6612
  31. // ZipEntry.cs
  32. //
  33. // Copyright (c) 2006, 2007, 2008, 2009 Microsoft Corporation. All rights reserved.
  34. //
  35. // Part of an implementation of a zipfile class library.
  36. // See the file ZipFile.cs for the license and for further information.
  37. //
  38. // Created: Tue, 27 Mar 2007 15:30
  39. //
  40. using System;
  41. using System.IO;
  42. using RE = System.Text.RegularExpressions;
  43. namespace Ionic.Zip
  44. {
  45. /// <summary>
  46. /// An enum that provides the various encryption algorithms supported by this library.
  47. /// </summary>
  48. /// <remarks>
  49. /// <para>
  50. /// PkzipWeak implies the use of Zip 2.0 encryption, which is known to be weak and subvertible.
  51. /// </para>
  52. /// <para>
  53. /// A note on interoperability: Values of PkzipWeak and None are specified in the PKWare AppNote.txt document, are
  54. /// considered to be "standard". Zip archives produced using these options will be interoperable with many other
  55. /// zip tools and libraries, including Windows Explorer.
  56. /// </para>
  57. /// <para>
  58. /// Values of WinZipAes128 and WinZipAes256 are not part of the Zip specification, but rather imply the use of a
  59. /// vendor-specific extension from WinZip. If you want to produce interoperable Zip archives, do not use these values.
  60. /// For example, if you
  61. /// produce a zip archive using WinZipAes256, you will be able to open it in Windows Explorer on Windows XP and Vista,
  62. /// but you will not be able to extract entries; trying this will lead to an "unspecified error". For this reason,
  63. /// some people have said that a zip archive that uses WinZip's AES encryption is not actually a zip archive at all.
  64. /// A zip archive produced this way will be readable with the WinZip tool
  65. /// (Version 11 and beyond).
  66. /// </para>
  67. /// <para>
  68. /// There are other third-party tools and libraries, both commercial and otherwise, that support WinZip's
  69. /// AES encryption. These will be able to read AES-encrypted zip archives produced by DotNetZip, and conversely applications
  70. /// that use DotNetZip to read zip archives will be able to read AES-encrypted archives produced by those tools
  71. /// or libraries. Consult the documentation for those other tools and libraries to find out if WinZip's AES
  72. /// encryption is supported.
  73. /// </para>
  74. /// <para>
  75. /// In case you care: According to the WinZip specification, the actual key used is derived from the
  76. /// <see cref="ZipEntry.Password"/> via an algorithm that complies with RFC 2898, using an iteration count of 1000.
  77. /// I am no security expert, but I think you should use a long-ish password if you employ 256-bit AES
  78. /// encryption. Make it 16 characters or more.
  79. /// </para>
  80. /// <para>
  81. /// The WinZip AES algorithms are not supported with the version of DotNetZip that runs on the .NET Compact Framework.
  82. /// This is because .NET CF lacks the HMACSHA1 class that is required for producing the archive.
  83. /// </para>
  84. /// </remarks>
  85. public enum EncryptionAlgorithm
  86. {
  87. /// <summary>
  88. /// No encryption at all.
  89. /// </summary>
  90. None = 0,
  91. /// <summary>
  92. /// Traditional or Classic pkzip encryption.
  93. /// </summary>
  94. PkzipWeak,
  95. #if AESCRYPTO
  96. /// <summary>
  97. /// WinZip AES encryption (128 key bits).
  98. /// </summary>
  99. WinZipAes128,
  100. /// <summary>
  101. /// WinZip AES encryption (256 key bits).
  102. /// </summary>
  103. WinZipAes256,
  104. #endif
  105. // others... not implemented (yet?)
  106. }
  107. /// <summary>
  108. /// An enum that specifies the source of the ZipEntry.
  109. /// </summary>
  110. internal enum EntrySource
  111. {
  112. /// <summary>
  113. /// Default value. Invalid on a bonafide ZipEntry.
  114. /// </summary>
  115. None = 0,
  116. /// <summary>
  117. /// Entry was instantiated by Adding an entry from the filesystem.
  118. /// </summary>
  119. Filesystem,
  120. /// <summary>
  121. /// Entry was instantiated by reading a zipfile.
  122. /// </summary>
  123. Zipfile,
  124. /// <summary>
  125. /// Entry was instantiated via a stream or string.
  126. /// </summary>
  127. Stream,
  128. }
  129. /// <summary>
  130. /// Represents a single entry in a ZipFile. Typically, applications
  131. /// get a ZipEntry by enumerating the entries within a ZipFile,
  132. /// or by adding an entry to a ZipFile.
  133. /// </summary>
  134. public partial class ZipEntry
  135. {
  136. internal ZipEntry() { }
  137. /// <summary>
  138. /// The time and date at which the file indicated by the ZipEntry was last modified.
  139. /// </summary>
  140. ///
  141. /// <remarks>
  142. /// <para>
  143. /// The DotNetZip library sets the LastModified value for an entry, equal to the
  144. /// Last Modified time of the file in the filesystem. If an entry is added from a stream,
  145. /// in which case no Last Modified attribute is available, the library uses
  146. /// <c>System.DateTime.Now</c> for this value, for the given entry.
  147. /// </para>
  148. ///
  149. /// <para>
  150. /// It is also possible to set the LastModified value on an entry, to an arbitrary
  151. /// value. Be aware that because of the way the PKZip specification describes how
  152. /// times are stored in the zip file, the full precision of the
  153. /// <c>System.DateTime</c> datatype is not stored in LastModified when saving zip
  154. /// files. For more information on how times are formatted, see the PKZip
  155. /// specification.
  156. /// </para>
  157. ///
  158. /// <para>
  159. /// The last modified time of the file created upon a call to <c>ZipEntry.Extract()</c>
  160. /// may be adjusted during extraction to compensate
  161. /// for differences in how the .NET Base Class Library deals
  162. /// with daylight saving time (DST) versus how the Windows
  163. /// filesystem deals with daylight saving time.
  164. /// See http://blogs.msdn.com/oldnewthing/archive/2003/10/24/55413.aspx for more context.
  165. /// </para>
  166. /// <para>
  167. /// In a nutshell: Daylight savings time rules change regularly. In
  168. /// 2007, for example, the inception week of DST changed. In 1977,
  169. /// DST was in place all year round. In 1945, likewise. And so on.
  170. /// Win32 does not attempt to guess which time zone rules were in
  171. /// effect at the time in question. It will render a time as
  172. /// "standard time" and allow the app to change to DST as necessary.
  173. /// .NET makes a different choice.
  174. /// </para>
  175. /// <para>
  176. /// Compare the output of FileInfo.LastWriteTime.ToString("f") with
  177. /// what you see in the Windows Explorer property sheet for a file that was last
  178. /// written to on the other side of the DST transition. For example,
  179. /// suppose the file was last modified on October 17, 2003, during DST but
  180. /// DST is not currently in effect. Explorer's file properties
  181. /// reports Thursday, October 17, 2003, 8:45:38 AM, but .NETs
  182. /// FileInfo reports Thursday, October 17, 2003, 9:45 AM.
  183. /// </para>
  184. /// <para>
  185. /// Win32 says, "Thursday, October 17, 2002 8:45:38 AM PST". Note:
  186. /// Pacific STANDARD Time. Even though October 17 of that year
  187. /// occurred during Pacific Daylight Time, Win32 displays the time as
  188. /// standard time because that's what time it is NOW.
  189. /// </para>
  190. /// <para>
  191. /// .NET BCL assumes that the current DST rules were in place at the
  192. /// time in question. So, .NET says, "Well, if the rules in effect
  193. /// now were also in effect on October 17, 2003, then that would be
  194. /// daylight time" so it displays "Thursday, October 17, 2003, 9:45
  195. /// AM PDT" - daylight time.
  196. /// </para>
  197. /// <para>
  198. /// So .NET gives a value which is more intuitively correct, but is
  199. /// also potentially incorrect, and which is not invertible. Win32
  200. /// gives a value which is intuitively incorrect, but is strictly
  201. /// correct.
  202. /// </para>
  203. /// <para>
  204. /// Because of this funkiness, this library adds one hour to the LastModified time
  205. /// on the extracted file, if necessary. That is to say, if the time in question
  206. /// had occurred in what the .NET Base Class Library assumed to be DST (an
  207. /// assumption that may be wrong given the constantly changing DST rules).
  208. /// </para>
  209. /// </remarks>
  210. ///
  211. public DateTime LastModified
  212. {
  213. get { return _LastModified; }
  214. set
  215. {
  216. _LastModified = value;
  217. //SetLastModDateTimeWithAdjustment(this);
  218. _metadataChanged = true;
  219. }
  220. }
  221. #if LEGACY
  222. /// <summary>
  223. /// When this is set, this class trims the volume (eg C:\) from any
  224. /// fully-qualified pathname on the ZipEntry, before writing the ZipEntry into
  225. /// the ZipFile. This flag affects only zip creation. By default, this flag is TRUE,
  226. /// which means volume names will not be included in the filenames on entries in
  227. /// the archive. Your best bet is to just leave this alone.
  228. /// </summary>
  229. internal bool TrimVolumeFromFullyQualifiedPaths
  230. {
  231. get { return _TrimVolumeFromFullyQualifiedPaths; }
  232. set { _TrimVolumeFromFullyQualifiedPaths = value; }
  233. }
  234. #endif
  235. /// <summary>
  236. /// When this is set, the entry is not compressed when written to
  237. /// the archive. For example, the application might want to set flag to <c>true</c>
  238. /// this when zipping up JPG or MP3 files, which are already compressed.
  239. /// </summary>
  240. /// <seealso cref="Ionic.Zip.ZipFile.ForceNoCompression"/>
  241. ///
  242. public bool ForceNoCompression
  243. {
  244. get { return _ForceNoCompression; }
  245. set
  246. {
  247. if (value == _ForceNoCompression) return; // nothing to do.
  248. // If the source is a zip archive and there was encryption on the
  249. // entry, changing the compression method is not supported.
  250. if (this._Source == EntrySource.Zipfile && _sourceIsEncrypted)
  251. throw new InvalidOperationException("Cannot change compression method on encrypted entries read from archives.");
  252. _ForceNoCompression = value;
  253. }
  254. }
  255. /// <summary>
  256. /// The name of the filesystem file, referred to by the ZipEntry.
  257. /// </summary>
  258. ///
  259. /// <remarks>
  260. /// <para>
  261. /// This may be different than the path used in the archive itself. What I mean is,
  262. /// if you call <c>Zip.AddFile("fooo.txt", AlternativeDirectory)</c>, then the
  263. /// path used for the ZipEntry within the zip archive will be different than this path.
  264. /// This path is used to locate the thing-to-be-zipped on disk.
  265. /// </para>
  266. /// <para>
  267. /// If the entry is being added from a stream, then this is null (Nothing in VB).
  268. /// </para>
  269. ///
  270. /// </remarks>
  271. /// <seealso cref="FileName"/>
  272. public string LocalFileName
  273. {
  274. get { return _LocalFileName; }
  275. }
  276. /// <summary>
  277. /// The name of the file contained in the ZipEntry.
  278. /// </summary>
  279. ///
  280. /// <remarks>
  281. /// <para>
  282. /// When writing a zip, this path has backslashes replaced with
  283. /// forward slashes, according to the zip spec, for compatibility
  284. /// with Unix(tm) and ... get this.... Amiga!
  285. /// </para>
  286. ///
  287. /// <para>
  288. /// This is the name of the entry in the ZipFile itself. This name may be different
  289. /// than the name of the filesystem file used to create the entry (LocalFileName). In fact, there
  290. /// may be no filesystem file at all, if the entry is created from a stream or a string.
  291. /// </para>
  292. ///
  293. /// <para>
  294. /// When setting this property, the value is made permanent only after a call to one of the ZipFile.Save() methods
  295. /// on the ZipFile that contains the ZipEntry. By reading in a ZipFile, then explicitly setting the FileName on an
  296. /// entry contained within the ZipFile, and then calling Save(), you will effectively rename the entry within
  297. /// the zip archive.
  298. /// </para>
  299. /// </remarks>
  300. /// <seealso cref="LocalFileName"/>
  301. public string FileName
  302. {
  303. get { return _FileNameInArchive; }
  304. set
  305. {
  306. // rename the entry!
  307. if (value == null || value == "") throw new ZipException("The FileName must be non empty and non-null.");
  308. var filename = ZipEntry.NameInArchive(value, null);
  309. _FileNameInArchive = value;
  310. if (this._zipfile != null) this._zipfile.NotifyEntryChanged();
  311. _metadataChanged = true;
  312. }
  313. }
  314. /// <summary>
  315. /// The version of the zip engine needed to read the ZipEntry.
  316. /// </summary>
  317. /// <remarks>
  318. /// This is usually 0x14.
  319. /// (Decimal 20). If ZIP64 is in use, the version will be decimal 45.
  320. /// </remarks>
  321. public Int16 VersionNeeded
  322. {
  323. get { return _VersionNeeded; }
  324. }
  325. /// <summary>
  326. /// The comment attached to the ZipEntry.
  327. /// </summary>
  328. ///
  329. /// <remarks>
  330. /// By default, the Comment is encoded in IBM437 code page. You can specify
  331. /// an alternative with <see cref="ProvisionalAlternateEncoding"/>
  332. /// </remarks>
  333. /// <seealso cref="ProvisionalAlternateEncoding">ProvisionalAlternateEncoding</seealso>
  334. public string Comment
  335. {
  336. get { return _Comment; }
  337. set
  338. {
  339. _Comment = value;
  340. _metadataChanged = true;
  341. }
  342. }
  343. /// <summary>
  344. /// Indicates whether the entry requires ZIP64 extensions.
  345. /// </summary>
  346. /// <remarks>
  347. /// <para>
  348. /// This property is null (Nothing in VB) until a Save() method on the containing
  349. /// <see cref="ZipFile"/> instance has been called. The property is non-null (HasValue is true)
  350. /// only after a Save() method has been called.
  351. /// </para>
  352. /// <para>
  353. /// After the containing ZipFile has been saved. the Value of this property is true if any of the following
  354. /// three conditions holds: the uncompressed size of the entry is larger than 0xFFFFFFFF;
  355. /// the compressed size of the entry is larger than 0xFFFFFFFF; the relative offset of the entry within the
  356. /// zip archive is larger than 0xFFFFFFFF. These quantities are not known until a Save() is attempted on the
  357. /// zip archive and the compression is applied.
  358. /// </para>
  359. /// <para>If none of the three conditions holds, then the Value is false.</para>
  360. /// <para>
  361. /// A value of false does not indicate that the entry, as saved in the zip archive, does not use
  362. /// ZIP64. It merely indicates that ZIP64 is not required. An entry may use ZIP64 even when not
  363. /// required if the <see cref="ZipFile.UseZip64WhenSaving"/> property on the containing ZipFile
  364. /// instance is set to <see cref="Zip64Option.Always"/>, or if
  365. /// the <see cref="ZipFile.UseZip64WhenSaving"/> property on the containing ZipFile
  366. /// instance is set to <see cref="Zip64Option.AsNecessary"/> and the output stream was not seekable.
  367. /// </para>
  368. /// </remarks>
  369. /// <seealso cref="OutputUsedZip64"/>
  370. public Nullable<bool> RequiresZip64
  371. {
  372. get
  373. {
  374. return _entryRequiresZip64;
  375. }
  376. }
  377. /// <summary>
  378. /// Indicates whether the entry actually used ZIP64 extensions, as it was most recently written
  379. /// to the output file or stream.
  380. /// </summary>
  381. /// <remarks>
  382. /// <para>
  383. /// This Nullable property is null (Nothing in VB) until a Save() method on the containing
  384. /// <see cref="ZipFile"/> instance has been called. HasValue is true only after a Save() method
  385. /// has been called.
  386. /// </para>
  387. /// <para>
  388. /// The value of this property for a particular ZipEntry may change over successive calls to
  389. /// Save() methods on the containing ZipFile, even if the file that corresponds to the ZipEntry does
  390. /// not. This may happen if other entries contained in the ZipFile expand, causing the offset
  391. /// for this particular entry to exceed 0xFFFFFFFF.
  392. /// </para>
  393. /// </remarks>
  394. /// <seealso cref="RequiresZip64"/>
  395. public Nullable<bool> OutputUsedZip64
  396. {
  397. get { return _OutputUsesZip64; }
  398. }
  399. /// <summary>
  400. /// The bitfield as defined in the zip spec. You probably never need to look at this.
  401. /// </summary>
  402. ///
  403. /// <remarks>
  404. /// <code>
  405. /// bit 0 - set if encryption is used.
  406. /// b. 1-2 - set to determine whether normal, max, fast deflation.
  407. /// This library always leaves these bits unset when writing (indicating
  408. /// "normal" deflation").
  409. ///
  410. /// bit 3 - indicates crc32, compressed and uncompressed sizes are zero in
  411. /// local header. We always leave this as zero on writing, but can read
  412. /// a zip with it nonzero.
  413. ///
  414. /// bit 4 - reserved for "enhanced deflating". This library doesn't do enhanced deflating.
  415. /// bit 5 - set to indicate the zip is compressed patched data. This library doesn't do that.
  416. /// bit 6 - set if strong encryption is used (must also set bit 1 if bit 6 is set)
  417. /// bit 7 - unused
  418. /// bit 8 - unused
  419. /// bit 9 - unused
  420. /// bit 10 - unused
  421. /// Bit 11 - Language encoding flag (EFS). If this bit is set,
  422. /// the filename and comment fields for this file
  423. /// must be encoded using UTF-8. This library currently does not support UTF-8.
  424. /// Bit 12 - Reserved by PKWARE for enhanced compression.
  425. /// Bit 13 - Used when encrypting the Central Directory to indicate
  426. /// selected data values in the Local Header are masked to
  427. /// hide their actual values. See the section describing
  428. /// the Strong Encryption Specification for details.
  429. /// Bit 14 - Reserved by PKWARE.
  430. /// Bit 15 - Reserved by PKWARE.
  431. /// </code>
  432. /// </remarks>
  433. public Int16 BitField
  434. {
  435. get { return _BitField; }
  436. }
  437. /// <summary>
  438. /// The compression method employed for this ZipEntry.
  439. /// </summary>
  440. ///
  441. /// <remarks>
  442. /// <para>
  443. /// The ZIP specification allows a variety of compression methods. This library
  444. /// supports just two: 0x08 = Deflate. 0x00 = Store (no compression).
  445. /// </para>
  446. ///
  447. /// <para>
  448. /// When reading an entry from an existing zipfile, the value you retrieve here
  449. /// indicates the compression method used on the entry by the original creator of the zip.
  450. /// When writing a zipfile, you can specify either 0x08 (Deflate) or 0x00 (None). If you
  451. /// try setting something else, you will get an exception.
  452. /// </para>
  453. ///
  454. /// <para>
  455. /// You may wish to set CompressionMethod to 0 (None) when zipping previously compressed
  456. /// data like a jpg, png, or mp3 file. This can save time and cpu cycles.
  457. /// Setting CompressionMethod to 0 is equivalent to setting ForceNoCompression to true.
  458. /// </para>
  459. ///
  460. /// <para>
  461. /// When updating a ZipFile, you may not modify the CompressionMethod on an entry that has been encrypted.
  462. /// In other words, if you read an existing ZipFile with one of the ZipFile.Read() methods, and then
  463. /// change the CompressionMethod on an entry that has Encryption not equal to None, you will receive an exception.
  464. /// There is no way to modify the compression on an encrypted entry, without extracting it and re-adding it
  465. /// into the ZipFile.
  466. /// </para>
  467. /// </remarks>
  468. ///
  469. /// <example>
  470. /// In this example, the first entry added to the zip archive uses
  471. /// the default behavior - compression is used where it makes sense.
  472. /// The second entry, the MP3 file, is added to the archive without being compressed.
  473. /// <code>
  474. /// using (ZipFile zip = new ZipFile(ZipFileToCreate))
  475. /// {
  476. /// ZipEntry e1= zip.AddFile(@"c:\temp\Readme.txt");
  477. /// ZipEntry e2= zip.AddFile(@"c:\temp\StopThisTrain.mp3");
  478. /// e2.CompressionMethod = 0;
  479. /// zip.Save();
  480. /// }
  481. /// </code>
  482. ///
  483. /// <code lang="VB">
  484. /// Using zip as new ZipFile(ZipFileToCreate)
  485. /// zip.AddFile("c:\temp\Readme.txt")
  486. /// Dim e2 as ZipEntry = zip.AddFile("c:\temp\StopThisTrain.mp3")
  487. /// e2.CompressionMethod = 0
  488. /// zip.Save
  489. /// End Using
  490. /// </code>
  491. /// </example>
  492. public Int16 CompressionMethod
  493. {
  494. get { return _CompressionMethod; }
  495. set
  496. {
  497. if (value == _CompressionMethod) return; // nothing to do.
  498. if (value != 0x00 && value != 0x08)
  499. throw new InvalidOperationException("Unsupported compression method. Specify 8 or 0.");
  500. // If the source is a zip archive and there was encryption on the
  501. // entry, changing the compression method is not supported.
  502. if (this._Source == EntrySource.Zipfile && _sourceIsEncrypted)
  503. throw new InvalidOperationException("Cannot change compression method on encrypted entries read from archives.");
  504. _CompressionMethod = value;
  505. _ForceNoCompression = (_CompressionMethod == 0x0);
  506. _restreamRequiredOnSave = true;
  507. }
  508. }
  509. /// <summary>
  510. /// The compressed size of the file, in bytes, within the zip archive.
  511. /// </summary>
  512. /// <remarks>
  513. /// The compressed size is computed during compression. This means that it is only
  514. /// valid to read this AFTER reading in an existing zip file, or AFTER saving a
  515. /// zipfile you are creating.
  516. /// </remarks>
  517. public Int64 CompressedSize
  518. {
  519. get { return _CompressedSize; }
  520. }
  521. /// <summary>
  522. /// The size of the file, in bytes, before compression, or after extraction.
  523. /// </summary>
  524. /// <remarks>
  525. /// This property is valid AFTER reading in an existing zip file, or AFTER saving the
  526. /// ZipFile that contains the ZipEntry.
  527. /// </remarks>
  528. public Int64 UncompressedSize
  529. {
  530. get { return _UncompressedSize; }
  531. }
  532. /// <summary>
  533. /// The ratio of compressed size to uncompressed size of the ZipEntry.
  534. /// </summary>
  535. ///
  536. /// <remarks>
  537. /// <para>
  538. /// This is a ratio of the compressed size to the uncompressed size of the entry,
  539. /// expressed as a double in the range of 0 to 100+. A value of 100 indicates no
  540. /// compression at all. It could be higher than 100 when the compression algorithm
  541. /// actually inflates the data.
  542. /// </para>
  543. ///
  544. /// <para>
  545. /// You could format it for presentation to a user via a format string of "{3,5:F0}%"
  546. /// to see it as a percentage.
  547. /// </para>
  548. ///
  549. /// <para>
  550. /// If the size of the original uncompressed file is 0, (indicating a denominator of 0)
  551. /// the return value will be zero.
  552. /// </para>
  553. ///
  554. /// <para>
  555. /// This property is valid AFTER reading in an existing zip file, or AFTER saving the
  556. /// ZipFile that contains the ZipEntry.
  557. /// </para>
  558. ///
  559. /// </remarks>
  560. public Double CompressionRatio
  561. {
  562. get
  563. {
  564. if (UncompressedSize == 0) return 0;
  565. return 100 * (1.0 - (1.0 * CompressedSize) / (1.0 * UncompressedSize));
  566. }
  567. }
  568. /// <summary>
  569. /// The CRC (Cyclic Redundancy Check) on the contents of the ZipEntry.
  570. /// </summary>
  571. ///
  572. /// <remarks>
  573. /// You probably don't need to concern yourself with this. The CRC is generated according
  574. /// to the algorithm described in the Pkzip specification. It is a read-only property;
  575. /// when creating a Zip archive, the CRC for each entry is set only after a call to
  576. /// Save() on the containing ZipFile.
  577. /// </remarks>
  578. public Int32 Crc32
  579. {
  580. get { return _Crc32; }
  581. }
  582. /// <summary>
  583. /// True if the entry is a directory (not a file).
  584. /// This is a readonly property on the entry.
  585. /// </summary>
  586. public bool IsDirectory
  587. {
  588. get { return _IsDirectory; }
  589. }
  590. /// <summary>
  591. /// A derived property that is <c>true</c> if the entry uses encryption.
  592. /// </summary>
  593. /// <remarks>
  594. /// This is a readonly property on the entry.
  595. /// Upon reading an entry, this bool is determined by
  596. /// the data read. When writing an entry, this bool is
  597. /// determined by whether the Encryption property is set to something other than
  598. /// EncryptionAlgorithm.None.
  599. /// </remarks>
  600. public bool UsesEncryption
  601. {
  602. get { return (Encryption != EncryptionAlgorithm.None); }
  603. }
  604. /// <summary>
  605. /// Set this to specify which encryption algorithm to use for the entry.
  606. /// </summary>
  607. ///
  608. /// <remarks>
  609. /// <para>
  610. /// When setting this property, you must also set a Password on the entry. The set of
  611. /// algorithms supported is determined by the authors of this library. The PKZIP
  612. /// specification from PKWare defines a set of encryption algorithms, and the data formats
  613. /// for the zip archive that support them. Other vendors of tools and libraries, such as
  614. /// WinZip or Xceed, also specify and support different encryption algorithms and data
  615. /// formats.
  616. /// </para>
  617. ///
  618. /// <para>
  619. /// There is no common, ubiquitous multi-vendor standard for strong encryption. There is
  620. /// broad support for "traditional" Zip encryption, sometimes called Zip 2.0 encryption,
  621. /// as specified by PKWare, but this encryption is considered weak. This library currently
  622. /// supports AES 128 and 256 in addition to the Zip 2.0 "weak" encryption.
  623. /// </para>
  624. ///
  625. /// <para>
  626. /// The WinZip AES encryption algorithms are not supported on the .NET Compact Framework.
  627. /// </para>
  628. /// </remarks>
  629. public EncryptionAlgorithm Encryption
  630. {
  631. get
  632. {
  633. return _Encryption;
  634. }
  635. set
  636. {
  637. if (value == _Encryption) return;
  638. _Encryption = value;
  639. // If the source is a zip archive and there was encryption
  640. // on the entry, this will not work.
  641. if (this._Source == EntrySource.Zipfile && _sourceIsEncrypted)
  642. throw new InvalidOperationException("You cannot change the encryption method on encrypted entries read from archives.");
  643. _restreamRequiredOnSave = true;
  644. #if AESCRYPTO
  645. if (value == EncryptionAlgorithm.WinZipAes256) this._KeyStrengthInBits = 256;
  646. else if (value == EncryptionAlgorithm.WinZipAes128) this._KeyStrengthInBits = 128;
  647. #endif
  648. }
  649. }
  650. /// <summary>
  651. /// The Password to be used when encrypting a ZipEntry upon ZipFile.Save(), or
  652. /// when decrypting an entry upon Extract().
  653. /// </summary>
  654. ///
  655. /// <remarks>
  656. /// <para>
  657. /// This is a write-only property on the entry.
  658. /// Set this to request that the entry be encrypted when writing the zip
  659. /// archive, or set it to specify the password to be used when extracting an
  660. /// existing entry that is encrypted.
  661. /// </para>
  662. ///
  663. /// <para>
  664. /// The password set here is implicitly
  665. /// used to encrypt the entry during the Save() operation, or to decrypt during
  666. /// the <see cref="Extract()"/> or <see cref="OpenReader()"/> operation.
  667. /// </para>
  668. ///
  669. /// <para>
  670. /// Some comments on Updating archives: Suppose you read a zipfile, and there is an
  671. /// encrypted entry. Setting the password on that entry and then saving the zipfile
  672. /// does not update the password on that entry in the archive. Instead, what happens
  673. /// is the existing entry is copied through to the new zip archive, in its original
  674. /// encrypted form. Upon re-reading that archive, the entry can be decrypted with its
  675. /// original password.
  676. /// </para>
  677. ///
  678. /// <para>
  679. /// If you read a zipfile, you cannot modify the password on any encrypted entry,
  680. /// except by extracting the entry with the first password (if any), removing the original
  681. /// entry via <see cref="ZipFile.RemoveEntry(ZipEntry)"/>, and then adding
  682. /// a new entry with a new password.
  683. /// </para>
  684. ///
  685. /// <para>
  686. /// If you read a zipfile, and there is an un-encrypted entry, you can set the password
  687. /// on the entry and then call Save() on the ZipFile, and get encryption on that entry.
  688. /// </para>
  689. ///
  690. /// </remarks>
  691. public string Password
  692. {
  693. set
  694. {
  695. _Password = value;
  696. if (_Password == null)
  697. {
  698. _Encryption = EncryptionAlgorithm.None;
  699. }
  700. else
  701. {
  702. // We're setting a non-null password.
  703. // For entries obtained from a zip file that are encrypted, we cannot
  704. // simply restream (recompress, re-encrypt) the file data, because we
  705. // need the old password in order to decrypt the data, and then we
  706. // need the new password to encrypt. So, setting the password is
  707. // never going to work on an entry that is stored encrypted in a zipfile.
  708. // But it is not en error to set the password, obviously: callers will
  709. // set the password in order to Extract encrypted archives.
  710. // If the source is a zip archive and there was previously no encryption
  711. // on the entry, then we must re-stream the entry in order to encrypt it.
  712. if (this._Source == EntrySource.Zipfile && !_sourceIsEncrypted)
  713. _restreamRequiredOnSave = true;
  714. if (Encryption == EncryptionAlgorithm.None)
  715. {
  716. _Encryption = EncryptionAlgorithm.PkzipWeak;
  717. }
  718. }
  719. }
  720. }
  721. /// <summary>
  722. /// Specifies that the extraction should overwrite any existing files.
  723. /// </summary>
  724. /// <remarks>
  725. /// This applies only when calling an Extract method. By default this
  726. /// property is false. Generally you will get overwrite behavior by calling
  727. /// one of the overloads of the Extract() method that accepts a boolean flag
  728. /// to indicate explicitly whether you want overwrite.
  729. /// </remarks>
  730. /// <seealso cref="Ionic.Zip.ZipEntry.Extract(bool)"/>
  731. public bool OverwriteOnExtract
  732. {
  733. get { return _OverwriteOnExtract; }
  734. set { _OverwriteOnExtract = value; }
  735. }
  736. /// <summary>
  737. /// A callback that allows the application to specify whether multiple reads of the
  738. /// stream should be performed, in the case that a compression operation actually
  739. /// inflates the size of the file data.
  740. /// </summary>
  741. ///
  742. /// <remarks>
  743. /// <para>
  744. /// In some cases, applying the Deflate compression algorithm in DeflateStream can
  745. /// result an increase in the size of the data. This "inflation" can happen with
  746. /// previously compressed files, such as a zip, jpg, png, mp3, and so on. In a few
  747. /// tests, inflation on zip files can be as large as 60%! Inflation can also happen
  748. /// with very small files. In these cases, by default, the DotNetZip library
  749. /// discards the compressed bytes, and stores the uncompressed file data into the
  750. /// zip archive. This is an optimization where smaller size is preferred over
  751. /// longer run times.
  752. /// </para>
  753. ///
  754. /// <para>
  755. /// The application can specify that compression is not even tried, by setting the
  756. /// ForceNoCompression flag. In this case, the compress-and-check-sizes process as
  757. /// decribed above, is not done.
  758. /// </para>
  759. ///
  760. /// <para>
  761. /// In some cases, neither choice is optimal. The application wants compression,
  762. /// but in some cases also wants to avoid reading the stream more than once. This
  763. /// may happen when the stream is very large, or when the read is very expensive, or
  764. /// when the difference between the compressed and uncompressed sizes is not
  765. /// significant.
  766. /// </para>
  767. ///
  768. /// <para>
  769. /// To satisfy these applications, this delegate allows the DotNetZip library to ask
  770. /// the application to for approval for re-reading the stream. As with other
  771. /// properties (like Password and ForceNoCompression), setting the corresponding
  772. /// delegate on the ZipFile class itself will set it on all ZipEntry items that are
  773. /// subsequently added to the ZipFile instance.
  774. /// </para>
  775. ///
  776. /// </remarks>
  777. /// <seealso cref="Ionic.Zip.ZipFile.WillReadTwiceOnInflation"/>
  778. /// <seealso cref="Ionic.Zip.ReReadApprovalCallback"/>
  779. public ReReadApprovalCallback WillReadTwiceOnInflation
  780. {
  781. get;
  782. set;
  783. }
  784. /// <summary>
  785. /// A callback that allows the application to specify whether compression should
  786. /// be used for a given entry that is about to be added to the zip archive.
  787. /// </summary>
  788. ///
  789. /// <remarks>
  790. /// See <see cref="ZipFile.WantCompression" />
  791. /// </remarks>
  792. public WantCompressionCallback WantCompression
  793. {
  794. get;
  795. set;
  796. }
  797. /// <summary>
  798. /// Set to indicate whether to use UTF-8 encoding on filenames and
  799. /// comments, according to the PKWare specification.
  800. /// </summary>
  801. /// <remarks>
  802. /// If this flag is set, the entry will be marked as encoded with UTF-8,
  803. /// according to the PWare spec, if necessary. Necessary means, if the filename or
  804. /// entry comment (if any) cannot be reflexively encoded with the default (IBM437) code page.
  805. /// </remarks>
  806. /// <remarks>
  807. /// Setting this flag to true is equivalent to setting <see cref="ProvisionalAlternateEncoding"/> to <c>System.Text.Encoding.UTF8</c>
  808. /// </remarks>
  809. public bool UseUnicodeAsNecessary
  810. {
  811. get
  812. {
  813. return _provisionalAlternateEncoding == System.Text.Encoding.GetEncoding("UTF-8");
  814. }
  815. set
  816. {
  817. _provisionalAlternateEncoding = (value) ? System.Text.Encoding.GetEncoding("UTF-8") : Ionic.Zip.ZipFile.DefaultEncoding;
  818. }
  819. }
  820. /// <summary>
  821. /// The text encoding to use for this ZipEntry, when the default
  822. /// encoding is insufficient.
  823. /// </summary>
  824. ///
  825. /// <remarks>
  826. /// <para>
  827. /// According to the zip specification from PKWare, filenames and comments for a
  828. /// ZipEntry are encoded either with IBM437 or with UTF8. But, some archivers do not
  829. /// follow the specification, and instead encode characters using the system default
  830. /// code page, or an arbitrary code page. For example, WinRAR when run on a machine in
  831. /// Shanghai may encode filenames with the Chinese (Big-5) code page. This behavior is
  832. /// contrary to the Zip specification, but it occurs anyway. This property exists to
  833. /// support that non-compliant behavior when reading or writing zip files.
  834. /// </para>
  835. /// <para>
  836. /// When writing zip archives that will be read by one of these other archivers, use this property to
  837. /// specify the code page to use when encoding filenames and comments into the zip
  838. /// file, when the IBM437 code page will not suffice.
  839. /// </para>
  840. /// <para>
  841. /// Be aware that a zip file created after you've explicitly specified the code page will not
  842. /// be compliant to the PKWare specification, and may not be readable by compliant archivers.
  843. /// On the other hand, many archivers are non-compliant and can read zip files created in
  844. /// arbitrary code pages.
  845. /// </para>
  846. /// <para>
  847. /// When using an arbitrary, non-UTF8 code page for encoding, there is no standard way for the
  848. /// creator (DotNetZip) to specify in the zip file which code page has been used. DotNetZip is not
  849. /// able to inspect the zip file and determine the codepage used for the entries within it. Therefore,
  850. /// you, the application author, must determine that. If you use a codepage which results in filenames
  851. /// that are not legal in Windows, you will get exceptions upon extract. Caveat Emptor.
  852. /// </para>
  853. /// </remarks>
  854. public System.Text.Encoding ProvisionalAlternateEncoding
  855. {
  856. get
  857. {
  858. return _provisionalAlternateEncoding;
  859. }
  860. set
  861. {
  862. _provisionalAlternateEncoding = value;
  863. }
  864. }
  865. /// <summary>
  866. /// The text encoding actually used for this ZipEntry.
  867. /// </summary>
  868. public System.Text.Encoding ActualEncoding
  869. {
  870. get
  871. {
  872. return _actualEncoding;
  873. }
  874. }
  875. private static bool ReadHeader(ZipEntry ze, System.Text.Encoding defaultEncoding)
  876. {
  877. int bytesRead = 0;
  878. ze._RelativeOffsetOfLocalHeader = (int)ze.ArchiveStream.Position;
  879. int signature = Ionic.Zip.SharedUtilities.ReadSignature(ze.ArchiveStream);
  880. bytesRead += 4;
  881. // Return false if this is not a local file header signature.
  882. if (ZipEntry.IsNotValidSig(signature))
  883. {
  884. // Getting "not a ZipEntry signature" is not always wrong or an error.
  885. // This will happen after the last entry in a zipfile. In that case, we
  886. // expect to read :
  887. // a ZipDirEntry signature (if a non-empty zip file) or
  888. // a ZipConstants.EndOfCentralDirectorySignature.
  889. //
  890. // Anything else is a surprise.
  891. ze.ArchiveStream.Seek(-4, System.IO.SeekOrigin.Current); // unread the signature
  892. if (ZipEntry.IsNotValidZipDirEntrySig(signature) && (signature != ZipConstants.EndOfCentralDirectorySignature))
  893. {
  894. throw new BadReadException(String.Format(" ZipEntry::ReadHeader(): Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, ze.ArchiveStream.Position));
  895. }
  896. return false;
  897. }
  898. byte[] block = new byte[26];
  899. int n = ze.ArchiveStream.Read(block, 0, block.Length);
  900. if (n != block.Length) return false;
  901. bytesRead += n;
  902. int i = 0;
  903. ze._VersionNeeded = (short)(block[i++] + block[i++] * 256);
  904. ze._BitField = (short)(block[i++] + block[i++] * 256);
  905. ze._CompressionMethod = (short)(block[i++] + block[i++] * 256);
  906. ze._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
  907. // transform the time data into something usable (a DateTime)
  908. ze._LastModified = Ionic.Zip.SharedUtilities.PackedToDateTime(ze._TimeBlob);
  909. // NB: if ((ze._BitField & 0x0008) != 0x0008), then the Compressed, uncompressed and
  910. // CRC values are not true values; the true values will follow the entry data.
  911. // Nevertheless, regardless of the statis of bit 3 in the bitfield, the slots for
  912. // the three amigos may contain marker values for ZIP64. So we must read them.
  913. {
  914. ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  915. ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  916. ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  917. // validate ZIP64? No. We don't need to be pedantic about it.
  918. //if (((uint)ze._CompressedSize == 0xFFFFFFFF &&
  919. // (uint)ze._UncompressedSize != 0xFFFFFFFF) ||
  920. // ((uint)ze._CompressedSize != 0xFFFFFFFF &&
  921. // (uint)ze._UncompressedSize == 0xFFFFFFFF))
  922. // throw new BadReadException(String.Format(" ZipEntry::Read(): Inconsistent uncompressed size (0x{0:X8}) for zip64, at position 0x{1:X16}", ze._UncompressedSize, ze.ArchiveStream.Position));
  923. if ((uint)ze._CompressedSize == 0xFFFFFFFF ||
  924. (uint)ze._UncompressedSize == 0xFFFFFFFF)
  925. ze._InputUsesZip64 = true;
  926. //throw new BadReadException(" DotNetZip does not currently support reading the ZIP64 format.");
  927. }
  928. // else
  929. // {
  930. // // The CRC, compressed size, and uncompressed size stored here are not valid.
  931. // // The actual values are stored later in the stream.
  932. // // Here, we advance the pointer to skip the dummy data.
  933. // i += 12;
  934. // }
  935. Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
  936. Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);
  937. block = new byte[filenameLength];
  938. n = ze.ArchiveStream.Read(block, 0, block.Length);
  939. bytesRead += n;
  940. // if the UTF8 bit is set for this entry, we override the encoding the application requested.
  941. ze._actualEncoding = ((ze._BitField & 0x0800) == 0x0800)
  942. ? System.Text.Encoding.UTF8
  943. : defaultEncoding;
  944. // need to use this form of GetString() for .NET CF
  945. ze._FileNameInArchive = ze._actualEncoding.GetString(block, 0, block.Length);
  946. // when creating an entry by reading, the LocalFileName is the same as the FileNameInArchive
  947. ze._LocalFileName = ze._FileNameInArchive;
  948. // workitem 6898
  949. if (ze._LocalFileName.EndsWith("/")) ze.MarkAsDirectory();
  950. bytesRead += ze.ProcessExtraField(extraFieldLength);
  951. ze._LengthOfTrailer = 0;
  952. // workitem 6607 - don't read for directories
  953. // actually get the compressed size and CRC if necessary
  954. if (!ze._LocalFileName.EndsWith("/") && (ze._BitField & 0x0008) == 0x0008)
  955. {
  956. // This descriptor exists only if bit 3 of the general
  957. // purpose bit flag is set (see below). It is byte aligned
  958. // and immediately follows the last byte of compressed data.
  959. // This descriptor is used only when it was not possible to
  960. // seek in the output .ZIP file, e.g., when the output .ZIP file
  961. // was standard output or a non-seekable device. For ZIP64(tm) format
  962. // archives, the compressed and uncompressed sizes are 8 bytes each.
  963. long posn = ze.ArchiveStream.Position;
  964. // Here, we're going to loop until we find a ZipEntryDataDescriptorSignature and
  965. // a consistent data record after that. To be consistent, the data record must
  966. // indicate the length of the entry data.
  967. bool wantMore = true;
  968. long SizeOfDataRead = 0;
  969. int tries = 0;
  970. while (wantMore)
  971. {
  972. tries++;
  973. // We call the FindSignature shared routine to find the specified signature
  974. // in the already-opened zip archive, starting from the current cursor
  975. // position in that filestream. There are two possibilities: either we
  976. // find the signature or we don't. If we cannot find it, then the routine
  977. // returns -1, and the ReadHeader() method returns false, indicating we
  978. // cannot read a legal entry header. If we have found it, then the
  979. // FindSignature() method returns the number of bytes in the stream we had
  980. // to seek forward, to find the sig. We need this to determine if the zip
  981. // entry is valid, later.
  982. ze._zipfile.OnReadBytes(ze);
  983. long d = Ionic.Zip.SharedUtilities.FindSignature(ze.ArchiveStream, ZipConstants.ZipEntryDataDescriptorSignature);
  984. if (d == -1) return false;
  985. // total size of data read (through all loops of this).
  986. SizeOfDataRead += d;
  987. if (ze._InputUsesZip64 == true)
  988. {
  989. // read 1x 4-byte (CRC) and 2x 8-bytes (Compressed Size, Uncompressed Size)
  990. block = new byte[20];
  991. n = ze.ArchiveStream.Read(block, 0, block.Length);
  992. if (n != 20) return false;
  993. // do not increment bytesRead - it is for entry header only.
  994. // the data we have just read is a footer (falls after the file data)
  995. //bytesRead += n;
  996. i = 0;
  997. ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  998. ze._CompressedSize = BitConverter.ToInt64(block, i);
  999. i += 8;
  1000. ze._UncompressedSize = BitConverter.ToInt64(block, i);
  1001. i += 8;
  1002. ze._LengthOfTrailer += 24; // bytes including sig, CRC, Comp and Uncomp sizes
  1003. }
  1004. else
  1005. {
  1006. // read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size)
  1007. block = new byte[12];
  1008. n = ze.ArchiveStream.Read(block, 0, block.Length);
  1009. if (n != 12) return false;
  1010. // do not increment bytesRead - it is for entry header only.
  1011. // the data we have just read is a footer (falls after the file data)
  1012. //bytesRead += n;
  1013. i = 0;
  1014. ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  1015. ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  1016. ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  1017. ze._LengthOfTrailer += 16; // bytes including sig, CRC, Comp and Uncomp sizes
  1018. }
  1019. wantMore = (SizeOfDataRead != ze._CompressedSize);
  1020. if (wantMore)
  1021. {
  1022. // Seek back to un-read the last 12 bytes - maybe THEY contain
  1023. // the ZipEntryDataDescriptorSignature.
  1024. // (12 bytes for the CRC, Comp and Uncomp size.)
  1025. ze.ArchiveStream.Seek(-12, System.IO.SeekOrigin.Current);
  1026. // Adjust the size to account for the false signature read in
  1027. // FindSignature().
  1028. SizeOfDataRead += 4;
  1029. }
  1030. }
  1031. //if (SizeOfDataRead != ze._CompressedSize)
  1032. // throw new BadReadException("Data format error (bit 3 is set)");
  1033. // seek back to previous position, to prepare to read file data
  1034. ze.ArchiveStream.Seek(posn, System.IO.SeekOrigin.Begin);
  1035. }
  1036. ze._CompressedFileDataSize = ze._CompressedSize;
  1037. // bit 0 set indicates that some kind of encryption is in use
  1038. if ((ze._BitField & 0x01) == 0x01)
  1039. {
  1040. #if AESCRYPTO
  1041. if (ze.Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1042. ze.Encryption == EncryptionAlgorithm.WinZipAes256)
  1043. {
  1044. // read in the WinZip AES metadata
  1045. ze._aesCrypto = WinZipAesCrypto.ReadFromStream(null, ze._KeyStrengthInBits, ze.ArchiveStream);
  1046. bytesRead += ze._aesCrypto.SizeOfEncryptionMetadata - 10;
  1047. ze._CompressedFileDataSize = ze.CompressedSize - ze._aesCrypto.SizeOfEncryptionMetadata;
  1048. ze._LengthOfTrailer += 10;
  1049. }
  1050. else
  1051. #endif
  1052. {
  1053. // read in the header data for "weak" encryption
  1054. ze._WeakEncryptionHeader = new byte[12];
  1055. bytesRead += ZipEntry.ReadWeakEncryptionHeader(ze._archiveStream, ze._WeakEncryptionHeader);
  1056. // decrease the filedata size by 12 bytes
  1057. ze._CompressedFileDataSize -= 12;
  1058. }
  1059. }
  1060. // Remember the size of the blob for this entry.
  1061. // We also have the starting position in the stream for this entry.
  1062. ze._LengthOfHeader = bytesRead;
  1063. ze._TotalEntrySize = ze._LengthOfHeader + ze._CompressedSize + ze._LengthOfTrailer;
  1064. // We've read in the regular entry header, the extra field, and any encryption
  1065. // header. The pointer in the file is now at the start of the filedata, which is
  1066. // potentially compressed and encrypted. Just ahead in the file, there are
  1067. // _CompressedFileDataSize bytes of data, followed by potentially a non-zero
  1068. // length trailer, consisting of optionally, some encryption stuff (10 byte MAC for AES), and
  1069. // the bit-3 trailer (16 or 24 bytes).
  1070. return true;
  1071. }
  1072. internal static int ReadWeakEncryptionHeader(Stream s, byte[] buffer)
  1073. {
  1074. // PKZIP encrypts the compressed data stream. Encrypted files must
  1075. // be decrypted before they can be extracted.
  1076. // Each PKZIP-encrypted file has an extra 12 bytes stored at the start of the data
  1077. // area defining the encryption header for that file. The encryption header is
  1078. // originally set to random values, and then itself encrypted, using three, 32-bit
  1079. // keys. The key values are initialized using the supplied encryption password.
  1080. // After each byte is encrypted, the keys are then updated using pseudo-random
  1081. // number generation techniques in combination with the same CRC-32 algorithm used
  1082. // in PKZIP and implemented in the CRC32.cs module in this project.
  1083. // read the 12-byte encryption header
  1084. int additionalBytesRead = s.Read(buffer, 0, 12);
  1085. if (additionalBytesRead != 12)
  1086. throw new ZipException(String.Format("Unexpected end of data at position 0x{0:X8}", s.Position));
  1087. return additionalBytesRead;
  1088. }
  1089. private static bool IsNotValidSig(int signature)
  1090. {
  1091. return (signature != ZipConstants.ZipEntrySignature);
  1092. }
  1093. /// <summary>
  1094. /// Reads one ZipEntry from the given stream. If the entry is encrypted, we don't
  1095. /// decrypt at this point. We also do not decompress. Mostly we read metadata.
  1096. /// </summary>
  1097. /// <param name="zf">the zipfile this entry belongs to.</param>
  1098. /// <param name="first">true of this is the first entry being read from the stream.</param>
  1099. /// <returns>the ZipEntry read from the stream.</returns>
  1100. internal static ZipEntry Read(ZipFile zf, bool first)
  1101. {
  1102. System.IO.Stream s = zf.ReadStream;
  1103. System.Text.Encoding defaultEncoding = zf.ProvisionalAlternateEncoding;
  1104. ZipEntry entry = new ZipEntry();
  1105. entry._Source = EntrySource.Zipfile;
  1106. entry._zipfile = zf;
  1107. entry._archiveStream = s;
  1108. zf.OnReadEntry(true, null);
  1109. if (first) HandlePK00Prefix(s);
  1110. if (!ReadHeader(entry, defaultEncoding)) return null;
  1111. // store the position in the stream for this entry
  1112. entry.__FileDataPosition = entry.ArchiveStream.Position;
  1113. // seek past the data without reading it. We will read on Extract()
  1114. s.Seek(entry._CompressedFileDataSize, System.IO.SeekOrigin.Current);
  1115. // workitem 6607 - don't seek for directories
  1116. // finally, seek past the (already read) Data descriptor if necessary
  1117. if (((entry._BitField & 0x0008) == 0x0008) && !entry.FileName.EndsWith("/"))
  1118. {
  1119. // _InputUsesZip64 is set in ReadHeader()
  1120. int DescriptorSize = (entry._InputUsesZip64) ? 24 : 16;
  1121. s.Seek(DescriptorSize, System.IO.SeekOrigin.Current);
  1122. }
  1123. // workitem 5306
  1124. // http://www.codeplex.com/DotNetZip/WorkItem/View.aspx?WorkItemId=5306
  1125. HandleUnexpectedDataDescriptor(entry);
  1126. zf.OnReadBytes(entry);
  1127. zf.OnReadEntry(false, entry);
  1128. return entry;
  1129. }
  1130. internal static void HandlePK00Prefix(Stream s)
  1131. {
  1132. // in some cases, the zip file begins with "PK00". This is a throwback and is rare,
  1133. // but we handle it anyway. We do not change behavior based on it.
  1134. uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
  1135. if (datum != ZipConstants.PackedToRemovableMedia)
  1136. {
  1137. s.Seek(-4, System.IO.SeekOrigin.Current); // unread the block
  1138. }
  1139. }
  1140. private static void HandleUnexpectedDataDescriptor(ZipEntry entry)
  1141. {
  1142. System.IO.Stream s = entry.ArchiveStream;
  1143. // In some cases, the "data descriptor" is present, without a signature, even when bit 3 of the BitField is NOT SET.
  1144. // This is the CRC, followed
  1145. // by the compressed length and the uncompressed length (4 bytes for each
  1146. // of those three elements). Need to check that here.
  1147. //
  1148. uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
  1149. if (datum == entry._Crc32)
  1150. {
  1151. int sz = Ionic.Zip.SharedUtilities.ReadInt(s);
  1152. if (sz == entry._CompressedSize)
  1153. {
  1154. sz = Ionic.Zip.SharedUtilities.ReadInt(s);
  1155. if (sz == entry._UncompressedSize)
  1156. {
  1157. // ignore everything and discard it.
  1158. }
  1159. else
  1160. s.Seek(-12, System.IO.SeekOrigin.Current); // unread the three blocks
  1161. }
  1162. else
  1163. s.Seek(-8, System.IO.SeekOrigin.Current); // unread the two blocks
  1164. }
  1165. else
  1166. s.Seek(-4, System.IO.SeekOrigin.Current); // unread the block
  1167. }
  1168. internal static string NameInArchive(String filename, string directoryPathInArchive)
  1169. {
  1170. string result = null;
  1171. if (directoryPathInArchive == null)
  1172. result = filename;
  1173. else
  1174. {
  1175. if (String.IsNullOrEmpty(directoryPathInArchive))
  1176. {
  1177. //if (filename.EndsWith("\\"))
  1178. //{
  1179. // result = System.IO.Path.GetFileName(filename.Substring(0, filename.Length - 1));
  1180. //}
  1181. //else
  1182. result = System.IO.Path.GetFileName(filename);
  1183. }
  1184. else
  1185. {
  1186. // explicitly specify a pathname for this file
  1187. result = System.IO.Path.Combine(directoryPathInArchive, System.IO.Path.GetFileName(filename));
  1188. }
  1189. }
  1190. return SharedUtilities.TrimVolumeAndSwapSlashes(result);
  1191. }
  1192. internal static ZipEntry Create(String filename, string nameInArchive)
  1193. {
  1194. return Create(filename, nameInArchive, null);
  1195. }
  1196. internal static ZipEntry Create(String filename, string nameInArchive, System.IO.Stream stream)
  1197. {
  1198. if (String.IsNullOrEmpty(filename))
  1199. throw new Ionic.Zip.ZipException("The entry name must be non-null and non-empty.");
  1200. ZipEntry entry = new ZipEntry();
  1201. if (stream != null)
  1202. {
  1203. entry._sourceStream = stream;
  1204. entry._LastModified = DateTime.Now;
  1205. }
  1206. else
  1207. {
  1208. entry._LastModified = (System.IO.File.Exists(filename) || System.IO.Directory.Exists(filename))
  1209. ? SharedUtilities.RoundToEvenSecond(System.IO.File.GetLastWriteTime(filename))
  1210. : DateTime.Now;
  1211. if (!entry._LastModified.IsDaylightSavingTime() &&
  1212. DateTime.Now.IsDaylightSavingTime())
  1213. {
  1214. entry._LastModified = entry._LastModified + new System.TimeSpan(1, 0, 0);
  1215. }
  1216. if (entry._LastModified.IsDaylightSavingTime() &&
  1217. !DateTime.Now.IsDaylightSavingTime())
  1218. {
  1219. entry._LastModified = entry._LastModified - new System.TimeSpan(1, 0, 0);
  1220. }
  1221. }
  1222. entry._LocalFileName = filename; // may include a path
  1223. entry._FileNameInArchive = nameInArchive.Replace('\\', '/');
  1224. // we don't actually slurp in the file until the caller invokes Write on this entry.
  1225. return entry;
  1226. }
  1227. #region Extract methods
  1228. /// <summary>
  1229. /// Extract the entry to the filesystem, starting at the current working directory.
  1230. /// </summary>
  1231. ///
  1232. /// <overloads>
  1233. /// This method has a bunch of overloads! One of them is sure to be
  1234. /// the right one for you... If you don't like these, check out the
  1235. /// <c>ExtractWithPassword()</c> methods.
  1236. /// </overloads>
  1237. ///
  1238. /// <seealso cref="Ionic.Zip.ZipEntry.OverwriteOnExtract"/>
  1239. /// <seealso cref="Ionic.Zip.ZipEntry.Extract(bool)"/>
  1240. ///
  1241. /// <remarks>
  1242. /// <para>
  1243. /// Existing entries in the filesystem will not be overwritten. If you would like to
  1244. /// force the overwrite of existing files, see the <c>OverwriteOnExtract</c> property,
  1245. /// or try one of the overloads of the Extract method that accepts a boolean flag
  1246. /// to indicate explicitly whether you want overwrite.
  1247. /// </para>
  1248. /// <para>
  1249. /// See the remarks on the LastModified property, for some details
  1250. /// about how the last modified time of the created file is set.
  1251. /// </para>
  1252. /// </remarks>
  1253. public void Extract()
  1254. {
  1255. InternalExtract(".", null, null);
  1256. }
  1257. /// <summary>
  1258. /// Extract the entry to a file in the filesystem, potentially overwriting
  1259. /// any existing file.
  1260. /// </summary>
  1261. /// <remarks>
  1262. /// <para>
  1263. /// See the remarks on the LastModified property, for some details
  1264. /// about how the last modified time of the created file is set.
  1265. /// </para>
  1266. /// </remarks>
  1267. /// <param name="overwrite">true if the caller wants to overwrite an existing file by the same name in the filesystem.</param>
  1268. public void Extract(bool overwrite)
  1269. {
  1270. OverwriteOnExtract = overwrite;
  1271. InternalExtract(".", null, null);
  1272. }
  1273. /// <summary>
  1274. /// Extracts the entry to the specified stream.
  1275. /// </summary>
  1276. ///
  1277. /// <remarks>
  1278. ///
  1279. /// <para>
  1280. /// For example, the caller could specify Console.Out, or a MemoryStream.
  1281. /// </para>
  1282. ///
  1283. /// </remarks>
  1284. ///
  1285. /// <param name="stream">the stream to which the entry should be extracted. </param>
  1286. ///
  1287. public void Extract(System.IO.Stream stream)
  1288. {
  1289. InternalExtract(null, stream, null);
  1290. }
  1291. /// <summary>
  1292. /// Extract the entry to the filesystem, starting at the specified base directory.
  1293. /// </summary>
  1294. ///
  1295. /// <param name="baseDirectory">the pathname of the base directory</param>
  1296. ///
  1297. /// <seealso cref="Ionic.Zip.ZipEntry.OverwriteOnExtract"/>
  1298. /// <seealso cref="Ionic.Zip.ZipEntry.Extract(string, bool)"/>
  1299. /// <seealso cref="Ionic.Zip.ZipFile.Extract(string)"/>
  1300. ///
  1301. /// <example>
  1302. /// This example extracts only the entries in a zip file that are .txt files, into a directory called "textfiles".
  1303. /// <code lang="C#">
  1304. /// using (ZipFile zip = ZipFile.Read("PackedDocuments.zip"))
  1305. /// {
  1306. /// foreach (string s1 in zip.EntryFilenames)
  1307. /// {
  1308. /// if (s1.EndsWith(".txt"))
  1309. /// {
  1310. /// ZipEntry entry= zip[s1];
  1311. /// entry.Extract("textfiles");
  1312. /// }
  1313. /// }
  1314. /// }
  1315. /// </code>
  1316. /// <code lang="VB">
  1317. /// Using zip As ZipFile = ZipFile.Read("PackedDocuments.zip")
  1318. /// Dim s1 As String
  1319. /// For Each s1 In zip.EntryFilenames
  1320. /// If s1.EndsWith(".txt") Then
  1321. /// Dim entry as ZipEntry
  1322. /// entry = zip(s1)
  1323. /// entry.Extract("textfiles")
  1324. /// End If
  1325. /// Next
  1326. /// End Using
  1327. /// </code>
  1328. /// </example>
  1329. ///
  1330. /// <remarks>
  1331. /// <para>
  1332. /// Existing entries in the filesystem will not be overwritten. If you would like to
  1333. /// force the overwrite of existing files, see the <c>OverwriteOnExtract</c> property,
  1334. /// or try one of the overloads of the Extract method that accept a boolean flag
  1335. /// to indicate explicitly whether you want overwrite.
  1336. /// </para>
  1337. /// <para>
  1338. /// See the remarks on the LastModified property, for some details
  1339. /// about how the last modified time of the created file is set.
  1340. /// </para>
  1341. /// </remarks>
  1342. public void Extract(string baseDirectory)
  1343. {
  1344. InternalExtract(baseDirectory, null, null);
  1345. }
  1346. /// <summary>
  1347. /// Extract the entry to the filesystem, starting at the specified base directory,
  1348. /// and potentially overwriting existing files in the filesystem.
  1349. /// </summary>
  1350. ///
  1351. /// <remarks>
  1352. /// <para>
  1353. /// See the remarks on the LastModified property, for some details
  1354. /// about how the last modified time of the created file is set.
  1355. /// </para>
  1356. /// </remarks>
  1357. ///
  1358. /// <param name="baseDirectory">the pathname of the base directory</param>
  1359. /// <param name="overwrite">If true, overwrite any existing files if necessary upon extraction.</param>
  1360. public void Extract(string baseDirectory, bool overwrite)
  1361. {
  1362. OverwriteOnExtract = overwrite;
  1363. InternalExtract(baseDirectory, null, null);
  1364. }
  1365. /// <summary>
  1366. /// Extract the entry to the filesystem, using the current working directory
  1367. /// and the specified password.
  1368. /// </summary>
  1369. ///
  1370. /// <overloads>
  1371. /// This method has a bunch of overloads! One of them is sure to be
  1372. /// the right one for you...
  1373. /// </overloads>
  1374. ///
  1375. /// <seealso cref="Ionic.Zip.ZipEntry.OverwriteOnExtract"/>
  1376. /// <seealso cref="Ionic.Zip.ZipEntry.ExtractWithPassword(bool, string)"/>
  1377. ///
  1378. /// <remarks>
  1379. /// <para>
  1380. /// Existing entries in the filesystem will not be overwritten. If you would like to
  1381. /// force the overwrite of existing files, see the <c>OverwriteOnExtract</c> property,
  1382. /// or try one of the overloads of the ExtractWithPassword method that accept a boolean flag
  1383. /// to indicate explicitly that you want overwrite.
  1384. /// </para>
  1385. /// <para>
  1386. /// See the remarks on the <see cref="LastModified"/> property for some details
  1387. /// about how the "last modified" time of the created file is set.
  1388. /// </para>
  1389. /// </remarks>
  1390. ///
  1391. /// <example>
  1392. /// In this example, entries that use encryption are extracted using a particular password.
  1393. /// <code lang="VB">
  1394. /// Using zip As new ZipFile(FilePath)
  1395. /// Dim e As ZipEntry
  1396. /// For Each e In zip
  1397. /// If (e.UsesEncryption)
  1398. /// e.ExtractWithPassword("Secret!")
  1399. /// Else
  1400. /// e.Extract
  1401. /// End If
  1402. /// Next
  1403. /// End Using
  1404. /// </code>
  1405. /// </example>
  1406. /// <param name="password">The Password to use for decrypting the entry.</param>
  1407. public void ExtractWithPassword(string password)
  1408. {
  1409. InternalExtract(".", null, password);
  1410. }
  1411. /// <summary>
  1412. /// Extract the entry to the filesystem, starting at the specified base directory,
  1413. /// and using the specified password.
  1414. /// </summary>
  1415. ///
  1416. /// <seealso cref="Ionic.Zip.ZipEntry.OverwriteOnExtract"/>
  1417. /// <seealso cref="Ionic.Zip.ZipEntry.ExtractWithPassword(string, bool, string)"/>
  1418. ///
  1419. /// <remarks>
  1420. /// <para>
  1421. /// Existing entries in the filesystem will not be overwritten. If you would like to
  1422. /// force the overwrite of existing files, see the <c>OverwriteOnExtract</c> property,
  1423. /// or try one of the overloads of the ExtractWithPassword method that accept a boolean flag
  1424. /// to indicate explicitly whether you want overwrite.
  1425. /// </para>
  1426. /// <para>
  1427. /// See the remarks on the LastModified property, for some details
  1428. /// about how the last modified time of the created file is set.
  1429. /// </para>
  1430. /// </remarks>
  1431. ///
  1432. /// <param name="baseDirectory">The pathname of the base directory.</param>
  1433. /// <param name="password">The Password to use for decrypting the entry.</param>
  1434. public void ExtractWithPassword(string baseDirectory, string password)
  1435. {
  1436. InternalExtract(baseDirectory, null, password);
  1437. }
  1438. /// <summary>
  1439. /// Extract the entry to a file in the filesystem, potentially overwriting
  1440. /// any existing file.
  1441. /// </summary>
  1442. ///
  1443. /// <remarks>
  1444. /// <para>
  1445. /// See the remarks on the LastModified property, for some details
  1446. /// about how the last modified time of the created file is set.
  1447. /// </para>
  1448. /// </remarks>
  1449. ///
  1450. /// <param name="overwrite">true if the caller wants to overwrite an existing
  1451. /// file by the same name in the filesystem.</param>
  1452. /// <param name="password">The Password to use for decrypting the entry.</param>
  1453. public void ExtractWithPassword(bool overwrite, string password)
  1454. {
  1455. OverwriteOnExtract = overwrite;
  1456. InternalExtract(".", null, password);
  1457. }
  1458. /// <summary>
  1459. /// Extract the entry to the filesystem, starting at the specified base directory,
  1460. /// and potentially overwriting existing files in the filesystem.
  1461. /// </summary>
  1462. ///
  1463. /// <remarks>
  1464. /// See the remarks on the LastModified property, for some details
  1465. /// about how the last modified time of the created file is set.
  1466. /// </remarks>
  1467. ///
  1468. /// <param name="baseDirectory">the pathname of the base directory</param>
  1469. /// <param name="overwrite">If true, overwrite any existing files if necessary upon extraction.</param>
  1470. /// <param name="password">The Password to use for decrypting the entry.</param>
  1471. public void ExtractWithPassword(string baseDirectory, bool overwrite, string password)
  1472. {
  1473. OverwriteOnExtract = overwrite;
  1474. InternalExtract(baseDirectory, null, password);
  1475. }
  1476. /// <summary>
  1477. /// Extracts the entry to the specified stream, using the specified Password.
  1478. /// For example, the caller could extract to Console.Out, or to a MemoryStream.
  1479. /// </summary>
  1480. ///
  1481. /// <remarks>
  1482. /// See the remarks on the LastModified property, for some details
  1483. /// about how the last modified time of the created file is set.
  1484. /// </remarks>
  1485. ///
  1486. /// <param name="stream">the stream to which the entry should be extracted. </param>
  1487. /// <param name="password">The password to use for decrypting the entry.</param>
  1488. public void ExtractWithPassword(System.IO.Stream stream, string password)
  1489. {
  1490. InternalExtract(null, stream, password);
  1491. }
  1492. /// <summary>
  1493. /// Opens the backing stream for the zip entry in the archive, for reading.
  1494. /// </summary>
  1495. ///
  1496. /// <remarks>
  1497. ///
  1498. /// <para>
  1499. /// The ZipEntry has methods that extract the entry to an already-opened stream.
  1500. /// This is an alternative method for those applications that wish to manipulate the stream directly.
  1501. /// </para>
  1502. ///
  1503. /// <para>
  1504. /// The <see cref="CrcCalculatorStream"/> that is returned is just a regular read-only stream - you can use it as you would
  1505. /// any stream. The one additional feature it adds is that it calculates a CRC32 on the bytes of the stream
  1506. /// as it is read. This CRC should be used by the application to validate the content of the ZipEntry, when
  1507. /// the read is complete. You don't have to validate the CRC, but you should. Check the example for how to do this.
  1508. /// </para>
  1509. ///
  1510. /// <para>
  1511. /// If the entry is protected with a password, then you need to set the password on the entry prior to calling
  1512. /// <see cref="OpenReader()"/>.
  1513. /// </para>
  1514. ///
  1515. /// </remarks>
  1516. ///
  1517. /// <example>
  1518. /// In this example, we open a zipfile, then read in a named entry via a stream, scanning
  1519. /// the bytes in the entry as we go. Finally, the CRC and the size of the entry are verified.
  1520. /// <code>
  1521. /// using (ZipFile zip = new ZipFile(ZipFileToRead))
  1522. /// {
  1523. /// ZipEntry e1= zip["Download.mp3"];
  1524. /// using (CrcCalculatorStream s = e1.OpenReader())
  1525. /// {
  1526. /// byte[] buffer = new byte[4096];
  1527. /// int n, totalBytesRead= 0;
  1528. /// do {
  1529. /// n = s.Read(buffer,0, buffer.Length);
  1530. /// totalBytesRead+=n;
  1531. /// } while (n&gt;0);
  1532. /// if (s.Crc32 != e1.Crc32)
  1533. /// throw new Exception(string.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc32, e1.Crc32));
  1534. /// if (totalBytesRead != e1.UncompressedSize)
  1535. /// throw new Exception(string.Format("We read an unexpected number of bytes. ({0}!={1})", totalBytesRead, e1.UncompressedSize));
  1536. /// }
  1537. /// }
  1538. /// </code>
  1539. /// <code lang="VB">
  1540. /// Using zip As New ZipFile(ZipFileToRead)
  1541. /// Dim e1 As ZipEntry = zip.Item("Download.mp3")
  1542. /// Using s As CrcCalculatorStream = e1.OpenReader
  1543. /// Dim n As Integer
  1544. /// Dim buffer As Byte() = New Byte(4096) {}
  1545. /// Dim totalBytesRead As Integer = 0
  1546. /// Do
  1547. /// n = s.Read(buffer, 0, buffer.Length)
  1548. /// totalBytesRead = (totalBytesRead + n)
  1549. /// Loop While (n &gt; 0)
  1550. /// If (s.Crc32 &lt;&gt; e1.Crc32) Then
  1551. /// Throw New Exception(String.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc32, e1.Crc32))
  1552. /// End If
  1553. /// If (totalBytesRead &lt;&gt; e1.UncompressedSize) Then
  1554. /// Throw New Exception(String.Format("We read an unexpected number of bytes. ({0}!={1})", totalBytesRead, e1.UncompressedSize))
  1555. /// End If
  1556. /// End Using
  1557. /// End Using
  1558. /// </code>
  1559. /// </example>
  1560. /// <seealso cref="Ionic.Zip.ZipEntry.Extract(System.IO.Stream)"/>
  1561. /// <returns>The Stream for reading.</returns>
  1562. public CrcCalculatorStream OpenReader()
  1563. {
  1564. return InternalOpenReader(this._Password);
  1565. }
  1566. /// <summary>
  1567. /// Opens the backing stream for an encrypted zip entry in the archive, for reading.
  1568. /// </summary>
  1569. ///
  1570. /// <remarks>
  1571. /// <para>
  1572. /// See the documentation on the OpenReader() method for full details. This overload allows the
  1573. /// application to specify a password for the ZipEntry to be read.
  1574. /// </para>
  1575. /// </remarks>
  1576. ///
  1577. /// <param name="password">The password to use for decrypting the entry.</param>
  1578. /// <returns>The Stream for reading.</returns>
  1579. public CrcCalculatorStream OpenReader(string password)
  1580. {
  1581. return InternalOpenReader(password);
  1582. }
  1583. private CrcCalculatorStream InternalOpenReader(string password)
  1584. {
  1585. ValidateCompression();
  1586. ValidateEncryption();
  1587. SetupCrypto(password);
  1588. Stream input = this.ArchiveStream;
  1589. this.ArchiveStream.Seek(this.FileDataPosition, System.IO.SeekOrigin.Begin);
  1590. // get a stream that either decrypts or not.
  1591. Stream input2 = input;
  1592. if (Encryption == EncryptionAlgorithm.PkzipWeak)
  1593. input2 = new ZipCipherStream(input, _zipCrypto, CryptoMode.Decrypt);
  1594. #if AESCRYPTO
  1595. else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1596. Encryption == EncryptionAlgorithm.WinZipAes256)
  1597. {
  1598. input2 = new WinZipAesCipherStream(input, _aesCrypto, _CompressedFileDataSize, CryptoMode.Decrypt);
  1599. }
  1600. #endif
  1601. return new CrcCalculatorStream((CompressionMethod == 0x08)
  1602. ? new Ionic.Zlib.DeflateStream(input2, Ionic.Zlib.CompressionMode.Decompress, true)
  1603. : input2,
  1604. _UncompressedSize);
  1605. }
  1606. internal System.IO.Stream ArchiveStream
  1607. {
  1608. get
  1609. {
  1610. if (_archiveStream == null)
  1611. {
  1612. if (_zipfile != null)
  1613. {
  1614. _zipfile.Reset();
  1615. _archiveStream = _zipfile.ReadStream;
  1616. }
  1617. }
  1618. return _archiveStream;
  1619. }
  1620. }
  1621. #endregion
  1622. private void OnExtractProgress(Int64 bytesWritten, Int64 totalBytesToWrite)
  1623. {
  1624. _ioOperationCanceled = _zipfile.OnExtractBlock(this, bytesWritten, totalBytesToWrite);
  1625. }
  1626. private void OnBeforeExtract(string path)
  1627. {
  1628. if (!_zipfile._inExtractAll)
  1629. {
  1630. _ioOperationCanceled =
  1631. _zipfile.OnSingleEntryExtract(this, path, true, OverwriteOnExtract);
  1632. }
  1633. }
  1634. private void OnAfterExtract(string path)
  1635. {
  1636. if (!_zipfile._inExtractAll)
  1637. {
  1638. _zipfile.OnSingleEntryExtract(this, path, false, OverwriteOnExtract);
  1639. }
  1640. }
  1641. private void OnWriteBlock(Int64 bytesXferred, Int64 totalBytesToXfer)
  1642. {
  1643. _ioOperationCanceled = _zipfile.OnSaveBlock(this, bytesXferred, totalBytesToXfer);
  1644. }
  1645. // Pass in either basedir or s, but not both.
  1646. // In other words, you can extract to a stream or to a directory (filesystem), but not both!
  1647. // The Password param is required for encrypted entries.
  1648. private void InternalExtract(string baseDir, System.IO.Stream outstream, string password)
  1649. {
  1650. OnBeforeExtract(baseDir);
  1651. _ioOperationCanceled = false;
  1652. string TargetFile = null;
  1653. System.IO.Stream output = null;
  1654. bool fileExistsBeforeExtraction = false;
  1655. try
  1656. {
  1657. ValidateCompression();
  1658. ValidateEncryption();
  1659. if (ValidateOutput(baseDir, outstream, out TargetFile))
  1660. {
  1661. // if true, then the entry was a directory and has been created.
  1662. // We need to fire the Extract Event.
  1663. OnAfterExtract(baseDir);
  1664. return;
  1665. }
  1666. // if no password explicitly specified, use the password on the entry itself.
  1667. if (password == null) password = this._Password; // may be null
  1668. SetupCrypto(password);
  1669. // set up the output stream
  1670. if (TargetFile != null)
  1671. {
  1672. // ensure the target path exists
  1673. if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(TargetFile)))
  1674. System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(TargetFile));
  1675. // and ensure we can create the file
  1676. if (System.IO.File.Exists(TargetFile))
  1677. {
  1678. fileExistsBeforeExtraction = true;
  1679. if (OverwriteOnExtract)
  1680. System.IO.File.Delete(TargetFile);
  1681. else throw new ZipException("The file already exists.");
  1682. }
  1683. output = new System.IO.FileStream(TargetFile, System.IO.FileMode.CreateNew);
  1684. }
  1685. else
  1686. output = outstream;
  1687. if (_ioOperationCanceled)
  1688. {
  1689. try
  1690. {
  1691. if (TargetFile != null)
  1692. {
  1693. if (output != null) output.Close();
  1694. // attempt to remove the target file if an exception has occurred:
  1695. if (System.IO.File.Exists(TargetFile))
  1696. System.IO.File.Delete(TargetFile);
  1697. }
  1698. }
  1699. finally { }
  1700. }
  1701. Int32 ActualCrc32 = _ExtractOne(output);
  1702. if (_ioOperationCanceled)
  1703. {
  1704. try
  1705. {
  1706. if (TargetFile != null)
  1707. {
  1708. if (output != null) output.Close();
  1709. // attempt to remove the target file if an exception has occurred:
  1710. if (System.IO.File.Exists(TargetFile))
  1711. System.IO.File.Delete(TargetFile);
  1712. }
  1713. }
  1714. finally { }
  1715. }
  1716. // After extracting, Validate the CRC32
  1717. if (ActualCrc32 != _Crc32)
  1718. {
  1719. #if AESCRYPTO
  1720. // CRC is not meaningful with WinZipAES and AES method 2 (AE-2)
  1721. if ((Encryption != EncryptionAlgorithm.WinZipAes128 &&
  1722. Encryption != EncryptionAlgorithm.WinZipAes256)
  1723. || _WinZipAesMethod != 0x02)
  1724. #endif
  1725. throw new BadCrcException("CRC error: the file being extracted appears to be corrupted. " +
  1726. String.Format("Expected 0x{0:X8}, Actual 0x{1:X8}", _Crc32, ActualCrc32));
  1727. }
  1728. #if AESCRYPTO
  1729. // Read the MAC if appropriate
  1730. if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1731. Encryption == EncryptionAlgorithm.WinZipAes256)
  1732. {
  1733. _aesCrypto.ReadAndVerifyMac(this.ArchiveStream); // throws if MAC is bad
  1734. // side effect: advances file position.
  1735. }
  1736. #endif
  1737. if (TargetFile != null)
  1738. {
  1739. output.Close();
  1740. output = null;
  1741. #if !NETCF
  1742. // workitem 6191
  1743. DateTime AdjustedLastModified = LastModified;
  1744. if (DateTime.Now.IsDaylightSavingTime() && !LastModified.IsDaylightSavingTime())
  1745. AdjustedLastModified = LastModified - new System.TimeSpan(1, 0, 0);
  1746. //if (!DateTime.Now.IsDaylightSavingTime() && LastModified.IsDaylightSavingTime())
  1747. //AdjustedLastModified = LastModified + new System.TimeSpan(1, 0, 0);
  1748. System.IO.File.SetLastWriteTime(TargetFile, AdjustedLastModified);
  1749. #endif
  1750. }
  1751. OnAfterExtract(baseDir);
  1752. }
  1753. catch
  1754. {
  1755. try
  1756. {
  1757. if (TargetFile != null)
  1758. {
  1759. if (output != null) output.Close();
  1760. // An exception has occurred.
  1761. // if the file exists, check to see if it existed before we tried extracting.
  1762. // if it did not, or if we were overwriting the file, attempt to remove the target file.
  1763. if (System.IO.File.Exists(TargetFile))
  1764. {
  1765. if (!fileExistsBeforeExtraction || OverwriteOnExtract)
  1766. System.IO.File.Delete(TargetFile);
  1767. }
  1768. }
  1769. }
  1770. finally { }
  1771. // re-raise the original exception
  1772. throw;
  1773. }
  1774. }
  1775. private void ValidateEncryption()
  1776. {
  1777. #if AESCRYPTO
  1778. if (Encryption != EncryptionAlgorithm.PkzipWeak &&
  1779. Encryption != EncryptionAlgorithm.WinZipAes128 &&
  1780. Encryption != EncryptionAlgorithm.WinZipAes256 &&
  1781. Encryption != EncryptionAlgorithm.None)
  1782. throw new ArgumentException(String.Format("Unsupported Encryption algorithm ({0:X2})",
  1783. Encryption));
  1784. #else
  1785. if (Encryption != EncryptionAlgorithm.PkzipWeak &&
  1786. Encryption != EncryptionAlgorithm.None)
  1787. throw new ArgumentException(String.Format("Unsupported Encryption algorithm ({0:X2})",
  1788. Encryption));
  1789. #endif
  1790. }
  1791. private void ValidateCompression()
  1792. {
  1793. if ((CompressionMethod != 0) && (CompressionMethod != 0x08)) // deflate
  1794. throw new ArgumentException(String.Format("Unsupported Compression method (0x{0:X2})",
  1795. CompressionMethod));
  1796. }
  1797. private void SetupCrypto(string password)
  1798. {
  1799. //Console.Write("SetupCrypto:");
  1800. if (password == null)
  1801. {
  1802. //Console.WriteLine(" -none-");
  1803. return;
  1804. }
  1805. if (Encryption == EncryptionAlgorithm.PkzipWeak)
  1806. {
  1807. //Console.WriteLine("Weak");
  1808. this.ArchiveStream.Seek(this.FileDataPosition - 12, System.IO.SeekOrigin.Begin);
  1809. _zipCrypto = ZipCrypto.ForRead(password, this);
  1810. }
  1811. #if AESCRYPTO
  1812. else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1813. Encryption == EncryptionAlgorithm.WinZipAes256)
  1814. {
  1815. // if we already have a WinZipAesCrypto object in place, use it.
  1816. if (_aesCrypto != null)
  1817. {
  1818. _aesCrypto.Password = password;
  1819. }
  1820. else
  1821. {
  1822. int sizeOfSaltAndPv = ((_KeyStrengthInBits / 8 / 2) + 2);
  1823. this.ArchiveStream.Seek(this.FileDataPosition - sizeOfSaltAndPv, System.IO.SeekOrigin.Begin);
  1824. _aesCrypto = WinZipAesCrypto.ReadFromStream(password, _KeyStrengthInBits, this.ArchiveStream);
  1825. }
  1826. }
  1827. #endif
  1828. }
  1829. private bool ValidateOutput(string basedir, Stream outstream, out string OutputFile)
  1830. {
  1831. if (basedir != null)
  1832. {
  1833. // Sometimes the name on the entry starts with a slash.
  1834. // Rather than unpack to the root of the volume, we're going to
  1835. // drop the slash and unpack to the specified base directory.
  1836. OutputFile = (this.FileName.StartsWith("/"))
  1837. ? System.IO.Path.Combine(basedir, this.FileName.Substring(1))
  1838. : System.IO.Path.Combine(basedir, this.FileName);
  1839. // check if a directory
  1840. if ((IsDirectory) || (FileName.EndsWith("/")))
  1841. {
  1842. if (!System.IO.Directory.Exists(OutputFile))
  1843. System.IO.Directory.CreateDirectory(OutputFile);
  1844. return true; // true == all done, caller will return
  1845. }
  1846. return false; // false == work to do by caller.
  1847. }
  1848. if (outstream != null)
  1849. {
  1850. OutputFile = null;
  1851. if ((IsDirectory) || (FileName.EndsWith("/")))
  1852. {
  1853. // extract a directory to streamwriter? nothing to do!
  1854. return true; // true == all done! caller can return
  1855. }
  1856. return false;
  1857. }
  1858. throw new ZipException("Cannot extract.", new ArgumentException("Invalid input.", "outstream | basedir"));
  1859. }
  1860. private void _CheckRead(int nbytes)
  1861. {
  1862. if (nbytes == 0)
  1863. throw new BadReadException(String.Format("bad read of entry {0} from compressed archive.",
  1864. this.FileName));
  1865. }
  1866. private Int32 _ExtractOne(System.IO.Stream output)
  1867. {
  1868. System.IO.Stream input = this.ArchiveStream;
  1869. input.Seek(this.FileDataPosition, System.IO.SeekOrigin.Begin);
  1870. // to validate the CRC.
  1871. Int32 CrcResult = 0;
  1872. byte[] bytes = new byte[WORKING_BUFFER_SIZE];
  1873. // The extraction process varies depending on how the entry was stored.
  1874. // It could have been encrypted, and it coould have been compressed, or both, or
  1875. // neither. So we need to check both the encryption flag and the compression flag,
  1876. // and take the proper action in all cases.
  1877. Int64 LeftToRead = (CompressionMethod == 0x08) ? this.UncompressedSize : this._CompressedFileDataSize;
  1878. // Get a stream that either decrypts or not.
  1879. Stream input2 = null;
  1880. if (Encryption == EncryptionAlgorithm.PkzipWeak)
  1881. input2 = new ZipCipherStream(input, _zipCrypto, CryptoMode.Decrypt);
  1882. #if AESCRYPTO
  1883. else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1884. Encryption == EncryptionAlgorithm.WinZipAes256)
  1885. input2 = new WinZipAesCipherStream(input, _aesCrypto, _CompressedFileDataSize, CryptoMode.Decrypt);
  1886. #endif
  1887. else
  1888. input2 = new CrcCalculatorStream(input, _CompressedFileDataSize);
  1889. //Stream input2a = new TraceStream(input2);
  1890. // Using the above, now we get a stream that either decompresses or not.
  1891. Stream input3 = (CompressionMethod == 0x08)
  1892. ? new Ionic.Zlib.DeflateStream(input2, Ionic.Zlib.CompressionMode.Decompress, true)
  1893. : input2;
  1894. Int64 bytesWritten = 0;
  1895. // As we read, we maybe decrypt, and then we maybe decompress. Then we write.
  1896. using (var s1 = new CrcCalculatorStream(input3))
  1897. {
  1898. while (LeftToRead > 0)
  1899. {
  1900. //Console.WriteLine("ExtractOne: LeftToRead {0}", LeftToRead);
  1901. // Casting LeftToRead down to an int is ok here in the else clause, because
  1902. // that only happens when it is less than bytes.Length, which is much less
  1903. // than MAX_INT.
  1904. int len = (LeftToRead > bytes.Length) ? bytes.Length : (int)LeftToRead;
  1905. int n = s1.Read(bytes, 0, len);
  1906. //Console.WriteLine("ExtractOne: Read {0} bytes\n{1}", n, Util.FormatByteArray(bytes,n));
  1907. _CheckRead(n);
  1908. output.Write(bytes, 0, n);
  1909. LeftToRead -= n;
  1910. bytesWritten += n;
  1911. // fire the progress event, check for cancels
  1912. OnExtractProgress(bytesWritten, UncompressedSize);
  1913. if (_ioOperationCanceled)
  1914. {
  1915. break;
  1916. }
  1917. }
  1918. CrcResult = s1.Crc32;
  1919. #if AESCRYPTO
  1920. // Read the MAC if appropriate
  1921. if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1922. Encryption == EncryptionAlgorithm.WinZipAes256)
  1923. {
  1924. var wzs = input2 as WinZipAesCipherStream;
  1925. _aesCrypto.CalculatedMac = wzs.FinalAuthentication;
  1926. }
  1927. #endif
  1928. }
  1929. return CrcResult;
  1930. }
  1931. internal void MarkAsDirectory()
  1932. {
  1933. _IsDirectory = true;
  1934. // workitem 6279
  1935. if (!_FileNameInArchive.EndsWith("/"))
  1936. _FileNameInArchive += "/";
  1937. }
  1938. internal void WriteCentralDirectoryEntry(System.IO.Stream s)
  1939. {
  1940. byte[] bytes = new byte[4096];
  1941. int i = 0;
  1942. // signature
  1943. bytes[i++] = (byte)(ZipConstants.ZipDirEntrySignature & 0x000000FF);
  1944. bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x0000FF00) >> 8);
  1945. bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x00FF0000) >> 16);
  1946. bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0xFF000000) >> 24);
  1947. // Version Made By
  1948. // workitem 7071
  1949. // We must not overwrite the VersionMadeBy field when writing out a zip archive.
  1950. // The VersionMadeBy tells the zip reader the meaning of the File attributes.
  1951. // Overwriting the VersionMadeBy will result in inconsistent metadata.
  1952. // Consider the scenario where the application opens and reads a zip file that had been created
  1953. // on Linux. Then the app adds one file to the Zip archive, and saves it.
  1954. // The file attributes for all the entries added on Linux will be significant
  1955. // for Linux. Therefore the VersionMadeBy for those entries must not be changed.
  1956. // Only the entries that are actually created on Windows NTFS should get the
  1957. // VersionMadeBy indicating Windows/NTFS.
  1958. bytes[i++] = (byte)(_VersionMadeBy & 0x00FF);
  1959. bytes[i++] = (byte)((_VersionMadeBy & 0xFF00) >> 8);
  1960. // Apparently we want to duplicate the extra field here; we cannot
  1961. // simply zero it out and assume tools and apps will use the right one.
  1962. ////Int16 extraFieldLengthSave = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
  1963. ////_EntryHeader[28] = 0;
  1964. ////_EntryHeader[29] = 0;
  1965. // Version Needed, Bitfield, compression method, lastmod,
  1966. // crc, compressed and uncompressed sizes, filename length and extra field length.
  1967. // These are all present in the local file header, but they may be zero values there.
  1968. // So we cannot just copy them.
  1969. Int16 versionNeededToExtract = (Int16)(_OutputUsesZip64.Value ? 45 : 20);
  1970. bytes[i++] = (byte)(versionNeededToExtract & 0x00FF);
  1971. bytes[i++] = (byte)((versionNeededToExtract & 0xFF00) >> 8);
  1972. bytes[i++] = (byte)(_BitField & 0x00FF);
  1973. bytes[i++] = (byte)((_BitField & 0xFF00) >> 8);
  1974. bytes[i++] = (byte)(CompressionMethod & 0x00FF);
  1975. bytes[i++] = (byte)((CompressionMethod & 0xFF00) >> 8);
  1976. #if AESCRYPTO
  1977. if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  1978. Encryption == EncryptionAlgorithm.WinZipAes256)
  1979. {
  1980. i -= 2;
  1981. bytes[i++] = 0x63;
  1982. bytes[i++] = 0;
  1983. }
  1984. #endif
  1985. bytes[i++] = (byte)(_TimeBlob & 0x000000FF);
  1986. bytes[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
  1987. bytes[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
  1988. bytes[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
  1989. bytes[i++] = (byte)(_Crc32 & 0x000000FF);
  1990. bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
  1991. bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
  1992. bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
  1993. int j = 0;
  1994. if (_OutputUsesZip64.Value)
  1995. {
  1996. // CompressedSize (Int32) and UncompressedSize - all 0xFF
  1997. for (j = 0; j < 8; j++)
  1998. bytes[i++] = 0xFF;
  1999. }
  2000. else
  2001. {
  2002. bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
  2003. bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
  2004. bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
  2005. bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
  2006. bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
  2007. bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
  2008. bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
  2009. bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
  2010. }
  2011. byte[] FileNameBytes = _GetEncodedFileNameBytes();
  2012. Int16 filenameLength = (Int16)FileNameBytes.Length;
  2013. bytes[i++] = (byte)(filenameLength & 0x00FF);
  2014. bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);
  2015. // do this again because now we have real data
  2016. _presumeZip64 = _OutputUsesZip64.Value;
  2017. _Extra = ConsExtraField();
  2018. Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
  2019. bytes[i++] = (byte)(extraFieldLength & 0x00FF);
  2020. bytes[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);
  2021. // File (entry) Comment Length
  2022. // the _CommentBytes private field was set during WriteHeader()
  2023. int commentLength = (_CommentBytes == null) ? 0 : _CommentBytes.Length;
  2024. // the size of our buffer defines the max length of the comment we can write
  2025. if (commentLength + i > bytes.Length) commentLength = bytes.Length - i;
  2026. bytes[i++] = (byte)(commentLength & 0x00FF);
  2027. bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);
  2028. // Disk number start
  2029. bytes[i++] = 0;
  2030. bytes[i++] = 0;
  2031. // internal file attrs
  2032. bytes[i++] = 0; // resrvd PKWARE. filetype hint. 0=bin, 1=txt. // (byte)((IsDirectory) ? 0 : 1);
  2033. bytes[i++] = 0;
  2034. // external file attrs
  2035. bytes[i++] = (byte)((IsDirectory) ? 0x10 : 0x20);
  2036. bytes[i++] = 0;
  2037. bytes[i++] = 0xb6; // ?? not sure, this might also be zero
  2038. bytes[i++] = 0x81; // ?? ditto
  2039. // relative offset of local header
  2040. if (_OutputUsesZip64.Value)
  2041. {
  2042. // Value==true means it used Zip64.
  2043. // write 0xFFFFFFFF, which is the placeholder for ZIP64.
  2044. for (j = 0; j < 4; j++) bytes[i++] = 0xFF;
  2045. }
  2046. else
  2047. {
  2048. bytes[i++] = (byte)(_RelativeOffsetOfLocalHeader & 0x000000FF);
  2049. bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x0000FF00) >> 8);
  2050. bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x00FF0000) >> 16);
  2051. bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0xFF000000) >> 24);
  2052. }
  2053. // actual filename
  2054. for (j = 0; j < filenameLength; j++)
  2055. bytes[i + j] = FileNameBytes[j];
  2056. i += j;
  2057. // "Extra field"
  2058. if (_Extra != null)
  2059. {
  2060. for (j = 0; j < extraFieldLength; j++)
  2061. bytes[i + j] = _Extra[j];
  2062. i += j;
  2063. }
  2064. // file (entry) comment
  2065. if (commentLength != 0)
  2066. {
  2067. // now actually write the comment itself into the byte buffer
  2068. for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
  2069. bytes[i + j] = _CommentBytes[j];
  2070. i += j;
  2071. }
  2072. s.Write(bytes, 0, i);
  2073. }
  2074. #if INFOZIP_UTF8
  2075. static private bool FileNameIsUtf8(char[] FileNameChars)
  2076. {
  2077. bool isUTF8 = false;
  2078. bool isUnicode = false;
  2079. for (int j = 0; j < FileNameChars.Length; j++)
  2080. {
  2081. byte[] b = System.BitConverter.GetBytes(FileNameChars[j]);
  2082. isUnicode |= (b.Length != 2);
  2083. isUnicode |= (b[1] != 0);
  2084. isUTF8 |= ((b[0] & 0x80) != 0);
  2085. }
  2086. return isUTF8;
  2087. }
  2088. #endif
  2089. private byte[] ConsExtraField()
  2090. {
  2091. byte[] blockZip64 = null;
  2092. byte[] blockWinZipAes = null;
  2093. // Always emit an extra field with zip64 information.
  2094. // Later, if we don't need it, we'll set the header ID to rubbish and
  2095. // the data will be ignored. This results in additional overhead metadata
  2096. // in the zip file, but it will be small in comparison to the entry data.
  2097. if (_zipfile._zip64 != Zip64Option.Never)
  2098. {
  2099. // add extra field for zip64 here
  2100. blockZip64 = new byte[4 + 28];
  2101. int i = 0;
  2102. // HeaderId = dummy data now, maybe set to 0x0001 (ZIP64) later.
  2103. //blockZip64[i++] = 0x99;
  2104. //blockZip64[i++] = 0x99;
  2105. if (_presumeZip64)
  2106. {
  2107. // HeaderId = always use zip64 extensions.
  2108. blockZip64[i++] = 0x01;
  2109. blockZip64[i++] = 0x00;
  2110. }
  2111. else
  2112. {
  2113. // HeaderId = dummy data now, maybe set to 0x0001 (ZIP64) later.
  2114. blockZip64[i++] = 0x99;
  2115. blockZip64[i++] = 0x99;
  2116. }
  2117. // DataSize
  2118. blockZip64[i++] = 0x1c; // decimal 28 - this is important
  2119. blockZip64[i++] = 0x00;
  2120. // The actual metadata - we may or may not have real values yet...
  2121. // uncompressed size
  2122. Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, blockZip64, i, 8);
  2123. i += 8;
  2124. // compressed size
  2125. Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, blockZip64, i, 8);
  2126. i += 8;
  2127. // relative offset
  2128. Array.Copy(BitConverter.GetBytes(_RelativeOffsetOfLocalHeader), 0, blockZip64, i, 8);
  2129. i += 8;
  2130. // starting disk number
  2131. Array.Copy(BitConverter.GetBytes(0), 0, blockZip64, i, 4);
  2132. }
  2133. #if AESCRYPTO
  2134. if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  2135. Encryption == EncryptionAlgorithm.WinZipAes256)
  2136. {
  2137. blockWinZipAes = new byte[4 + 7];
  2138. int i = 0;
  2139. // extra field for WinZip AES
  2140. // header id
  2141. blockWinZipAes[i++] = 0x01;
  2142. blockWinZipAes[i++] = 0x99;
  2143. // data size
  2144. blockWinZipAes[i++] = 0x07;
  2145. blockWinZipAes[i++] = 0x00;
  2146. // vendor number
  2147. blockWinZipAes[i++] = 0x01; // AE-1 - means "Verify CRC"
  2148. blockWinZipAes[i++] = 0x00;
  2149. // vendor id "AE"
  2150. blockWinZipAes[i++] = 0x41;
  2151. blockWinZipAes[i++] = 0x45;
  2152. // key strength
  2153. blockWinZipAes[i] = 0xFF;
  2154. if (_KeyStrengthInBits == 128)
  2155. blockWinZipAes[i] = 1;
  2156. if (_KeyStrengthInBits == 256)
  2157. blockWinZipAes[i] = 3;
  2158. i++;
  2159. // actual compression method
  2160. blockWinZipAes[i++] = (byte)(_CompressionMethod & 0x00FF);
  2161. blockWinZipAes[i++] = (byte)(_CompressionMethod & 0xFF00);
  2162. }
  2163. #endif
  2164. // could inject other blocks here...
  2165. // concatenate any blocks we've got:
  2166. byte[] block = null;
  2167. int totalLength = 0;
  2168. if (blockZip64 != null)
  2169. totalLength += blockZip64.Length;
  2170. if (blockWinZipAes != null)
  2171. totalLength += blockWinZipAes.Length;
  2172. if (totalLength > 0)
  2173. {
  2174. block = new byte[totalLength];
  2175. int current = 0;
  2176. if (blockZip64 != null)
  2177. {
  2178. System.Array.Copy(blockZip64, 0, block, current,
  2179. blockZip64.Length);
  2180. current += blockZip64.Length;
  2181. }
  2182. if (blockWinZipAes != null)
  2183. {
  2184. System.Array.Copy(blockWinZipAes, 0, block, current,
  2185. blockWinZipAes.Length);
  2186. current += blockWinZipAes.Length;
  2187. }
  2188. }
  2189. return block;
  2190. }
  2191. // workitem 6513: when writing, use alt encoding only when ibm437 will not do
  2192. private System.Text.Encoding GenerateCommentBytes()
  2193. {
  2194. _CommentBytes = ibm437.GetBytes(_Comment);
  2195. // need to use this form of GetString() for .NET CF
  2196. string s1 = ibm437.GetString(_CommentBytes, 0, _CommentBytes.Length);
  2197. if (s1 == _Comment)
  2198. return ibm437;
  2199. else
  2200. {
  2201. _CommentBytes = _provisionalAlternateEncoding.GetBytes(_Comment);
  2202. return _provisionalAlternateEncoding;
  2203. }
  2204. }
  2205. // workitem 6513
  2206. private byte[] _GetEncodedFileNameBytes()
  2207. {
  2208. // here, we need to flip the backslashes to forward-slashes,
  2209. // also, we need to trim the \\server\share syntax from any UNC path.
  2210. // and finally, we need to remove any leading .\
  2211. string SlashFixed = FileName.Replace("\\", "/");
  2212. string s1 = null;
  2213. if ((_TrimVolumeFromFullyQualifiedPaths) && (FileName.Length >= 3)
  2214. && (FileName[1] == ':') && (SlashFixed[2] == '/'))
  2215. {
  2216. // trim off volume letter, colon, and slash
  2217. s1 = SlashFixed.Substring(3);
  2218. }
  2219. else if ((FileName.Length >= 4)
  2220. && ((SlashFixed[0] == '/') && (SlashFixed[1] == '/')))
  2221. {
  2222. int n = SlashFixed.IndexOf('/', 2);
  2223. //System.Console.WriteLine("input Path '{0}'", FileName);
  2224. //System.Console.WriteLine("xformed: '{0}'", SlashFixed);
  2225. //System.Console.WriteLine("third slash: {0}\n", n);
  2226. if (n == -1)
  2227. throw new ArgumentException("The path for that entry appears to be badly formatted");
  2228. s1 = SlashFixed.Substring(n + 1);
  2229. }
  2230. else if ((FileName.Length >= 3)
  2231. && ((SlashFixed[0] == '.') && (SlashFixed[1] == '/')))
  2232. {
  2233. // trim off dot and slash
  2234. s1 = SlashFixed.Substring(2);
  2235. }
  2236. else
  2237. {
  2238. s1 = SlashFixed;
  2239. }
  2240. // workitem 6513: when writing, use the alternative encoding only when ibm437 will not do.
  2241. byte[] result = ibm437.GetBytes(s1);
  2242. // need to use this form of GetString() for .NET CF
  2243. string s2 = ibm437.GetString(result, 0, result.Length);
  2244. _CommentBytes = null;
  2245. if (s2 == s1)
  2246. {
  2247. // file can be encoded with ibm437, now try comment
  2248. // case 1: no comment. use ibm437
  2249. if (_Comment == null || _Comment.Length == 0)
  2250. {
  2251. _actualEncoding = ibm437;
  2252. return result;
  2253. }
  2254. // there is a comment. Get the encoded form.
  2255. System.Text.Encoding commentEncoding = GenerateCommentBytes();
  2256. // case 2: if the comment also uses 437, we're good.
  2257. if (commentEncoding.CodePage == 437)
  2258. {
  2259. _actualEncoding = ibm437;
  2260. return result;
  2261. }
  2262. // case 3: comment requires non-437 code page. Use the same
  2263. // code page for the filename.
  2264. _actualEncoding = commentEncoding;
  2265. result = commentEncoding.GetBytes(s1);
  2266. return result;
  2267. }
  2268. else
  2269. {
  2270. // Cannot encode with ibm437 safely.
  2271. // Therefore, use the provisional encoding
  2272. result = _provisionalAlternateEncoding.GetBytes(s1);
  2273. if (_Comment != null && _Comment.Length != 0)
  2274. {
  2275. _CommentBytes = _provisionalAlternateEncoding.GetBytes(_Comment);
  2276. }
  2277. _actualEncoding = _provisionalAlternateEncoding;
  2278. return result;
  2279. }
  2280. }
  2281. private bool WantReadAgain()
  2282. {
  2283. if (_CompressionMethod == 0x00) return false;
  2284. if (_CompressedSize < _UncompressedSize) return false;
  2285. if (ForceNoCompression) return false;
  2286. // check delegate
  2287. if (WillReadTwiceOnInflation != null)
  2288. return WillReadTwiceOnInflation(_UncompressedSize, _CompressedSize, FileName);
  2289. return true;
  2290. }
  2291. // heuristic - if the filename is one of a known list of non-compressible files,
  2292. // return false. else true. We apply this by just checking the extension.
  2293. private bool SeemsCompressible(string filename)
  2294. {
  2295. string re = "(?i)^(.+)\\.(mp3|png|docx|xlsx|jpg|zip)$";
  2296. if (RE.Regex.IsMatch(filename, re))
  2297. {
  2298. return false;
  2299. }
  2300. return true;
  2301. }
  2302. private bool DefaultWantCompression()
  2303. {
  2304. if (_LocalFileName != null)
  2305. return SeemsCompressible(_LocalFileName);
  2306. if (_FileNameInArchive != null)
  2307. return SeemsCompressible(_FileNameInArchive);
  2308. return true;
  2309. }
  2310. private void FigureCompressionMethodForWriting(int cycle)
  2311. {
  2312. // if we've already tried with compression... turn it off this time
  2313. if (cycle > 1)
  2314. {
  2315. _CompressionMethod = 0x0;
  2316. }
  2317. // compression for directories = 0x00 (No Compression)
  2318. else if (IsDirectory)
  2319. {
  2320. _CompressionMethod = 0x0;
  2321. }
  2322. else if (__FileDataPosition != -1)
  2323. {
  2324. // If at this point, __FileDataPosition is non-zero, that means we've read this
  2325. // entry from an existing zip archive.
  2326. //
  2327. // In this case, we just keep the existing file data and metadata (including
  2328. // CompressionMethod, CRC, compressed size, uncompressed size, etc).
  2329. //
  2330. // All those member variables have been set during read!
  2331. //
  2332. }
  2333. else
  2334. {
  2335. // If __FileDataPosition is zero, then that means we will get the data from a file
  2336. // or stream.
  2337. // It is never possible to compress a zero-length file, so we check for
  2338. // this condition.
  2339. long fileLength = 0;
  2340. if (_sourceStream != null)
  2341. {
  2342. fileLength = _sourceStream.Length;
  2343. }
  2344. else
  2345. {
  2346. // special case zero-length files
  2347. System.IO.FileInfo fi = new System.IO.FileInfo(LocalFileName);
  2348. fileLength = fi.Length;
  2349. }
  2350. if (fileLength == 0)
  2351. {
  2352. _CompressionMethod = 0x00;
  2353. }
  2354. else if (_ForceNoCompression)
  2355. {
  2356. _CompressionMethod = 0x00;
  2357. }
  2358. // Ok, we're getting the data to be compressed from a non-zero length file
  2359. // or stream. In that case we check the callback to see if the app
  2360. // wants to tell us whether to compress or not.
  2361. else if (WantCompression != null)
  2362. {
  2363. _CompressionMethod = (short)(WantCompression(LocalFileName, _FileNameInArchive)
  2364. ? 0x08 : 0x00);
  2365. }
  2366. else
  2367. {
  2368. // if there is no callback set, we use the default behavior.
  2369. _CompressionMethod = (short)(DefaultWantCompression()
  2370. ? 0x08 : 0x00);
  2371. }
  2372. }
  2373. }
  2374. // write the header info for an entry
  2375. private void WriteHeader(System.IO.Stream s, int cycle)
  2376. {
  2377. int j = 0;
  2378. // remember the offset, within the output stream, of this particular entry header
  2379. var counter = s as CountingStream;
  2380. _RelativeOffsetOfLocalHeader = (counter != null) ? counter.BytesWritten : s.Position;
  2381. byte[] bytes = new byte[512]; // large enough for looooong filenames (MAX_PATH == 260)
  2382. int i = 0;
  2383. // signature
  2384. bytes[i++] = (byte)(ZipConstants.ZipEntrySignature & 0x000000FF);
  2385. bytes[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x0000FF00) >> 8);
  2386. bytes[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x00FF0000) >> 16);
  2387. bytes[i++] = (byte)((ZipConstants.ZipEntrySignature & 0xFF000000) >> 24);
  2388. // validate the ZIP64 usage
  2389. if (_zipfile._zip64 == Zip64Option.Never && (uint)_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF)
  2390. throw new ZipException("Offset within the zip archive exceeds 0xFFFFFFFF. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
  2391. // Design notes for ZIP64:
  2392. // The specification says that the header must include the Compressed and Uncompressed
  2393. // sizes, as well as the CRC32 value. When creating a zip via streamed processing,
  2394. // these quantities are not known until after the compression is done. Thus, a typical
  2395. // way to do it is to insert zeroes for these quantities, then do the compression, then
  2396. // seek back to insert the appropriate values, then seek forward to the end of the file data.
  2397. // There is also the option of using bit 3 in the GP bitfield - to specify that there
  2398. // is a data descriptor after the file data containing these three quantities.
  2399. // This works when the size of the quantities is known, either 32-bits or 64 bits as
  2400. // with the ZIP64 extensions.
  2401. // With Zip64, the 4-byte fields are set to 0xffffffff, and there is a corresponding
  2402. // data block in the "extra field" that contains the actual Compressed, uncompressed sizes.
  2403. // (As well as an additional field, the "Relative Offset of Local Header")
  2404. // The problem is when the app desires to use ZIP64 extensions optionally, only when
  2405. // necessary. Suppose the library assumes no zip64 extensions when writing the
  2406. // header, then after compression finds that the size of the data requires zip64. At
  2407. // this point, the header, already written to the file, won't have the necessary data
  2408. // block in the "extra field". The size of the entry header is fixed, so it is not
  2409. // possible to just "add on" the zip64 data block after compressing the file. On the
  2410. // other hand, always using zip64 will break interoperability with many other systems
  2411. // and apps.
  2412. // The approach we take is to insert a 32-byte dummy data block in the extra field,
  2413. // whenever zip64 is to be used "as necessary". This data block will get the actual
  2414. // zip64 HeaderId and zip64 metadata if necessary. If not necessary, the data block
  2415. // will get a meaningless HeaderId (0x1111), and will be filled with zeroes.
  2416. // When zip64 is actually in use, we also need to set the VersionNeededToExtract field to 45.
  2417. //
  2418. // There is one additional wrinkle: using zip64 as necessary conflicts with output to non-seekable
  2419. // devices. The header is emitted and must indicate whether zip64 is in use, before we know if
  2420. // zip64 is necessary. Because there is no seeking, the header can never be changed. Therefore,
  2421. // on non-seekable devices, Zip64Option.AsNecessary is the same as Zip64Option.Always.
  2422. // version needed- see AppNote.txt
  2423. // need v5.1 for PKZIP strong encryption, or v2.0 for no encryption or for PK encryption, 4.5 for zip64.
  2424. // We may reset this later, as necessary or zip64.
  2425. _presumeZip64 = (_zipfile._zip64 == Zip64Option.Always || (_zipfile._zip64 == Zip64Option.AsNecessary && !s.CanSeek));
  2426. Int16 VersionNeededToExtract = (Int16)(_presumeZip64 ? 45 : 20);
  2427. // (i==4)
  2428. bytes[i++] = (byte)(VersionNeededToExtract & 0x00FF);
  2429. bytes[i++] = (byte)((VersionNeededToExtract & 0xFF00) >> 8);
  2430. // get byte array including any encoding
  2431. // workitem 6513
  2432. byte[] FileNameBytes = _GetEncodedFileNameBytes();
  2433. Int16 filenameLength = (Int16)FileNameBytes.Length;
  2434. // general purpose bitfield
  2435. // In the current implementation, this library uses only these bits
  2436. // in the GP bitfield:
  2437. // bit 0 = if set, indicates the entry is encrypted
  2438. // bit 3 = if set, indicates the CRC, C and UC sizes follow the file data.
  2439. // bit 6 = strong encryption
  2440. // bit 11 = UTF-8 encoding is used in the comment and filename
  2441. _BitField = (Int16)((UsesEncryption) ? 1 : 0);
  2442. if (UsesEncryption && (IsStrong(Encryption)))
  2443. _BitField |= 0x0020;
  2444. // set the UTF8 bit if necessary
  2445. if (ActualEncoding.CodePage == System.Text.Encoding.UTF8.CodePage) _BitField |= 0x0800;
  2446. // The PKZIP spec says that if bit 3 is set (0x0008) in the General Purpose BitField,
  2447. // then the CRC, Compressed size, and uncompressed size are written directly after the
  2448. // file data.
  2449. //
  2450. // Those 3 quantities are not knowable until after the compression is done. Yet they
  2451. // are required to be in the header. Normally, we'd
  2452. // - write the header, using zeros for these quantities
  2453. // - compress the data, and incidentally compute these quantities.
  2454. // - seek back and write the correct values them into the header.
  2455. //
  2456. // This is nice because it is simpler and less error prone to read the zip file.
  2457. //
  2458. // But if seeking in the output stream is not possible, then we need to set the
  2459. // appropriate bitfield and emit these quantities after the compressed file data in
  2460. // the output.
  2461. // workitem 7216 - having trouble formatting a zip64 file that is readable by WinZip.
  2462. // not sure why!
  2463. if (!s.CanSeek || _presumeZip64)
  2464. _BitField |= 0x0008;
  2465. // (i==6)
  2466. bytes[i++] = (byte)(_BitField & 0x00FF);
  2467. bytes[i++] = (byte)((_BitField & 0xFF00) >> 8);
  2468. // Here, we want to set values for Compressed Size, Uncompressed Size, and CRC.
  2469. // If we have __FileDataPosition as not -1 (zero is a valid FDP), then that means we are reading this
  2470. // zip entry from a zip file, and we have good values for those quantities.
  2471. //
  2472. // If _FileDataPosition is -1, then we are consing up this Entry from scratch.
  2473. // We zero those quantities now, and we will compute
  2474. // actual values for the three quantities later, when we do the compression, and then
  2475. // seek back to write them into the appropriate place in the header.
  2476. if (this.__FileDataPosition == -1)
  2477. {
  2478. _UncompressedSize = 0;
  2479. _CompressedSize = 0;
  2480. _Crc32 = 0;
  2481. _crcCalculated = false;
  2482. }
  2483. // set compression method here
  2484. FigureCompressionMethodForWriting(cycle);
  2485. // (i==8) compression method
  2486. bytes[i++] = (byte)(CompressionMethod & 0x00FF);
  2487. bytes[i++] = (byte)((CompressionMethod & 0xFF00) >> 8);
  2488. #if AESCRYPTO
  2489. if (Encryption == EncryptionAlgorithm.WinZipAes128 || Encryption == EncryptionAlgorithm.WinZipAes256)
  2490. {
  2491. i -= 2;
  2492. bytes[i++] = 0x63;
  2493. bytes[i++] = 0;
  2494. }
  2495. #endif
  2496. // LastMod
  2497. _TimeBlob = Ionic.Zip.SharedUtilities.DateTimeToPacked(LastModified);
  2498. // (i==10) time blob
  2499. bytes[i++] = (byte)(_TimeBlob & 0x000000FF);
  2500. bytes[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
  2501. bytes[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
  2502. bytes[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
  2503. // (i==14) CRC - if source==filesystem, this is zero now, actual value will be calculated later.
  2504. // if source==archive, this is a bonafide value.
  2505. bytes[i++] = (byte)(_Crc32 & 0x000000FF);
  2506. bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
  2507. bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
  2508. bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
  2509. if (_presumeZip64)
  2510. {
  2511. // (i==18) CompressedSize (Int32) and UncompressedSize - all 0xFF for now
  2512. for (j = 0; j < 8; j++)
  2513. bytes[i++] = 0xFF;
  2514. }
  2515. else
  2516. {
  2517. // (i==18) CompressedSize (Int32) - this value may or may not be bonafide.
  2518. // if source == filesystem, then it is zero, and we'll learn it after we compress.
  2519. // if source == archive, then it is bonafide data.
  2520. bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
  2521. bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
  2522. bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
  2523. bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
  2524. // (i==22) UncompressedSize (Int32) - this value may or may not be bonafide.
  2525. bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
  2526. bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
  2527. bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
  2528. bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
  2529. }
  2530. // (i==26) filename length (Int16)
  2531. bytes[i++] = (byte)(filenameLength & 0x00FF);
  2532. bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);
  2533. _Extra = ConsExtraField();
  2534. // (i==28) extra field length (short)
  2535. Int16 ExtraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
  2536. bytes[i++] = (byte)(ExtraFieldLength & 0x00FF);
  2537. bytes[i++] = (byte)((ExtraFieldLength & 0xFF00) >> 8);
  2538. // The filename written to the archive.
  2539. // The buffer is already encoded; we just copy across the bytes.
  2540. for (j = 0; (j < FileNameBytes.Length) && (i + j < bytes.Length); j++)
  2541. bytes[i + j] = FileNameBytes[j];
  2542. i += j;
  2543. // "Extra field"
  2544. if (_Extra != null)
  2545. {
  2546. for (j = 0; j < _Extra.Length; j++)
  2547. bytes[i + j] = _Extra[j];
  2548. i += j;
  2549. }
  2550. _LengthOfHeader = i;
  2551. // finally, write the header to the stream
  2552. s.Write(bytes, 0, i);
  2553. // preserve this header data, we'll use it again later.
  2554. // ..when seeking backward, to write again, after we have the Crc, compressed and uncompressed sizes.
  2555. // ..and when writing the central directory structure.
  2556. _EntryHeader = new byte[i];
  2557. for (j = 0; j < i; j++)
  2558. _EntryHeader[j] = bytes[j];
  2559. }
  2560. private Int32 FigureCrc32()
  2561. {
  2562. if (_crcCalculated == false)
  2563. {
  2564. Stream input = null;
  2565. // get the original stream:
  2566. if (_sourceStream != null)
  2567. {
  2568. _sourceStream.Position = 0;
  2569. input = _sourceStream;
  2570. }
  2571. else
  2572. {
  2573. //input = System.IO.File.OpenRead(LocalFileName);
  2574. input = System.IO.File.Open(LocalFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  2575. }
  2576. var crc32 = new CRC32();
  2577. _Crc32 = crc32.GetCrc32(input);
  2578. if (_sourceStream == null)
  2579. {
  2580. input.Close();
  2581. #if !NETCF20
  2582. input.Dispose();
  2583. #endif
  2584. }
  2585. _crcCalculated = true;
  2586. }
  2587. return _Crc32;
  2588. }
  2589. // Copy metadata that may have been changed by the app.
  2590. // We do this when resetting the zipFile instance. If the app calls Save() on a ZipFile,
  2591. // then tries to party on that file some more, we may need to Reset() it , which
  2592. // means re-reading the entries and then copying the metadata. I think.
  2593. internal void CopyMetaData(ZipEntry source)
  2594. {
  2595. this.__FileDataPosition = source.__FileDataPosition;
  2596. this.CompressionMethod = source.CompressionMethod;
  2597. this._CompressedFileDataSize = source._CompressedFileDataSize;
  2598. this._UncompressedSize = source._UncompressedSize;
  2599. this._BitField = source._BitField;
  2600. this._LastModified = source._LastModified;
  2601. }
  2602. private void _WriteFileData(System.IO.Stream s)
  2603. {
  2604. // Read in the data from the input stream (often a file in the filesystem),
  2605. // and write it to the output stream, calculating a CRC on it as we go.
  2606. // We will also deflate and encrypt as necessary.
  2607. Stream input = null;
  2608. CrcCalculatorStream input1 = null;
  2609. CountingStream outputCounter = null;
  2610. try
  2611. {
  2612. // s.Position may fail on some write-only streams, eg stdout or System.Web.HttpResponseStream
  2613. // We swallow that exception, because we don't care!
  2614. this.__FileDataPosition = s.Position;
  2615. }
  2616. catch { }
  2617. try
  2618. {
  2619. // get the original stream:
  2620. if (_sourceStream != null)
  2621. {
  2622. _sourceStream.Position = 0;
  2623. input = _sourceStream;
  2624. }
  2625. else
  2626. {
  2627. //input = System.IO.File.OpenRead(LocalFileName);
  2628. // workitem 7145
  2629. FileShare fs = FileShare.ReadWrite;
  2630. #if !NETCF
  2631. // FileShare.Delete is not defined for the Compact Framework
  2632. fs |= FileShare.Delete;
  2633. #endif
  2634. input = System.IO.File.Open(LocalFileName, FileMode.Open, FileAccess.Read, fs);
  2635. }
  2636. long fileLength = 0;
  2637. if (_sourceStream == null)
  2638. {
  2639. System.IO.FileInfo fi = new System.IO.FileInfo(LocalFileName);
  2640. fileLength = fi.Length;
  2641. }
  2642. // wrap a CRC Calculator Stream around the raw input stream.
  2643. input1 = new CrcCalculatorStream(input);
  2644. // wrap a counting stream around the raw output stream:
  2645. outputCounter = new CountingStream(s);
  2646. // maybe wrap an encrypting stream around that:
  2647. Stream output1 = outputCounter;
  2648. if (Encryption == EncryptionAlgorithm.PkzipWeak)
  2649. output1 = new ZipCipherStream(outputCounter, _zipCrypto, CryptoMode.Encrypt);
  2650. #if AESCRYPTO
  2651. else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  2652. Encryption == EncryptionAlgorithm.WinZipAes256)
  2653. {
  2654. output1 = new WinZipAesCipherStream(outputCounter, _aesCrypto, CryptoMode.Encrypt);
  2655. }
  2656. #endif
  2657. //Stream output1a = new TraceStream(output1);
  2658. // maybe wrap a DeflateStream around that
  2659. Stream output2 = null;
  2660. bool mustCloseDeflateStream = false;
  2661. if (CompressionMethod == 0x08)
  2662. {
  2663. output2 = new Ionic.Zlib.DeflateStream(output1, Ionic.Zlib.CompressionMode.Compress,
  2664. _zipfile.CompressionLevel,
  2665. true);
  2666. mustCloseDeflateStream = true;
  2667. }
  2668. else
  2669. {
  2670. output2 = output1;
  2671. }
  2672. //Stream output2 = new TraceStream(output2a);
  2673. // as we emit the file, we maybe deflate, then maybe encrypt, then write the bytes.
  2674. byte[] buffer = new byte[WORKING_BUFFER_SIZE];
  2675. int n = input1.Read(buffer, 0, WORKING_BUFFER_SIZE);
  2676. while (n > 0)
  2677. {
  2678. output2.Write(buffer, 0, n);
  2679. OnWriteBlock(input1.TotalBytesSlurped, fileLength);
  2680. if (_ioOperationCanceled)
  2681. break;
  2682. n = input1.Read(buffer, 0, WORKING_BUFFER_SIZE);
  2683. }
  2684. // by calling Close() on the deflate stream, we write the footer bytes, as necessary.
  2685. if (mustCloseDeflateStream)
  2686. output2.Close();
  2687. output1.Flush();
  2688. output1.Close();
  2689. _LengthOfTrailer = 0;
  2690. #if AESCRYPTO
  2691. WinZipAesCipherStream wzacs = output1 as WinZipAesCipherStream;
  2692. if (wzacs != null)
  2693. {
  2694. s.Write(wzacs.FinalAuthentication, 0, 10);
  2695. _LengthOfTrailer += 10;
  2696. }
  2697. #endif
  2698. }
  2699. finally
  2700. {
  2701. if (_sourceStream == null && input != null)
  2702. {
  2703. input.Close();
  2704. #if !NETCF20
  2705. input.Dispose();
  2706. #endif
  2707. }
  2708. }
  2709. if (_ioOperationCanceled)
  2710. return;
  2711. _UncompressedSize = input1.TotalBytesSlurped;
  2712. _CompressedSize = outputCounter.BytesWritten;
  2713. _Crc32 = input1.Crc32;
  2714. // Console.WriteLine("\nWriting Entry : {0}", _LocalFileName);
  2715. // Console.WriteLine(" CRC: 0x{0:X8}", _Crc32);
  2716. // Console.WriteLine(" Compressed: 0x{0:X8} ({0})", _CompressedSize);
  2717. // Console.WriteLine(" Uncompressed: 0x{0:X8} ({0})", _UncompressedSize);
  2718. if (_Password != null)
  2719. {
  2720. if (Encryption == EncryptionAlgorithm.PkzipWeak)
  2721. {
  2722. _CompressedSize += 12; // 12 extra bytes for the encryption header
  2723. }
  2724. #if AESCRYPTO
  2725. else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  2726. Encryption == EncryptionAlgorithm.WinZipAes256)
  2727. {
  2728. // adjust the compressed size to include the variable (salt+pv)
  2729. // security header and 10-byte trailer. according to the winzip AES
  2730. // spec, that metadata is included in the "Compressed Size" figure
  2731. // when encoding the zip archive.
  2732. _CompressedSize += _aesCrypto.SizeOfEncryptionMetadata;
  2733. }
  2734. #endif
  2735. }
  2736. int i = 8;
  2737. _EntryHeader[i++] = (byte)(CompressionMethod & 0x00FF);
  2738. _EntryHeader[i++] = (byte)((CompressionMethod & 0xFF00) >> 8);
  2739. i = 14;
  2740. // CRC - the correct value now
  2741. _EntryHeader[i++] = (byte)(_Crc32 & 0x000000FF);
  2742. _EntryHeader[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
  2743. _EntryHeader[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
  2744. _EntryHeader[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
  2745. // zip64 housekeeping
  2746. _entryRequiresZip64 = new Nullable<bool>
  2747. (_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF || _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
  2748. // validate the ZIP64 usage
  2749. if (_zipfile._zip64 == Zip64Option.Never && _entryRequiresZip64.Value)
  2750. throw new ZipException("Compressed or Uncompressed size, or offset exceeds the maximum value. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
  2751. _OutputUsesZip64 = new Nullable<bool>(_zipfile._zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
  2752. // (i==26) filename length (Int16)
  2753. Int16 filenameLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
  2754. Int16 extraFieldLength = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
  2755. if (_OutputUsesZip64.Value)
  2756. {
  2757. // VersionNeededToExtract - set to 45 to indicate zip64
  2758. _EntryHeader[4] = (byte)(45 & 0x00FF);
  2759. _EntryHeader[5] = 0x00;
  2760. // CompressedSize and UncompressedSize - 0xFF
  2761. for (int j = 0; j < 8; j++)
  2762. _EntryHeader[i++] = 0xff;
  2763. // At this point we need to find the "Extra field" that follows
  2764. // the filename. We had already emitted it, but the data
  2765. // (uncomp, comp, Relative Offset) was not available at the
  2766. // time we did so. Here, we emit it again, with final values.
  2767. i = 30 + filenameLength;
  2768. _EntryHeader[i++] = 0x01; // zip64
  2769. _EntryHeader[i++] = 0x00;
  2770. i += 2; // skip over data size, which is 28 (Decimal)
  2771. Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, _EntryHeader, i, 8);
  2772. i += 8;
  2773. Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, _EntryHeader, i, 8);
  2774. i += 8;
  2775. Array.Copy(BitConverter.GetBytes(_RelativeOffsetOfLocalHeader), 0, _EntryHeader, i, 8);
  2776. }
  2777. else
  2778. {
  2779. // VersionNeededToExtract - reset to 20 since no zip64
  2780. _EntryHeader[4] = (byte)(20 & 0x00FF);
  2781. _EntryHeader[5] = 0x00;
  2782. // CompressedSize - the correct value now
  2783. i = 18;
  2784. _EntryHeader[i++] = (byte)(_CompressedSize & 0x000000FF);
  2785. _EntryHeader[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
  2786. _EntryHeader[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
  2787. _EntryHeader[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
  2788. // UncompressedSize - the correct value now
  2789. _EntryHeader[i++] = (byte)(_UncompressedSize & 0x000000FF);
  2790. _EntryHeader[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
  2791. _EntryHeader[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
  2792. _EntryHeader[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
  2793. // The HeaderId in the extra field header, is already dummied out.
  2794. if (extraFieldLength != 0)
  2795. {
  2796. i = 30 + filenameLength;
  2797. // For zip archives written by this library, if the zip64 header exists,
  2798. // it is the first header. Because of the logic used when first writing the
  2799. // _EntryHeader bytes, the HeaderId is not guaranteed to be any
  2800. // particular value. So we determine if the first header is a putative zip64
  2801. // header by examining the datasize.
  2802. //UInt16 HeaderId = (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
  2803. Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
  2804. if (DataSize == 28)
  2805. {
  2806. // reset to Header Id to dummy value, effectively dummy-ing out the zip64 metadata
  2807. _EntryHeader[i++] = 0x99;
  2808. _EntryHeader[i++] = 0x99;
  2809. }
  2810. }
  2811. }
  2812. #if AESCRYPTO
  2813. if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  2814. Encryption == EncryptionAlgorithm.WinZipAes256)
  2815. {
  2816. // Must set compressionmethod to 0x0063 (decimal 99)
  2817. // and then set the compression method bytes inside the extra field to the actual
  2818. // compression method value.
  2819. i = 8;
  2820. _EntryHeader[i++] = 0x63;
  2821. _EntryHeader[i++] = 0;
  2822. i = 30 + filenameLength;
  2823. do
  2824. {
  2825. UInt16 HeaderId = (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
  2826. Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
  2827. if (HeaderId != 0x9901)
  2828. {
  2829. // skip this header
  2830. i += DataSize + 4;
  2831. }
  2832. else
  2833. {
  2834. i += 9;
  2835. // actual compression method
  2836. _EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
  2837. _EntryHeader[i++] = (byte)(_CompressionMethod & 0xFF00);
  2838. }
  2839. } while (i < (extraFieldLength - 30 - filenameLength));
  2840. }
  2841. #endif
  2842. // workitem 6414
  2843. //if (s.CanSeek)
  2844. // workitem 7216 - sometimes we don't seek even if we CAN
  2845. if ((_BitField & 0x0008) != 0x0008)
  2846. {
  2847. // seek in the raw output stream, to the beginning of the header for this entry.
  2848. s.Seek(this._RelativeOffsetOfLocalHeader, System.IO.SeekOrigin.Begin);
  2849. // write the updated header to the output stream
  2850. s.Write(_EntryHeader, 0, _EntryHeader.Length);
  2851. // adjust the count on the CountingStream as necessary
  2852. var s1 = s as CountingStream;
  2853. if (s1 != null) s1.Adjust(_EntryHeader.Length);
  2854. // seek in the raw output stream, to the end of the file data for this entry
  2855. s.Seek(_CompressedSize, System.IO.SeekOrigin.Current);
  2856. }
  2857. else
  2858. {
  2859. // ASP.NET Response.OutputStream, or stdout are non-seekable.
  2860. // But we may also want to set bit 3 in other cases, eg zip64.
  2861. //if ((_BitField & 0x0008) != 0x0008)
  2862. //throw new ZipException("Logic error.");
  2863. byte[] Descriptor = null;
  2864. // on non-seekable device, Zip64Option.AsNecessary is equivalent to Zip64Option.Always
  2865. if (_zipfile._zip64 == Zip64Option.Always || _zipfile._zip64 == Zip64Option.AsNecessary)
  2866. {
  2867. Descriptor = new byte[24];
  2868. i = 0;
  2869. // signature
  2870. Array.Copy(BitConverter.GetBytes(ZipConstants.ZipEntryDataDescriptorSignature), 0, Descriptor, i, 4);
  2871. i += 4;
  2872. // CRC - the correct value now
  2873. Array.Copy(BitConverter.GetBytes(_Crc32), 0, Descriptor, i, 4);
  2874. i += 4;
  2875. // CompressedSize - the correct value now
  2876. Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, Descriptor, i, 8);
  2877. i += 8;
  2878. // UncompressedSize - the correct value now
  2879. Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, Descriptor, i, 8);
  2880. i += 8;
  2881. }
  2882. else
  2883. {
  2884. Descriptor = new byte[16];
  2885. i = 0;
  2886. // signature
  2887. int sig = ZipConstants.ZipEntryDataDescriptorSignature;
  2888. Descriptor[i++] = (byte)(sig & 0x000000FF);
  2889. Descriptor[i++] = (byte)((sig & 0x0000FF00) >> 8);
  2890. Descriptor[i++] = (byte)((sig & 0x00FF0000) >> 16);
  2891. Descriptor[i++] = (byte)((sig & 0xFF000000) >> 24);
  2892. // CRC - the correct value now
  2893. Descriptor[i++] = (byte)(_Crc32 & 0x000000FF);
  2894. Descriptor[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
  2895. Descriptor[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
  2896. Descriptor[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
  2897. // CompressedSize - the correct value now
  2898. Descriptor[i++] = (byte)(_CompressedSize & 0x000000FF);
  2899. Descriptor[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
  2900. Descriptor[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
  2901. Descriptor[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
  2902. // UncompressedSize - the correct value now
  2903. Descriptor[i++] = (byte)(_UncompressedSize & 0x000000FF);
  2904. Descriptor[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
  2905. Descriptor[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
  2906. Descriptor[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
  2907. }
  2908. // finally, write the bit-3 descriptor to the output stream
  2909. s.Write(Descriptor, 0, Descriptor.Length);
  2910. _LengthOfTrailer += Descriptor.Length;
  2911. }
  2912. }
  2913. internal void Write(System.IO.Stream outstream)
  2914. {
  2915. if (_Source == EntrySource.Zipfile && !_restreamRequiredOnSave)
  2916. {
  2917. CopyThroughOneEntry(outstream);
  2918. return;
  2919. }
  2920. // Ok, the source for this entry is not a previously created zip file. Therefore we
  2921. // will need to process the bytestream (compute crc, maybe compress, maybe encrypt)
  2922. // in order to create the zip.
  2923. //
  2924. // We do this in potentially 2 passes: The first time we do it as requested, maybe
  2925. // with compression and maybe encryption. If that causes the bytestream to inflate
  2926. // in size, and if compression was on, then we turn off compression and do it again.
  2927. bool readAgain = true;
  2928. int nCycles = 0;
  2929. do
  2930. {
  2931. nCycles++;
  2932. // write the header:
  2933. //Console.WriteLine("calling WriteHeader({0}): 0x{1:X8}..", FileName, _CompressionMethod);
  2934. WriteHeader(outstream, nCycles);
  2935. //Console.WriteLine("done calling WriteHeader({0}): 0x{1:X8}..", FileName, _CompressionMethod);
  2936. if (IsDirectory)
  2937. {
  2938. // nothing more to write, but we need to do some housekeeping.
  2939. _entryRequiresZip64 = new Nullable<bool>(_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
  2940. _OutputUsesZip64 = new Nullable<bool>(_zipfile._zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
  2941. return;
  2942. }
  2943. // now, write the actual file data. (incl the encrypted header)
  2944. _EmitOne(outstream);
  2945. // The file data has now been written to the stream, and
  2946. // the file pointer is positioned directly after file data.
  2947. if (readAgain)
  2948. {
  2949. if (nCycles > 1) readAgain = false;
  2950. else if (!outstream.CanSeek) readAgain = false;
  2951. #if AESCRYPTO
  2952. else if (_aesCrypto != null && (CompressedSize - _aesCrypto.SizeOfEncryptionMetadata) <= UncompressedSize) readAgain = false;
  2953. #endif
  2954. else if (_zipCrypto != null && (CompressedSize - 12) <= UncompressedSize) readAgain = false;
  2955. else readAgain = WantReadAgain();
  2956. }
  2957. if (readAgain)
  2958. {
  2959. // seek back!
  2960. // seek in the raw output stream, to the beginning of the file data for this entry
  2961. outstream.Seek(_RelativeOffsetOfLocalHeader, System.IO.SeekOrigin.Begin);
  2962. // if the last entry expands, we read again; but here, we must truncate the stream
  2963. // to prevent garbage data after the end-of-central-directory.
  2964. outstream.SetLength(outstream.Position);
  2965. // adjust the count on the CountingStream as necessary
  2966. var s1 = outstream as CountingStream;
  2967. if (s1 != null) s1.Adjust(_TotalEntrySize);
  2968. }
  2969. }
  2970. while (readAgain);
  2971. }
  2972. private void _EmitOne(System.IO.Stream outstream)
  2973. {
  2974. _WriteSecurityMetadata(outstream);
  2975. // write the (potentially compressed, potentially encrypted) file data
  2976. _WriteFileData(outstream);
  2977. // track total entry size (including the trailing descriptor and MAC)
  2978. _TotalEntrySize = _LengthOfHeader + _CompressedSize + _LengthOfTrailer;
  2979. }
  2980. private void _WriteSecurityMetadata(System.IO.Stream outstream)
  2981. {
  2982. //Console.WriteLine("_WriteSecurityMetadata({0})", FileName);
  2983. if (_Password == null) return;
  2984. if (Encryption == EncryptionAlgorithm.PkzipWeak)
  2985. {
  2986. // If PKZip (weak) encryption is in use, then the encrypted entry data is preceded by
  2987. // 12-byte "encryption header" for the entry.
  2988. _zipCrypto = ZipCrypto.ForWrite(_Password);
  2989. // generate the random 12-byte header:
  2990. var rnd = new System.Random();
  2991. byte[] encryptionHeader = new byte[12];
  2992. rnd.NextBytes(encryptionHeader);
  2993. // Here, it is important to encrypt the random header, INCLUDING the final byte
  2994. // which is the high-order byte of the CRC32. We must do this before
  2995. // we encrypt the file data. This step changes the state of the cipher, or in the
  2996. // words of the PKZIP spec, it "further initializes" the cipher keys.
  2997. // No way around this: must read the stream to compute the actual CRC
  2998. FigureCrc32();
  2999. encryptionHeader[11] = (byte)((this._Crc32 >> 24) & 0xff);
  3000. byte[] cipherText = _zipCrypto.EncryptMessage(encryptionHeader, encryptionHeader.Length);
  3001. // Write the ciphered bonafide encryption header.
  3002. outstream.Write(cipherText, 0, cipherText.Length);
  3003. }
  3004. #if AESCRYPTO
  3005. else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
  3006. Encryption == EncryptionAlgorithm.WinZipAes256)
  3007. {
  3008. // If WinZip AES encryption is in use, then the encrypted entry data is preceded by
  3009. // a variable-sized Salt and a 2-byte "password verification" value for the entry.
  3010. //Console.WriteLine("WinZipAesCrypto.Generate(_Password={0}, _KeyStrengthInBits={1});",
  3011. //_Password, _KeyStrengthInBits);
  3012. _aesCrypto = WinZipAesCrypto.Generate(_Password, _KeyStrengthInBits);
  3013. // Console.WriteLine("WinZipAesCrypto : writing at position {0} (0x{0:X8})\n Salt: {1} PV: {2}",
  3014. // outstream.Position,
  3015. // Util.FormatByteArray(_aesCrypto.Salt),
  3016. // Util.FormatByteArray(_aesCrypto.GeneratedPV));
  3017. outstream.Write(_aesCrypto.Salt, 0, _aesCrypto._Salt.Length);
  3018. outstream.Write(_aesCrypto.GeneratedPV, 0, _aesCrypto.GeneratedPV.Length);
  3019. }
  3020. #endif
  3021. }
  3022. private void CopyThroughOneEntry(System.IO.Stream outstream)
  3023. {
  3024. int n;
  3025. byte[] bytes = new byte[WORKING_BUFFER_SIZE];
  3026. // just read from the existing input zipfile and write to the output
  3027. System.IO.Stream input = this.ArchiveStream;
  3028. // must re-compute metadata if it changed, or if the Zip64 option changed.
  3029. if (_metadataChanged ||
  3030. (_InputUsesZip64 && _zipfile.UseZip64WhenSaving == Zip64Option.Never) ||
  3031. (!_InputUsesZip64 && _zipfile.UseZip64WhenSaving == Zip64Option.Always))
  3032. {
  3033. //Console.WriteLine("CopyThroughOneEntry: re-constituting the entry header.");
  3034. long origRelativeOffsetOfHeader = _RelativeOffsetOfLocalHeader;
  3035. if (this.LengthOfHeader == 0)
  3036. throw new ZipException("Bad header length.");
  3037. // The header length may change due to rename of file, add a comment, etc.
  3038. // We need to retain the original.
  3039. int origLengthOfHeader = LengthOfHeader;
  3040. // WriteHeader() has the side effect of changing _RelativeOffsetOfLocalHeader
  3041. // and setting _LengthOfHeader
  3042. WriteHeader(outstream, 0);
  3043. if (!this.FileName.EndsWith("/"))
  3044. {
  3045. // not a directory, we have file data
  3046. // seek to the beginning of the entry data in the input stream
  3047. input.Seek(origRelativeOffsetOfHeader + origLengthOfHeader, System.IO.SeekOrigin.Begin);
  3048. // copy through everything after the header to the output stream
  3049. long Remaining = this._CompressedSize;
  3050. while (Remaining > 0)
  3051. {
  3052. int len = (Remaining > bytes.Length) ? bytes.Length : (int)Remaining;
  3053. // read
  3054. n = input.Read(bytes, 0, len);
  3055. _CheckRead(n);
  3056. // write
  3057. outstream.Write(bytes, 0, n);
  3058. Remaining -= n;
  3059. }
  3060. _LengthOfTrailer = 0;
  3061. #if AESCRYPTO
  3062. // 10 byte AES MAC
  3063. if (this.Encryption == EncryptionAlgorithm.WinZipAes128 ||
  3064. this.Encryption == EncryptionAlgorithm.WinZipAes256)
  3065. {
  3066. byte[] MAC = new byte[10];
  3067. input.Read(MAC, 0, 10);
  3068. outstream.Write(MAC, 0, 10);
  3069. _LengthOfTrailer += 10;
  3070. }
  3071. #endif
  3072. // bit 3 descriptor
  3073. if ((this._BitField & 0x0008) == 0x0008)
  3074. {
  3075. int size = 16;
  3076. if (_InputUsesZip64) size += 8;
  3077. byte[] Descriptor = new byte[size];
  3078. input.Read(Descriptor, 0, size);
  3079. if (_InputUsesZip64 && _zipfile.UseZip64WhenSaving == Zip64Option.Never)
  3080. {
  3081. // original descriptor was 24 bytes, now we need 16
  3082. // signature + CRC
  3083. outstream.Write(Descriptor, 0, 8);
  3084. // Compressed
  3085. outstream.Write(Descriptor, 8, 4);
  3086. // UnCompressed
  3087. outstream.Write(Descriptor, 16, 4);
  3088. _LengthOfTrailer += 16;
  3089. }
  3090. else if (!_InputUsesZip64 && _zipfile.UseZip64WhenSaving == Zip64Option.Always)
  3091. {
  3092. // original descriptor was 16 bytes, now we need 24
  3093. // signature + CRC
  3094. byte[] pad = new byte[4];
  3095. outstream.Write(Descriptor, 0, 8);
  3096. // Compressed
  3097. outstream.Write(Descriptor, 8, 4);
  3098. outstream.Write(pad, 0, 4);
  3099. // UnCompressed
  3100. outstream.Write(Descriptor, 12, 4);
  3101. outstream.Write(pad, 0, 4);
  3102. _LengthOfTrailer += 24;
  3103. }
  3104. else
  3105. {
  3106. // same descriptor on input and output. Copy it through.
  3107. outstream.Write(Descriptor, 0, size);
  3108. _LengthOfTrailer += size;
  3109. }
  3110. }
  3111. }
  3112. _TotalEntrySize = _LengthOfHeader + _CompressedSize + _LengthOfTrailer;
  3113. }
  3114. else
  3115. {
  3116. if (this.LengthOfHeader == 0)
  3117. throw new ZipException("Bad header length.");
  3118. long origRelativeOffsetOfHeader = _RelativeOffsetOfLocalHeader;
  3119. // seek to the beginning of the entry data (header + file data) in the input stream
  3120. //input.Seek(this._RelativeOffsetOfLocalHeader, System.IO.SeekOrigin.Begin);
  3121. // Here, we need to grab the header and fill it with real data. Some of
  3122. // the fields may take marker values - eg, the CRC may be all zero and
  3123. // the Uncomp and Comp sizes may be 0xFFFFFFFF. Those are all "fake"
  3124. // values, but we need to set the real ones into the header. We don't
  3125. // write the header here; instead we're just copying through. But the
  3126. // _EntryHeader array is used later when writing the Central Directory
  3127. // Structure, and the header data must be correct at that point.
  3128. // ?? that is a doomed approach !!
  3129. //_EntryHeader = new byte[this._LengthOfHeader];
  3130. //n = input.Read(_EntryHeader, 0, _EntryHeader.Length);
  3131. //_CheckRead(n);
  3132. // once again, seek to the beginning of the entry data in the input stream
  3133. input.Seek(this._RelativeOffsetOfLocalHeader, System.IO.SeekOrigin.Begin);
  3134. if (this._TotalEntrySize == 0)
  3135. {
  3136. // We've never set the length of the entry.
  3137. // Set it here.
  3138. this._TotalEntrySize = this._LengthOfHeader + this._CompressedSize
  3139. + _LengthOfTrailer;
  3140. // The CompressedSize includes all the leading metadata associated to encryption,
  3141. // if any, as well as the compressed data, or compressed-then-encrypted data.
  3142. // The _LengthOfTrailer includes the 10-byte MAC for AES, where appropriate,
  3143. // and the bit-3 Descriptor, where applicable.
  3144. }
  3145. // workitem 5616
  3146. // remember the offset, within the output stream, of this particular entry header.
  3147. // This may have changed if any of the other entries changed (eg, if a different
  3148. // entry was removed or added.)
  3149. var counter = outstream as CountingStream;
  3150. _RelativeOffsetOfLocalHeader = (int)((counter != null) ? counter.BytesWritten : outstream.Position);
  3151. // copy through the header, filedata, trailer, everything...
  3152. long Remaining = this._TotalEntrySize;
  3153. while (Remaining > 0)
  3154. {
  3155. int len = (Remaining > bytes.Length) ? bytes.Length : (int)Remaining;
  3156. // read
  3157. n = input.Read(bytes, 0, len);
  3158. _CheckRead(n);
  3159. // write
  3160. outstream.Write(bytes, 0, n);
  3161. Remaining -= n;
  3162. }
  3163. }
  3164. // zip64 housekeeping
  3165. _entryRequiresZip64 = new Nullable<bool>
  3166. (_CompressedSize >= 0xFFFFFFFF ||
  3167. _UncompressedSize >= 0xFFFFFFFF ||
  3168. _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF
  3169. );
  3170. _OutputUsesZip64 = new Nullable<bool>(_zipfile._zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
  3171. }
  3172. static internal bool IsStrong(EncryptionAlgorithm e)
  3173. {
  3174. return ((e != EncryptionAlgorithm.None)
  3175. && (e != EncryptionAlgorithm.PkzipWeak));
  3176. }
  3177. // At current cursor position in the stream, read the extra field,
  3178. // and set the properties on the ZipEntry instance appropriately.
  3179. // This can be called when processing the Extra field in the Central Directory,
  3180. // or in the local header.
  3181. internal int ProcessExtraField(Int16 extraFieldLength)
  3182. {
  3183. int additionalBytesRead = 0;
  3184. System.IO.Stream s = ArchiveStream;
  3185. if (extraFieldLength > 0)
  3186. {
  3187. byte[] Buffer = this._Extra = new byte[extraFieldLength];
  3188. additionalBytesRead = s.Read(Buffer, 0, Buffer.Length);
  3189. int j = 0;
  3190. while (j < Buffer.Length)
  3191. {
  3192. int start = j;
  3193. UInt16 HeaderId = (UInt16)(Buffer[j] + Buffer[j + 1] * 256);
  3194. Int16 DataSize = (short)(Buffer[j + 2] + Buffer[j + 3] * 256);
  3195. j += 4;
  3196. switch (HeaderId)
  3197. {
  3198. case 0x0001: // ZIP64
  3199. {
  3200. // The _IsZip64Format flag is true IFF the prior compressed/uncompressed size values were 0xFFFFFFFF.
  3201. // But we don't need to be rigid about this. Some zip archives don't behave this way.
  3202. //if (!ze._IsZip64Format)
  3203. //throw new BadReadException(String.Format(" Found zip64 metadata when none expected at position 0x{0:X16}", s.Position - additionalBytesRead));
  3204. this._InputUsesZip64 = true;
  3205. if (DataSize > 28)
  3206. throw new BadReadException(String.Format(" Inconsistent datasize (0x{0:X4}) for ZIP64 extra field at position 0x{1:X16}", DataSize, s.Position - additionalBytesRead));
  3207. if (this._UncompressedSize == 0xFFFFFFFF)
  3208. {
  3209. this._UncompressedSize = BitConverter.ToInt64(Buffer, j);
  3210. j += 8;
  3211. }
  3212. if (this._CompressedSize == 0xFFFFFFFF)
  3213. {
  3214. this._CompressedSize = BitConverter.ToInt64(Buffer, j);
  3215. j += 8;
  3216. }
  3217. if (this._RelativeOffsetOfLocalHeader == 0xFFFFFFFF)
  3218. {
  3219. this._RelativeOffsetOfLocalHeader = BitConverter.ToInt64(Buffer, j);
  3220. j += 8;
  3221. }
  3222. // ignore the potential last 4 bytes - I don't know what to do with them anyway.
  3223. }
  3224. break;
  3225. #if AESCRYPTO
  3226. case 0x9901: // WinZip AES encryption is in use. (workitem 6834)
  3227. // we will handle this extra field only if compressionmethod is 0x63
  3228. //Console.WriteLine("Found WinZip AES Encryption header (compression:0x{0:X2})", this._CompressionMethod);
  3229. if (this._CompressionMethod == 0x0063)
  3230. {
  3231. if ((this._BitField & 0x01) != 0x01)
  3232. throw new BadReadException(String.Format(" Inconsistent metadata at position 0x{0:X16}", s.Position - additionalBytesRead));
  3233. this._sourceIsEncrypted = true;
  3234. //this._aesCrypto = new WinZipAesCrypto(this);
  3235. // see spec at http://www.winzip.com/aes_info.htm
  3236. if (DataSize != 7)
  3237. throw new BadReadException(String.Format(" Inconsistent WinZip AES datasize (0x{0:X4}) at position 0x{1:X16}", DataSize, s.Position - additionalBytesRead));
  3238. this._WinZipAesMethod = BitConverter.ToInt16(Buffer, j);
  3239. j += 2;
  3240. if (this._WinZipAesMethod != 0x01 && this._WinZipAesMethod != 0x02)
  3241. throw new BadReadException(String.Format(" Unexpected vendor version number (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}",
  3242. this._WinZipAesMethod, s.Position - additionalBytesRead));
  3243. Int16 vendorId = BitConverter.ToInt16(Buffer, j);
  3244. j += 2;
  3245. if (vendorId != 0x4541)
  3246. throw new BadReadException(String.Format(" Unexpected vendor ID (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}", vendorId, s.Position - additionalBytesRead));
  3247. this._KeyStrengthInBits = -1;
  3248. if (Buffer[j] == 1) _KeyStrengthInBits = 128;
  3249. if (Buffer[j] == 3) _KeyStrengthInBits = 256;
  3250. if (this._KeyStrengthInBits < 0)
  3251. throw new Exception(String.Format("Invalid key strength ({0})", this._KeyStrengthInBits));
  3252. this.Encryption = (this._KeyStrengthInBits == 128)
  3253. ? EncryptionAlgorithm.WinZipAes128
  3254. : EncryptionAlgorithm.WinZipAes256;
  3255. j++;
  3256. // set the actual compression method
  3257. this._CompressionMethod = BitConverter.ToInt16(Buffer, j);
  3258. j += 2; // a formality
  3259. }
  3260. break;
  3261. #endif
  3262. }
  3263. // move to the next Header in the extra field
  3264. j = start + DataSize + 4;
  3265. }
  3266. }
  3267. return additionalBytesRead;
  3268. }
  3269. private void SetFdpLoh()
  3270. {
  3271. // Indicates that the value has not yet been set.
  3272. // Therefore, seek to the local header, figure the start of file data.
  3273. long origPosition = this.ArchiveStream.Position;
  3274. this.ArchiveStream.Seek(this._RelativeOffsetOfLocalHeader, System.IO.SeekOrigin.Begin);
  3275. byte[] block = new byte[30];
  3276. this.ArchiveStream.Read(block, 0, block.Length);
  3277. // At this point we could verify the contents read from the local header
  3278. // with the contents read from the central header. We could, but don't need to.
  3279. // So we won't.
  3280. Int16 filenameLength = (short)(block[26] + block[27] * 256);
  3281. Int16 extraFieldLength = (short)(block[28] + block[29] * 256);
  3282. this.ArchiveStream.Seek(filenameLength + extraFieldLength, System.IO.SeekOrigin.Current);
  3283. this._LengthOfHeader = 30 + extraFieldLength + filenameLength;
  3284. this.__FileDataPosition = _RelativeOffsetOfLocalHeader + 30 + filenameLength + extraFieldLength;
  3285. if (this._Encryption == EncryptionAlgorithm.PkzipWeak)
  3286. {
  3287. this.__FileDataPosition += 12;
  3288. }
  3289. #if AESCRYPTO
  3290. else if (this.Encryption == EncryptionAlgorithm.WinZipAes128 ||
  3291. this.Encryption == EncryptionAlgorithm.WinZipAes256)
  3292. {
  3293. this.__FileDataPosition += ((this._KeyStrengthInBits / 8 / 2) + 2);// _aesCrypto.SizeOfEncryptionMetadata;
  3294. }
  3295. #endif
  3296. // restore file position:
  3297. this.ArchiveStream.Seek(origPosition, System.IO.SeekOrigin.Begin);
  3298. }
  3299. internal long FileDataPosition
  3300. {
  3301. get
  3302. {
  3303. if (__FileDataPosition == -1)
  3304. SetFdpLoh();
  3305. return __FileDataPosition;
  3306. }
  3307. }
  3308. private int LengthOfHeader
  3309. {
  3310. get
  3311. {
  3312. if (_LengthOfHeader == 0)
  3313. SetFdpLoh();
  3314. return _LengthOfHeader;
  3315. }
  3316. }
  3317. internal ZipCrypto _zipCrypto;
  3318. #if AESCRYPTO
  3319. internal WinZipAesCrypto _aesCrypto;
  3320. internal Int16 _KeyStrengthInBits;
  3321. private Int16 _WinZipAesMethod;
  3322. #endif
  3323. internal DateTime _LastModified;
  3324. private bool _TrimVolumeFromFullyQualifiedPaths = true; // by default, trim them.
  3325. private bool _ForceNoCompression; // by default, false: do compression if it makes sense.
  3326. internal string _LocalFileName;
  3327. private string _FileNameInArchive;
  3328. internal Int16 _VersionNeeded;
  3329. internal Int16 _BitField;
  3330. internal Int16 _CompressionMethod;
  3331. internal string _Comment;
  3332. private bool _IsDirectory;
  3333. private byte[] _CommentBytes;
  3334. internal Int64 _CompressedSize;
  3335. internal Int64 _CompressedFileDataSize; // CompressedSize less 12 bytes for the encryption header, if any
  3336. internal Int64 _UncompressedSize;
  3337. internal Int32 _TimeBlob;
  3338. private bool _crcCalculated = false;
  3339. internal Int32 _Crc32;
  3340. internal byte[] _Extra;
  3341. private bool _OverwriteOnExtract;
  3342. private bool _metadataChanged;
  3343. private bool _restreamRequiredOnSave;
  3344. private bool _sourceIsEncrypted;
  3345. private long _cdrPosition;
  3346. private static System.Text.Encoding ibm437 = System.Text.Encoding.GetEncoding("IBM437");
  3347. private System.Text.Encoding _provisionalAlternateEncoding = System.Text.Encoding.GetEncoding("IBM437");
  3348. private System.Text.Encoding _actualEncoding = null;
  3349. internal ZipFile _zipfile;
  3350. internal long __FileDataPosition = -1;
  3351. private byte[] _EntryHeader;
  3352. internal Int64 _RelativeOffsetOfLocalHeader;
  3353. private Int64 _TotalEntrySize;
  3354. internal int _LengthOfHeader;
  3355. internal int _LengthOfTrailer;
  3356. private bool _InputUsesZip64;
  3357. internal string _Password;
  3358. internal EntrySource _Source = EntrySource.None;
  3359. internal EncryptionAlgorithm _Encryption = EncryptionAlgorithm.None;
  3360. internal byte[] _WeakEncryptionHeader;
  3361. internal System.IO.Stream _archiveStream;
  3362. private System.IO.Stream _sourceStream;
  3363. private object LOCK = new object();
  3364. private bool _ioOperationCanceled;
  3365. private bool _presumeZip64;
  3366. private Nullable<bool> _entryRequiresZip64;
  3367. private Nullable<bool> _OutputUsesZip64;
  3368. private const int WORKING_BUFFER_SIZE = 0x4400;
  3369. private const int Rfc2898KeygenIterations = 1000;
  3370. }
  3371. }