PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Compression/Streams/ZipInputStream.cs

#
C# | 691 lines | 423 code | 76 blank | 192 comment | 95 complexity | eba91b4308abefe2848cb4962e88c40a MD5 | raw file
Possible License(s): Apache-2.0
  1. // Based on Mike Krueger's SharpZipLib, Copyright (C) 2001 (GNU license).
  2. // Authors of the original java version: Jochen Hoenicke, John Leuner
  3. // See http://www.ISeeSharpCode.com for more information.
  4. using System;
  5. using System.IO;
  6. using System.Text;
  7. using Delta.Utilities.Compression.Checksums;
  8. using Delta.Utilities.Compression.Inflaters;
  9. using Delta.Utilities.Helpers;
  10. namespace Delta.Utilities.Compression.Streams
  11. {
  12. #region Summary
  13. /// <summary>
  14. /// This is an InflaterInputStream that reads the files baseInputStream an
  15. /// zip archive one after another. It has a special method to get the zip
  16. /// entry of the next file. The zip entry contains information about the
  17. /// file name size, compressed size, Crc, etc.
  18. /// It includes support for Stored and Deflated entries.
  19. /// </summary>
  20. /// <example>This sample shows how to read a zip file
  21. /// <code lang="C#">
  22. /// using System;
  23. /// using System.Text;
  24. /// using System.IO;
  25. /// using Delta.Utilities.Compression;
  26. ///
  27. /// class MainClass
  28. /// {
  29. /// public static void Main(string[] args)
  30. /// {
  31. /// ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]));
  32. ///
  33. /// ZipEntry theEntry;
  34. /// while ((theEntry = s.GetNextEntry()) != null)
  35. /// {
  36. /// int size = 2048;
  37. /// byte[] data = new byte[2048];
  38. ///
  39. /// Console.Write("Show contents (y/n) ?");
  40. /// if (Console.ReadLine() == "y")
  41. /// {
  42. /// while (true)
  43. /// {
  44. /// size = s.Read(data, 0, data.Length);
  45. /// if (size > 0)
  46. /// {
  47. /// Console.Write(new ASCIIEncoding().GetString(data, 0, size));
  48. /// } // if
  49. /// else
  50. /// {
  51. /// break;
  52. /// } // else
  53. /// } // while
  54. /// } // if
  55. /// } // while
  56. /// s.Close();
  57. /// } // Main(args)
  58. /// } // class MainClass
  59. /// </code>
  60. /// </example>
  61. #endregion
  62. public class ZipInputStream : InflaterInputStream
  63. {
  64. #region Delegates
  65. /// <summary>
  66. /// Delegate for reading bytes from a stream.
  67. /// </summary>
  68. /// <param name="buffer">Buffer</param>
  69. /// <param name="offset">Offset</param>
  70. /// <param name="length">Length</param>
  71. /// <returns>Number of bytes read</returns>
  72. private delegate int ReaderDelegate(byte[] buffer, int offset, int length);
  73. #endregion
  74. #region Password (Public)
  75. /// <summary>
  76. /// Optional password used for encryption when non-null
  77. /// </summary>
  78. public string Password
  79. {
  80. get
  81. {
  82. return password;
  83. } // get
  84. set
  85. {
  86. password = value;
  87. } // set
  88. }
  89. #endregion
  90. #region CanDecompressEntry (Public)
  91. /// <summary>
  92. /// Gets a value indicating if the entry can be decompressed
  93. /// </summary>
  94. /// <remarks>
  95. /// The entry can only be decompressed if the library supports the zip
  96. /// features required to extract it. See the
  97. /// <see cref="ZipEntry.Version">ZipEntry Version</see> property for more
  98. /// details.
  99. /// </remarks>
  100. public bool CanDecompressEntry
  101. {
  102. get
  103. {
  104. return entry != null && entry.Version <= ZipConstants.VersionMadeBy;
  105. } // get
  106. }
  107. #endregion
  108. #region IsEntryAvailable (Public)
  109. /// <summary>
  110. /// Returns 1 if there is an entry available
  111. /// Otherwise returns 0.
  112. /// </summary>
  113. public override int IsEntryAvailable
  114. {
  115. get
  116. {
  117. return entry != null
  118. ? 1
  119. : 0;
  120. } // get
  121. }
  122. #endregion
  123. #region Private
  124. #region internalReader (Private)
  125. /// <summary>
  126. /// The current reader this instance.
  127. /// </summary>
  128. private ReaderDelegate internalReader;
  129. #endregion
  130. #region crc (Private)
  131. /// <summary>
  132. /// Crc32 checksum
  133. /// </summary>
  134. private Crc32 crc = new Crc32();
  135. #endregion
  136. #region entry (Private)
  137. /// <summary>
  138. /// Entry
  139. /// </summary>
  140. private ZipEntry entry;
  141. #endregion
  142. #region size (Private)
  143. /// <summary>
  144. /// Size
  145. /// </summary>
  146. private long size;
  147. #endregion
  148. #region method (Private)
  149. /// <summary>
  150. /// Method
  151. /// </summary>
  152. private int method;
  153. #endregion
  154. #region flags (Private)
  155. /// <summary>
  156. /// Flags
  157. /// </summary>
  158. private int flags;
  159. #endregion
  160. #region password (Private)
  161. /// <summary>
  162. /// Password
  163. /// </summary>
  164. private string password;
  165. #endregion
  166. #endregion
  167. #region Constructors
  168. /// <summary>
  169. /// Creates a new Zip input stream, for reading a zip archive.
  170. /// </summary>
  171. public ZipInputStream(Stream baseInputStream)
  172. : base(baseInputStream, new Inflater(true))
  173. {
  174. internalReader = InitialRead;
  175. }
  176. #endregion
  177. #region GetNextEntry (Public)
  178. /// <summary>
  179. /// Advances to the next entry in the archive
  180. /// </summary>
  181. /// <returns>
  182. /// The next <see cref="ZipEntry">entry</see> in the archive or null if
  183. /// there are no more entries.
  184. /// </returns>
  185. /// <remarks>
  186. /// If the previous entry is still open
  187. /// <see cref="CloseEntry">CloseEntry</see> is called.
  188. /// </remarks>
  189. /// <exception cref="InvalidOperationException">
  190. /// Input stream is closed
  191. /// </exception>
  192. /// <exception cref="ZipException">
  193. /// Password is not set, password is invalid, compression method is
  194. /// invalid, version required to extract is not supported.
  195. /// </exception>
  196. public ZipEntry GetNextEntry()
  197. {
  198. if (crc == null)
  199. {
  200. throw new InvalidOperationException("Closed.");
  201. }
  202. if (entry != null)
  203. {
  204. CloseEntry();
  205. }
  206. int header = inputBuffer.ReadLeInt();
  207. if (header == ZipConstants.CentralDirectorySig ||
  208. header == ZipConstants.EndSig ||
  209. header == ZipConstants.CentralDigitalSig ||
  210. header == ZipConstants.CentralDirectorySig64)
  211. {
  212. // No more individual entries exist
  213. Close();
  214. return null;
  215. }
  216. // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
  217. // SpanningSig is same as descriptor signature and is untested as yet.
  218. if (header == ZipConstants.SpanningTempSig ||
  219. header == ZipConstants.SpanningSig)
  220. {
  221. header = inputBuffer.ReadLeInt();
  222. }
  223. if (header != ZipConstants.LocalSignature)
  224. {
  225. throw new ZipException(
  226. "Wrong Local header signature: 0x" + String.Format("{0:X}", header));
  227. }
  228. short versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
  229. flags = inputBuffer.ReadLeShort();
  230. method = inputBuffer.ReadLeShort();
  231. uint dostime = (uint)inputBuffer.ReadLeInt();
  232. int crc2 = inputBuffer.ReadLeInt();
  233. csize = inputBuffer.ReadLeInt();
  234. size = inputBuffer.ReadLeInt();
  235. int nameLen = inputBuffer.ReadLeShort();
  236. int extraLen = inputBuffer.ReadLeShort();
  237. bool isCrypted = (flags & 1) == 1;
  238. byte[] buffer = new byte[nameLen];
  239. inputBuffer.ReadRawBuffer(buffer);
  240. string name = ZipConstants.ConvertToString(buffer);
  241. entry = new ZipEntry(name, versionRequiredToExtract);
  242. entry.Flags = flags;
  243. if (method == (int)CompressionMethod.Stored &&
  244. (!isCrypted && csize != size ||
  245. (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
  246. {
  247. throw new ZipException("Stored, but compressed != uncompressed");
  248. }
  249. if (method != (int)CompressionMethod.Stored &&
  250. method != (int)CompressionMethod.Deflated)
  251. {
  252. throw new ZipException("Unknown compression method " + method);
  253. }
  254. entry.CompressionMethod = (CompressionMethod)method;
  255. if ((flags & 8) == 0)
  256. {
  257. entry.Crc = crc2 & 0xFFFFFFFFL;
  258. entry.Size = size & 0xFFFFFFFFL;
  259. entry.CompressedSize = csize & 0xFFFFFFFFL;
  260. }
  261. else
  262. {
  263. // This allows for GNU, WinZip and possibly other archives,
  264. // the PKZIP spec says these are zero under these circumstances.
  265. if (crc2 != 0)
  266. {
  267. entry.Crc = crc2 & 0xFFFFFFFFL;
  268. }
  269. if (size != 0)
  270. {
  271. entry.Size = size & 0xFFFFFFFFL;
  272. }
  273. if (csize != 0)
  274. {
  275. entry.CompressedSize = csize & 0xFFFFFFFFL;
  276. }
  277. }
  278. entry.DosTime = dostime;
  279. if (extraLen > 0)
  280. {
  281. byte[] extra = new byte[extraLen];
  282. inputBuffer.ReadRawBuffer(extra);
  283. entry.SetExtraData(extra);
  284. }
  285. internalReader = InitialRead;
  286. return entry;
  287. }
  288. #endregion
  289. #region CloseEntry (Public)
  290. /// <summary>
  291. /// Closes the current zip entry and moves to the next one.
  292. /// </summary>
  293. /// <exception cref="InvalidOperationException">
  294. /// The stream is closed
  295. /// </exception>
  296. /// <exception cref="ZipException">
  297. /// The Zip stream ends early
  298. /// </exception>
  299. public void CloseEntry()
  300. {
  301. if (crc == null)
  302. {
  303. throw new InvalidOperationException("Closed.");
  304. }
  305. if (entry == null)
  306. {
  307. return;
  308. }
  309. if (method == (int)CompressionMethod.Deflated)
  310. {
  311. if ((flags & 8) != 0)
  312. {
  313. // We don't know how much we must skip, read until end.
  314. byte[] tmp = new byte[2048];
  315. while (Read(tmp, 0, tmp.Length) > 0)
  316. {
  317. ;
  318. }
  319. // read will close this entry
  320. return;
  321. }
  322. csize -= inf.TotalIn;
  323. inputBuffer.Available -= inf.RemainingInput;
  324. }
  325. if (inputBuffer.Available > csize && csize >= 0)
  326. {
  327. inputBuffer.Available = (int)(inputBuffer.Available - csize);
  328. }
  329. else
  330. {
  331. csize -= inputBuffer.Available;
  332. inputBuffer.Available = 0;
  333. while (csize != 0)
  334. {
  335. int skipped = (int)base.Skip(csize & 0xFFFFFFFFL);
  336. if (skipped <= 0)
  337. {
  338. throw new ZipException("Zip archive ends early.");
  339. }
  340. csize -= skipped;
  341. }
  342. }
  343. size = 0;
  344. crc.Reset();
  345. if (method == (int)CompressionMethod.Deflated)
  346. {
  347. inf.Reset();
  348. }
  349. entry = null;
  350. }
  351. #endregion
  352. #region ReadByte (Public)
  353. /// <summary>
  354. /// Reads a byte from the current zip entry.
  355. /// </summary>
  356. /// <returns>
  357. /// The byte or -1 if end of stream is reached.
  358. /// </returns>
  359. /// <exception name="System.IO.IOException">
  360. /// An i/o error occured.
  361. /// </exception>
  362. /// <exception name="Delta.Utilities.Compression.ZipException">
  363. /// The deflated stream is corrupted.
  364. /// </exception>
  365. public override int ReadByte()
  366. {
  367. byte[] b = new byte[1];
  368. if (Read(b, 0, 1) <= 0)
  369. {
  370. return MathHelper.InvalidIndex;
  371. }
  372. return b[0] & 0xff;
  373. }
  374. #endregion
  375. #region Read (Public)
  376. /// <summary>
  377. /// Read a block of bytes from the stream.
  378. /// </summary>
  379. /// <param name="buffer">The destination for the bytes.</param>
  380. /// <param name="offset">The index to start storing data.</param>
  381. /// <param name="count">The number of bytes to attempt to read.</param>
  382. /// <returns>Returns the number of bytes read.</returns>
  383. /// <remarks>Zero bytes read means end of stream.</remarks>
  384. public override int Read(byte[] buffer, int offset, int count)
  385. {
  386. return internalReader(buffer, offset, count);
  387. }
  388. #endregion
  389. #region BodyRead (Public)
  390. /// <summary>
  391. /// Reads a block of bytes from the current zip entry.
  392. /// </summary>
  393. /// <returns>
  394. /// The number of bytes read (this may be less than the length requested,
  395. /// even before the end of stream), or 0 on end of stream.
  396. /// </returns>
  397. /// <exception name="IOException">
  398. /// An i/o error occured.
  399. /// </exception>
  400. /// <exception cref="ZipException">
  401. /// The deflated stream is corrupted.
  402. /// </exception>
  403. /// <exception cref="InvalidOperationException">
  404. /// The stream is not open.
  405. /// </exception>
  406. public int BodyRead(byte[] b, int off, int len)
  407. {
  408. if (crc == null)
  409. {
  410. throw new InvalidOperationException("Closed.");
  411. }
  412. if (entry == null ||
  413. len <= 0)
  414. {
  415. return 0;
  416. }
  417. bool finished = false;
  418. switch (method)
  419. {
  420. case (int)CompressionMethod.Deflated:
  421. len = base.Read(b, off, len);
  422. if (len <= 0)
  423. {
  424. if (inf.IsFinished == false)
  425. {
  426. throw new ZipException("Inflater not finished!?");
  427. }
  428. inputBuffer.Available = inf.RemainingInput;
  429. if ((flags & 8) == 0 &&
  430. (inf.TotalIn != csize ||
  431. inf.TotalOut != size))
  432. {
  433. throw new ZipException("size mismatch: " + csize + ";" + size +
  434. " <-> " + inf.TotalIn + ";" + inf.TotalOut);
  435. }
  436. inf.Reset();
  437. finished = true;
  438. }
  439. break;
  440. case (int)CompressionMethod.Stored:
  441. if (len > csize && csize >= 0)
  442. {
  443. len = (int)csize;
  444. }
  445. len = inputBuffer.ReadClearTextBuffer(b, off, len);
  446. if (len > 0)
  447. {
  448. csize -= len;
  449. size -= len;
  450. }
  451. if (csize == 0)
  452. {
  453. finished = true;
  454. }
  455. else
  456. {
  457. if (len < 0)
  458. {
  459. throw new ZipException("EOF in stored block");
  460. }
  461. }
  462. break;
  463. }
  464. if (len > 0)
  465. {
  466. crc.Update(b, off, len);
  467. }
  468. if (finished)
  469. {
  470. StopDecrypting();
  471. if ((flags & 8) != 0)
  472. {
  473. ReadDataDescriptor();
  474. }
  475. if ((crc.Value & 0xFFFFFFFFL) != entry.Crc &&
  476. entry.Crc != MathHelper.InvalidIndex)
  477. {
  478. throw new ZipException("CRC mismatch");
  479. }
  480. crc.Reset();
  481. entry = null;
  482. }
  483. return len;
  484. }
  485. #endregion
  486. #region Close (Public)
  487. /// <summary>
  488. /// Closes the zip input stream
  489. /// </summary>
  490. public override void Close()
  491. {
  492. base.Close();
  493. crc = null;
  494. entry = null;
  495. }
  496. #endregion
  497. #region ExtractZipEntry (Public)
  498. /// <summary>
  499. /// Extract zip currentEntry
  500. /// </summary>
  501. /// <param name="zipEntry">Zip entry</param>
  502. /// <returns>Memory stream</returns>
  503. public MemoryStream ExtractZipEntry(ZipEntry zipEntry)
  504. {
  505. if (zipEntry == null)
  506. {
  507. throw new ArgumentNullException("entry",
  508. "ZipEntry must be valid");
  509. }
  510. // Max. length for reading a block of data: 4096
  511. // If we use a greater value we get errors like a lot of 0'str ..
  512. const int MaxReadBlockLength = 4096; //2048;
  513. int length = MaxReadBlockLength;
  514. byte[] data = new Byte[length];
  515. MemoryStream memStream = new MemoryStream();
  516. do
  517. {
  518. length = Read(data, 0, data.Length);
  519. if (length > 0)
  520. {
  521. memStream.Write(data, 0, length);
  522. }
  523. } while (length > 0);
  524. // Always go to beginning of stream for reading!
  525. memStream.Position = 0;
  526. return memStream;
  527. }
  528. #endregion
  529. #region Methods (Private)
  530. #region ReadDataDescriptor
  531. /// <summary>
  532. /// Read data descriptor at the end of compressed data.
  533. /// </summary>
  534. private void ReadDataDescriptor()
  535. {
  536. if (inputBuffer.ReadLeInt() != ZipConstants.ExternSig)
  537. {
  538. throw new ZipException("Data descriptor signature not found");
  539. }
  540. entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
  541. csize = inputBuffer.ReadLeInt();
  542. size = inputBuffer.ReadLeInt();
  543. entry.Size = size & 0xFFFFFFFFL;
  544. entry.CompressedSize = csize & 0xFFFFFFFFL;
  545. }
  546. #endregion
  547. #region InitialRead
  548. /// <summary>
  549. /// Perform the initial read on an entry which may include
  550. /// reading encryption headers and setting up inflation.
  551. /// </summary>
  552. /// <param name="destination">Destination</param>
  553. /// <param name="offset">Offset</param>
  554. /// <param name="count">Count</param>
  555. /// <returns>Int</returns>
  556. private int InitialRead(byte[] destination, int offset, int count)
  557. {
  558. if (entry.Version > ZipConstants.VersionMadeBy)
  559. {
  560. throw new ZipException(
  561. "Library cannot extract this entry version required (" +
  562. entry.Version.ToString() + ")");
  563. }
  564. // test for encryption
  565. if (entry.IsCrypted)
  566. {
  567. if (password == null)
  568. {
  569. throw new ZipException("No password set.");
  570. }
  571. // Generate and set crypto transform...
  572. ZipEncryptionManaged managed = new ZipEncryptionManaged();
  573. byte[] key = ZipEncryption.GenerateKeys(
  574. Encoding.ASCII.GetBytes(password));
  575. inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
  576. byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
  577. inputBuffer.ReadClearTextBuffer(cryptbuffer, 0,
  578. ZipConstants.CryptoHeaderSize);
  579. if ((flags & 8) == 0)
  580. {
  581. if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
  582. (byte)(entry.Crc >> 24))
  583. {
  584. throw new ZipException("Invalid password");
  585. } // if
  586. } // if
  587. else
  588. {
  589. if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
  590. (byte)((entry.DosTime >> 8) & 0xff))
  591. {
  592. throw new ZipException("Invalid password");
  593. } // if
  594. } // else
  595. if (csize >= ZipConstants.CryptoHeaderSize)
  596. {
  597. csize -= ZipConstants.CryptoHeaderSize;
  598. } // if
  599. }
  600. else
  601. {
  602. inputBuffer.CryptoTransform = null;
  603. } // else
  604. if (method == (int)CompressionMethod.Deflated &&
  605. inputBuffer.Available > 0)
  606. {
  607. inputBuffer.SetInflaterInput(inf);
  608. } // if
  609. internalReader = BodyRead;
  610. return BodyRead(destination, offset, count);
  611. }
  612. #endregion
  613. #endregion
  614. }
  615. }