ZipFile.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. // ZipFile.cs
  2. // Copyright (C) 2001 Mike Krueger
  3. //
  4. // This file was translated from java, it was part of the GNU Classpath
  5. // Copyright (C) 2001 Free Software Foundation, Inc.
  6. //
  7. // This program is free software; you can redistribute it and/or
  8. // modify it under the terms of the GNU General Public License
  9. // as published by the Free Software Foundation; either version 2
  10. // of the License, or (at your option) any later version.
  11. //
  12. // This program is distributed in the hope that it will be useful,
  13. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. // GNU General Public License for more details.
  16. //
  17. // You should have received a copy of the GNU General Public License
  18. // along with this program; if not, write to the Free Software
  19. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. //
  21. // Linking this library statically or dynamically with other modules is
  22. // making a combined work based on this library. Thus, the terms and
  23. // conditions of the GNU General Public License cover the whole
  24. // combination.
  25. //
  26. // As a special exception, the copyright holders of this library give you
  27. // permission to link this library with independent modules to produce an
  28. // executable, regardless of the license terms of these independent
  29. // modules, and to copy and distribute the resulting executable under
  30. // terms of your choice, provided that you also meet, for each linked
  31. // independent module, the terms and conditions of the license of that
  32. // module. An independent module is a module which is not derived from
  33. // or based on this library. If you modify this library, you may extend
  34. // this exception to your version of the library, but you are not
  35. // obligated to do so. If you do not wish to do so, delete this
  36. // exception statement from your version.
  37. using System;
  38. using System.Collections;
  39. using System.IO;
  40. using System.Text;
  41. using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
  42. using ICSharpCode.SharpZipLib.Zip.Compression;
  43. namespace ICSharpCode.SharpZipLib.Zip
  44. {
  45. /// <summary>
  46. /// This class represents a Zip archive. You can ask for the contained
  47. /// entries, or get an input stream for a file entry. The entry is
  48. /// automatically decompressed.
  49. ///
  50. /// This class is thread safe: You can open input streams for arbitrary
  51. /// entries in different threads.
  52. ///
  53. /// author of the original java version : Jochen Hoenicke
  54. /// </summary>
  55. /// <example>
  56. /// using System;
  57. /// using System.Text;
  58. /// using System.Collections;
  59. /// using System.IO;
  60. ///
  61. /// using NZlib.Zip;
  62. ///
  63. /// class MainClass
  64. /// {
  65. /// static public void Main(string[] args)
  66. /// {
  67. /// ZipFile zFile = new ZipFile(args[0]);
  68. /// //Console.WriteLine("Listing of : " + zFile.Name);
  69. /// //Console.WriteLine("");
  70. /// //Console.WriteLine("Raw Size Size Date Time Name");
  71. /// //Console.WriteLine("-------- -------- -------- ------ ---------");
  72. /// foreach (ZipEntry e in zFile) {
  73. /// DateTime d = e.DateTime;
  74. /// //Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize,
  75. /// d.ToString("dd-MM-yy"), d.ToString("t"),
  76. /// e.Name);
  77. /// }
  78. /// }
  79. /// }
  80. /// </example>
  81. public class ZipFile : IEnumerable
  82. {
  83. string name;
  84. string comment;
  85. Stream baseStream;
  86. ZipEntry[] entries;
  87. /// <summary>
  88. /// Opens a Zip file with the given name for reading.
  89. /// </summary>
  90. /// <exception name="System.IO.IOException">
  91. /// IOException if a i/o error occured.
  92. /// </exception>
  93. /// <exception name="ICSharpCode.SharpZipLib.ZipException">
  94. /// if the file doesn't contain a valid zip archive.
  95. /// </exception>
  96. public ZipFile(string name) : this(File.OpenRead(name))
  97. {
  98. }
  99. /// <summary>
  100. /// Opens a Zip file reading the given FileStream
  101. /// </summary>
  102. /// <exception name="System.IO.IOException">
  103. /// IOException if a i/o error occured.
  104. /// </exception>
  105. /// <exception name="ICSharpCode.SharpZipLib.ZipException">
  106. /// if the file doesn't contain a valid zip archive.
  107. /// </exception>
  108. public ZipFile(FileStream file)
  109. {
  110. this.baseStream = file;
  111. this.name = file.Name;
  112. ReadEntries();
  113. }
  114. /// <summary>
  115. /// Opens a Zip file reading the given Stream
  116. /// </summary>
  117. /// <exception name="System.IO.IOException">
  118. /// IOException if a i/o error occured.
  119. /// </exception>
  120. /// <exception name="ICSharpCode.SharpZipLib.ZipException">
  121. /// if the file doesn't contain a valid zip archive.
  122. /// </exception>
  123. public ZipFile(Stream baseStream)
  124. {
  125. this.baseStream = baseStream;
  126. this.name = null;
  127. ReadEntries();
  128. }
  129. /// <summary>
  130. /// Read an unsigned short in little endian byte order.
  131. /// </summary>
  132. /// <exception name="System.IO.IOException">
  133. /// if a i/o error occured.
  134. /// </exception>
  135. /// <exception name="System.IO.EndOfStreamException">
  136. /// if the file ends prematurely
  137. /// </exception>
  138. int ReadLeShort()
  139. {
  140. return baseStream.ReadByte() | baseStream.ReadByte() << 8;
  141. }
  142. /// <summary>
  143. /// Read an int in little endian byte order.
  144. /// </summary>
  145. /// <exception name="System.IO.IOException">
  146. /// if a i/o error occured.
  147. /// </exception>
  148. /// <exception name="System.IO.EndOfStreamException">
  149. /// if the file ends prematurely
  150. /// </exception>
  151. int ReadLeInt()
  152. {
  153. return ReadLeShort() | ReadLeShort() << 16;
  154. }
  155. /// <summary>
  156. /// Read the central directory of a zip file and fill the entries
  157. /// array. This is called exactly once by the constructors.
  158. /// </summary>
  159. /// <exception name="System.IO.IOException">
  160. /// if a i/o error occured.
  161. /// </exception>
  162. /// <exception name="ICSharpCode.SharpZipLib.ZipException">
  163. /// if the central directory is malformed
  164. /// </exception>
  165. void ReadEntries()
  166. {
  167. /* Search for the End Of Central Directory. When a zip comment is
  168. * present the directory may start earlier.
  169. * FIXME: This searches the whole file in a very slow manner if the
  170. * file isn't a zip file.
  171. */
  172. long pos = baseStream.Length - ZipConstants.ENDHDR;
  173. do {
  174. if (pos < 0) {
  175. throw new ZipException("central directory not found, probably not a zip file");
  176. }
  177. baseStream.Seek(pos--, SeekOrigin.Begin);
  178. } while (ReadLeInt() != ZipConstants.ENDSIG);
  179. long oldPos = baseStream.Position;
  180. baseStream.Position += ZipConstants.ENDTOT - ZipConstants.ENDNRD;
  181. if (baseStream.Position - oldPos != ZipConstants.ENDTOT - ZipConstants.ENDNRD) {
  182. throw new EndOfStreamException();
  183. }
  184. int count = ReadLeShort();
  185. oldPos = baseStream.Position;
  186. baseStream.Position += ZipConstants.ENDOFF - ZipConstants.ENDSIZ;
  187. if (baseStream.Position - oldPos != ZipConstants.ENDOFF - ZipConstants.ENDSIZ) {
  188. throw new EndOfStreamException();
  189. }
  190. int centralOffset = ReadLeInt();
  191. // GET COMMENT SIZE (COMES AFTER CENTRALOFFSET)
  192. int commentSize = ReadLeShort();
  193. byte[] zipComment = new byte[commentSize];
  194. baseStream.Read(zipComment, 0, zipComment.Length);
  195. comment = ZipConstants.ConvertToString(zipComment);
  196. entries = new ZipEntry[count];
  197. baseStream.Seek(centralOffset, SeekOrigin.Begin);
  198. for (int i = 0; i < count; i++) {
  199. if (ReadLeInt() != ZipConstants.CENSIG) {
  200. throw new ZipException("Wrong Central Directory signature");
  201. }
  202. oldPos = baseStream.Position;
  203. baseStream.Position += ZipConstants.CENHOW - ZipConstants.CENVEM;
  204. if (baseStream.Position - oldPos != ZipConstants.CENHOW - ZipConstants.CENVEM) {
  205. throw new EndOfStreamException();
  206. }
  207. int method = ReadLeShort();
  208. int dostime = ReadLeInt();
  209. int crc = ReadLeInt();
  210. int csize = ReadLeInt();
  211. int size = ReadLeInt();
  212. int nameLen = ReadLeShort();
  213. int extraLen = ReadLeShort();
  214. int commentLen = ReadLeShort();
  215. oldPos = baseStream.Position;
  216. baseStream.Position += ZipConstants.CENOFF - ZipConstants.CENDSK;
  217. if (baseStream.Position - oldPos != ZipConstants.CENOFF - ZipConstants.CENDSK) {
  218. throw new EndOfStreamException();
  219. }
  220. int offset = ReadLeInt();
  221. byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
  222. baseStream.Read(buffer, 0, nameLen);
  223. string name = ZipConstants.ConvertToString(buffer);
  224. ZipEntry entry = new ZipEntry(name);
  225. entry.CompressionMethod = (CompressionMethod)method;
  226. entry.Crc = crc & 0xffffffffL;
  227. entry.Size = size & 0xffffffffL;
  228. entry.CompressedSize = csize & 0xffffffffL;
  229. entry.DosTime = (uint)dostime;
  230. if (extraLen > 0) {
  231. byte[] extra = new byte[extraLen];
  232. baseStream.Read(extra, 0, extraLen);
  233. entry.ExtraData = extra;
  234. }
  235. if (commentLen > 0) {
  236. baseStream.Read(buffer, 0, commentLen);
  237. entry.Comment = ZipConstants.ConvertToString(buffer);
  238. }
  239. entry.ZipFileIndex = i;
  240. entry.Offset = offset;
  241. entries[i] = entry;
  242. }
  243. }
  244. /// <summary>
  245. /// Closes the ZipFile. This also closes all input streams given by
  246. /// this class. After this is called, no further method should be
  247. /// called.
  248. /// </summary>
  249. /// <exception name="System.IO.IOException">
  250. /// if a i/o error occured.
  251. /// </exception>
  252. public void Close()
  253. {
  254. entries = null;
  255. lock(baseStream) {
  256. baseStream.Close();
  257. }
  258. }
  259. /// <summary>
  260. /// Returns an IEnumerator of all Zip entries in this Zip file.
  261. /// </summary>
  262. public IEnumerator GetEnumerator()
  263. {
  264. if (entries == null) {
  265. throw new InvalidOperationException("ZipFile has closed");
  266. }
  267. return new ZipEntryEnumeration(entries);
  268. }
  269. int GetEntryIndex(string name)
  270. {
  271. for (int i = 0; i < entries.Length; i++) {
  272. if (name.Equals(entries[i].Name)) {
  273. return i;
  274. }
  275. }
  276. return -1; // ok
  277. }
  278. /// <summary>
  279. /// Searches for a zip entry in this archive with the given name.
  280. /// </summary>
  281. /// <param name="name">
  282. /// the name. May contain directory components separated by slashes ('/').
  283. /// </param>
  284. /// <returns>
  285. /// the zip entry, or null if no entry with that name exists.
  286. /// </returns>
  287. public ZipEntry GetEntry(string name)
  288. {
  289. if (entries == null) {
  290. throw new InvalidOperationException("ZipFile has closed");
  291. }
  292. int index = GetEntryIndex(name);
  293. return index >= 0 ? (ZipEntry) entries[index].Clone() : null;
  294. }
  295. /// <summary>
  296. /// Checks, if the local header of the entry at index i matches the
  297. /// central directory, and returns the offset to the data.
  298. /// </summary>
  299. /// <returns>
  300. /// the start offset of the (compressed) data.
  301. /// </returns>
  302. /// <exception name="System.IO.IOException">
  303. /// if a i/o error occured.
  304. /// </exception>
  305. /// <exception name="ICSharpCode.SharpZipLib.ZipException">
  306. /// if the local header doesn't match the central directory header
  307. /// </exception>
  308. long CheckLocalHeader(ZipEntry entry)
  309. {
  310. lock(baseStream) {
  311. baseStream.Seek(entry.Offset, SeekOrigin.Begin);
  312. if (ReadLeInt() != ZipConstants.LOCSIG) {
  313. throw new ZipException("Wrong Local header signature");
  314. }
  315. /* skip version and flags */
  316. long oldPos = baseStream.Position;
  317. baseStream.Position += ZipConstants.LOCHOW - ZipConstants.LOCVER;
  318. if (baseStream.Position - oldPos != ZipConstants.LOCHOW - ZipConstants.LOCVER) {
  319. throw new EndOfStreamException();
  320. }
  321. if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {
  322. throw new ZipException("Compression method mismatch");
  323. }
  324. /* Skip time, crc, size and csize */
  325. oldPos = baseStream.Position;
  326. baseStream.Position += ZipConstants.LOCNAM - ZipConstants.LOCTIM;
  327. if (baseStream.Position - oldPos != ZipConstants.LOCNAM - ZipConstants.LOCTIM) {
  328. throw new EndOfStreamException();
  329. }
  330. if (entry.Name.Length != ReadLeShort()) {
  331. throw new ZipException("file name length mismatch");
  332. }
  333. int extraLen = entry.Name.Length + ReadLeShort();
  334. return entry.Offset + ZipConstants.LOCHDR + extraLen;
  335. }
  336. }
  337. /// <summary>
  338. /// Creates an input stream reading the given zip entry as
  339. /// uncompressed data. Normally zip entry should be an entry
  340. /// returned by GetEntry().
  341. /// </summary>
  342. /// <returns>
  343. /// the input stream.
  344. /// </returns>
  345. /// <exception name="System.IO.IOException">
  346. /// if a i/o error occured.
  347. /// </exception>
  348. /// <exception name="ICSharpCode.SharpZipLib.ZipException">
  349. /// if the Zip archive is malformed.
  350. /// </exception>
  351. public Stream GetInputStream(ZipEntry entry)
  352. {
  353. if (entries == null) {
  354. throw new InvalidOperationException("ZipFile has closed");
  355. }
  356. int index = entry.ZipFileIndex;
  357. if (index < 0 || index >= entries.Length || entries[index].Name != entry.Name) {
  358. index = GetEntryIndex(entry.Name);
  359. if (index < 0) {
  360. throw new IndexOutOfRangeException();
  361. }
  362. }
  363. long start = CheckLocalHeader(entries[index]);
  364. CompressionMethod method = entries[index].CompressionMethod;
  365. Stream istr = new PartialInputStream(baseStream, start, entries[index].CompressedSize);
  366. switch (method) {
  367. case CompressionMethod.Stored:
  368. return istr;
  369. case CompressionMethod.Deflated:
  370. return new InflaterInputStream(istr, new Inflater(true));
  371. default:
  372. throw new ZipException("Unknown compression method " + method);
  373. }
  374. }
  375. /// <summary>
  376. /// The comment for the whole zip file.
  377. /// </summary>
  378. public string ZipFileComment {
  379. get {
  380. return comment;
  381. }
  382. }
  383. /// <summary>
  384. /// Returns the name of this zip file.
  385. /// </summary>
  386. public string Name {
  387. get {
  388. return name;
  389. }
  390. }
  391. /// <summary>
  392. /// Returns the number of entries in this zip file.
  393. /// </summary>
  394. public int Size {
  395. get {
  396. try {
  397. return entries.Length;
  398. } catch (Exception) {
  399. throw new InvalidOperationException("ZipFile has closed");
  400. }
  401. }
  402. }
  403. class ZipEntryEnumeration : IEnumerator
  404. {
  405. ZipEntry[] array;
  406. int ptr = -1;
  407. public ZipEntryEnumeration(ZipEntry[] arr)
  408. {
  409. array = arr;
  410. }
  411. public object Current {
  412. get {
  413. return array[ptr];
  414. }
  415. }
  416. public void Reset()
  417. {
  418. ptr = -1;
  419. }
  420. public bool MoveNext()
  421. {
  422. return (++ptr < array.Length);
  423. }
  424. }
  425. class PartialInputStream : InflaterInputStream
  426. {
  427. Stream baseStream;
  428. long filepos, end;
  429. public PartialInputStream(Stream baseStream, long start, long len) : base(baseStream)
  430. {
  431. this.baseStream = baseStream;
  432. filepos = start;
  433. end = start + len;
  434. }
  435. public override int Available
  436. {
  437. get {
  438. long amount = end - filepos;
  439. if (amount > Int32.MaxValue) {
  440. return Int32.MaxValue;
  441. }
  442. return (int) amount;
  443. }
  444. }
  445. public override int ReadByte()
  446. {
  447. if (filepos == end) {
  448. return -1; //ok
  449. }
  450. lock(baseStream) {
  451. baseStream.Seek(filepos++, SeekOrigin.Begin);
  452. return baseStream.ReadByte();
  453. }
  454. }
  455. public override int Read(byte[] b, int off, int len)
  456. {
  457. if (len > end - filepos) {
  458. len = (int) (end - filepos);
  459. if (len == 0) {
  460. return 0;
  461. }
  462. }
  463. lock(baseStream) {
  464. baseStream.Seek(filepos, SeekOrigin.Begin);
  465. int count = baseStream.Read(b, off, len);
  466. if (count > 0) {
  467. filepos += len;
  468. }
  469. return count;
  470. }
  471. }
  472. public long SkipBytes(long amount)
  473. {
  474. if (amount < 0) {
  475. throw new ArgumentOutOfRangeException();
  476. }
  477. if (amount > end - filepos) {
  478. amount = end - filepos;
  479. }
  480. filepos += amount;
  481. return amount;
  482. }
  483. }
  484. }
  485. }