TarInputStream.cs 19 KB

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