ZipOutputStream.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. using System;
  2. using System.IO;
  3. using System.Collections;
  4. using ICSharpCode.SharpZipLib.Checksum;
  5. using ICSharpCode.SharpZipLib.Zip.Compression;
  6. using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
  7. using System.Collections.Generic;
  8. namespace ICSharpCode.SharpZipLib.Zip
  9. {
  10. /// <summary>
  11. /// This is a DeflaterOutputStream that writes the files into a zip
  12. /// archive one after another. It has a special method to start a new
  13. /// zip entry. The zip entries contains information about the file name
  14. /// size, compressed size, CRC, etc.
  15. ///
  16. /// It includes support for Stored and Deflated entries.
  17. /// This class is not thread safe.
  18. /// <br/>
  19. /// <br/>Author of the original java version : Jochen Hoenicke
  20. /// </summary>
  21. /// <example> This sample shows how to create a zip file
  22. /// <code>
  23. /// using System;
  24. /// using System.IO;
  25. ///
  26. /// using ICSharpCode.SharpZipLib.Core;
  27. /// using ICSharpCode.SharpZipLib.Zip;
  28. ///
  29. /// class MainClass
  30. /// {
  31. /// public static void Main(string[] args)
  32. /// {
  33. /// string[] filenames = Directory.GetFiles(args[0]);
  34. /// byte[] buffer = new byte[4096];
  35. ///
  36. /// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) {
  37. ///
  38. /// s.SetLevel(9); // 0 - store only to 9 - means best compression
  39. ///
  40. /// foreach (string file in filenames) {
  41. /// ZipEntry entry = new ZipEntry(file);
  42. /// s.PutNextEntry(entry);
  43. ///
  44. /// using (FileStream fs = File.OpenRead(file)) {
  45. /// StreamUtils.Copy(fs, s, buffer);
  46. /// }
  47. /// }
  48. /// }
  49. /// }
  50. /// }
  51. /// </code>
  52. /// </example>
  53. public class ZipOutputStream : DeflaterOutputStream
  54. {
  55. #region Constructors
  56. /// <summary>
  57. /// Creates a new Zip output stream, writing a zip archive.
  58. /// </summary>
  59. /// <param name="baseOutputStream">
  60. /// The output stream to which the archive contents are written.
  61. /// </param>
  62. public ZipOutputStream(Stream baseOutputStream)
  63. : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true))
  64. {
  65. }
  66. /// <summary>
  67. /// Creates a new Zip output stream, writing a zip archive.
  68. /// </summary>
  69. /// <param name="baseOutputStream">The output stream to which the archive contents are written.</param>
  70. /// <param name="bufferSize">Size of the buffer to use.</param>
  71. public ZipOutputStream(Stream baseOutputStream, int bufferSize)
  72. : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize)
  73. {
  74. }
  75. #endregion
  76. /// <summary>
  77. /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added.
  78. /// </summary>
  79. /// <remarks>No further entries can be added once this has been done.</remarks>
  80. public bool IsFinished {
  81. get {
  82. return entries == null;
  83. }
  84. }
  85. /// <summary>
  86. /// Set the zip file comment.
  87. /// </summary>
  88. /// <param name="comment">
  89. /// The comment text for the entire archive.
  90. /// </param>
  91. /// <exception name ="ArgumentOutOfRangeException">
  92. /// The converted comment is longer than 0xffff bytes.
  93. /// </exception>
  94. public void SetComment(string comment)
  95. {
  96. // TODO: Its not yet clear how to handle unicode comments here.
  97. byte[] commentBytes = ZipConstants.ConvertToArray(comment);
  98. if (commentBytes.Length > 0xffff) {
  99. throw new ArgumentOutOfRangeException("nameof(comment)");
  100. }
  101. zipComment = commentBytes;
  102. }
  103. /// <summary>
  104. /// Sets the compression level. The new level will be activated
  105. /// immediately.
  106. /// </summary>
  107. /// <param name="level">The new compression level (1 to 9).</param>
  108. /// <exception cref="ArgumentOutOfRangeException">
  109. /// Level specified is not supported.
  110. /// </exception>
  111. /// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Deflater"/>
  112. public void SetLevel(int level)
  113. {
  114. deflater_.SetLevel(level);
  115. defaultCompressionLevel = level;
  116. }
  117. /// <summary>
  118. /// Get the current deflater compression level
  119. /// </summary>
  120. /// <returns>The current compression level</returns>
  121. public int GetLevel()
  122. {
  123. return deflater_.GetLevel();
  124. }
  125. /// <summary>
  126. /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
  127. /// </summary>
  128. /// <remarks>Older archivers may not understand Zip64 extensions.
  129. /// If backwards compatability is an issue be careful when adding <see cref="ZipEntry.Size">entries</see> to an archive.
  130. /// Setting this property to off is workable but less desirable as in those circumstances adding a file
  131. /// larger then 4GB will fail.</remarks>
  132. public UseZip64 UseZip64 {
  133. get { return useZip64_; }
  134. set { useZip64_ = value; }
  135. }
  136. /// <summary>
  137. /// Write an unsigned short in little endian byte order.
  138. /// </summary>
  139. private void WriteLeShort(int value)
  140. {
  141. unchecked {
  142. baseOutputStream_.WriteByte((byte)(value & 0xff));
  143. baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff));
  144. }
  145. }
  146. /// <summary>
  147. /// Write an int in little endian byte order.
  148. /// </summary>
  149. private void WriteLeInt(int value)
  150. {
  151. unchecked {
  152. WriteLeShort(value);
  153. WriteLeShort(value >> 16);
  154. }
  155. }
  156. /// <summary>
  157. /// Write an int in little endian byte order.
  158. /// </summary>
  159. private void WriteLeLong(long value)
  160. {
  161. unchecked {
  162. WriteLeInt((int)value);
  163. WriteLeInt((int)(value >> 32));
  164. }
  165. }
  166. /// <summary>
  167. /// Starts a new Zip entry. It automatically closes the previous
  168. /// entry if present.
  169. /// All entry elements bar name are optional, but must be correct if present.
  170. /// If the compression method is stored and the output is not patchable
  171. /// the compression for that entry is automatically changed to deflate level 0
  172. /// </summary>
  173. /// <param name="entry">
  174. /// the entry.
  175. /// </param>
  176. /// <exception cref="System.ArgumentNullException">
  177. /// if entry passed is null.
  178. /// </exception>
  179. /// <exception cref="System.IO.IOException">
  180. /// if an I/O error occured.
  181. /// </exception>
  182. /// <exception cref="System.InvalidOperationException">
  183. /// if stream was finished
  184. /// </exception>
  185. /// <exception cref="ZipException">
  186. /// Too many entries in the Zip file<br/>
  187. /// Entry name is too long<br/>
  188. /// Finish has already been called<br/>
  189. /// </exception>
  190. public void PutNextEntry(ZipEntry entry)
  191. {
  192. if (entry == null) {
  193. throw new ArgumentNullException("nameof(entry)");
  194. }
  195. if (entries == null) {
  196. throw new InvalidOperationException("ZipOutputStream was finished");
  197. }
  198. if (curEntry != null) {
  199. CloseEntry();
  200. }
  201. if (entries.Count == int.MaxValue) {
  202. throw new ZipException("Too many entries for Zip file");
  203. }
  204. CompressionMethod method = entry.CompressionMethod;
  205. int compressionLevel = defaultCompressionLevel;
  206. // Clear flags that the library manages internally
  207. entry.Flags &= (int)GeneralBitFlags.UnicodeText;
  208. patchEntryHeader = false;
  209. bool headerInfoAvailable;
  210. // No need to compress - definitely no data.
  211. if (entry.Size == 0) {
  212. entry.CompressedSize = entry.Size;
  213. entry.Crc = 0;
  214. method = CompressionMethod.Stored;
  215. headerInfoAvailable = true;
  216. } else {
  217. headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0;
  218. // Switch to deflation if storing isnt possible.
  219. if (method == CompressionMethod.Stored) {
  220. if (!headerInfoAvailable) {
  221. if (!CanPatchEntries) {
  222. // Can't patch entries so storing is not possible.
  223. method = CompressionMethod.Deflated;
  224. compressionLevel = 0;
  225. }
  226. } else // entry.size must be > 0
  227. {
  228. entry.CompressedSize = entry.Size;
  229. headerInfoAvailable = entry.HasCrc;
  230. }
  231. }
  232. }
  233. if (headerInfoAvailable == false) {
  234. if (CanPatchEntries == false) {
  235. // Only way to record size and compressed size is to append a data descriptor
  236. // after compressed data.
  237. // Stored entries of this form have already been converted to deflating.
  238. entry.Flags |= 8;
  239. } else {
  240. patchEntryHeader = true;
  241. }
  242. }
  243. if (Password != null) {
  244. entry.IsCrypted = true;
  245. if (entry.Crc < 0) {
  246. // Need to append a data descriptor as the crc isnt available for use
  247. // with encryption, the date is used instead. Setting the flag
  248. // indicates this to the decompressor.
  249. entry.Flags |= 8;
  250. }
  251. }
  252. entry.Offset = offset;
  253. entry.CompressionMethod = (CompressionMethod)method;
  254. curMethod = method;
  255. sizePatchPos = -1;
  256. if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) {
  257. entry.ForceZip64();
  258. }
  259. // Write the local file header
  260. WriteLeInt(ZipConstants.LocalHeaderSignature);
  261. WriteLeShort(entry.Version);
  262. WriteLeShort(entry.Flags);
  263. WriteLeShort((byte)entry.CompressionMethodForHeader);
  264. WriteLeInt((int)entry.DosTime);
  265. // TODO: Refactor header writing. Its done in several places.
  266. if (headerInfoAvailable) {
  267. WriteLeInt((int)entry.Crc);
  268. if (entry.LocalHeaderRequiresZip64) {
  269. WriteLeInt(-1);
  270. WriteLeInt(-1);
  271. } else {
  272. WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
  273. WriteLeInt((int)entry.Size);
  274. }
  275. } else {
  276. if (patchEntryHeader) {
  277. crcPatchPos = baseOutputStream_.Position;
  278. }
  279. WriteLeInt(0); // Crc
  280. if (patchEntryHeader) {
  281. sizePatchPos = baseOutputStream_.Position;
  282. }
  283. // For local header both sizes appear in Zip64 Extended Information
  284. if (entry.LocalHeaderRequiresZip64 || patchEntryHeader) {
  285. WriteLeInt(-1);
  286. WriteLeInt(-1);
  287. } else {
  288. WriteLeInt(0); // Compressed size
  289. WriteLeInt(0); // Uncompressed size
  290. }
  291. }
  292. byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
  293. if (name.Length > 0xFFFF) {
  294. throw new ZipException("Entry name too long.");
  295. }
  296. var ed = new ZipExtraData(entry.ExtraData);
  297. if (entry.LocalHeaderRequiresZip64) {
  298. ed.StartNewEntry();
  299. if (headerInfoAvailable) {
  300. ed.AddLeLong(entry.Size);
  301. ed.AddLeLong(entry.CompressedSize);
  302. } else {
  303. ed.AddLeLong(-1);
  304. ed.AddLeLong(-1);
  305. }
  306. ed.AddNewEntry(1);
  307. if (!ed.Find(1)) {
  308. throw new ZipException("Internal error cant find extra data");
  309. }
  310. if (patchEntryHeader) {
  311. sizePatchPos = ed.CurrentReadIndex;
  312. }
  313. } else {
  314. ed.Delete(1);
  315. }
  316. if (entry.AESKeySize > 0) {
  317. AddExtraDataAES(entry, ed);
  318. }
  319. byte[] extra = ed.GetEntryData();
  320. WriteLeShort(name.Length);
  321. WriteLeShort(extra.Length);
  322. if (name.Length > 0) {
  323. baseOutputStream_.Write(name, 0, name.Length);
  324. }
  325. if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) {
  326. sizePatchPos += baseOutputStream_.Position;
  327. }
  328. if (extra.Length > 0) {
  329. baseOutputStream_.Write(extra, 0, extra.Length);
  330. }
  331. offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
  332. // Fix offsetOfCentraldir for AES
  333. if (entry.AESKeySize > 0)
  334. offset += entry.AESOverheadSize;
  335. // Activate the entry.
  336. curEntry = entry;
  337. crc.Reset();
  338. if (method == CompressionMethod.Deflated) {
  339. deflater_.Reset();
  340. deflater_.SetLevel(compressionLevel);
  341. }
  342. size = 0;
  343. if (entry.IsCrypted) {
  344. if (entry.AESKeySize > 0) {
  345. WriteAESHeader(entry);
  346. } else {
  347. if (entry.Crc < 0) { // so testing Zip will says its ok
  348. WriteEncryptionHeader(entry.DosTime << 16);
  349. } else {
  350. WriteEncryptionHeader(entry.Crc);
  351. }
  352. }
  353. }
  354. }
  355. /// <summary>
  356. /// Closes the current entry, updating header and footer information as required
  357. /// </summary>
  358. /// <exception cref="System.IO.IOException">
  359. /// An I/O error occurs.
  360. /// </exception>
  361. /// <exception cref="System.InvalidOperationException">
  362. /// No entry is active.
  363. /// </exception>
  364. public void CloseEntry()
  365. {
  366. if (curEntry == null) {
  367. throw new InvalidOperationException("No open entry");
  368. }
  369. long csize = size;
  370. // First finish the deflater, if appropriate
  371. if (curMethod == CompressionMethod.Deflated) {
  372. if (size >= 0) {
  373. base.Finish();
  374. csize = deflater_.TotalOut;
  375. } else {
  376. deflater_.Reset();
  377. }
  378. }
  379. // Write the AES Authentication Code (a hash of the compressed and encrypted data)
  380. if (curEntry.AESKeySize > 0) {
  381. baseOutputStream_.Write(AESAuthCode, 0, 10);
  382. }
  383. if (curEntry.Size < 0) {
  384. curEntry.Size = size;
  385. } else if (curEntry.Size != size) {
  386. throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
  387. }
  388. if (curEntry.CompressedSize < 0) {
  389. curEntry.CompressedSize = csize;
  390. } else if (curEntry.CompressedSize != csize) {
  391. throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
  392. }
  393. if (curEntry.Crc < 0) {
  394. curEntry.Crc = crc.Value;
  395. } else if (curEntry.Crc != crc.Value) {
  396. throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
  397. }
  398. offset += csize;
  399. if (curEntry.IsCrypted) {
  400. if (curEntry.AESKeySize > 0) {
  401. curEntry.CompressedSize += curEntry.AESOverheadSize;
  402. } else {
  403. curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
  404. }
  405. }
  406. // Patch the header if possible
  407. if (patchEntryHeader) {
  408. patchEntryHeader = false;
  409. long curPos = baseOutputStream_.Position;
  410. baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
  411. WriteLeInt((int)curEntry.Crc);
  412. if (curEntry.LocalHeaderRequiresZip64) {
  413. if (sizePatchPos == -1) {
  414. throw new ZipException("Entry requires zip64 but this has been turned off");
  415. }
  416. baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
  417. WriteLeLong(curEntry.Size);
  418. WriteLeLong(curEntry.CompressedSize);
  419. } else {
  420. WriteLeInt((int)curEntry.CompressedSize);
  421. WriteLeInt((int)curEntry.Size);
  422. }
  423. baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
  424. }
  425. // Add data descriptor if flagged as required
  426. if ((curEntry.Flags & 8) != 0) {
  427. WriteLeInt(ZipConstants.DataDescriptorSignature);
  428. WriteLeInt(unchecked((int)curEntry.Crc));
  429. if (curEntry.LocalHeaderRequiresZip64) {
  430. WriteLeLong(curEntry.CompressedSize);
  431. WriteLeLong(curEntry.Size);
  432. offset += ZipConstants.Zip64DataDescriptorSize;
  433. } else {
  434. WriteLeInt((int)curEntry.CompressedSize);
  435. WriteLeInt((int)curEntry.Size);
  436. offset += ZipConstants.DataDescriptorSize;
  437. }
  438. }
  439. entries.Add(curEntry);
  440. curEntry = null;
  441. }
  442. void WriteEncryptionHeader(long crcValue)
  443. {
  444. offset += ZipConstants.CryptoHeaderSize;
  445. InitializePassword(Password);
  446. byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
  447. var rnd = new Random();
  448. rnd.NextBytes(cryptBuffer);
  449. cryptBuffer[11] = (byte)(crcValue >> 24);
  450. EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
  451. baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
  452. }
  453. private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData)
  454. {
  455. // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
  456. const int VENDOR_VERSION = 2;
  457. // Vendor ID is the two ASCII characters "AE".
  458. const int VENDOR_ID = 0x4541; //not 6965;
  459. extraData.StartNewEntry();
  460. // Pack AES extra data field see http://www.winzip.com/aes_info.htm
  461. //extraData.AddLeShort(7); // Data size (currently 7)
  462. extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2
  463. extraData.AddLeShort(VENDOR_ID); // "AE"
  464. extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256
  465. extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file
  466. extraData.AddNewEntry(0x9901);
  467. }
  468. // Replaces WriteEncryptionHeader for AES
  469. //
  470. private void WriteAESHeader(ZipEntry entry)
  471. {
  472. byte[] salt;
  473. byte[] pwdVerifier;
  474. InitializeAESPassword(entry, Password, out salt, out pwdVerifier);
  475. // File format for AES:
  476. // Size (bytes) Content
  477. // ------------ -------
  478. // Variable Salt value
  479. // 2 Password verification value
  480. // Variable Encrypted file data
  481. // 10 Authentication code
  482. //
  483. // Value in the "compressed size" fields of the local file header and the central directory entry
  484. // is the total size of all the items listed above. In other words, it is the total size of the
  485. // salt value, password verification value, encrypted data, and authentication code.
  486. baseOutputStream_.Write(salt, 0, salt.Length);
  487. baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length);
  488. }
  489. /// <summary>
  490. /// Writes the given buffer to the current entry.
  491. /// </summary>
  492. /// <param name="buffer">The buffer containing data to write.</param>
  493. /// <param name="offset">The offset of the first byte to write.</param>
  494. /// <param name="count">The number of bytes to write.</param>
  495. /// <exception cref="ZipException">Archive size is invalid</exception>
  496. /// <exception cref="System.InvalidOperationException">No entry is active.</exception>
  497. public override void Write(byte[] buffer, int offset, int count)
  498. {
  499. if (curEntry == null) {
  500. throw new InvalidOperationException("No open entry.");
  501. }
  502. if (buffer == null) {
  503. throw new ArgumentNullException("nameof(buffer)");
  504. }
  505. if (offset < 0) {
  506. throw new ArgumentOutOfRangeException("nameof(offset)", "Cannot be negative");
  507. }
  508. if (count < 0) {
  509. throw new ArgumentOutOfRangeException("nameof(count)", "Cannot be negative");
  510. }
  511. if ((buffer.Length - offset) < count) {
  512. throw new ArgumentException("Invalid offset/count combination");
  513. }
  514. crc.Update(buffer, offset, count);
  515. size += count;
  516. switch (curMethod) {
  517. case CompressionMethod.Deflated:
  518. base.Write(buffer, offset, count);
  519. break;
  520. case CompressionMethod.Stored:
  521. if (Password != null) {
  522. CopyAndEncrypt(buffer, offset, count);
  523. } else {
  524. baseOutputStream_.Write(buffer, offset, count);
  525. }
  526. break;
  527. }
  528. }
  529. void CopyAndEncrypt(byte[] buffer, int offset, int count)
  530. {
  531. const int CopyBufferSize = 4096;
  532. byte[] localBuffer = new byte[CopyBufferSize];
  533. while (count > 0) {
  534. int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize;
  535. Array.Copy(buffer, offset, localBuffer, 0, bufferCount);
  536. EncryptBlock(localBuffer, 0, bufferCount);
  537. baseOutputStream_.Write(localBuffer, 0, bufferCount);
  538. count -= bufferCount;
  539. offset += bufferCount;
  540. }
  541. }
  542. /// <summary>
  543. /// Finishes the stream. This will write the central directory at the
  544. /// end of the zip file and flush the stream.
  545. /// </summary>
  546. /// <remarks>
  547. /// This is automatically called when the stream is closed.
  548. /// </remarks>
  549. /// <exception cref="System.IO.IOException">
  550. /// An I/O error occurs.
  551. /// </exception>
  552. /// <exception cref="ZipException">
  553. /// Comment exceeds the maximum length<br/>
  554. /// Entry name exceeds the maximum length
  555. /// </exception>
  556. public override void Finish()
  557. {
  558. if (entries == null) {
  559. return;
  560. }
  561. if (curEntry != null) {
  562. CloseEntry();
  563. }
  564. long numEntries = entries.Count;
  565. long sizeEntries = 0;
  566. foreach (ZipEntry entry in entries) {
  567. WriteLeInt(ZipConstants.CentralHeaderSignature);
  568. WriteLeShort(ZipConstants.VersionMadeBy);
  569. WriteLeShort(entry.Version);
  570. WriteLeShort(entry.Flags);
  571. WriteLeShort((short)entry.CompressionMethodForHeader);
  572. WriteLeInt((int)entry.DosTime);
  573. WriteLeInt((int)entry.Crc);
  574. if (entry.IsZip64Forced() ||
  575. (entry.CompressedSize >= uint.MaxValue)) {
  576. WriteLeInt(-1);
  577. } else {
  578. WriteLeInt((int)entry.CompressedSize);
  579. }
  580. if (entry.IsZip64Forced() ||
  581. (entry.Size >= uint.MaxValue)) {
  582. WriteLeInt(-1);
  583. } else {
  584. WriteLeInt((int)entry.Size);
  585. }
  586. byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
  587. if (name.Length > 0xffff) {
  588. throw new ZipException("Name too long.");
  589. }
  590. var ed = new ZipExtraData(entry.ExtraData);
  591. if (entry.CentralHeaderRequiresZip64) {
  592. ed.StartNewEntry();
  593. if (entry.IsZip64Forced() ||
  594. (entry.Size >= 0xffffffff)) {
  595. ed.AddLeLong(entry.Size);
  596. }
  597. if (entry.IsZip64Forced() ||
  598. (entry.CompressedSize >= 0xffffffff)) {
  599. ed.AddLeLong(entry.CompressedSize);
  600. }
  601. if (entry.Offset >= 0xffffffff) {
  602. ed.AddLeLong(entry.Offset);
  603. }
  604. ed.AddNewEntry(1);
  605. } else {
  606. ed.Delete(1);
  607. }
  608. if (entry.AESKeySize > 0) {
  609. AddExtraDataAES(entry, ed);
  610. }
  611. byte[] extra = ed.GetEntryData();
  612. byte[] entryComment =
  613. (entry.Comment != null) ?
  614. ZipConstants.ConvertToArray(entry.Flags, entry.Comment) :
  615. new byte[0];
  616. if (entryComment.Length > 0xffff) {
  617. throw new ZipException("Comment too long.");
  618. }
  619. WriteLeShort(name.Length);
  620. WriteLeShort(extra.Length);
  621. WriteLeShort(entryComment.Length);
  622. WriteLeShort(0); // disk number
  623. WriteLeShort(0); // internal file attributes
  624. // external file attributes
  625. if (entry.ExternalFileAttributes != -1) {
  626. WriteLeInt(entry.ExternalFileAttributes);
  627. } else {
  628. if (entry.IsDirectory) { // mark entry as directory (from nikolam.AT.perfectinfo.com)
  629. WriteLeInt(16);
  630. } else {
  631. WriteLeInt(0);
  632. }
  633. }
  634. if (entry.Offset >= uint.MaxValue) {
  635. WriteLeInt(-1);
  636. } else {
  637. WriteLeInt((int)entry.Offset);
  638. }
  639. if (name.Length > 0) {
  640. baseOutputStream_.Write(name, 0, name.Length);
  641. }
  642. if (extra.Length > 0) {
  643. baseOutputStream_.Write(extra, 0, extra.Length);
  644. }
  645. if (entryComment.Length > 0) {
  646. baseOutputStream_.Write(entryComment, 0, entryComment.Length);
  647. }
  648. sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
  649. }
  650. using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_)) {
  651. zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
  652. }
  653. entries = null;
  654. }
  655. #region Instance Fields
  656. /// <summary>
  657. /// The entries for the archive.
  658. /// </summary>
  659. List<ZipEntry> entries = new List<ZipEntry>();
  660. /// <summary>
  661. /// Used to track the crc of data added to entries.
  662. /// </summary>
  663. Crc32 crc = new Crc32();
  664. /// <summary>
  665. /// The current entry being added.
  666. /// </summary>
  667. ZipEntry curEntry;
  668. int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;
  669. CompressionMethod curMethod = CompressionMethod.Deflated;
  670. /// <summary>
  671. /// Used to track the size of data for an entry during writing.
  672. /// </summary>
  673. long size;
  674. /// <summary>
  675. /// Offset to be recorded for each entry in the central header.
  676. /// </summary>
  677. long offset;
  678. /// <summary>
  679. /// Comment for the entire archive recorded in central header.
  680. /// </summary>
  681. byte[] zipComment = new byte[0];
  682. /// <summary>
  683. /// Flag indicating that header patching is required for the current entry.
  684. /// </summary>
  685. bool patchEntryHeader;
  686. /// <summary>
  687. /// Position to patch crc
  688. /// </summary>
  689. long crcPatchPos = -1;
  690. /// <summary>
  691. /// Position to patch size.
  692. /// </summary>
  693. long sizePatchPos = -1;
  694. // Default is dynamic which is not backwards compatible and can cause problems
  695. // with XP's built in compression which cant read Zip64 archives.
  696. // However it does avoid the situation were a large file is added and cannot be completed correctly.
  697. // NOTE: Setting the size for entries before they are added is the best solution!
  698. UseZip64 useZip64_ = UseZip64.Dynamic;
  699. #endregion
  700. }
  701. }