TarInputStream.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. namespace ICSharpCode.SharpZipLib.Tar
  5. {
  6. /// <summary>
  7. /// The TarInputStream reads a UNIX tar archive as an InputStream.
  8. /// methods are provided to position at each successive entry in
  9. /// the archive, and the read each entry as a normal input stream
  10. /// using read().
  11. /// </summary>
  12. public class TarInputStream : Stream
  13. {
  14. #region Constructors
  15. /// <summary>
  16. /// Construct a TarInputStream with default block factor
  17. /// </summary>
  18. /// <param name="inputStream">stream to source data from</param>
  19. public TarInputStream(Stream inputStream)
  20. : this(inputStream, TarBuffer.DefaultBlockFactor)
  21. {
  22. }
  23. /// <summary>
  24. /// Construct a TarInputStream with user specified block factor
  25. /// </summary>
  26. /// <param name="inputStream">stream to source data from</param>
  27. /// <param name="blockFactor">block factor to apply to archive</param>
  28. public TarInputStream(Stream inputStream, int blockFactor)
  29. {
  30. this.inputStream = inputStream;
  31. tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
  32. }
  33. #endregion
  34. /// <summary>
  35. /// Gets or sets a flag indicating ownership of underlying stream.
  36. /// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
  37. /// </summary>
  38. /// <remarks>The default value is true.</remarks>
  39. public bool IsStreamOwner {
  40. get { return tarBuffer.IsStreamOwner; }
  41. set { tarBuffer.IsStreamOwner = value; }
  42. }
  43. #region Stream Overrides
  44. /// <summary>
  45. /// Gets a value indicating whether the current stream supports reading
  46. /// </summary>
  47. public override bool CanRead {
  48. get {
  49. return inputStream.CanRead;
  50. }
  51. }
  52. /// <summary>
  53. /// Gets a value indicating whether the current stream supports seeking
  54. /// This property always returns false.
  55. /// </summary>
  56. public override bool CanSeek {
  57. get {
  58. return false;
  59. }
  60. }
  61. /// <summary>
  62. /// Gets a value indicating if the stream supports writing.
  63. /// This property always returns false.
  64. /// </summary>
  65. public override bool CanWrite {
  66. get {
  67. return false;
  68. }
  69. }
  70. /// <summary>
  71. /// The length in bytes of the stream
  72. /// </summary>
  73. public override long Length {
  74. get {
  75. return inputStream.Length;
  76. }
  77. }
  78. /// <summary>
  79. /// Gets or sets the position within the stream.
  80. /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException
  81. /// </summary>
  82. /// <exception cref="NotSupportedException">Any attempt to set position</exception>
  83. public override long Position {
  84. get {
  85. return inputStream.Position;
  86. }
  87. set {
  88. throw new NotSupportedException("TarInputStream Seek not supported");
  89. }
  90. }
  91. /// <summary>
  92. /// Flushes the baseInputStream
  93. /// </summary>
  94. public override void Flush()
  95. {
  96. inputStream.Flush();
  97. }
  98. /// <summary>
  99. /// Set the streams position. This operation is not supported and will throw a NotSupportedException
  100. /// </summary>
  101. /// <param name="offset">The offset relative to the origin to seek to.</param>
  102. /// <param name="origin">The <see cref="SeekOrigin"/> to start seeking from.</param>
  103. /// <returns>The new position in the stream.</returns>
  104. /// <exception cref="NotSupportedException">Any access</exception>
  105. public override long Seek(long offset, SeekOrigin origin)
  106. {
  107. throw new NotSupportedException("TarInputStream Seek not supported");
  108. }
  109. /// <summary>
  110. /// Sets the length of the stream
  111. /// This operation is not supported and will throw a NotSupportedException
  112. /// </summary>
  113. /// <param name="value">The new stream length.</param>
  114. /// <exception cref="NotSupportedException">Any access</exception>
  115. public override void SetLength(long value)
  116. {
  117. throw new NotSupportedException("TarInputStream SetLength not supported");
  118. }
  119. /// <summary>
  120. /// Writes a block of bytes to this stream using data from a buffer.
  121. /// This operation is not supported and will throw a NotSupportedException
  122. /// </summary>
  123. /// <param name="buffer">The buffer containing bytes to write.</param>
  124. /// <param name="offset">The offset in the buffer of the frist byte to write.</param>
  125. /// <param name="count">The number of bytes to write.</param>
  126. /// <exception cref="NotSupportedException">Any access</exception>
  127. public override void Write(byte[] buffer, int offset, int count)
  128. {
  129. throw new NotSupportedException("TarInputStream Write not supported");
  130. }
  131. /// <summary>
  132. /// Writes a byte to the current position in the file stream.
  133. /// This operation is not supported and will throw a NotSupportedException
  134. /// </summary>
  135. /// <param name="value">The byte value to write.</param>
  136. /// <exception cref="NotSupportedException">Any access</exception>
  137. public override void WriteByte(byte value)
  138. {
  139. throw new NotSupportedException("TarInputStream WriteByte not supported");
  140. }
  141. /// <summary>
  142. /// Reads a byte from the current tar archive entry.
  143. /// </summary>
  144. /// <returns>A byte cast to an int; -1 if the at the end of the stream.</returns>
  145. public override int ReadByte()
  146. {
  147. byte[] oneByteBuffer = new byte[1];
  148. int num = Read(oneByteBuffer, 0, 1);
  149. if (num <= 0) {
  150. // return -1 to indicate that no byte was read.
  151. return -1;
  152. }
  153. return oneByteBuffer[0];
  154. }
  155. /// <summary>
  156. /// Reads bytes from the current tar archive entry.
  157. ///
  158. /// This method is aware of the boundaries of the current
  159. /// entry in the archive and will deal with them appropriately
  160. /// </summary>
  161. /// <param name="buffer">
  162. /// The buffer into which to place bytes read.
  163. /// </param>
  164. /// <param name="offset">
  165. /// The offset at which to place bytes read.
  166. /// </param>
  167. /// <param name="count">
  168. /// The number of bytes to read.
  169. /// </param>
  170. /// <returns>
  171. /// The number of bytes read, or 0 at end of stream/EOF.
  172. /// </returns>
  173. public override int Read(byte[] buffer, int offset, int count)
  174. {
  175. if (buffer == null) {
  176. throw new ArgumentNullException("nameof(buffer)");
  177. }
  178. int totalRead = 0;
  179. if (entryOffset >= entrySize) {
  180. return 0;
  181. }
  182. long numToRead = count;
  183. if ((numToRead + entryOffset) > entrySize) {
  184. numToRead = entrySize - entryOffset;
  185. }
  186. if (readBuffer != null) {
  187. int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead;
  188. Array.Copy(readBuffer, 0, buffer, offset, sz);
  189. if (sz >= readBuffer.Length) {
  190. readBuffer = null;
  191. } else {
  192. int newLen = readBuffer.Length - sz;
  193. byte[] newBuf = new byte[newLen];
  194. Array.Copy(readBuffer, sz, newBuf, 0, newLen);
  195. readBuffer = newBuf;
  196. }
  197. totalRead += sz;
  198. numToRead -= sz;
  199. offset += sz;
  200. }
  201. while (numToRead > 0) {
  202. byte[] rec = tarBuffer.ReadBlock();
  203. if (rec == null) {
  204. // Unexpected EOF!
  205. throw new TarException("unexpected EOF with " + numToRead + " bytes unread");
  206. }
  207. var sz = (int)numToRead;
  208. int recLen = rec.Length;
  209. if (recLen > sz) {
  210. Array.Copy(rec, 0, buffer, offset, sz);
  211. readBuffer = new byte[recLen - sz];
  212. Array.Copy(rec, sz, readBuffer, 0, recLen - sz);
  213. } else {
  214. sz = recLen;
  215. Array.Copy(rec, 0, buffer, offset, recLen);
  216. }
  217. totalRead += sz;
  218. numToRead -= sz;
  219. offset += sz;
  220. }
  221. entryOffset += totalRead;
  222. return totalRead;
  223. }
  224. /// <summary>
  225. /// Closes this stream. Calls the TarBuffer's close() method.
  226. /// The underlying stream is closed by the TarBuffer.
  227. /// </summary>
  228. protected override void Dispose(bool disposing)
  229. {
  230. if (disposing)
  231. {
  232. tarBuffer.Close();
  233. }
  234. }
  235. #endregion
  236. /// <summary>
  237. /// Set the entry factory for this instance.
  238. /// </summary>
  239. /// <param name="factory">The factory for creating new entries</param>
  240. public void SetEntryFactory(IEntryFactory factory)
  241. {
  242. entryFactory = factory;
  243. }
  244. /// <summary>
  245. /// Get the record size being used by this stream's TarBuffer.
  246. /// </summary>
  247. public int RecordSize {
  248. get { return tarBuffer.RecordSize; }
  249. }
  250. /// <summary>
  251. /// Get the record size being used by this stream's TarBuffer.
  252. /// </summary>
  253. /// <returns>
  254. /// TarBuffer record size.
  255. /// </returns>
  256. [Obsolete("Use RecordSize property instead")]
  257. public int GetRecordSize()
  258. {
  259. return tarBuffer.RecordSize;
  260. }
  261. /// <summary>
  262. /// Get the available data that can be read from the current
  263. /// entry in the archive. This does not indicate how much data
  264. /// is left in the entire archive, only in the current entry.
  265. /// This value is determined from the entry's size header field
  266. /// and the amount of data already read from the current entry.
  267. /// </summary>
  268. /// <returns>
  269. /// The number of available bytes for the current entry.
  270. /// </returns>
  271. public long Available {
  272. get {
  273. return entrySize - entryOffset;
  274. }
  275. }
  276. /// <summary>
  277. /// Skip bytes in the input buffer. This skips bytes in the
  278. /// current entry's data, not the entire archive, and will
  279. /// stop at the end of the current entry's data if the number
  280. /// to skip extends beyond that point.
  281. /// </summary>
  282. /// <param name="skipCount">
  283. /// The number of bytes to skip.
  284. /// </param>
  285. public void Skip(long skipCount)
  286. {
  287. // TODO: REVIEW efficiency of TarInputStream.Skip
  288. // This is horribly inefficient, but it ensures that we
  289. // properly skip over bytes via the TarBuffer...
  290. //
  291. byte[] skipBuf = new byte[8 * 1024];
  292. for (long num = skipCount; num > 0;) {
  293. int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num;
  294. int numRead = Read(skipBuf, 0, toRead);
  295. if (numRead == -1) {
  296. break;
  297. }
  298. num -= numRead;
  299. }
  300. }
  301. /// <summary>
  302. /// Return a value of true if marking is supported; false otherwise.
  303. /// </summary>
  304. /// <remarks>Currently marking is not supported, the return value is always false.</remarks>
  305. public bool IsMarkSupported {
  306. get {
  307. return false;
  308. }
  309. }
  310. /// <summary>
  311. /// Since we do not support marking just yet, we do nothing.
  312. /// </summary>
  313. /// <param name ="markLimit">
  314. /// The limit to mark.
  315. /// </param>
  316. public void Mark(int markLimit)
  317. {
  318. }
  319. /// <summary>
  320. /// Since we do not support marking just yet, we do nothing.
  321. /// </summary>
  322. public void Reset()
  323. {
  324. }
  325. /// <summary>
  326. /// Get the next entry in this tar archive. This will skip
  327. /// over any remaining data in the current entry, if there
  328. /// is one, and place the input stream at the header of the
  329. /// next entry, and read the header and instantiate a new
  330. /// TarEntry from the header bytes and return that entry.
  331. /// If there are no more entries in the archive, null will
  332. /// be returned to indicate that the end of the archive has
  333. /// been reached.
  334. /// </summary>
  335. /// <returns>
  336. /// The next TarEntry in the archive, or null.
  337. /// </returns>
  338. public TarEntry GetNextEntry()
  339. {
  340. if (hasHitEOF) {
  341. return null;
  342. }
  343. if (currentEntry != null) {
  344. SkipToNextEntry();
  345. }
  346. byte[] headerBuf = tarBuffer.ReadBlock();
  347. if (headerBuf == null) {
  348. hasHitEOF = true;
  349. } else
  350. hasHitEOF |= TarBuffer.IsEndOfArchiveBlock(headerBuf);
  351. if (hasHitEOF) {
  352. currentEntry = null;
  353. } else {
  354. try {
  355. var header = new TarHeader();
  356. header.ParseBuffer(headerBuf);
  357. if (!header.IsChecksumValid) {
  358. throw new TarException("Header checksum is invalid");
  359. }
  360. this.entryOffset = 0;
  361. this.entrySize = header.Size;
  362. StringBuilder longName = null;
  363. if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) {
  364. byte[] nameBuffer = new byte[TarBuffer.BlockSize];
  365. long numToRead = this.entrySize;
  366. longName = new StringBuilder();
  367. while (numToRead > 0) {
  368. int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead));
  369. if (numRead == -1) {
  370. throw new InvalidHeaderException("Failed to read long name entry");
  371. }
  372. longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString());
  373. numToRead -= numRead;
  374. }
  375. SkipToNextEntry();
  376. headerBuf = this.tarBuffer.ReadBlock();
  377. } else if (header.TypeFlag == TarHeader.LF_GHDR) { // POSIX global extended header
  378. // Ignore things we dont understand completely for now
  379. SkipToNextEntry();
  380. headerBuf = this.tarBuffer.ReadBlock();
  381. } else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header
  382. // Ignore things we dont understand completely for now
  383. SkipToNextEntry();
  384. headerBuf = this.tarBuffer.ReadBlock();
  385. } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) {
  386. // TODO: could show volume name when verbose
  387. SkipToNextEntry();
  388. headerBuf = this.tarBuffer.ReadBlock();
  389. } else if (header.TypeFlag != TarHeader.LF_NORMAL &&
  390. header.TypeFlag != TarHeader.LF_OLDNORM &&
  391. header.TypeFlag != TarHeader.LF_LINK &&
  392. header.TypeFlag != TarHeader.LF_SYMLINK &&
  393. header.TypeFlag != TarHeader.LF_DIR) {
  394. // Ignore things we dont understand completely for now
  395. SkipToNextEntry();
  396. headerBuf = tarBuffer.ReadBlock();
  397. }
  398. if (entryFactory == null) {
  399. currentEntry = new TarEntry(headerBuf);
  400. if (longName != null) {
  401. currentEntry.Name = longName.ToString();
  402. }
  403. } else {
  404. currentEntry = entryFactory.CreateEntry(headerBuf);
  405. }
  406. // Magic was checked here for 'ustar' but there are multiple valid possibilities
  407. // so this is not done anymore.
  408. entryOffset = 0;
  409. // TODO: Review How do we resolve this discrepancy?!
  410. entrySize = this.currentEntry.Size;
  411. } catch (InvalidHeaderException ex) {
  412. entrySize = 0;
  413. entryOffset = 0;
  414. currentEntry = null;
  415. string errorText = string.Format("Bad header in record {0} block {1} {2}",
  416. tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message);
  417. throw new InvalidHeaderException(errorText);
  418. }
  419. }
  420. return currentEntry;
  421. }
  422. /// <summary>
  423. /// Copies the contents of the current tar archive entry directly into
  424. /// an output stream.
  425. /// </summary>
  426. /// <param name="outputStream">
  427. /// The OutputStream into which to write the entry's data.
  428. /// </param>
  429. public void CopyEntryContents(Stream outputStream)
  430. {
  431. byte[] tempBuffer = new byte[32 * 1024];
  432. while (true) {
  433. int numRead = Read(tempBuffer, 0, tempBuffer.Length);
  434. if (numRead <= 0) {
  435. break;
  436. }
  437. outputStream.Write(tempBuffer, 0, numRead);
  438. }
  439. }
  440. void SkipToNextEntry()
  441. {
  442. long numToSkip = entrySize - entryOffset;
  443. if (numToSkip > 0) {
  444. Skip(numToSkip);
  445. }
  446. readBuffer = null;
  447. }
  448. /// <summary>
  449. /// This interface is provided, along with the method <see cref="SetEntryFactory"/>, to allow
  450. /// the programmer to have their own <see cref="TarEntry"/> subclass instantiated for the
  451. /// entries return from <see cref="GetNextEntry"/>.
  452. /// </summary>
  453. public interface IEntryFactory
  454. {
  455. /// <summary>
  456. /// Create an entry based on name alone
  457. /// </summary>
  458. /// <param name="name">
  459. /// Name of the new EntryPointNotFoundException to create
  460. /// </param>
  461. /// <returns>created TarEntry or descendant class</returns>
  462. TarEntry CreateEntry(string name);
  463. /// <summary>
  464. /// Create an instance based on an actual file
  465. /// </summary>
  466. /// <param name="fileName">
  467. /// Name of file to represent in the entry
  468. /// </param>
  469. /// <returns>
  470. /// Created TarEntry or descendant class
  471. /// </returns>
  472. TarEntry CreateEntryFromFile(string fileName);
  473. /// <summary>
  474. /// Create a tar entry based on the header information passed
  475. /// </summary>
  476. /// <param name="headerBuffer">
  477. /// Buffer containing header information to create an an entry from.
  478. /// </param>
  479. /// <returns>
  480. /// Created TarEntry or descendant class
  481. /// </returns>
  482. TarEntry CreateEntry(byte[] headerBuffer);
  483. }
  484. /// <summary>
  485. /// Standard entry factory class creating instances of the class TarEntry
  486. /// </summary>
  487. public class EntryFactoryAdapter : IEntryFactory
  488. {
  489. /// <summary>
  490. /// Create a <see cref="TarEntry"/> based on named
  491. /// </summary>
  492. /// <param name="name">The name to use for the entry</param>
  493. /// <returns>A new <see cref="TarEntry"/></returns>
  494. public TarEntry CreateEntry(string name)
  495. {
  496. return TarEntry.CreateTarEntry(name);
  497. }
  498. /// <summary>
  499. /// Create a tar entry with details obtained from <paramref name="fileName">file</paramref>
  500. /// </summary>
  501. /// <param name="fileName">The name of the file to retrieve details from.</param>
  502. /// <returns>A new <see cref="TarEntry"/></returns>
  503. public TarEntry CreateEntryFromFile(string fileName)
  504. {
  505. return TarEntry.CreateEntryFromFile(fileName);
  506. }
  507. /// <summary>
  508. /// Create an entry based on details in <paramref name="headerBuffer">header</paramref>
  509. /// </summary>
  510. /// <param name="headerBuffer">The buffer containing entry details.</param>
  511. /// <returns>A new <see cref="TarEntry"/></returns>
  512. public TarEntry CreateEntry(byte[] headerBuffer)
  513. {
  514. return new TarEntry(headerBuffer);
  515. }
  516. }
  517. #region Instance Fields
  518. /// <summary>
  519. /// Flag set when last block has been read
  520. /// </summary>
  521. protected bool hasHitEOF;
  522. /// <summary>
  523. /// Size of this entry as recorded in header
  524. /// </summary>
  525. protected long entrySize;
  526. /// <summary>
  527. /// Number of bytes read for this entry so far
  528. /// </summary>
  529. protected long entryOffset;
  530. /// <summary>
  531. /// Buffer used with calls to <code>Read()</code>
  532. /// </summary>
  533. protected byte[] readBuffer;
  534. /// <summary>
  535. /// Working buffer
  536. /// </summary>
  537. protected TarBuffer tarBuffer;
  538. /// <summary>
  539. /// Current entry being read
  540. /// </summary>
  541. TarEntry currentEntry;
  542. /// <summary>
  543. /// Factory used to create TarEntry or descendant class instance
  544. /// </summary>
  545. protected IEntryFactory entryFactory;
  546. /// <summary>
  547. /// Stream used as the source of input data.
  548. /// </summary>
  549. readonly Stream inputStream;
  550. #endregion
  551. }
  552. }