TarInputStream.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. // TarInputStream.cs
  2. // Copyright (C) 2001 Mike Krueger
  3. //
  4. // This program is free software; you can redistribute it and/or
  5. // modify it under the terms of the GNU General Public License
  6. // as published by the Free Software Foundation; either version 2
  7. // of the License, or (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program; if not, write to the Free Software
  16. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  17. //
  18. // Linking this library statically or dynamically with other modules is
  19. // making a combined work based on this library. Thus, the terms and
  20. // conditions of the GNU General Public License cover the whole
  21. // combination.
  22. //
  23. // As a special exception, the copyright holders of this library give you
  24. // permission to link this library with independent modules to produce an
  25. // executable, regardless of the license terms of these independent
  26. // modules, and to copy and distribute the resulting executable under
  27. // terms of your choice, provided that you also meet, for each linked
  28. // independent module, the terms and conditions of the license of that
  29. // module. An independent module is a module which is not derived from
  30. // or based on this library. If you modify this library, you may extend
  31. // this exception to your version of the library, but you are not
  32. // obligated to do so. If you do not wish to do so, delete this
  33. // exception statement from your version.
  34. using System;
  35. using System.IO;
  36. using System.Text;
  37. namespace ICSharpCode.SharpZipLib.Tar
  38. {
  39. /// <summary>
  40. /// The TarInputStream reads a UNIX tar archive as an InputStream.
  41. /// methods are provided to position at each successive entry in
  42. /// the archive, and the read each entry as a normal input stream
  43. /// using read().
  44. /// </summary>
  45. public class TarInputStream : Stream
  46. {
  47. protected bool debug;
  48. protected bool hasHitEOF;
  49. protected int entrySize;
  50. protected int entryOffset;
  51. protected byte[] readBuf;
  52. protected TarBuffer buffer;
  53. protected TarEntry currEntry;
  54. protected IEntryFactory eFactory;
  55. Stream inputStream;
  56. /// <summary>
  57. /// I needed to implement the abstract member.
  58. /// </summary>
  59. public override bool CanRead
  60. {
  61. get
  62. {
  63. return inputStream.CanRead;
  64. }
  65. }
  66. /// <summary>
  67. /// I needed to implement the abstract member.
  68. /// </summary>
  69. public override bool CanSeek
  70. {
  71. // TODO is this valid? should it return false?
  72. get
  73. {
  74. return inputStream.CanSeek;
  75. }
  76. }
  77. /// <summary>
  78. /// I needed to implement the abstract member.
  79. /// </summary>
  80. public override bool CanWrite
  81. {
  82. get
  83. {
  84. return inputStream.CanWrite;
  85. }
  86. }
  87. /// <summary>
  88. /// I needed to implement the abstract member.
  89. /// </summary>
  90. public override long Length
  91. {
  92. get
  93. {
  94. return inputStream.Length;
  95. }
  96. }
  97. /// <summary>
  98. /// I needed to implement the abstract member.
  99. /// </summary>
  100. public override long Position
  101. {
  102. get
  103. {
  104. return inputStream.Position;
  105. }
  106. set
  107. {
  108. inputStream.Position = value;
  109. }
  110. }
  111. /// <summary>
  112. /// Flushes the baseInputStream
  113. /// </summary>
  114. public override void Flush()
  115. {
  116. inputStream.Flush();
  117. }
  118. /// <summary>
  119. /// I needed to implement the abstract member.
  120. /// </summary>
  121. public override long Seek(long offset, SeekOrigin origin)
  122. {
  123. // TODO allow this?
  124. return inputStream.Seek(offset, origin);
  125. }
  126. /// <summary>
  127. /// I needed to implement the abstract member.
  128. /// </summary>
  129. public override void SetLength(long val)
  130. {
  131. inputStream.SetLength(val);
  132. }
  133. /// <summary>
  134. /// I needed to implement the abstract member.
  135. /// </summary>
  136. public override void Write(byte[] array, int offset, int count)
  137. {
  138. inputStream.Write(array, offset, count);
  139. }
  140. /// <summary>
  141. /// I needed to implement the abstract member.
  142. /// </summary>
  143. public override void WriteByte(byte val)
  144. {
  145. inputStream.WriteByte(val);
  146. }
  147. public TarInputStream(Stream inputStream) : this(inputStream, TarBuffer.DefaultBlockFactor)
  148. {
  149. }
  150. public TarInputStream(Stream inputStream, int blockFactor)
  151. {
  152. this.inputStream = inputStream;
  153. this.buffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
  154. this.readBuf = null;
  155. this.debug = false;
  156. this.hasHitEOF = false;
  157. this.eFactory = null;
  158. }
  159. public void SetDebug(bool debugFlag)
  160. {
  161. this.debug = debugFlag;
  162. SetBufferDebug(debugFlag);
  163. }
  164. public void SetBufferDebug(bool debug)
  165. {
  166. this.buffer.SetDebug(debug);
  167. }
  168. public void SetEntryFactory(IEntryFactory factory)
  169. {
  170. this.eFactory = factory;
  171. }
  172. /// <summary>
  173. /// Closes this stream. Calls the TarBuffer's close() method.
  174. /// The underlying stream is closed by the TarBuffer.
  175. /// </summary>
  176. public override void Close()
  177. {
  178. this.buffer.Close();
  179. }
  180. /// <summary>
  181. /// Get the record size being used by this stream's TarBuffer.
  182. /// </summary>
  183. /// <returns>
  184. /// TarBuffer record size.
  185. /// </returns>
  186. public int GetRecordSize()
  187. {
  188. return this.buffer.GetRecordSize();
  189. }
  190. /// <summary>
  191. /// Get the available data that can be read from the current
  192. /// entry in the archive. This does not indicate how much data
  193. /// is left in the entire archive, only in the current entry.
  194. /// This value is determined from the entry's size header field
  195. /// and the amount of data already read from the current entry.
  196. /// </summary>
  197. /// <returns>
  198. /// The number of available bytes for the current entry.
  199. /// </returns>
  200. public int Available
  201. {
  202. get
  203. {
  204. return this.entrySize - this.entryOffset;
  205. }
  206. }
  207. /// <summary>
  208. /// Skip bytes in the input buffer. This skips bytes in the
  209. /// current entry's data, not the entire archive, and will
  210. /// stop at the end of the current entry's data if the number
  211. /// to skip extends beyond that point.
  212. /// </summary>
  213. /// <param name="numToSkip">
  214. /// The number of bytes to skip.
  215. /// </param>
  216. public void Skip(int numToSkip)
  217. {
  218. // TODO REVIEW
  219. // This is horribly inefficient, but it ensures that we
  220. // properly skip over bytes via the TarBuffer...
  221. //
  222. byte[] skipBuf = new byte[8 * 1024];
  223. for (int num = numToSkip; num > 0;)
  224. {
  225. int numRead = this.Read(skipBuf, 0, (num > skipBuf.Length ? skipBuf.Length : num));
  226. if (numRead == -1)
  227. {
  228. break;
  229. }
  230. num -= numRead;
  231. }
  232. }
  233. /// <summary>
  234. /// Since we do not support marking just yet, we return false.
  235. /// </summary>
  236. public bool IsMarkSupported
  237. {
  238. get
  239. {
  240. return false;
  241. }
  242. }
  243. /// <summary>
  244. /// Since we do not support marking just yet, we do nothing.
  245. /// </summary>
  246. /// <param name ="markLimit">
  247. /// The limit to mark.
  248. /// </param>
  249. public void Mark(int markLimit)
  250. {
  251. }
  252. /// <summary>
  253. /// Since we do not support marking just yet, we do nothing.
  254. /// </summary>
  255. public void Reset()
  256. {
  257. }
  258. void SkipToNextEntry()
  259. {
  260. int numToSkip = this.entrySize - this.entryOffset;
  261. if (this.debug)
  262. {
  263. //Console.WriteLine.WriteLine("TarInputStream: SKIP currENTRY '" + this.currEntry.Name + "' SZ " + this.entrySize + " OFF " + this.entryOffset + " skipping " + numToSkip + " bytes");
  264. }
  265. if (numToSkip > 0)
  266. {
  267. this.Skip(numToSkip);
  268. }
  269. this.readBuf = null;
  270. }
  271. /// <summary>
  272. /// Get the next entry in this tar archive. This will skip
  273. /// over any remaining data in the current entry, if there
  274. /// is one, and place the input stream at the header of the
  275. /// next entry, and read the header and instantiate a new
  276. /// TarEntry from the header bytes and return that entry.
  277. /// If there are no more entries in the archive, null will
  278. /// be returned to indicate that the end of the archive has
  279. /// been reached.
  280. /// </summary>
  281. /// <returns>
  282. /// The next TarEntry in the archive, or null.
  283. /// </returns>
  284. public TarEntry GetNextEntry()
  285. {
  286. if (this.hasHitEOF)
  287. {
  288. return null;
  289. }
  290. if (this.currEntry != null)
  291. {
  292. SkipToNextEntry();
  293. }
  294. byte[] headerBuf = this.buffer.ReadBlock();
  295. if (headerBuf == null)
  296. {
  297. if (this.debug)
  298. {
  299. //Console.WriteLine.WriteLine("READ NULL BLOCK");
  300. }
  301. this.hasHitEOF = true;
  302. }
  303. else if (this.buffer.IsEOFBlock(headerBuf))
  304. {
  305. if (this.debug)
  306. {
  307. //Console.WriteLine.WriteLine( "READ EOF BLOCK" );
  308. }
  309. this.hasHitEOF = true;
  310. }
  311. if (this.hasHitEOF)
  312. {
  313. this.currEntry = null;
  314. }
  315. else
  316. {
  317. try
  318. {
  319. TarHeader header = new TarHeader();
  320. header.ParseBuffer(headerBuf);
  321. this.entryOffset = 0;
  322. this.entrySize = (int)header.size;
  323. StringBuilder longName = null;
  324. if (header.typeFlag == TarHeader.LF_GNU_LONGNAME)
  325. {
  326. Console.WriteLine("TarInputStream: Long name found '" + header.name + "' size = " + header.size); // DEBUG
  327. byte[] nameBuffer = new byte[TarBuffer.BlockSize];
  328. int numToRead = this.entrySize;
  329. longName = new StringBuilder();
  330. while (numToRead > 0)
  331. {
  332. int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : numToRead));
  333. if (numRead == -1)
  334. {
  335. throw new InvalidHeaderException("Failed to read long name entry");
  336. }
  337. longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString());
  338. numToRead -= numRead;
  339. }
  340. Console.WriteLine("TarInputStream: Long name is '" + longName.ToString()); // DEBUG
  341. SkipToNextEntry();
  342. headerBuf = this.buffer.ReadBlock();
  343. }
  344. else if (header.typeFlag == TarHeader.LF_GHDR) // POSIX global extended header
  345. {
  346. // Ignore things we dont understand completely for now
  347. SkipToNextEntry();
  348. headerBuf = this.buffer.ReadBlock();
  349. }
  350. else if (header.typeFlag == TarHeader.LF_XHDR) // POSIX extended header
  351. {
  352. // Ignore things we dont understand completely for now
  353. SkipToNextEntry();
  354. headerBuf = this.buffer.ReadBlock();
  355. }
  356. else if (header.typeFlag == TarHeader.LF_GNU_VOLHDR)
  357. {
  358. // TODO could show volume name when verbose?
  359. SkipToNextEntry();
  360. headerBuf = this.buffer.ReadBlock();
  361. }
  362. else if (header.typeFlag != TarHeader.LF_NORMAL
  363. && header.typeFlag != TarHeader.LF_OLDNORM)
  364. {
  365. // Ignore things we dont understand completely for now
  366. SkipToNextEntry();
  367. headerBuf = this.buffer.ReadBlock();
  368. }
  369. if (this.eFactory == null)
  370. {
  371. this.currEntry = new TarEntry(headerBuf);
  372. if (longName != null)
  373. {
  374. this.currEntry.TarHeader.name.Length = 0;
  375. this.currEntry.TarHeader.name.Append(longName.ToString());
  376. }
  377. }
  378. else
  379. {
  380. this.currEntry = this.eFactory.CreateEntry(headerBuf);
  381. }
  382. // TODO -jr- ustar is not the only magic possible by any means
  383. // tar, xtar, ...
  384. if (!(headerBuf[257] == 'u' && headerBuf[258] == 's' && headerBuf[259] == 't' && headerBuf[260] == 'a' && headerBuf[261] == 'r'))
  385. {
  386. throw new InvalidHeaderException("header magic is not 'ustar', but '" + headerBuf[257] + headerBuf[258] + headerBuf[259] + headerBuf[260] + headerBuf[261] +
  387. "', or (dec) " + ((int)headerBuf[257]) + ", " + ((int)headerBuf[258]) + ", " + ((int)headerBuf[259]) + ", " + ((int)headerBuf[260]) + ", " + ((int)headerBuf[261]));
  388. }
  389. if (this.debug)
  390. {
  391. //Console.WriteLine.WriteLine("TarInputStream: SET CURRENTRY '" + this.currEntry.Name + "' size = " + this.currEntry.Size);
  392. }
  393. this.entryOffset = 0;
  394. // TODO REVIEW How do we resolve this discrepancy?!
  395. this.entrySize = (int) this.currEntry.Size;
  396. }
  397. catch (InvalidHeaderException ex)
  398. {
  399. this.entrySize = 0;
  400. this.entryOffset = 0;
  401. this.currEntry = null;
  402. throw new InvalidHeaderException("bad header in record " + this.buffer.GetCurrentBlockNum() + " block " + this.buffer.GetCurrentBlockNum() + ", " + ex.Message);
  403. }
  404. }
  405. return this.currEntry;
  406. }
  407. /// <summary>
  408. /// Reads a byte from the current tar archive entry.
  409. /// This method simply calls read(byte[], int, int).
  410. /// </summary>
  411. public override int ReadByte()
  412. {
  413. byte[] oneByteBuffer = new byte[1];
  414. int num = this.Read(oneByteBuffer, 0, 1);
  415. if (num <= 0) // return -1 to indicate that no byte was read.
  416. {
  417. return -1;
  418. }
  419. return (int)oneByteBuffer[0];
  420. }
  421. /// <summary>
  422. /// Reads bytes from the current tar archive entry.
  423. ///
  424. /// This method is aware of the boundaries of the current
  425. /// entry in the archive and will deal with them appropriately
  426. /// </summary>
  427. /// <param name="outputBuffer">
  428. /// The buffer into which to place bytes read.
  429. /// </param>
  430. /// <param name="offset">
  431. /// The offset at which to place bytes read.
  432. /// </param>
  433. /// <param name="numToRead">
  434. /// The number of bytes to read.
  435. /// </param>
  436. /// <returns>
  437. /// The number of bytes read, or 0 at end of stream/EOF.
  438. /// </returns>
  439. public override int Read(byte[] outputBuffer, int offset, int numToRead)
  440. {
  441. int totalRead = 0;
  442. if (this.entryOffset >= this.entrySize)
  443. {
  444. return 0;
  445. }
  446. if ((numToRead + this.entryOffset) > this.entrySize)
  447. {
  448. numToRead = this.entrySize - this.entryOffset;
  449. }
  450. if (this.readBuf != null)
  451. {
  452. int sz = (numToRead > this.readBuf.Length) ? this.readBuf.Length : numToRead;
  453. Array.Copy(this.readBuf, 0, outputBuffer, offset, sz);
  454. if (sz >= this.readBuf.Length)
  455. {
  456. this.readBuf = null;
  457. }
  458. else
  459. {
  460. int newLen = this.readBuf.Length - sz;
  461. byte[] newBuf = new byte[newLen];
  462. Array.Copy(this.readBuf, sz, newBuf, 0, newLen);
  463. this.readBuf = newBuf;
  464. }
  465. totalRead += sz;
  466. numToRead -= sz;
  467. offset += sz;
  468. }
  469. while (numToRead > 0)
  470. {
  471. byte[] rec = this.buffer.ReadBlock();
  472. if (rec == null)
  473. {
  474. // Unexpected EOF!
  475. throw new IOException("unexpected EOF with " + numToRead + " bytes unread");
  476. }
  477. int sz = numToRead;
  478. int recLen = rec.Length;
  479. if (recLen > sz)
  480. {
  481. Array.Copy(rec, 0, outputBuffer, offset, sz);
  482. this.readBuf = new byte[recLen - sz];
  483. Array.Copy(rec, sz, this.readBuf, 0, recLen - sz);
  484. }
  485. else
  486. {
  487. sz = recLen;
  488. Array.Copy(rec, 0, outputBuffer, offset, recLen);
  489. }
  490. totalRead += sz;
  491. numToRead -= sz;
  492. offset += sz;
  493. }
  494. this.entryOffset += totalRead;
  495. return totalRead;
  496. }
  497. /// <summary>
  498. /// Copies the contents of the current tar archive entry directly into
  499. /// an output stream.
  500. /// </summary>
  501. /// <param name="outputStream">
  502. /// The OutputStream into which to write the entry's data.
  503. /// </param>
  504. public void CopyEntryContents(Stream outputStream)
  505. {
  506. byte[] buf = new byte[32 * 1024];
  507. while (true)
  508. {
  509. int numRead = this.Read(buf, 0, buf.Length);
  510. if (numRead <= 0)
  511. {
  512. break;
  513. }
  514. outputStream.Write(buf, 0, numRead);
  515. }
  516. }
  517. /// <summary>
  518. /// This interface is provided, with the method setEntryFactory(), to allow
  519. /// the programmer to have their own TarEntry subclass instantiated for the
  520. /// entries return from getNextEntry().
  521. /// </summary>
  522. public interface IEntryFactory
  523. {
  524. TarEntry CreateEntry(string name);
  525. TarEntry CreateEntryFromFile(string fileName);
  526. TarEntry CreateEntry(byte[] headerBuf);
  527. }
  528. public class EntryFactoryAdapter : IEntryFactory
  529. {
  530. public TarEntry CreateEntry(string name)
  531. {
  532. return TarEntry.CreateTarEntry(name);
  533. }
  534. public TarEntry CreateEntryFromFile(string fileName)
  535. {
  536. return TarEntry.CreateEntryFromFile(fileName);
  537. }
  538. public TarEntry CreateEntry(byte[] headerBuf)
  539. {
  540. return new TarEntry(headerBuf);
  541. }
  542. }
  543. }
  544. }
  545. /* The original Java file had this header:
  546. ** Authored by Timothy Gerard Endres
  547. ** <mailto:[email protected]> <http://www.trustice.com>
  548. **
  549. ** This work has been placed into the public domain.
  550. ** You may use this work in any way and for any purpose you wish.
  551. **
  552. ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
  553. ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
  554. ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
  555. ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
  556. ** REDISTRIBUTION OF THIS SOFTWARE.
  557. **
  558. */