ZipFile.Save.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  1. // ZipFile.Save.cs
  2. // ------------------------------------------------------------------
  3. //
  4. // Copyright (c) 2009 Dino Chiesa.
  5. // All rights reserved.
  6. //
  7. // This code module is part of DotNetZip, a zipfile class library.
  8. //
  9. // ------------------------------------------------------------------
  10. //
  11. // This code is licensed under the Microsoft Public License.
  12. // See the file License.txt for the license details.
  13. // More info on: http://dotnetzip.codeplex.com
  14. //
  15. // ------------------------------------------------------------------
  16. //
  17. // last saved (in emacs):
  18. // Time-stamp: <2011-August-05 13:31:23>
  19. //
  20. // ------------------------------------------------------------------
  21. //
  22. // This module defines the methods for Save operations on zip files.
  23. //
  24. // ------------------------------------------------------------------
  25. //
  26. using System;
  27. using System.IO;
  28. using System.Collections.Generic;
  29. namespace Ionic.Zip
  30. {
  31. public partial class ZipFile
  32. {
  33. /// <summary>
  34. /// Delete file with retry on UnauthorizedAccessException.
  35. /// </summary>
  36. ///
  37. /// <remarks>
  38. /// <para>
  39. /// When calling File.Delete() on a file that has been "recently"
  40. /// created, the call sometimes fails with
  41. /// UnauthorizedAccessException. This method simply retries the Delete 3
  42. /// times with a sleep between tries.
  43. /// </para>
  44. /// </remarks>
  45. ///
  46. /// <param name='filename'>the name of the file to be deleted</param>
  47. private void DeleteFileWithRetry(string filename)
  48. {
  49. bool done = false;
  50. int nRetries = 3;
  51. for (int i=0; i < nRetries && !done; i++)
  52. {
  53. try
  54. {
  55. File.Delete(filename);
  56. done = true;
  57. }
  58. catch (System.UnauthorizedAccessException)
  59. {
  60. Console.WriteLine("************************************************** Retry delete.");
  61. System.Threading.Thread.Sleep(200+i*200);
  62. }
  63. }
  64. }
  65. /// <summary>
  66. /// Saves the Zip archive to a file, specified by the Name property of the
  67. /// <c>ZipFile</c>.
  68. /// </summary>
  69. ///
  70. /// <remarks>
  71. /// <para>
  72. /// The <c>ZipFile</c> instance is written to storage, typically a zip file
  73. /// in a filesystem, only when the caller calls <c>Save</c>. In the typical
  74. /// case, the Save operation writes the zip content to a temporary file, and
  75. /// then renames the temporary file to the desired name. If necessary, this
  76. /// method will delete a pre-existing file before the rename.
  77. /// </para>
  78. ///
  79. /// <para>
  80. /// The <see cref="ZipFile.Name"/> property is specified either explicitly,
  81. /// or implicitly using one of the parameterized ZipFile constructors. For
  82. /// COM Automation clients, the <c>Name</c> property must be set explicitly,
  83. /// because COM Automation clients cannot call parameterized constructors.
  84. /// </para>
  85. ///
  86. /// <para>
  87. /// When using a filesystem file for the Zip output, it is possible to call
  88. /// <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each
  89. /// call the zip content is re-written to the same output file.
  90. /// </para>
  91. ///
  92. /// <para>
  93. /// Data for entries that have been added to the <c>ZipFile</c> instance is
  94. /// written to the output when the <c>Save</c> method is called. This means
  95. /// that the input streams for those entries must be available at the time
  96. /// the application calls <c>Save</c>. If, for example, the application
  97. /// adds entries with <c>AddEntry</c> using a dynamically-allocated
  98. /// <c>MemoryStream</c>, the memory stream must not have been disposed
  99. /// before the call to <c>Save</c>. See the <see
  100. /// cref="ZipEntry.InputStream"/> property for more discussion of the
  101. /// availability requirements of the input stream for an entry, and an
  102. /// approach for providing just-in-time stream lifecycle management.
  103. /// </para>
  104. ///
  105. /// </remarks>
  106. ///
  107. /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/>
  108. ///
  109. /// <exception cref="Ionic.Zip.BadStateException">
  110. /// Thrown if you haven't specified a location or stream for saving the zip,
  111. /// either in the constructor or by setting the Name property, or if you try
  112. /// to save a regular zip archive to a filename with a .exe extension.
  113. /// </exception>
  114. ///
  115. /// <exception cref="System.OverflowException">
  116. /// Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number
  117. /// of segments that would be generated for the spanned zip file during the
  118. /// save operation exceeds 99. If this happens, you need to increase the
  119. /// segment size.
  120. /// </exception>
  121. ///
  122. public void Save()
  123. {
  124. try
  125. {
  126. bool thisSaveUsedZip64 = false;
  127. _saveOperationCanceled = false;
  128. _numberOfSegmentsForMostRecentSave = 0;
  129. OnSaveStarted();
  130. if (WriteStream == null)
  131. throw new BadStateException("You haven't specified where to save the zip.");
  132. if (_name != null && _name.EndsWith(".exe") && !_SavingSfx)
  133. throw new BadStateException("You specified an EXE for a plain zip file.");
  134. // check if modified, before saving.
  135. if (!_contentsChanged)
  136. {
  137. OnSaveCompleted();
  138. if (Verbose) StatusMessageTextWriter.WriteLine("No save is necessary....");
  139. return;
  140. }
  141. Reset(true);
  142. if (Verbose) StatusMessageTextWriter.WriteLine("saving....");
  143. // validate the number of entries
  144. if (_entries.Count >= 0xFFFF && _zip64 == Zip64Option.Never)
  145. throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
  146. // write an entry in the zip for each file
  147. int n = 0;
  148. // workitem 9831
  149. ICollection<ZipEntry> c = (SortEntriesBeforeSaving) ? EntriesSorted : Entries;
  150. foreach (ZipEntry e in c) // _entries.Values
  151. {
  152. OnSaveEntry(n, e, true);
  153. e.Write(WriteStream);
  154. if (_saveOperationCanceled)
  155. break;
  156. n++;
  157. OnSaveEntry(n, e, false);
  158. if (_saveOperationCanceled)
  159. break;
  160. // Some entries can be skipped during the save.
  161. if (e.IncludedInMostRecentSave)
  162. thisSaveUsedZip64 |= e.OutputUsedZip64.Value;
  163. }
  164. if (_saveOperationCanceled)
  165. return;
  166. var zss = WriteStream as ZipSegmentedStream;
  167. _numberOfSegmentsForMostRecentSave = (zss!=null)
  168. ? zss.CurrentSegment
  169. : 1;
  170. bool directoryNeededZip64 =
  171. ZipOutput.WriteCentralDirectoryStructure
  172. (WriteStream,
  173. c,
  174. _numberOfSegmentsForMostRecentSave,
  175. _zip64,
  176. Comment,
  177. new ZipContainer(this));
  178. OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
  179. _hasBeenSaved = true;
  180. _contentsChanged = false;
  181. thisSaveUsedZip64 |= directoryNeededZip64;
  182. _OutputUsesZip64 = new Nullable<bool>(thisSaveUsedZip64);
  183. if (_fileAlreadyExists && this._readstream != null)
  184. {
  185. // This means we opened and read a zip file.
  186. // If we are now saving, we need to close the orig file, first.
  187. this._readstream.Close();
  188. this._readstream = null;
  189. }
  190. // the archiveStream for each entry needs to be null
  191. foreach (var e in c)
  192. {
  193. var zss1 = e._archiveStream as ZipSegmentedStream;
  194. if (zss1 != null)
  195. #if NETCF
  196. zss1.Close();
  197. #else
  198. zss1.Dispose();
  199. #endif
  200. e._archiveStream = null;
  201. }
  202. // do the rename as necessary
  203. if (_name != null &&
  204. (_temporaryFileName!=null || zss != null))
  205. {
  206. // _temporaryFileName may remain null if we are writing to a stream.
  207. // only close the stream if there is a file behind it.
  208. #if NETCF
  209. WriteStream.Close();
  210. #else
  211. WriteStream.Dispose();
  212. #endif
  213. if (_saveOperationCanceled)
  214. return;
  215. string tmpName = null;
  216. if (File.Exists(_name))
  217. {
  218. // the steps:
  219. //
  220. // 1. Delete tmpName
  221. // 2. move existing zip to tmpName
  222. // 3. rename (File.Move) working file to name of existing zip
  223. // 4. delete tmpName
  224. //
  225. // This series of steps avoids the exception,
  226. // System.IO.IOException:
  227. // "Cannot create a file when that file already exists."
  228. //
  229. // Cannot just call File.Replace() here because
  230. // there is a possibility that the TEMP volume is different
  231. // that the volume for the final file (c:\ vs d:\).
  232. // So we need to do a Delete+Move pair.
  233. //
  234. // But, when doing the delete, Windows allows a process to
  235. // delete the file, even though it is held open by, say, a
  236. // virus scanner. It gets internally marked as "delete
  237. // pending". The file does not actually get removed from the
  238. // file system, it is still there after the File.Delete
  239. // call.
  240. //
  241. // Therefore, we need to move the existing zip, which may be
  242. // held open, to some other name. Then rename our working
  243. // file to the desired name, then delete (possibly delete
  244. // pending) the "other name".
  245. //
  246. // Ideally this would be transactional. It's possible that the
  247. // delete succeeds and the move fails. Lacking transactions, if
  248. // this kind of failure happens, we're hosed, and this logic will
  249. // throw on the next File.Move().
  250. //
  251. //File.Delete(_name);
  252. // workitem 10447
  253. #if NETCF || SILVERLIGHT
  254. tmpName = _name + "." + SharedUtilities.GenerateRandomStringImpl(8,0) + ".tmp";
  255. #else
  256. tmpName = _name + "." + Path.GetRandomFileName();
  257. #endif
  258. if (File.Exists(tmpName))
  259. DeleteFileWithRetry(tmpName);
  260. File.Move(_name, tmpName);
  261. }
  262. OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
  263. File.Move((zss != null) ? zss.CurrentTempName : _temporaryFileName,
  264. _name);
  265. OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);
  266. if (tmpName != null)
  267. {
  268. try
  269. {
  270. // not critical
  271. if (File.Exists(tmpName))
  272. File.Delete(tmpName);
  273. }
  274. catch
  275. {
  276. // don't care about exceptions here.
  277. }
  278. }
  279. _fileAlreadyExists = true;
  280. }
  281. _readName = _name;
  282. NotifyEntriesSaveComplete(c);
  283. OnSaveCompleted();
  284. _JustSaved = true;
  285. }
  286. // workitem 5043
  287. finally
  288. {
  289. CleanupAfterSaveOperation();
  290. }
  291. return;
  292. }
  293. private static void NotifyEntriesSaveComplete(ICollection<ZipEntry> c)
  294. {
  295. foreach (ZipEntry e in c)
  296. {
  297. e.NotifySaveComplete();
  298. }
  299. }
  300. private void RemoveTempFile()
  301. {
  302. try
  303. {
  304. if (File.Exists(_temporaryFileName))
  305. {
  306. File.Delete(_temporaryFileName);
  307. }
  308. }
  309. catch (IOException ex1)
  310. {
  311. if (Verbose)
  312. StatusMessageTextWriter.WriteLine("ZipFile::Save: could not delete temp file: {0}.", ex1.Message);
  313. }
  314. }
  315. private void CleanupAfterSaveOperation()
  316. {
  317. if (_name != null)
  318. {
  319. // close the stream if there is a file behind it.
  320. if (_writestream != null)
  321. {
  322. try
  323. {
  324. // workitem 7704
  325. #if NETCF
  326. _writestream.Close();
  327. #else
  328. _writestream.Dispose();
  329. #endif
  330. }
  331. catch (System.IO.IOException) { }
  332. }
  333. _writestream = null;
  334. if (_temporaryFileName != null)
  335. {
  336. RemoveTempFile();
  337. _temporaryFileName = null;
  338. }
  339. }
  340. }
  341. /// <summary>
  342. /// Save the file to a new zipfile, with the given name.
  343. /// </summary>
  344. ///
  345. /// <remarks>
  346. /// <para>
  347. /// This method allows the application to explicitly specify the name of the zip
  348. /// file when saving. Use this when creating a new zip file, or when
  349. /// updating a zip archive.
  350. /// </para>
  351. ///
  352. /// <para>
  353. /// An application can also save a zip archive in several places by calling this
  354. /// method multiple times in succession, with different filenames.
  355. /// </para>
  356. ///
  357. /// <para>
  358. /// The <c>ZipFile</c> instance is written to storage, typically a zip file in a
  359. /// filesystem, only when the caller calls <c>Save</c>. The Save operation writes
  360. /// the zip content to a temporary file, and then renames the temporary file
  361. /// to the desired name. If necessary, this method will delete a pre-existing file
  362. /// before the rename.
  363. /// </para>
  364. ///
  365. /// </remarks>
  366. ///
  367. /// <exception cref="System.ArgumentException">
  368. /// Thrown if you specify a directory for the filename.
  369. /// </exception>
  370. ///
  371. /// <param name="fileName">
  372. /// The name of the zip archive to save to. Existing files will
  373. /// be overwritten with great prejudice.
  374. /// </param>
  375. ///
  376. /// <example>
  377. /// This example shows how to create and Save a zip file.
  378. /// <code>
  379. /// using (ZipFile zip = new ZipFile())
  380. /// {
  381. /// zip.AddDirectory(@"c:\reports\January");
  382. /// zip.Save("January.zip");
  383. /// }
  384. /// </code>
  385. ///
  386. /// <code lang="VB">
  387. /// Using zip As New ZipFile()
  388. /// zip.AddDirectory("c:\reports\January")
  389. /// zip.Save("January.zip")
  390. /// End Using
  391. /// </code>
  392. ///
  393. /// </example>
  394. ///
  395. /// <example>
  396. /// This example shows how to update a zip file.
  397. /// <code>
  398. /// using (ZipFile zip = ZipFile.Read("ExistingArchive.zip"))
  399. /// {
  400. /// zip.AddFile("NewData.csv");
  401. /// zip.Save("UpdatedArchive.zip");
  402. /// }
  403. /// </code>
  404. ///
  405. /// <code lang="VB">
  406. /// Using zip As ZipFile = ZipFile.Read("ExistingArchive.zip")
  407. /// zip.AddFile("NewData.csv")
  408. /// zip.Save("UpdatedArchive.zip")
  409. /// End Using
  410. /// </code>
  411. ///
  412. /// </example>
  413. public void Save(String fileName)
  414. {
  415. // Check for the case where we are re-saving a zip archive
  416. // that was originally instantiated with a stream. In that case,
  417. // the _name will be null. If so, we set _writestream to null,
  418. // which insures that we'll cons up a new WriteStream (with a filesystem
  419. // file backing it) in the Save() method.
  420. if (_name == null)
  421. _writestream = null;
  422. else _readName = _name; // workitem 13915
  423. _name = fileName;
  424. if (Directory.Exists(_name))
  425. throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "fileName"));
  426. _contentsChanged = true;
  427. _fileAlreadyExists = File.Exists(_readName);
  428. Save();
  429. }
  430. /// <summary>
  431. /// Save the zip archive to the specified stream.
  432. /// </summary>
  433. ///
  434. /// <remarks>
  435. /// <para>
  436. /// The <c>ZipFile</c> instance is written to storage - typically a zip file
  437. /// in a filesystem, but using this overload, the storage can be anything
  438. /// accessible via a writable stream - only when the caller calls <c>Save</c>.
  439. /// </para>
  440. ///
  441. /// <para>
  442. /// Use this method to save the zip content to a stream directly. A common
  443. /// scenario is an ASP.NET application that dynamically generates a zip file
  444. /// and allows the browser to download it. The application can call
  445. /// <c>Save(Response.OutputStream)</c> to write a zipfile directly to the
  446. /// output stream, without creating a zip file on the disk on the ASP.NET
  447. /// server.
  448. /// </para>
  449. ///
  450. /// <para>
  451. /// Be careful when saving a file to a non-seekable stream, including
  452. /// <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable
  453. /// stream, the zip archive is formatted in such a way that may not be
  454. /// compatible with all zip tools on all platforms. It's a perfectly legal
  455. /// and compliant zip file, but some people have reported problems opening
  456. /// files produced this way using the Mac OS archive utility.
  457. /// </para>
  458. ///
  459. /// </remarks>
  460. ///
  461. /// <example>
  462. ///
  463. /// This example saves the zipfile content into a MemoryStream, and
  464. /// then gets the array of bytes from that MemoryStream.
  465. ///
  466. /// <code lang="C#">
  467. /// using (var zip = new Ionic.Zip.ZipFile())
  468. /// {
  469. /// zip.CompressionLevel= Ionic.Zlib.CompressionLevel.BestCompression;
  470. /// zip.Password = "VerySecret.";
  471. /// zip.Encryption = EncryptionAlgorithm.WinZipAes128;
  472. /// zip.AddFile(sourceFileName);
  473. /// MemoryStream output = new MemoryStream();
  474. /// zip.Save(output);
  475. ///
  476. /// byte[] zipbytes = output.ToArray();
  477. /// }
  478. /// </code>
  479. /// </example>
  480. ///
  481. /// <example>
  482. /// <para>
  483. /// This example shows a pitfall you should avoid. DO NOT read
  484. /// from a stream, then try to save to the same stream. DO
  485. /// NOT DO THIS:
  486. /// </para>
  487. ///
  488. /// <code lang="C#">
  489. /// using (var fs = new FileStream(filename, FileMode.Open))
  490. /// {
  491. /// using (var zip = Ionic.Zip.ZipFile.Read(inputStream))
  492. /// {
  493. /// zip.AddEntry("Name1.txt", "this is the content");
  494. /// zip.Save(inputStream); // NO NO NO!!
  495. /// }
  496. /// }
  497. /// </code>
  498. ///
  499. /// <para>
  500. /// Better like this:
  501. /// </para>
  502. ///
  503. /// <code lang="C#">
  504. /// using (var zip = Ionic.Zip.ZipFile.Read(filename))
  505. /// {
  506. /// zip.AddEntry("Name1.txt", "this is the content");
  507. /// zip.Save(); // YES!
  508. /// }
  509. /// </code>
  510. ///
  511. /// </example>
  512. ///
  513. /// <param name="outputStream">
  514. /// The <c>System.IO.Stream</c> to write to. It must be
  515. /// writable. If you created the ZipFile instance by calling
  516. /// ZipFile.Read(), this stream must not be the same stream
  517. /// you passed to ZipFile.Read().
  518. /// </param>
  519. public void Save(Stream outputStream)
  520. {
  521. if (outputStream == null)
  522. throw new ArgumentNullException("outputStream");
  523. if (!outputStream.CanWrite)
  524. throw new ArgumentException("Must be a writable stream.", "outputStream");
  525. // if we had a filename to save to, we are now obliterating it.
  526. _name = null;
  527. if(_writestream != null) // if we saved to a stream before read from there
  528. _readstream = _writestream;
  529. _writestream = new CountingStream(outputStream);
  530. _contentsChanged = true;
  531. _fileAlreadyExists = File.Exists(_readName); // if we saved to or read from a file before
  532. Save();
  533. _fileAlreadyExists = false;
  534. _readName = null; // if we had a filename to save to, we are now obliterating it.
  535. }
  536. }
  537. internal static class ZipOutput
  538. {
  539. public static bool WriteCentralDirectoryStructure(Stream s,
  540. ICollection<ZipEntry> entries,
  541. uint numSegments,
  542. Zip64Option zip64,
  543. String comment,
  544. ZipContainer container)
  545. {
  546. var zss = s as ZipSegmentedStream;
  547. if (zss != null)
  548. zss.ContiguousWrite = true;
  549. // write to a memory stream in order to keep the
  550. // CDR contiguous
  551. Int64 aLength = 0;
  552. using (var ms = new MemoryStream())
  553. {
  554. foreach (ZipEntry e in entries)
  555. {
  556. if (e.IncludedInMostRecentSave)
  557. {
  558. // this writes a ZipDirEntry corresponding to the ZipEntry
  559. e.WriteCentralDirectoryEntry(ms);
  560. }
  561. }
  562. var a = ms.ToArray();
  563. s.Write(a, 0, a.Length);
  564. aLength = a.Length;
  565. }
  566. // We need to keep track of the start and
  567. // Finish of the Central Directory Structure.
  568. // Cannot always use WriteStream.Length or Position; some streams do
  569. // not support these. (eg, ASP.NET Response.OutputStream) In those
  570. // cases we have a CountingStream.
  571. // Also, we cannot just set Start as s.Position bfore the write, and Finish
  572. // as s.Position after the write. In a split zip, the write may actually
  573. // flip to the next segment. In that case, Start will be zero. But we
  574. // don't know that til after we know the size of the thing to write. So the
  575. // answer is to compute the directory, then ask the ZipSegmentedStream which
  576. // segment that directory would fall in, it it were written. Then, include
  577. // that data into the directory, and finally, write the directory to the
  578. // output stream.
  579. var output = s as CountingStream;
  580. long Finish = (output != null) ? output.ComputedPosition : s.Position; // BytesWritten
  581. long Start = Finish - aLength;
  582. // need to know which segment the EOCD record starts in
  583. UInt32 startSegment = (zss != null)
  584. ? zss.CurrentSegment
  585. : 0;
  586. Int64 SizeOfCentralDirectory = Finish - Start;
  587. int countOfEntries = CountEntries(entries);
  588. bool needZip64CentralDirectory =
  589. zip64 == Zip64Option.Always ||
  590. countOfEntries >= 0xFFFF ||
  591. SizeOfCentralDirectory > 0xFFFFFFFF ||
  592. Start > 0xFFFFFFFF;
  593. byte[] a2 = null;
  594. // emit ZIP64 extensions as required
  595. if (needZip64CentralDirectory)
  596. {
  597. if (zip64 == Zip64Option.Never)
  598. {
  599. #if NETCF
  600. throw new ZipException("The archive requires a ZIP64 Central Directory. Consider enabling ZIP64 extensions.");
  601. #else
  602. System.Diagnostics.StackFrame sf = new System.Diagnostics.StackFrame(1);
  603. if (sf.GetMethod().DeclaringType == typeof(ZipFile))
  604. throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipFile.UseZip64WhenSaving property.");
  605. else
  606. throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipOutputStream.EnableZip64 property.");
  607. #endif
  608. }
  609. var a = GenZip64EndOfCentralDirectory(Start, Finish, countOfEntries, numSegments);
  610. a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
  611. if (startSegment != 0)
  612. {
  613. UInt32 thisSegment = zss.ComputeSegment(a.Length + a2.Length);
  614. int i = 16;
  615. // number of this disk
  616. Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
  617. i += 4;
  618. // number of the disk with the start of the central directory
  619. //Array.Copy(BitConverter.GetBytes(startSegment), 0, a, i, 4);
  620. Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
  621. i = 60;
  622. // offset 60
  623. // number of the disk with the start of the zip64 eocd
  624. Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
  625. i += 4;
  626. i += 8;
  627. // offset 72
  628. // total number of disks
  629. Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
  630. }
  631. s.Write(a, 0, a.Length);
  632. }
  633. else
  634. a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
  635. // now, the regular footer
  636. if (startSegment != 0)
  637. {
  638. // The assumption is the central directory is never split across
  639. // segment boundaries.
  640. UInt16 thisSegment = (UInt16) zss.ComputeSegment(a2.Length);
  641. int i = 4;
  642. // number of this disk
  643. Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
  644. i += 2;
  645. // number of the disk with the start of the central directory
  646. //Array.Copy(BitConverter.GetBytes((UInt16)startSegment), 0, a2, i, 2);
  647. Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
  648. i += 2;
  649. }
  650. s.Write(a2, 0, a2.Length);
  651. // reset the contiguous write property if necessary
  652. if (zss != null)
  653. zss.ContiguousWrite = false;
  654. return needZip64CentralDirectory;
  655. }
  656. private static System.Text.Encoding GetEncoding(ZipContainer container, string t)
  657. {
  658. switch (container.AlternateEncodingUsage)
  659. {
  660. case ZipOption.Always:
  661. return container.AlternateEncoding;
  662. case ZipOption.Never:
  663. return container.DefaultEncoding;
  664. }
  665. // AsNecessary is in force
  666. var e = container.DefaultEncoding;
  667. if (t == null) return e;
  668. var bytes = e.GetBytes(t);
  669. var t2 = e.GetString(bytes,0,bytes.Length);
  670. if (t2.Equals(t)) return e;
  671. return container.AlternateEncoding;
  672. }
  673. private static byte[] GenCentralDirectoryFooter(long StartOfCentralDirectory,
  674. long EndOfCentralDirectory,
  675. Zip64Option zip64,
  676. int entryCount,
  677. string comment,
  678. ZipContainer container)
  679. {
  680. System.Text.Encoding encoding = GetEncoding(container, comment);
  681. int j = 0;
  682. int bufferLength = 22;
  683. byte[] block = null;
  684. Int16 commentLength = 0;
  685. if ((comment != null) && (comment.Length != 0))
  686. {
  687. block = encoding.GetBytes(comment);
  688. commentLength = (Int16)block.Length;
  689. }
  690. bufferLength += commentLength;
  691. byte[] bytes = new byte[bufferLength];
  692. int i = 0;
  693. // signature
  694. byte[] sig = BitConverter.GetBytes(ZipConstants.EndOfCentralDirectorySignature);
  695. Array.Copy(sig, 0, bytes, i, 4);
  696. i+=4;
  697. // number of this disk
  698. // (this number may change later)
  699. bytes[i++] = 0;
  700. bytes[i++] = 0;
  701. // number of the disk with the start of the central directory
  702. // (this number may change later)
  703. bytes[i++] = 0;
  704. bytes[i++] = 0;
  705. // handle ZIP64 extensions for the end-of-central-directory
  706. if (entryCount >= 0xFFFF || zip64 == Zip64Option.Always)
  707. {
  708. // the ZIP64 version.
  709. for (j = 0; j < 4; j++)
  710. bytes[i++] = 0xFF;
  711. }
  712. else
  713. {
  714. // the standard version.
  715. // total number of entries in the central dir on this disk
  716. bytes[i++] = (byte)(entryCount & 0x00FF);
  717. bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
  718. // total number of entries in the central directory
  719. bytes[i++] = (byte)(entryCount & 0x00FF);
  720. bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
  721. }
  722. // size of the central directory
  723. Int64 SizeOfCentralDirectory = EndOfCentralDirectory - StartOfCentralDirectory;
  724. if (SizeOfCentralDirectory >= 0xFFFFFFFF || StartOfCentralDirectory >= 0xFFFFFFFF)
  725. {
  726. // The actual data is in the ZIP64 central directory structure
  727. for (j = 0; j < 8; j++)
  728. bytes[i++] = 0xFF;
  729. }
  730. else
  731. {
  732. // size of the central directory (we just get the low 4 bytes)
  733. bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF);
  734. bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8);
  735. bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16);
  736. bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24);
  737. // offset of the start of the central directory (we just get the low 4 bytes)
  738. bytes[i++] = (byte)(StartOfCentralDirectory & 0x000000FF);
  739. bytes[i++] = (byte)((StartOfCentralDirectory & 0x0000FF00) >> 8);
  740. bytes[i++] = (byte)((StartOfCentralDirectory & 0x00FF0000) >> 16);
  741. bytes[i++] = (byte)((StartOfCentralDirectory & 0xFF000000) >> 24);
  742. }
  743. // zip archive comment
  744. if ((comment == null) || (comment.Length == 0))
  745. {
  746. // no comment!
  747. bytes[i++] = (byte)0;
  748. bytes[i++] = (byte)0;
  749. }
  750. else
  751. {
  752. // the size of our buffer defines the max length of the comment we can write
  753. if (commentLength + i + 2 > bytes.Length) commentLength = (Int16)(bytes.Length - i - 2);
  754. bytes[i++] = (byte)(commentLength & 0x00FF);
  755. bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);
  756. if (commentLength != 0)
  757. {
  758. // now actually write the comment itself into the byte buffer
  759. for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
  760. {
  761. bytes[i + j] = block[j];
  762. }
  763. i += j;
  764. }
  765. }
  766. // s.Write(bytes, 0, i);
  767. return bytes;
  768. }
  769. private static byte[] GenZip64EndOfCentralDirectory(long StartOfCentralDirectory,
  770. long EndOfCentralDirectory,
  771. int entryCount,
  772. uint numSegments)
  773. {
  774. const int bufferLength = 12 + 44 + 20;
  775. byte[] bytes = new byte[bufferLength];
  776. int i = 0;
  777. // signature
  778. byte[] sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryRecordSignature);
  779. Array.Copy(sig, 0, bytes, i, 4);
  780. i+=4;
  781. // There is a possibility to include "Extensible" data in the zip64
  782. // end-of-central-dir record. I cannot figure out what it might be used to
  783. // store, so the size of this record is always fixed. Maybe it is used for
  784. // strong encryption data? That is for another day.
  785. long DataSize = 44;
  786. Array.Copy(BitConverter.GetBytes(DataSize), 0, bytes, i, 8);
  787. i += 8;
  788. // offset 12
  789. // VersionMadeBy = 45;
  790. bytes[i++] = 45;
  791. bytes[i++] = 0x00;
  792. // VersionNeededToExtract = 45;
  793. bytes[i++] = 45;
  794. bytes[i++] = 0x00;
  795. // offset 16
  796. // number of the disk, and the disk with the start of the central dir.
  797. // (this may change later)
  798. for (int j = 0; j < 8; j++)
  799. bytes[i++] = 0x00;
  800. // offset 24
  801. long numberOfEntries = entryCount;
  802. Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
  803. i += 8;
  804. Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
  805. i += 8;
  806. // offset 40
  807. Int64 SizeofCentraldirectory = EndOfCentralDirectory - StartOfCentralDirectory;
  808. Array.Copy(BitConverter.GetBytes(SizeofCentraldirectory), 0, bytes, i, 8);
  809. i += 8;
  810. Array.Copy(BitConverter.GetBytes(StartOfCentralDirectory), 0, bytes, i, 8);
  811. i += 8;
  812. // offset 56
  813. // now, the locator
  814. // signature
  815. sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature);
  816. Array.Copy(sig, 0, bytes, i, 4);
  817. i+=4;
  818. // offset 60
  819. // number of the disk with the start of the zip64 eocd
  820. // (this will change later) (it will?)
  821. uint x2 = (numSegments==0)?0:(uint)(numSegments-1);
  822. Array.Copy(BitConverter.GetBytes(x2), 0, bytes, i, 4);
  823. i+=4;
  824. // offset 64
  825. // relative offset of the zip64 eocd
  826. Array.Copy(BitConverter.GetBytes(EndOfCentralDirectory), 0, bytes, i, 8);
  827. i += 8;
  828. // offset 72
  829. // total number of disks
  830. // (this will change later)
  831. Array.Copy(BitConverter.GetBytes(numSegments), 0, bytes, i, 4);
  832. i+=4;
  833. return bytes;
  834. }
  835. private static int CountEntries(ICollection<ZipEntry> _entries)
  836. {
  837. // Cannot just emit _entries.Count, because some of the entries
  838. // may have been skipped.
  839. int count = 0;
  840. foreach (var entry in _entries)
  841. if (entry.IncludedInMostRecentSave) count++;
  842. return count;
  843. }
  844. }
  845. }