ZipHelperStream.cs 17 KB

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