ZipHelperStream.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. using System;
  2. using System.IO;
  3. namespace ICSharpCode.SharpZipLib.Zip
  4. {
  5. /// <summary>
  6. /// Holds data pertinent to a data descriptor.
  7. /// </summary>
  8. public class DescriptorData
  9. {
  10. /// <summary>
  11. /// Get /set the compressed size of data.
  12. /// </summary>
  13. public long CompressedSize
  14. {
  15. get { return compressedSize; }
  16. set { compressedSize = value; }
  17. }
  18. /// <summary>
  19. /// Get / set the uncompressed size of data
  20. /// </summary>
  21. public long Size
  22. {
  23. get { return size; }
  24. set { size = value; }
  25. }
  26. /// <summary>
  27. /// Get /set the crc value.
  28. /// </summary>
  29. public long Crc
  30. {
  31. get { return crc; }
  32. set { crc = (value & 0xffffffff); }
  33. }
  34. #region Instance Fields
  35. private long size;
  36. private long compressedSize;
  37. private long crc;
  38. #endregion Instance Fields
  39. }
  40. internal class EntryPatchData
  41. {
  42. public long SizePatchOffset
  43. {
  44. get { return sizePatchOffset_; }
  45. set { sizePatchOffset_ = value; }
  46. }
  47. public long CrcPatchOffset
  48. {
  49. get { return crcPatchOffset_; }
  50. set { crcPatchOffset_ = value; }
  51. }
  52. #region Instance Fields
  53. private long sizePatchOffset_;
  54. private long crcPatchOffset_;
  55. #endregion Instance Fields
  56. }
  57. /// <summary>
  58. /// This class assists with writing/reading from Zip files.
  59. /// </summary>
  60. internal class ZipHelperStream : Stream
  61. {
  62. #region Constructors
  63. /// <summary>
  64. /// Initialise an instance of this class.
  65. /// </summary>
  66. /// <param name="name">The name of the file to open.</param>
  67. public ZipHelperStream(string name)
  68. {
  69. stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite);
  70. isOwner_ = true;
  71. }
  72. /// <summary>
  73. /// Initialise a new instance of <see cref="ZipHelperStream"/>.
  74. /// </summary>
  75. /// <param name="stream">The stream to use.</param>
  76. public ZipHelperStream(Stream stream)
  77. {
  78. stream_ = stream;
  79. }
  80. #endregion Constructors
  81. /// <summary>
  82. /// Get / set a value indicating wether the the underlying stream is owned or not.
  83. /// </summary>
  84. /// <remarks>If the stream is owned it is closed when this instance is closed.</remarks>
  85. public bool IsStreamOwner
  86. {
  87. get { return isOwner_; }
  88. set { isOwner_ = value; }
  89. }
  90. #region Base Stream Methods
  91. public override bool CanRead
  92. {
  93. get { return stream_.CanRead; }
  94. }
  95. public override bool CanSeek
  96. {
  97. get { return stream_.CanSeek; }
  98. }
  99. public override bool CanTimeout
  100. {
  101. get { return stream_.CanTimeout; }
  102. }
  103. public override long Length
  104. {
  105. get { return stream_.Length; }
  106. }
  107. public override long Position
  108. {
  109. get { return stream_.Position; }
  110. set { stream_.Position = value; }
  111. }
  112. public override bool CanWrite
  113. {
  114. get { return stream_.CanWrite; }
  115. }
  116. public override void Flush()
  117. {
  118. stream_.Flush();
  119. }
  120. public override long Seek(long offset, SeekOrigin origin)
  121. {
  122. return stream_.Seek(offset, origin);
  123. }
  124. public override void SetLength(long value)
  125. {
  126. stream_.SetLength(value);
  127. }
  128. public override int Read(byte[] buffer, int offset, int count)
  129. {
  130. return stream_.Read(buffer, offset, count);
  131. }
  132. public override void Write(byte[] buffer, int offset, int count)
  133. {
  134. stream_.Write(buffer, offset, count);
  135. }
  136. /// <summary>
  137. /// Close the stream.
  138. /// </summary>
  139. /// <remarks>
  140. /// The underlying stream is closed only if <see cref="IsStreamOwner"/> is true.
  141. /// </remarks>
  142. protected override void Dispose(bool disposing)
  143. {
  144. Stream toClose = stream_;
  145. stream_ = null;
  146. if (isOwner_ && (toClose != null))
  147. {
  148. isOwner_ = false;
  149. toClose.Dispose();
  150. }
  151. }
  152. #endregion Base Stream Methods
  153. // Write the local file header
  154. // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage
  155. private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData)
  156. {
  157. CompressionMethod method = entry.CompressionMethod;
  158. bool headerInfoAvailable = true; // How to get this?
  159. bool patchEntryHeader = false;
  160. WriteLEInt(ZipConstants.LocalHeaderSignature);
  161. WriteLEShort(entry.Version);
  162. WriteLEShort(entry.Flags);
  163. WriteLEShort((byte)method);
  164. WriteLEInt((int)entry.DosTime);
  165. if (headerInfoAvailable == true)
  166. {
  167. WriteLEInt((int)entry.Crc);
  168. if (entry.LocalHeaderRequiresZip64)
  169. {
  170. WriteLEInt(-1);
  171. WriteLEInt(-1);
  172. }
  173. else
  174. {
  175. WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
  176. WriteLEInt((int)entry.Size);
  177. }
  178. }
  179. else
  180. {
  181. if (patchData != null)
  182. {
  183. patchData.CrcPatchOffset = stream_.Position;
  184. }
  185. WriteLEInt(0); // Crc
  186. if (patchData != null)
  187. {
  188. patchData.SizePatchOffset = stream_.Position;
  189. }
  190. // For local header both sizes appear in Zip64 Extended Information
  191. if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
  192. {
  193. WriteLEInt(-1);
  194. WriteLEInt(-1);
  195. }
  196. else
  197. {
  198. WriteLEInt(0); // Compressed size
  199. WriteLEInt(0); // Uncompressed size
  200. }
  201. }
  202. byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
  203. if (name.Length > 0xFFFF)
  204. {
  205. throw new ZipException("Entry name too long.");
  206. }
  207. var ed = new ZipExtraData(entry.ExtraData);
  208. if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader))
  209. {
  210. ed.StartNewEntry();
  211. if (headerInfoAvailable)
  212. {
  213. ed.AddLeLong(entry.Size);
  214. ed.AddLeLong(entry.CompressedSize);
  215. }
  216. else
  217. {
  218. ed.AddLeLong(-1);
  219. ed.AddLeLong(-1);
  220. }
  221. ed.AddNewEntry(1);
  222. if (!ed.Find(1))
  223. {
  224. throw new ZipException("Internal error cant find extra data");
  225. }
  226. if (patchData != null)
  227. {
  228. patchData.SizePatchOffset = ed.CurrentReadIndex;
  229. }
  230. }
  231. else
  232. {
  233. ed.Delete(1);
  234. }
  235. byte[] extra = ed.GetEntryData();
  236. WriteLEShort(name.Length);
  237. WriteLEShort(extra.Length);
  238. if (name.Length > 0)
  239. {
  240. stream_.Write(name, 0, name.Length);
  241. }
  242. if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
  243. {
  244. patchData.SizePatchOffset += stream_.Position;
  245. }
  246. if (extra.Length > 0)
  247. {
  248. stream_.Write(extra, 0, extra.Length);
  249. }
  250. }
  251. /// <summary>
  252. /// Locates a block with the desired <paramref name="signature"/>.
  253. /// </summary>
  254. /// <param name="signature">The signature to find.</param>
  255. /// <param name="endLocation">Location, marking the end of block.</param>
  256. /// <param name="minimumBlockSize">Minimum size of the block.</param>
  257. /// <param name="maximumVariableData">The maximum variable data.</param>
  258. /// <returns>Eeturns the offset of the first byte after the signature; -1 if not found</returns>
  259. public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
  260. {
  261. long pos = endLocation - minimumBlockSize;
  262. if (pos < 0)
  263. {
  264. return -1;
  265. }
  266. long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
  267. // TODO: This loop could be optimised for speed.
  268. do
  269. {
  270. if (pos < giveUpMarker)
  271. {
  272. return -1;
  273. }
  274. Seek(pos--, SeekOrigin.Begin);
  275. } while (ReadLEInt() != signature);
  276. return Position;
  277. }
  278. /// <summary>
  279. /// Write Zip64 end of central directory records (File header and locator).
  280. /// </summary>
  281. /// <param name="noOfEntries">The number of entries in the central directory.</param>
  282. /// <param name="sizeEntries">The size of entries in the central directory.</param>
  283. /// <param name="centralDirOffset">The offset of the dentral directory.</param>
  284. public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset)
  285. {
  286. long centralSignatureOffset = centralDirOffset + sizeEntries;
  287. WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature);
  288. WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12)
  289. WriteLEShort(ZipConstants.VersionMadeBy); // Version made by
  290. WriteLEShort(ZipConstants.VersionZip64); // Version to extract
  291. WriteLEInt(0); // Number of this disk
  292. WriteLEInt(0); // number of the disk with the start of the central directory
  293. WriteLELong(noOfEntries); // No of entries on this disk
  294. WriteLELong(noOfEntries); // Total No of entries in central directory
  295. WriteLELong(sizeEntries); // Size of the central directory
  296. WriteLELong(centralDirOffset); // offset of start of central directory
  297. // zip64 extensible data sector not catered for here (variable size)
  298. // Write the Zip64 end of central directory locator
  299. WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature);
  300. // no of the disk with the start of the zip64 end of central directory
  301. WriteLEInt(0);
  302. // relative offset of the zip64 end of central directory record
  303. WriteLELong(centralSignatureOffset);
  304. // total number of disks
  305. WriteLEInt(1);
  306. }
  307. /// <summary>
  308. /// Write the required records to end the central directory.
  309. /// </summary>
  310. /// <param name="noOfEntries">The number of entries in the directory.</param>
  311. /// <param name="sizeEntries">The size of the entries in the directory.</param>
  312. /// <param name="startOfCentralDirectory">The start of the central directory.</param>
  313. /// <param name="comment">The archive comment. (This can be null).</param>
  314. public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries,
  315. long startOfCentralDirectory, byte[] comment)
  316. {
  317. if ((noOfEntries >= 0xffff) ||
  318. (startOfCentralDirectory >= 0xffffffff) ||
  319. (sizeEntries >= 0xffffffff))
  320. {
  321. WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory);
  322. }
  323. WriteLEInt(ZipConstants.EndOfCentralDirectorySignature);
  324. // TODO: ZipFile Multi disk handling not done
  325. WriteLEShort(0); // number of this disk
  326. WriteLEShort(0); // no of disk with start of central dir
  327. // Number of entries
  328. if (noOfEntries >= 0xffff)
  329. {
  330. WriteLEUshort(0xffff); // Zip64 marker
  331. WriteLEUshort(0xffff);
  332. }
  333. else
  334. {
  335. WriteLEShort((short)noOfEntries); // entries in central dir for this disk
  336. WriteLEShort((short)noOfEntries); // total entries in central directory
  337. }
  338. // Size of the central directory
  339. if (sizeEntries >= 0xffffffff)
  340. {
  341. WriteLEUint(0xffffffff); // Zip64 marker
  342. }
  343. else
  344. {
  345. WriteLEInt((int)sizeEntries);
  346. }
  347. // offset of start of central directory
  348. if (startOfCentralDirectory >= 0xffffffff)
  349. {
  350. WriteLEUint(0xffffffff); // Zip64 marker
  351. }
  352. else
  353. {
  354. WriteLEInt((int)startOfCentralDirectory);
  355. }
  356. int commentLength = (comment != null) ? comment.Length : 0;
  357. if (commentLength > 0xffff)
  358. {
  359. throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength));
  360. }
  361. WriteLEShort(commentLength);
  362. if (commentLength > 0)
  363. {
  364. Write(comment, 0, comment.Length);
  365. }
  366. }
  367. #region LE value reading/writing
  368. /// <summary>
  369. /// Read an unsigned short in little endian byte order.
  370. /// </summary>
  371. /// <returns>Returns the value read.</returns>
  372. /// <exception cref="IOException">
  373. /// An i/o error occurs.
  374. /// </exception>
  375. /// <exception cref="EndOfStreamException">
  376. /// The file ends prematurely
  377. /// </exception>
  378. public int ReadLEShort()
  379. {
  380. int byteValue1 = stream_.ReadByte();
  381. if (byteValue1 < 0)
  382. {
  383. throw new EndOfStreamException();
  384. }
  385. int byteValue2 = stream_.ReadByte();
  386. if (byteValue2 < 0)
  387. {
  388. throw new EndOfStreamException();
  389. }
  390. return byteValue1 | (byteValue2 << 8);
  391. }
  392. /// <summary>
  393. /// Read an int in little endian byte order.
  394. /// </summary>
  395. /// <returns>Returns the value read.</returns>
  396. /// <exception cref="IOException">
  397. /// An i/o error occurs.
  398. /// </exception>
  399. /// <exception cref="System.IO.EndOfStreamException">
  400. /// The file ends prematurely
  401. /// </exception>
  402. public int ReadLEInt()
  403. {
  404. return ReadLEShort() | (ReadLEShort() << 16);
  405. }
  406. /// <summary>
  407. /// Read a long in little endian byte order.
  408. /// </summary>
  409. /// <returns>The value read.</returns>
  410. public long ReadLELong()
  411. {
  412. return (uint)ReadLEInt() | ((long)ReadLEInt() << 32);
  413. }
  414. /// <summary>
  415. /// Write an unsigned short in little endian byte order.
  416. /// </summary>
  417. /// <param name="value">The value to write.</param>
  418. public void WriteLEShort(int value)
  419. {
  420. stream_.WriteByte((byte)(value & 0xff));
  421. stream_.WriteByte((byte)((value >> 8) & 0xff));
  422. }
  423. /// <summary>
  424. /// Write a ushort in little endian byte order.
  425. /// </summary>
  426. /// <param name="value">The value to write.</param>
  427. public void WriteLEUshort(ushort value)
  428. {
  429. stream_.WriteByte((byte)(value & 0xff));
  430. stream_.WriteByte((byte)(value >> 8));
  431. }
  432. /// <summary>
  433. /// Write an int in little endian byte order.
  434. /// </summary>
  435. /// <param name="value">The value to write.</param>
  436. public void WriteLEInt(int value)
  437. {
  438. WriteLEShort(value);
  439. WriteLEShort(value >> 16);
  440. }
  441. /// <summary>
  442. /// Write a uint in little endian byte order.
  443. /// </summary>
  444. /// <param name="value">The value to write.</param>
  445. public void WriteLEUint(uint value)
  446. {
  447. WriteLEUshort((ushort)(value & 0xffff));
  448. WriteLEUshort((ushort)(value >> 16));
  449. }
  450. /// <summary>
  451. /// Write a long in little endian byte order.
  452. /// </summary>
  453. /// <param name="value">The value to write.</param>
  454. public void WriteLELong(long value)
  455. {
  456. WriteLEInt((int)value);
  457. WriteLEInt((int)(value >> 32));
  458. }
  459. /// <summary>
  460. /// Write a ulong in little endian byte order.
  461. /// </summary>
  462. /// <param name="value">The value to write.</param>
  463. public void WriteLEUlong(ulong value)
  464. {
  465. WriteLEUint((uint)(value & 0xffffffff));
  466. WriteLEUint((uint)(value >> 32));
  467. }
  468. #endregion LE value reading/writing
  469. /// <summary>
  470. /// Write a data descriptor.
  471. /// </summary>
  472. /// <param name="entry">The entry to write a descriptor for.</param>
  473. /// <returns>Returns the number of descriptor bytes written.</returns>
  474. public int WriteDataDescriptor(ZipEntry entry)
  475. {
  476. if (entry == null)
  477. {
  478. throw new ArgumentNullException(nameof(entry));
  479. }
  480. int result = 0;
  481. // Add data descriptor if flagged as required
  482. if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
  483. {
  484. // The signature is not PKZIP originally but is now described as optional
  485. // in the PKZIP Appnote documenting trhe format.
  486. WriteLEInt(ZipConstants.DataDescriptorSignature);
  487. WriteLEInt(unchecked((int)(entry.Crc)));
  488. result += 8;
  489. if (entry.LocalHeaderRequiresZip64)
  490. {
  491. WriteLELong(entry.CompressedSize);
  492. WriteLELong(entry.Size);
  493. result += 16;
  494. }
  495. else
  496. {
  497. WriteLEInt((int)entry.CompressedSize);
  498. WriteLEInt((int)entry.Size);
  499. result += 8;
  500. }
  501. }
  502. return result;
  503. }
  504. /// <summary>
  505. /// Read data descriptor at the end of compressed data.
  506. /// </summary>
  507. /// <param name="zip64">if set to <c>true</c> [zip64].</param>
  508. /// <param name="data">The data to fill in.</param>
  509. /// <returns>Returns the number of bytes read in the descriptor.</returns>
  510. public void ReadDataDescriptor(bool zip64, DescriptorData data)
  511. {
  512. int intValue = ReadLEInt();
  513. // In theory this may not be a descriptor according to PKZIP appnote.
  514. // In practise its always there.
  515. if (intValue != ZipConstants.DataDescriptorSignature)
  516. {
  517. throw new ZipException("Data descriptor signature not found");
  518. }
  519. data.Crc = ReadLEInt();
  520. if (zip64)
  521. {
  522. data.CompressedSize = ReadLELong();
  523. data.Size = ReadLELong();
  524. }
  525. else
  526. {
  527. data.CompressedSize = ReadLEInt();
  528. data.Size = ReadLEInt();
  529. }
  530. }
  531. #region Instance Fields
  532. private bool isOwner_;
  533. private Stream stream_;
  534. #endregion Instance Fields
  535. }
  536. }