PageRenderTime 31ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/Common/SharpZipLib/Zip/ZipInputStream.cs

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