ZipHelperStream.cs 15 KB

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