Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace DocX.iOS.Zip
  9. {
  10. /// <summary>
  11. /// Unique class for compression/decompression file. Represents a Zip file.
  12. /// </summary>
  13. public class ZipStorer : IDisposable
  14. {
  15. /// <summary>
  16. /// Compression method enumeration
  17. /// </summary>
  18. public enum Compression : ushort
  19. {
  20. /// <summary>Uncompressed storage</summary>
  21. Store = 0,
  22. /// <summary>Deflate compression method</summary>
  23. Deflate = 8
  24. }
  25. /// <summary>
  26. /// Represents an entry in Zip file directory
  27. /// </summary>
  28. public struct ZipFileEntry
  29. {
  30. /// <summary>Compression method</summary>
  31. public Compression Method;
  32. /// <summary>Full path and filename as stored in Zip</summary>
  33. public string FilenameInZip;
  34. /// <summary>Original file size</summary>
  35. public uint FileSize;
  36. /// <summary>Compressed file size</summary>
  37. public uint CompressedSize;
  38. /// <summary>Offset of header information inside Zip storage</summary>
  39. public uint HeaderOffset;
  40. /// <summary>Offset of file inside Zip storage</summary>
  41. public uint FileOffset;
  42. /// <summary>Size of header information</summary>
  43. public uint HeaderSize;
  44. /// <summary>32-bit checksum of entire file</summary>
  45. public uint Crc32;
  46. /// <summary>Last modification time of file</summary>
  47. public DateTime ModifyTime;
  48. /// <summary>User comment for file</summary>
  49. public string Comment;
  50. /// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
  51. public bool EncodeUTF8;
  52. /// <summary>Overriden method</summary>
  53. /// <returns>Filename in Zip</returns>
  54. public override string ToString()
  55. {
  56. return this.FilenameInZip;
  57. }
  58. }
  59. #region Public fields
  60. /// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
  61. public bool EncodeUTF8 = false;
  62. /// <summary>Force deflate algotithm even if it inflates the stored file. Off by default.</summary>
  63. public bool ForceDeflating = false;
  64. #endregion
  65. #region Private fields
  66. // List of files to store
  67. private List<ZipFileEntry> Files = new List<ZipFileEntry>();
  68. // Filename of storage file
  69. private string FileName;
  70. // Stream object of storage file
  71. private Stream ZipFileStream;
  72. private bool OwnsStream = true;
  73. // General comment
  74. private string Comment = "";
  75. // Central dir image
  76. private byte[] CentralDirImage = null;
  77. // Existing files in zip
  78. private ushort ExistingFiles = 0;
  79. // File access for Open method
  80. private FileAccess Access;
  81. // Static CRC32 Table
  82. private static UInt32[] CrcTable = null;
  83. // Default filename encoder
  84. private static Encoding DefaultEncoding = Encoding.GetEncoding(437);
  85. #endregion
  86. #region Public methods
  87. // Static constructor. Just invoked once in order to create the CRC32 lookup table.
  88. static ZipStorer()
  89. {
  90. // Generate CRC32 table
  91. CrcTable = new UInt32[256];
  92. for (int i = 0; i < CrcTable.Length; i++)
  93. {
  94. UInt32 c = (UInt32)i;
  95. for (int j = 0; j < 8; j++)
  96. {
  97. if ((c & 1) != 0)
  98. c = 3988292384 ^ (c >> 1);
  99. else
  100. c >>= 1;
  101. }
  102. CrcTable[i] = c;
  103. }
  104. }
  105. /// <summary>
  106. /// Method to create a new storage file
  107. /// </summary>
  108. /// <param name="_filename">Full path of Zip file to create</param>
  109. /// <param name="_comment">General comment for Zip file</param>
  110. /// <returns>A valid ZipStorer object</returns>
  111. public static ZipStorer Create(string _filename, string _comment)
  112. {
  113. Stream stream = new FileStream(_filename, FileMode.Create, FileAccess.ReadWrite);
  114. ZipStorer zip = Create(stream, _comment, true);
  115. zip.Comment = _comment;
  116. zip.FileName = _filename;
  117. return zip;
  118. }
  119. /// <summary>
  120. /// Method to create a new zip storage in a stream
  121. /// </summary>
  122. /// <param name="_stream"></param>
  123. /// <param name="_comment"></param>
  124. /// <returns>A valid ZipStorer object</returns>
  125. public static ZipStorer Create(Stream _stream, string _comment, bool _ownsStream = false)
  126. {
  127. ZipStorer zip = new ZipStorer();
  128. zip.Comment = _comment;
  129. zip.ZipFileStream = _stream;
  130. zip.OwnsStream = _ownsStream;
  131. zip.Access = FileAccess.Write;
  132. return zip;
  133. }
  134. /// <summary>
  135. /// Method to open an existing storage file
  136. /// </summary>
  137. /// <param name="_filename">Full path of Zip file to open</param>
  138. /// <param name="_access">File access mode as used in FileStream constructor</param>
  139. /// <returns>A valid ZipStorer object</returns>
  140. public static ZipStorer Open(string _filename, FileAccess _access)
  141. {
  142. Stream stream = (Stream)new FileStream(_filename, FileMode.Open, _access == FileAccess.Read ? FileAccess.Read : FileAccess.ReadWrite);
  143. ZipStorer zip = Open(stream, _access, true);
  144. zip.FileName = _filename;
  145. return zip;
  146. }
  147. /// <summary>
  148. /// Method to open an existing storage from stream
  149. /// </summary>
  150. /// <param name="_stream">Already opened stream with zip contents</param>
  151. /// <param name="_access">File access mode for stream operations</param>
  152. /// <returns>A valid ZipStorer object</returns>
  153. public static ZipStorer Open(Stream _stream, FileAccess _access, bool _ownsStream = false)
  154. {
  155. if (!_stream.CanSeek && _access != FileAccess.Read)
  156. throw new InvalidOperationException("Stream cannot seek");
  157. ZipStorer zip = new ZipStorer();
  158. //zip.FileName = _filename;
  159. zip.ZipFileStream = _stream;
  160. zip.OwnsStream = _ownsStream;
  161. zip.Access = _access;
  162. if (zip.ReadFileInfo())
  163. return zip;
  164. throw new System.IO.InvalidDataException();
  165. }
  166. /// <summary>
  167. /// Add full contents of a file into the Zip storage
  168. /// </summary>
  169. /// <param name="_method">Compression method</param>
  170. /// <param name="_pathname">Full path of file to add to Zip storage</param>
  171. /// <param name="_filenameInZip">Filename and path as desired in Zip directory</param>
  172. /// <param name="_comment">Comment for stored file</param>
  173. public void AddFile(Compression _method, string _pathname, string _filenameInZip, string _comment)
  174. {
  175. if (Access == FileAccess.Read)
  176. throw new InvalidOperationException("Writing is not alowed");
  177. FileStream stream = new FileStream(_pathname, FileMode.Open, FileAccess.Read);
  178. AddStream(_method, _filenameInZip, stream, File.GetLastWriteTime(_pathname), _comment);
  179. stream.Close();
  180. }
  181. /// <summary>
  182. /// Add full contents of a stream into the Zip storage
  183. /// </summary>
  184. /// <param name="_method">Compression method</param>
  185. /// <param name="_filenameInZip">Filename and path as desired in Zip directory</param>
  186. /// <param name="_source">Stream object containing the data to store in Zip</param>
  187. /// <param name="_modTime">Modification time of the data to store</param>
  188. /// <param name="_comment">Comment for stored file</param>
  189. public void AddStream(Compression _method, string _filenameInZip, Stream _source, DateTime _modTime, string _comment)
  190. {
  191. if (Access == FileAccess.Read)
  192. throw new InvalidOperationException("Writing is not alowed");
  193. long offset;
  194. if (this.Files.Count == 0)
  195. offset = 0;
  196. else
  197. {
  198. ZipFileEntry last = this.Files[this.Files.Count - 1];
  199. offset = last.HeaderOffset + last.HeaderSize;
  200. }
  201. // Prepare the fileinfo
  202. ZipFileEntry zfe = new ZipFileEntry();
  203. zfe.Method = _method;
  204. zfe.EncodeUTF8 = this.EncodeUTF8;
  205. zfe.FilenameInZip = NormalizedFilename(_filenameInZip);
  206. zfe.Comment = (_comment == null ? "" : _comment);
  207. // Even though we write the header now, it will have to be rewritten, since we don't know compressed size or crc.
  208. zfe.Crc32 = 0; // to be updated later
  209. zfe.HeaderOffset = (uint)this.ZipFileStream.Position; // offset within file of the start of this local record
  210. zfe.ModifyTime = _modTime;
  211. // Write local header
  212. WriteLocalHeader(ref zfe);
  213. zfe.FileOffset = (uint)this.ZipFileStream.Position;
  214. // Write file to zip (store)
  215. Store(ref zfe, _source);
  216. _source.Close();
  217. this.UpdateCrcAndSizes(ref zfe);
  218. Files.Add(zfe);
  219. }
  220. /// <summary>
  221. /// Updates central directory (if pertinent) and close the Zip storage
  222. /// </summary>
  223. /// <remarks>This is a required step, unless automatic dispose is used</remarks>
  224. public void Close()
  225. {
  226. if (this.Access != FileAccess.Read)
  227. {
  228. uint centralOffset = (uint)this.ZipFileStream.Position;
  229. uint centralSize = 0;
  230. if (this.CentralDirImage != null)
  231. this.ZipFileStream.Write(CentralDirImage, 0, CentralDirImage.Length);
  232. for (int i = 0; i < Files.Count; i++)
  233. {
  234. long pos = this.ZipFileStream.Position;
  235. this.WriteCentralDirRecord(Files[i]);
  236. centralSize += (uint)(this.ZipFileStream.Position - pos);
  237. }
  238. if (this.CentralDirImage != null)
  239. this.WriteEndRecord(centralSize + (uint)CentralDirImage.Length, centralOffset);
  240. else
  241. this.WriteEndRecord(centralSize, centralOffset);
  242. }
  243. if (this.ZipFileStream != null && ZipFileStream.CanSeek)
  244. {
  245. this.ZipFileStream.Flush();
  246. // GZE added ownsStream, to prevent premature closure
  247. if (this.OwnsStream)
  248. {
  249. this.ZipFileStream.Dispose();
  250. }
  251. this.ZipFileStream = null;
  252. }
  253. }
  254. /// <summary>
  255. /// Read all the file records in the central directory
  256. /// </summary>
  257. /// <returns>List of all entries in directory</returns>
  258. public List<ZipFileEntry> ReadCentralDir()
  259. {
  260. if (this.CentralDirImage == null)
  261. throw new InvalidOperationException("Central directory currently does not exist");
  262. List<ZipFileEntry> result = new List<ZipFileEntry>();
  263. for (int pointer = 0; pointer < this.CentralDirImage.Length; )
  264. {
  265. uint signature = BitConverter.ToUInt32(CentralDirImage, pointer);
  266. if (signature != 0x02014b50)
  267. break;
  268. bool encodeUTF8 = (BitConverter.ToUInt16(CentralDirImage, pointer + 8) & 0x0800) != 0;
  269. ushort method = BitConverter.ToUInt16(CentralDirImage, pointer + 10);
  270. uint modifyTime = BitConverter.ToUInt32(CentralDirImage, pointer + 12);
  271. uint crc32 = BitConverter.ToUInt32(CentralDirImage, pointer + 16);
  272. uint comprSize = BitConverter.ToUInt32(CentralDirImage, pointer + 20);
  273. uint fileSize = BitConverter.ToUInt32(CentralDirImage, pointer + 24);
  274. ushort filenameSize = BitConverter.ToUInt16(CentralDirImage, pointer + 28);
  275. ushort extraSize = BitConverter.ToUInt16(CentralDirImage, pointer + 30);
  276. ushort commentSize = BitConverter.ToUInt16(CentralDirImage, pointer + 32);
  277. uint headerOffset = BitConverter.ToUInt32(CentralDirImage, pointer + 42);
  278. uint headerSize = (uint)(46 + filenameSize + extraSize + commentSize);
  279. Encoding encoder = encodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
  280. ZipFileEntry zfe = new ZipFileEntry();
  281. zfe.Method = (Compression)method;
  282. zfe.FilenameInZip = encoder.GetString(CentralDirImage, pointer + 46, filenameSize);
  283. zfe.FileOffset = GetFileOffset(headerOffset);
  284. zfe.FileSize = fileSize;
  285. zfe.CompressedSize = comprSize;
  286. zfe.HeaderOffset = headerOffset;
  287. zfe.HeaderSize = headerSize;
  288. zfe.Crc32 = crc32;
  289. zfe.ModifyTime = DosTimeToDateTime(modifyTime);
  290. if (commentSize > 0)
  291. zfe.Comment = encoder.GetString(CentralDirImage, pointer + 46 + filenameSize + extraSize, commentSize);
  292. result.Add(zfe);
  293. pointer += (46 + filenameSize + extraSize + commentSize);
  294. }
  295. return result;
  296. }
  297. /// <summary>
  298. /// Copy the contents of a stored file into a physical file
  299. /// </summary>
  300. /// <param name="_zfe">Entry information of file to extract</param>
  301. /// <param name="_filename">Name of file to store uncompressed data</param>
  302. /// <returns>True if success, false if not.</returns>
  303. /// <remarks>Unique compression methods are Store and Deflate</remarks>
  304. public bool ExtractFile(ZipFileEntry _zfe, string _filename)
  305. {
  306. // Make sure the parent directory exist
  307. string path = System.IO.Path.GetDirectoryName(_filename);
  308. if (!Directory.Exists(path))
  309. Directory.CreateDirectory(path);
  310. // Check it is directory. If so, do nothing
  311. if (Directory.Exists(_filename))
  312. return true;
  313. Stream output = new FileStream(_filename, FileMode.Create, FileAccess.Write);
  314. bool result = ExtractFile(_zfe, output);
  315. if (result)
  316. output.Close();
  317. File.SetCreationTime(_filename, _zfe.ModifyTime);
  318. File.SetLastWriteTime(_filename, _zfe.ModifyTime);
  319. return result;
  320. }
  321. /// <summary>
  322. /// Copy the contents of a stored file into an opened stream
  323. /// </summary>
  324. /// <param name="_zfe">Entry information of file to extract</param>
  325. /// <param name="_stream">Stream to store the uncompressed data</param>
  326. /// <returns>True if success, false if not.</returns>
  327. /// <remarks>Unique compression methods are Store and Deflate</remarks>
  328. public bool ExtractFile(ZipFileEntry _zfe, Stream _stream)
  329. {
  330. if (!_stream.CanWrite)
  331. throw new InvalidOperationException("Stream cannot be written");
  332. // check signature
  333. byte[] signature = new byte[4];
  334. this.ZipFileStream.Seek(_zfe.HeaderOffset, SeekOrigin.Begin);
  335. this.ZipFileStream.Read(signature, 0, 4);
  336. if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
  337. return false;
  338. // Select input stream for inflating or just reading
  339. Stream inStream;
  340. if (_zfe.Method == Compression.Store)
  341. inStream = this.ZipFileStream;
  342. else if (_zfe.Method == Compression.Deflate)
  343. inStream = new DeflateStream(this.ZipFileStream, CompressionMode.Decompress, true);
  344. else
  345. return false;
  346. // Buffered copy
  347. byte[] buffer = new byte[16384];
  348. this.ZipFileStream.Seek(_zfe.FileOffset, SeekOrigin.Begin);
  349. uint bytesPending = _zfe.FileSize;
  350. while (bytesPending > 0)
  351. {
  352. int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length));
  353. _stream.Write(buffer, 0, bytesRead);
  354. bytesPending -= (uint)bytesRead;
  355. }
  356. _stream.Flush();
  357. if (_zfe.Method == Compression.Deflate)
  358. inStream.Dispose();
  359. return true;
  360. }
  361. /// <summary>
  362. /// Removes one of many files in storage. It creates a new Zip file.
  363. /// </summary>
  364. /// <param name="_zip">Reference to the current Zip object</param>
  365. /// <param name="_zfes">List of Entries to remove from storage</param>
  366. /// <returns>True if success, false if not</returns>
  367. /// <remarks>This method only works for storage of type FileStream</remarks>
  368. public static bool RemoveEntries(ref ZipStorer _zip, List<ZipFileEntry> _zfes)
  369. {
  370. if (!(_zip.ZipFileStream is FileStream))
  371. throw new InvalidOperationException("RemoveEntries is allowed just over streams of type FileStream");
  372. //Get full list of entries
  373. List<ZipFileEntry> fullList = _zip.ReadCentralDir();
  374. //In order to delete we need to create a copy of the zip file excluding the selected items
  375. string tempZipName = Path.GetTempFileName();
  376. string tempEntryName = Path.GetTempFileName();
  377. try
  378. {
  379. ZipStorer tempZip = ZipStorer.Create(tempZipName, string.Empty);
  380. foreach (ZipFileEntry zfe in fullList)
  381. {
  382. if (!_zfes.Contains(zfe))
  383. {
  384. if (_zip.ExtractFile(zfe, tempEntryName))
  385. {
  386. tempZip.AddFile(zfe.Method, tempEntryName, zfe.FilenameInZip, zfe.Comment);
  387. }
  388. }
  389. }
  390. _zip.Close();
  391. tempZip.Close();
  392. File.Delete(_zip.FileName);
  393. File.Move(tempZipName, _zip.FileName);
  394. _zip = ZipStorer.Open(_zip.FileName, _zip.Access);
  395. }
  396. catch
  397. {
  398. return false;
  399. }
  400. finally
  401. {
  402. if (File.Exists(tempZipName))
  403. File.Delete(tempZipName);
  404. if (File.Exists(tempEntryName))
  405. File.Delete(tempEntryName);
  406. }
  407. return true;
  408. }
  409. #endregion
  410. #region Private methods
  411. // Calculate the file offset by reading the corresponding local header
  412. private uint GetFileOffset(uint _headerOffset)
  413. {
  414. byte[] buffer = new byte[2];
  415. this.ZipFileStream.Seek(_headerOffset + 26, SeekOrigin.Begin);
  416. this.ZipFileStream.Read(buffer, 0, 2);
  417. ushort filenameSize = BitConverter.ToUInt16(buffer, 0);
  418. this.ZipFileStream.Read(buffer, 0, 2);
  419. ushort extraSize = BitConverter.ToUInt16(buffer, 0);
  420. return (uint)(30 + filenameSize + extraSize + _headerOffset);
  421. }
  422. /* Local file header:
  423. local file header signature 4 bytes (0x04034b50)
  424. version needed to extract 2 bytes
  425. general purpose bit flag 2 bytes
  426. compression method 2 bytes
  427. last mod file time 2 bytes
  428. last mod file date 2 bytes
  429. crc-32 4 bytes
  430. compressed size 4 bytes
  431. uncompressed size 4 bytes
  432. filename length 2 bytes
  433. extra field length 2 bytes
  434. filename (variable size)
  435. extra field (variable size)
  436. */
  437. private void WriteLocalHeader(ref ZipFileEntry _zfe)
  438. {
  439. long pos = this.ZipFileStream.Position;
  440. Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
  441. byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip);
  442. this.ZipFileStream.Write(new byte[] { 80, 75, 3, 4, 20, 0 }, 0, 6); // No extra header
  443. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding
  444. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method
  445. this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time
  446. this.ZipFileStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 12); // unused CRC, un/compressed size, updated later
  447. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // filename length
  448. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length
  449. this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length);
  450. _zfe.HeaderSize = (uint)(this.ZipFileStream.Position - pos);
  451. }
  452. /* Central directory's File header:
  453. central file header signature 4 bytes (0x02014b50)
  454. version made by 2 bytes
  455. version needed to extract 2 bytes
  456. general purpose bit flag 2 bytes
  457. compression method 2 bytes
  458. last mod file time 2 bytes
  459. last mod file date 2 bytes
  460. crc-32 4 bytes
  461. compressed size 4 bytes
  462. uncompressed size 4 bytes
  463. filename length 2 bytes
  464. extra field length 2 bytes
  465. file comment length 2 bytes
  466. disk number start 2 bytes
  467. internal file attributes 2 bytes
  468. external file attributes 4 bytes
  469. relative offset of local header 4 bytes
  470. filename (variable size)
  471. extra field (variable size)
  472. file comment (variable size)
  473. */
  474. private void WriteCentralDirRecord(ZipFileEntry _zfe)
  475. {
  476. Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
  477. byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip);
  478. byte[] encodedComment = encoder.GetBytes(_zfe.Comment);
  479. this.ZipFileStream.Write(new byte[] { 80, 75, 1, 2, 23, 0xB, 20, 0 }, 0, 8);
  480. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding
  481. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method
  482. this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time
  483. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // file CRC
  484. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // compressed file size
  485. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // uncompressed file size
  486. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // Filename in zip
  487. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length
  488. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2);
  489. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // disk=0
  490. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // file type: binary
  491. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // Internal file attributes
  492. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0x8100), 0, 2); // External file attributes (normal/readable)
  493. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.HeaderOffset), 0, 4); // Offset of header
  494. this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length);
  495. this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length);
  496. }
  497. /* End of central dir record:
  498. end of central dir signature 4 bytes (0x06054b50)
  499. number of this disk 2 bytes
  500. number of the disk with the
  501. start of the central directory 2 bytes
  502. total number of entries in
  503. the central dir on this disk 2 bytes
  504. total number of entries in
  505. the central dir 2 bytes
  506. size of the central directory 4 bytes
  507. offset of start of central
  508. directory with respect to
  509. the starting disk number 4 bytes
  510. zipfile comment length 2 bytes
  511. zipfile comment (variable size)
  512. */
  513. private void WriteEndRecord(uint _size, uint _offset)
  514. {
  515. Encoding encoder = this.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
  516. byte[] encodedComment = encoder.GetBytes(this.Comment);
  517. this.ZipFileStream.Write(new byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }, 0, 8);
  518. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count + ExistingFiles), 0, 2);
  519. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count + ExistingFiles), 0, 2);
  520. this.ZipFileStream.Write(BitConverter.GetBytes(_size), 0, 4);
  521. this.ZipFileStream.Write(BitConverter.GetBytes(_offset), 0, 4);
  522. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2);
  523. this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length);
  524. }
  525. // Copies all source file into storage file
  526. private void Store(ref ZipFileEntry _zfe, Stream _source)
  527. {
  528. byte[] buffer = new byte[16384];
  529. int bytesRead;
  530. uint totalRead = 0;
  531. Stream outStream;
  532. long posStart = this.ZipFileStream.Position;
  533. long sourceStart = _source.Position;
  534. if (_zfe.Method == Compression.Store)
  535. outStream = this.ZipFileStream;
  536. else
  537. outStream = new DeflateStream(this.ZipFileStream, CompressionMode.Compress, true);
  538. _zfe.Crc32 = 0 ^ 0xffffffff;
  539. do
  540. {
  541. bytesRead = _source.Read(buffer, 0, buffer.Length);
  542. totalRead += (uint)bytesRead;
  543. if (bytesRead > 0)
  544. {
  545. outStream.Write(buffer, 0, bytesRead);
  546. for (uint i = 0; i < bytesRead; i++)
  547. {
  548. _zfe.Crc32 = ZipStorer.CrcTable[(_zfe.Crc32 ^ buffer[i]) & 0xFF] ^ (_zfe.Crc32 >> 8);
  549. }
  550. }
  551. } while (bytesRead == buffer.Length);
  552. outStream.Flush();
  553. if (_zfe.Method == Compression.Deflate)
  554. outStream.Dispose();
  555. _zfe.Crc32 ^= 0xffffffff;
  556. _zfe.FileSize = totalRead;
  557. _zfe.CompressedSize = (uint)(this.ZipFileStream.Position - posStart);
  558. // Verify for real compression
  559. if (_zfe.Method == Compression.Deflate && !this.ForceDeflating && _source.CanSeek && _zfe.CompressedSize > _zfe.FileSize)
  560. {
  561. // Start operation again with Store algorithm
  562. _zfe.Method = Compression.Store;
  563. this.ZipFileStream.Position = posStart;
  564. this.ZipFileStream.SetLength(posStart);
  565. _source.Position = sourceStart;
  566. this.Store(ref _zfe, _source);
  567. }
  568. }
  569. /* DOS Date and time:
  570. MS-DOS date. The date is a packed value with the following format. Bits Description
  571. 0-4 Day of the month (1–31)
  572. 5-8 Month (1 = January, 2 = February, and so on)
  573. 9-15 Year offset from 1980 (add 1980 to get actual year)
  574. MS-DOS time. The time is a packed value with the following format. Bits Description
  575. 0-4 Second divided by 2
  576. 5-10 Minute (0–59)
  577. 11-15 Hour (0–23 on a 24-hour clock)
  578. */
  579. private uint DateTimeToDosTime(DateTime _dt)
  580. {
  581. return (uint)(
  582. (_dt.Second / 2) | (_dt.Minute << 5) | (_dt.Hour << 11) |
  583. (_dt.Day << 16) | (_dt.Month << 21) | ((_dt.Year - 1980) << 25));
  584. }
  585. private DateTime DosTimeToDateTime(uint _dt)
  586. {
  587. return new DateTime(
  588. (int)(_dt >> 25) + 1980,
  589. (int)(_dt >> 21) & 15,
  590. (int)(_dt >> 16) & 31,
  591. (int)(_dt >> 11) & 31,
  592. (int)(_dt >> 5) & 63,
  593. (int)(_dt & 31) * 2);
  594. }
  595. /* CRC32 algorithm
  596. The 'magic number' for the CRC is 0xdebb20e3.
  597. The proper CRC pre and post conditioning
  598. is used, meaning that the CRC register is
  599. pre-conditioned with all ones (a starting value
  600. of 0xffffffff) and the value is post-conditioned by
  601. taking the one's complement of the CRC residual.
  602. If bit 3 of the general purpose flag is set, this
  603. field is set to zero in the local header and the correct
  604. value is put in the data descriptor and in the central
  605. directory.
  606. */
  607. private void UpdateCrcAndSizes(ref ZipFileEntry _zfe)
  608. {
  609. long lastPos = this.ZipFileStream.Position; // remember position
  610. this.ZipFileStream.Position = _zfe.HeaderOffset + 8;
  611. this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method
  612. this.ZipFileStream.Position = _zfe.HeaderOffset + 14;
  613. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // Update CRC
  614. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // Compressed size
  615. this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // Uncompressed size
  616. this.ZipFileStream.Position = lastPos; // restore position
  617. }
  618. // Replaces backslashes with slashes to store in zip header
  619. private string NormalizedFilename(string _filename)
  620. {
  621. string filename = _filename.Replace('\\', '/');
  622. int pos = filename.IndexOf(':');
  623. if (pos >= 0)
  624. filename = filename.Remove(0, pos + 1);
  625. return filename.Trim('/');
  626. }
  627. // Reads the end-of-central-directory record
  628. private bool ReadFileInfo()
  629. {
  630. if (this.ZipFileStream.Length < 22)
  631. return false;
  632. try
  633. {
  634. this.ZipFileStream.Seek(-17, SeekOrigin.End);
  635. BinaryReader br = new BinaryReader(this.ZipFileStream);
  636. do
  637. {
  638. this.ZipFileStream.Seek(-5, SeekOrigin.Current);
  639. UInt32 sig = br.ReadUInt32();
  640. if (sig == 0x06054b50)
  641. {
  642. this.ZipFileStream.Seek(6, SeekOrigin.Current);
  643. UInt16 entries = br.ReadUInt16();
  644. Int32 centralSize = br.ReadInt32();
  645. UInt32 centralDirOffset = br.ReadUInt32();
  646. UInt16 commentSize = br.ReadUInt16();
  647. // check if comment field is the very last data in file
  648. if (this.ZipFileStream.Position + commentSize != this.ZipFileStream.Length)
  649. return false;
  650. // Copy entire central directory to a memory buffer
  651. this.ExistingFiles = entries;
  652. this.CentralDirImage = new byte[centralSize];
  653. this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
  654. this.ZipFileStream.Read(this.CentralDirImage, 0, centralSize);
  655. // Leave the pointer at the begining of central dir, to append new files
  656. this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
  657. return true;
  658. }
  659. } while (this.ZipFileStream.Position > 0);
  660. }
  661. catch { }
  662. return false;
  663. }
  664. #endregion
  665. #region IDisposable Members
  666. /// <summary>
  667. /// Closes the Zip file stream
  668. /// </summary>
  669. public void Dispose()
  670. {
  671. this.Close();
  672. }
  673. #endregion
  674. }
  675. }