ZipOutputStream.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. // ZipOutputStream.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.IO;
  39. using System.Collections;
  40. using System.Text;
  41. using ICSharpCode.SharpZipLib.Checksums;
  42. using ICSharpCode.SharpZipLib.Zip.Compression;
  43. using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
  44. namespace ICSharpCode.SharpZipLib.Zip
  45. {
  46. /// <summary>
  47. /// This is a FilterOutputStream that writes the files into a zip
  48. /// archive one after another. It has a special method to start a new
  49. /// zip entry. The zip entries contains information about the file name
  50. /// size, compressed size, CRC, etc.
  51. ///
  52. /// It includes support for STORED and DEFLATED entries.
  53. /// This class is not thread safe.
  54. ///
  55. /// author of the original java version : Jochen Hoenicke
  56. /// </summary>
  57. /// <example> This sample shows how to create a zip file
  58. /// <code>
  59. /// using System;
  60. /// using System.IO;
  61. ///
  62. /// using NZlib.Zip;
  63. ///
  64. /// class MainClass
  65. /// {
  66. /// public static void Main(string[] args)
  67. /// {
  68. /// string[] filenames = Directory.GetFiles(args[0]);
  69. ///
  70. /// ZipOutputStream s = new ZipOutputStream(File.Create(args[1]));
  71. ///
  72. /// s.SetLevel(5); // 0 - store only to 9 - means best compression
  73. ///
  74. /// foreach (string file in filenames) {
  75. /// FileStream fs = File.OpenRead(file);
  76. ///
  77. /// byte[] buffer = new byte[fs.Length];
  78. /// fs.Read(buffer, 0, buffer.Length);
  79. ///
  80. /// ZipEntry entry = new ZipEntry(file);
  81. ///
  82. /// s.PutNextEntry(entry);
  83. ///
  84. /// s.Write(buffer, 0, buffer.Length);
  85. ///
  86. /// }
  87. ///
  88. /// s.Finish();
  89. /// s.Close();
  90. /// }
  91. /// }
  92. /// </code>
  93. /// </example>
  94. public class ZipOutputStream : DeflaterOutputStream
  95. {
  96. private ArrayList entries = new ArrayList();
  97. private Crc32 crc = new Crc32();
  98. private ZipEntry curEntry = null;
  99. private CompressionMethod curMethod;
  100. private int size;
  101. private int offset = 0;
  102. private byte[] zipComment = new byte[0];
  103. private int defaultMethod = DEFLATED;
  104. /// <summary>
  105. /// Our Zip version is hard coded to 1.0 resp. 2.0
  106. /// </summary>
  107. private const int ZIP_STORED_VERSION = 10;
  108. private const int ZIP_DEFLATED_VERSION = 20;
  109. /// <summary>
  110. /// Compression method. This method doesn't compress at all.
  111. /// </summary>
  112. public const int STORED = 0;
  113. /// <summary>
  114. /// Compression method. This method uses the Deflater.
  115. /// </summary>
  116. public const int DEFLATED = 8;
  117. /// <summary>
  118. /// Creates a new Zip output stream, writing a zip archive.
  119. /// </summary>
  120. /// <param name="baseOutputStream">
  121. /// the output stream to which the zip archive is written.
  122. /// </param>
  123. public ZipOutputStream(Stream baseOutputStream) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true))
  124. {
  125. }
  126. /// <summary>
  127. /// Set the zip file comment.
  128. /// </summary>
  129. /// <param name="comment">
  130. /// the comment.
  131. /// </param>
  132. /// <exception name ="ArgumentException">
  133. /// if UTF8 encoding of comment is longer than 0xffff bytes.
  134. /// </exception>
  135. public void SetComment(string comment)
  136. {
  137. byte[] commentBytes = ZipConstants.ConvertToArray(comment);
  138. if (commentBytes.Length > 0xffff) {
  139. throw new ArgumentException("Comment too long.");
  140. }
  141. zipComment = commentBytes;
  142. }
  143. /// <summary>
  144. /// Sets default compression method. If the Zip entry specifies
  145. /// another method its method takes precedence.
  146. /// </summary>
  147. /// <param name = "method">
  148. /// the method.
  149. /// </param>
  150. /// <exception name = "ArgumentException">
  151. /// if method is not supported.
  152. /// </exception>
  153. public void SetMethod(int method)
  154. {
  155. if (method != STORED && method != DEFLATED) {
  156. throw new ArgumentException("Method not supported.");
  157. }
  158. defaultMethod = method;
  159. }
  160. /// <summary>
  161. /// Sets default compression level. The new level will be activated
  162. /// immediately.
  163. /// </summary>
  164. /// <exception cref="System.ArgumentOutOfRangeException">
  165. /// if level is not supported.
  166. /// </exception>
  167. /// <see cref="Deflater"/>
  168. public void SetLevel(int level)
  169. {
  170. def.SetLevel(level);
  171. }
  172. /// <summary>
  173. /// Write an unsigned short in little endian byte order.
  174. /// </summary>
  175. private void WriteLeShort(int value)
  176. {
  177. baseOutputStream.WriteByte((byte)value);
  178. baseOutputStream.WriteByte((byte)(value >> 8));
  179. }
  180. /// <summary>
  181. /// Write an int in little endian byte order.
  182. /// </summary>
  183. private void WriteLeInt(int value)
  184. {
  185. WriteLeShort(value);
  186. WriteLeShort(value >> 16);
  187. }
  188. /// <summary>
  189. /// Write an int in little endian byte order.
  190. /// </summary>
  191. private void WriteLeLong(long value)
  192. {
  193. WriteLeInt((int)value);
  194. WriteLeInt((int)(value >> 32));
  195. }
  196. bool shouldWriteBack = false;
  197. // -jr- Having a hard coded flag for seek capability requires this so users can determine
  198. // if the entries will be patched or not.
  199. public bool CanPatchEntries {
  200. get {
  201. return baseOutputStream.CanSeek;
  202. }
  203. }
  204. long seekPos = -1;
  205. /// <summary>
  206. /// Starts a new Zip entry. It automatically closes the previous
  207. /// entry if present. If the compression method is stored, the entry
  208. /// must have a valid size and crc, otherwise all elements (except
  209. /// name) are optional, but must be correct if present. If the time
  210. /// is not set in the entry, the current time is used.
  211. /// </summary>
  212. /// <param name="entry">
  213. /// the entry.
  214. /// </param>
  215. /// <exception cref="System.IO.IOException">
  216. /// if an I/O error occured.
  217. /// </exception>
  218. /// <exception cref="System.InvalidOperationException">
  219. /// if stream was finished
  220. /// </exception>
  221. public void PutNextEntry(ZipEntry entry)
  222. {
  223. if (entries == null) {
  224. throw new InvalidOperationException("ZipOutputStream was finished");
  225. }
  226. CompressionMethod method = entry.CompressionMethod;
  227. int flags = 0;
  228. entry.IsCrypted = Password != null;
  229. switch (method) {
  230. case CompressionMethod.Stored:
  231. if (entry.CompressedSize >= 0) {
  232. if (entry.Size < 0) {
  233. entry.Size = entry.CompressedSize;
  234. } else if (entry.Size != entry.CompressedSize) {
  235. throw new ZipException("Method STORED, but compressed size != size");
  236. }
  237. } else {
  238. entry.CompressedSize = entry.Size;
  239. }
  240. if (entry.IsCrypted) {
  241. entry.CompressedSize += 12;
  242. }
  243. if (entry.Size < 0) {
  244. throw new ZipException("Method STORED, but size not set");
  245. } else if (entry.Crc < 0) {
  246. throw new ZipException("Method STORED, but crc not set");
  247. }
  248. break;
  249. case CompressionMethod.Deflated:
  250. if (entry.CompressedSize < 0 || entry.Size < 0 || entry.Crc < 0) {
  251. flags |= 8;
  252. }
  253. break;
  254. }
  255. if (curEntry != null) {
  256. CloseEntry();
  257. }
  258. // if (entry.DosTime < 0) {
  259. // entry.Time = System.Environment.TickCount;
  260. // }
  261. if (entry.IsCrypted) {
  262. flags |= 1;
  263. }
  264. entry.Flags = flags;
  265. entry.Offset = offset;
  266. entry.CompressionMethod = (CompressionMethod)method;
  267. curMethod = method;
  268. // Write the local file header
  269. WriteLeInt(ZipConstants.LOCSIG);
  270. // write ZIP version
  271. WriteLeShort(method == CompressionMethod.Stored ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
  272. if ((flags & 8) == 0) {
  273. WriteLeShort(flags);
  274. WriteLeShort((byte)method);
  275. WriteLeInt((int)entry.DosTime);
  276. WriteLeInt((int)entry.Crc);
  277. WriteLeInt((int)entry.CompressedSize);
  278. WriteLeInt((int)entry.Size);
  279. } else {
  280. if (baseOutputStream.CanSeek) {
  281. shouldWriteBack = true;
  282. WriteLeShort((short)(flags & ~8));
  283. } else {
  284. shouldWriteBack = false;
  285. WriteLeShort(flags);
  286. }
  287. WriteLeShort((byte)method);
  288. WriteLeInt((int)entry.DosTime);
  289. if (baseOutputStream.CanSeek) {
  290. seekPos = baseOutputStream.Position;
  291. }
  292. WriteLeInt(0);
  293. WriteLeInt(0);
  294. WriteLeInt(0);
  295. }
  296. byte[] name = ZipConstants.ConvertToArray(entry.Name);
  297. if (name.Length > 0xFFFF) {
  298. throw new ZipException("Name too long.");
  299. }
  300. byte[] extra = entry.ExtraData;
  301. if (extra == null) {
  302. extra = new byte[0];
  303. }
  304. if (extra.Length > 0xFFFF) {
  305. throw new ZipException("Extra data too long.");
  306. }
  307. WriteLeShort(name.Length);
  308. WriteLeShort(extra.Length);
  309. baseOutputStream.Write(name, 0, name.Length);
  310. baseOutputStream.Write(extra, 0, extra.Length);
  311. if (Password != null) {
  312. InitializePassword(Password);
  313. byte[] cryptbuffer = new byte[12];
  314. Random rnd = new Random();
  315. for (int i = 0; i < cryptbuffer.Length; ++i) {
  316. cryptbuffer[i] = (byte)rnd.Next();
  317. }
  318. EncryptBlock(cryptbuffer, 0, cryptbuffer.Length);
  319. baseOutputStream.Write(cryptbuffer, 0, cryptbuffer.Length);
  320. }
  321. offset += ZipConstants.LOCHDR + name.Length + extra.Length;
  322. /* Activate the entry. */
  323. curEntry = entry;
  324. crc.Reset();
  325. if (method == CompressionMethod.Deflated) {
  326. def.Reset();
  327. }
  328. size = 0;
  329. }
  330. /// <summary>
  331. /// Closes the current entry.
  332. /// </summary>
  333. /// <exception cref="System.IO.IOException">
  334. /// if an I/O error occured.
  335. /// </exception>
  336. /// <exception cref="System.InvalidOperationException">
  337. /// if no entry is active.
  338. /// </exception>
  339. public void CloseEntry()
  340. {
  341. if (curEntry == null) {
  342. throw new InvalidOperationException("No open entry");
  343. }
  344. /* First finish the deflater, if appropriate */
  345. if (curMethod == CompressionMethod.Deflated) {
  346. base.Finish();
  347. }
  348. int csize = curMethod == CompressionMethod.Deflated ? def.TotalOut : size;
  349. if (curEntry.Size < 0) {
  350. curEntry.Size = size;
  351. } else if (curEntry.Size != size) {
  352. throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
  353. }
  354. if (curEntry.IsCrypted) {
  355. csize += 12;
  356. }
  357. if (curEntry.CompressedSize < 0) {
  358. curEntry.CompressedSize = csize;
  359. } else if (curEntry.CompressedSize != csize) {
  360. throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
  361. }
  362. if (curEntry.Crc < 0) {
  363. curEntry.Crc = crc.Value;
  364. } else if (curEntry.Crc != crc.Value) {
  365. throw new ZipException("crc was " + crc.Value +
  366. ", but I expected " +
  367. curEntry.Crc);
  368. }
  369. offset += csize;
  370. /* Now write the data descriptor entry if needed. */
  371. if (curMethod == CompressionMethod.Deflated && (curEntry.Flags & 8) != 0) {
  372. if (shouldWriteBack) {
  373. curEntry.Flags &= ~8;
  374. long curPos = baseOutputStream.Position;
  375. baseOutputStream.Seek(seekPos, SeekOrigin.Begin);
  376. WriteLeInt((int)curEntry.Crc);
  377. WriteLeInt((int)curEntry.CompressedSize);
  378. WriteLeInt((int)curEntry.Size);
  379. baseOutputStream.Seek(curPos, SeekOrigin.Begin);
  380. shouldWriteBack = false;
  381. } else {
  382. WriteLeInt(ZipConstants.EXTSIG);
  383. WriteLeInt((int)curEntry.Crc);
  384. WriteLeInt((int)curEntry.CompressedSize);
  385. WriteLeInt((int)curEntry.Size);
  386. offset += ZipConstants.EXTHDR;
  387. }
  388. }
  389. entries.Add(curEntry);
  390. curEntry = null;
  391. }
  392. /// <summary>
  393. /// Writes the given buffer to the current entry.
  394. /// </summary>
  395. /// <exception cref="System.IO.IOException">
  396. /// if an I/O error occured.
  397. /// </exception>
  398. /// <exception cref="System.InvalidOperationException">
  399. /// if no entry is active.
  400. /// </exception>
  401. public override void Write(byte[] b, int off, int len)
  402. {
  403. if (curEntry == null) {
  404. throw new InvalidOperationException("No open entry.");
  405. }
  406. switch (curMethod) {
  407. case CompressionMethod.Deflated:
  408. base.Write(b, off, len);
  409. break;
  410. case CompressionMethod.Stored:
  411. if (Password != null) {
  412. byte[] buf = new byte[len];
  413. Array.Copy(b, off, buf, 0, len);
  414. EncryptBlock(buf, 0, len);
  415. baseOutputStream.Write(buf, off, len);
  416. } else {
  417. baseOutputStream.Write(b, off, len);
  418. }
  419. break;
  420. }
  421. crc.Update(b, off, len);
  422. size += len;
  423. }
  424. /// <summary>
  425. /// Finishes the stream. This will write the central directory at the
  426. /// end of the zip file and flush the stream.
  427. /// </summary>
  428. /// <exception cref="System.IO.IOException">
  429. /// if an I/O error occured.
  430. /// </exception>
  431. public override void Finish()
  432. {
  433. if (entries == null) {
  434. return;
  435. }
  436. if (curEntry != null) {
  437. CloseEntry();
  438. }
  439. int numEntries = 0;
  440. int sizeEntries = 0;
  441. foreach (ZipEntry entry in entries) {
  442. // TODO : check the appnote file for compilance with the central directory standard
  443. CompressionMethod method = entry.CompressionMethod;
  444. WriteLeInt(ZipConstants.CENSIG);
  445. WriteLeShort(method == CompressionMethod.Stored ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
  446. WriteLeShort(method == CompressionMethod.Stored ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
  447. WriteLeShort(entry.Flags);
  448. WriteLeShort((short)method);
  449. WriteLeInt((int)entry.DosTime);
  450. WriteLeInt((int)entry.Crc);
  451. WriteLeInt((int)entry.CompressedSize);
  452. WriteLeInt((int)entry.Size);
  453. byte[] name = ZipConstants.ConvertToArray(entry.Name);
  454. if (name.Length > 0xffff) {
  455. throw new ZipException("Name too long.");
  456. }
  457. byte[] extra = entry.ExtraData;
  458. if (extra == null) {
  459. extra = new byte[0];
  460. }
  461. string strComment = entry.Comment;
  462. byte[] comment = strComment != null ? ZipConstants.ConvertToArray(strComment) : new byte[0];
  463. if (comment.Length > 0xffff) {
  464. throw new ZipException("Comment too long.");
  465. }
  466. WriteLeShort(name.Length);
  467. WriteLeShort(extra.Length);
  468. WriteLeShort(comment.Length);
  469. WriteLeShort(0); // disk number
  470. WriteLeShort(0); // internal file attr
  471. if (entry.IsDirectory) { // -jr- 17-12-2003 mark entry as directory (from nikolam.AT.perfectinfo.com)
  472. WriteLeInt(16);
  473. } else {
  474. WriteLeInt(0); // external file attr
  475. }
  476. WriteLeInt(entry.Offset);
  477. baseOutputStream.Write(name, 0, name.Length);
  478. baseOutputStream.Write(extra, 0, extra.Length);
  479. baseOutputStream.Write(comment, 0, comment.Length);
  480. ++numEntries;
  481. sizeEntries += ZipConstants.CENHDR + name.Length + extra.Length + comment.Length;
  482. }
  483. WriteLeInt(ZipConstants.ENDSIG);
  484. WriteLeShort(0); // disk number
  485. WriteLeShort(0); // disk with start of central dir
  486. WriteLeShort(numEntries);
  487. WriteLeShort(numEntries);
  488. WriteLeInt(sizeEntries);
  489. WriteLeInt(offset);
  490. WriteLeShort(zipComment.Length);
  491. baseOutputStream.Write(zipComment, 0, zipComment.Length);
  492. baseOutputStream.Flush();
  493. entries = null;
  494. }
  495. }
  496. }