TarOutputStream.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. // TarOutputStream.cs
  2. //
  3. // Copyright (C) 2001 Mike Krueger
  4. // Copyright 2005 John Reilly
  5. //
  6. // This program is free software; you can redistribute it and/or
  7. // modify it under the terms of the GNU General Public License
  8. // as published by the Free Software Foundation; either version 2
  9. // of the License, or (at your option) any later version.
  10. //
  11. // This program is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU General Public License
  17. // along with this program; if not, write to the Free Software
  18. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. //
  20. // Linking this library statically or dynamically with other modules is
  21. // making a combined work based on this library. Thus, the terms and
  22. // conditions of the GNU General Public License cover the whole
  23. // combination.
  24. //
  25. // As a special exception, the copyright holders of this library give you
  26. // permission to link this library with independent modules to produce an
  27. // executable, regardless of the license terms of these independent
  28. // modules, and to copy and distribute the resulting executable under
  29. // terms of your choice, provided that you also meet, for each linked
  30. // independent module, the terms and conditions of the license of that
  31. // module. An independent module is a module which is not derived from
  32. // or based on this library. If you modify this library, you may extend
  33. // this exception to your version of the library, but you are not
  34. // obligated to do so. If you do not wish to do so, delete this
  35. // exception statement from your version.
  36. // HISTORY
  37. // 2012-06-04 Z-1419 Last char of file name was dropped if path length > 100
  38. using System;
  39. using System.IO;
  40. namespace CommonMPQ.SharpZipLib.Tar
  41. {
  42. /// <summary>
  43. /// The TarOutputStream writes a UNIX tar archive as an OutputStream.
  44. /// Methods are provided to put entries, and then write their contents
  45. /// by writing to this stream using write().
  46. /// </summary>
  47. /// public
  48. public class TarOutputStream : Stream
  49. {
  50. #region Constructors
  51. /// <summary>
  52. /// Construct TarOutputStream using default block factor
  53. /// </summary>
  54. /// <param name="outputStream">stream to write to</param>
  55. public TarOutputStream(Stream outputStream)
  56. : this(outputStream, TarBuffer.DefaultBlockFactor)
  57. {
  58. }
  59. /// <summary>
  60. /// Construct TarOutputStream with user specified block factor
  61. /// </summary>
  62. /// <param name="outputStream">stream to write to</param>
  63. /// <param name="blockFactor">blocking factor</param>
  64. public TarOutputStream(Stream outputStream, int blockFactor)
  65. {
  66. if ( outputStream == null )
  67. {
  68. throw new ArgumentNullException("outputStream");
  69. }
  70. this.outputStream = outputStream;
  71. buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor);
  72. assemblyBuffer = new byte[TarBuffer.BlockSize];
  73. blockBuffer = new byte[TarBuffer.BlockSize];
  74. }
  75. #endregion
  76. /// <summary>
  77. /// Get/set flag indicating ownership of the underlying stream.
  78. /// When the flag is true <see cref="Close"></see> will close the underlying stream also.
  79. /// </summary>
  80. public bool IsStreamOwner
  81. {
  82. get { return buffer.IsStreamOwner; }
  83. set { buffer.IsStreamOwner = value; }
  84. }
  85. /// <summary>
  86. /// true if the stream supports reading; otherwise, false.
  87. /// </summary>
  88. public override bool CanRead
  89. {
  90. get
  91. {
  92. return outputStream.CanRead;
  93. }
  94. }
  95. /// <summary>
  96. /// true if the stream supports seeking; otherwise, false.
  97. /// </summary>
  98. public override bool CanSeek
  99. {
  100. get
  101. {
  102. return outputStream.CanSeek;
  103. }
  104. }
  105. /// <summary>
  106. /// true if stream supports writing; otherwise, false.
  107. /// </summary>
  108. public override bool CanWrite
  109. {
  110. get
  111. {
  112. return outputStream.CanWrite;
  113. }
  114. }
  115. /// <summary>
  116. /// length of stream in bytes
  117. /// </summary>
  118. public override long Length
  119. {
  120. get
  121. {
  122. return outputStream.Length;
  123. }
  124. }
  125. /// <summary>
  126. /// gets or sets the position within the current stream.
  127. /// </summary>
  128. public override long Position
  129. {
  130. get
  131. {
  132. return outputStream.Position;
  133. }
  134. set
  135. {
  136. outputStream.Position = value;
  137. }
  138. }
  139. /// <summary>
  140. /// set the position within the current stream
  141. /// </summary>
  142. /// <param name="offset">The offset relative to the <paramref name="origin"/> to seek to</param>
  143. /// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param>
  144. /// <returns>The new position in the stream.</returns>
  145. public override long Seek(long offset, SeekOrigin origin)
  146. {
  147. return outputStream.Seek(offset, origin);
  148. }
  149. /// <summary>
  150. /// Set the length of the current stream
  151. /// </summary>
  152. /// <param name="value">The new stream length.</param>
  153. public override void SetLength(long value)
  154. {
  155. outputStream.SetLength(value);
  156. }
  157. /// <summary>
  158. /// Read a byte from the stream and advance the position within the stream
  159. /// by one byte or returns -1 if at the end of the stream.
  160. /// </summary>
  161. /// <returns>The byte value or -1 if at end of stream</returns>
  162. public override int ReadByte()
  163. {
  164. return outputStream.ReadByte();
  165. }
  166. /// <summary>
  167. /// read bytes from the current stream and advance the position within the
  168. /// stream by the number of bytes read.
  169. /// </summary>
  170. /// <param name="buffer">The buffer to store read bytes in.</param>
  171. /// <param name="offset">The index into the buffer to being storing bytes at.</param>
  172. /// <param name="count">The desired number of bytes to read.</param>
  173. /// <returns>The total number of bytes read, or zero if at the end of the stream.
  174. /// The number of bytes may be less than the <paramref name="count">count</paramref>
  175. /// requested if data is not avialable.</returns>
  176. public override int Read(byte[] buffer, int offset, int count)
  177. {
  178. return outputStream.Read(buffer, offset, count);
  179. }
  180. /// <summary>
  181. /// All buffered data is written to destination
  182. /// </summary>
  183. public override void Flush()
  184. {
  185. outputStream.Flush();
  186. }
  187. /// <summary>
  188. /// Ends the TAR archive without closing the underlying OutputStream.
  189. /// The result is that the EOF block of nulls is written.
  190. /// </summary>
  191. public void Finish()
  192. {
  193. if ( IsEntryOpen )
  194. {
  195. CloseEntry();
  196. }
  197. WriteEofBlock();
  198. }
  199. /// <summary>
  200. /// Ends the TAR archive and closes the underlying OutputStream.
  201. /// </summary>
  202. /// <remarks>This means that Finish() is called followed by calling the
  203. /// TarBuffer's Close().</remarks>
  204. public override void Close()
  205. {
  206. if ( !isClosed )
  207. {
  208. isClosed = true;
  209. Finish();
  210. buffer.Close();
  211. }
  212. }
  213. /// <summary>
  214. /// Get the record size being used by this stream's TarBuffer.
  215. /// </summary>
  216. public int RecordSize
  217. {
  218. get { return buffer.RecordSize; }
  219. }
  220. /// <summary>
  221. /// Get the record size being used by this stream's TarBuffer.
  222. /// </summary>
  223. /// <returns>
  224. /// The TarBuffer record size.
  225. /// </returns>
  226. [Obsolete("Use RecordSize property instead")]
  227. public int GetRecordSize()
  228. {
  229. return buffer.RecordSize;
  230. }
  231. /// <summary>
  232. /// Get a value indicating wether an entry is open, requiring more data to be written.
  233. /// </summary>
  234. bool IsEntryOpen
  235. {
  236. get { return (currBytes < currSize); }
  237. }
  238. /// <summary>
  239. /// Put an entry on the output stream. This writes the entry's
  240. /// header and positions the output stream for writing
  241. /// the contents of the entry. Once this method is called, the
  242. /// stream is ready for calls to write() to write the entry's
  243. /// contents. Once the contents are written, closeEntry()
  244. /// <B>MUST</B> be called to ensure that all buffered data
  245. /// is completely written to the output stream.
  246. /// </summary>
  247. /// <param name="entry">
  248. /// The TarEntry to be written to the archive.
  249. /// </param>
  250. public void PutNextEntry(TarEntry entry)
  251. {
  252. if ( entry == null ) {
  253. throw new ArgumentNullException("entry");
  254. }
  255. if (entry.TarHeader.Name.Length >= TarHeader.NAMELEN) {
  256. TarHeader longHeader = new TarHeader();
  257. longHeader.TypeFlag = TarHeader.LF_GNU_LONGNAME;
  258. longHeader.Name = longHeader.Name + "././@LongLink";
  259. longHeader.UserId = 0;
  260. longHeader.GroupId = 0;
  261. longHeader.GroupName = "";
  262. longHeader.UserName = "";
  263. longHeader.LinkName = "";
  264. longHeader.Size = entry.TarHeader.Name.Length + 1; // Plus one to avoid dropping last char
  265. longHeader.WriteHeader(blockBuffer);
  266. buffer.WriteBlock(blockBuffer); // Add special long filename header block
  267. int nameCharIndex = 0;
  268. while (nameCharIndex < entry.TarHeader.Name.Length) {
  269. Array.Clear(blockBuffer, 0, blockBuffer.Length);
  270. TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize);
  271. nameCharIndex += TarBuffer.BlockSize;
  272. buffer.WriteBlock(blockBuffer);
  273. }
  274. }
  275. entry.WriteEntryHeader(blockBuffer);
  276. buffer.WriteBlock(blockBuffer);
  277. currBytes = 0;
  278. currSize = entry.IsDirectory ? 0 : entry.Size;
  279. }
  280. /// <summary>
  281. /// Close an entry. This method MUST be called for all file
  282. /// entries that contain data. The reason is that we must
  283. /// buffer data written to the stream in order to satisfy
  284. /// the buffer's block based writes. Thus, there may be
  285. /// data fragments still being assembled that must be written
  286. /// to the output stream before this entry is closed and the
  287. /// next entry written.
  288. /// </summary>
  289. public void CloseEntry()
  290. {
  291. if (assemblyBufferLength > 0) {
  292. Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength);
  293. buffer.WriteBlock(assemblyBuffer);
  294. currBytes += assemblyBufferLength;
  295. assemblyBufferLength = 0;
  296. }
  297. if (currBytes < currSize) {
  298. string errorText = string.Format(
  299. "Entry closed at '{0}' before the '{1}' bytes specified in the header were written",
  300. currBytes, currSize);
  301. throw new TarException(errorText);
  302. }
  303. }
  304. /// <summary>
  305. /// Writes a byte to the current tar archive entry.
  306. /// This method simply calls Write(byte[], int, int).
  307. /// </summary>
  308. /// <param name="value">
  309. /// The byte to be written.
  310. /// </param>
  311. public override void WriteByte(byte value)
  312. {
  313. Write(new byte[] { value }, 0, 1);
  314. }
  315. /// <summary>
  316. /// Writes bytes to the current tar archive entry. This method
  317. /// is aware of the current entry and will throw an exception if
  318. /// you attempt to write bytes past the length specified for the
  319. /// current entry. The method is also (painfully) aware of the
  320. /// record buffering required by TarBuffer, and manages buffers
  321. /// that are not a multiple of recordsize in length, including
  322. /// assembling records from small buffers.
  323. /// </summary>
  324. /// <param name = "buffer">
  325. /// The buffer to write to the archive.
  326. /// </param>
  327. /// <param name = "offset">
  328. /// The offset in the buffer from which to get bytes.
  329. /// </param>
  330. /// <param name = "count">
  331. /// The number of bytes to write.
  332. /// </param>
  333. public override void Write(byte[] buffer, int offset, int count)
  334. {
  335. if ( buffer == null ) {
  336. throw new ArgumentNullException("buffer");
  337. }
  338. if ( offset < 0 )
  339. {
  340. #if NETCF_1_0
  341. throw new ArgumentOutOfRangeException("offset");
  342. #else
  343. throw new ArgumentOutOfRangeException("offset", "Cannot be negative");
  344. #endif
  345. }
  346. if ( buffer.Length - offset < count )
  347. {
  348. throw new ArgumentException("offset and count combination is invalid");
  349. }
  350. if ( count < 0 )
  351. {
  352. #if NETCF_1_0
  353. throw new ArgumentOutOfRangeException("count");
  354. #else
  355. throw new ArgumentOutOfRangeException("count", "Cannot be negative");
  356. #endif
  357. }
  358. if ( (currBytes + count) > currSize ) {
  359. string errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes",
  360. count, this.currSize);
  361. #if NETCF_1_0
  362. throw new ArgumentOutOfRangeException("count");
  363. #else
  364. throw new ArgumentOutOfRangeException("count", errorText);
  365. #endif
  366. }
  367. //
  368. // We have to deal with assembly!!!
  369. // The programmer can be writing little 32 byte chunks for all
  370. // we know, and we must assemble complete blocks for writing.
  371. // TODO REVIEW Maybe this should be in TarBuffer? Could that help to
  372. // eliminate some of the buffer copying.
  373. //
  374. if (assemblyBufferLength > 0) {
  375. if ((assemblyBufferLength + count ) >= blockBuffer.Length) {
  376. int aLen = blockBuffer.Length - assemblyBufferLength;
  377. Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength);
  378. Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen);
  379. this.buffer.WriteBlock(blockBuffer);
  380. currBytes += blockBuffer.Length;
  381. offset += aLen;
  382. count -= aLen;
  383. assemblyBufferLength = 0;
  384. } else {
  385. Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count);
  386. offset += count;
  387. assemblyBufferLength += count;
  388. count -= count;
  389. }
  390. }
  391. //
  392. // When we get here we have EITHER:
  393. // o An empty "assembly" buffer.
  394. // o No bytes to write (count == 0)
  395. //
  396. while (count > 0) {
  397. if (count < blockBuffer.Length) {
  398. Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count);
  399. assemblyBufferLength += count;
  400. break;
  401. }
  402. this.buffer.WriteBlock(buffer, offset);
  403. int bufferLength = blockBuffer.Length;
  404. currBytes += bufferLength;
  405. count -= bufferLength;
  406. offset += bufferLength;
  407. }
  408. }
  409. /// <summary>
  410. /// Write an EOF (end of archive) block to the tar archive.
  411. /// An EOF block consists of all zeros.
  412. /// </summary>
  413. void WriteEofBlock()
  414. {
  415. Array.Clear(blockBuffer, 0, blockBuffer.Length);
  416. buffer.WriteBlock(blockBuffer);
  417. }
  418. #region Instance Fields
  419. /// <summary>
  420. /// bytes written for this entry so far
  421. /// </summary>
  422. long currBytes;
  423. /// <summary>
  424. /// current 'Assembly' buffer length
  425. /// </summary>
  426. int assemblyBufferLength;
  427. /// <summary>
  428. /// Flag indicating wether this instance has been closed or not.
  429. /// </summary>
  430. bool isClosed;
  431. /// <summary>
  432. /// Size for the current entry
  433. /// </summary>
  434. protected long currSize;
  435. /// <summary>
  436. /// single block working buffer
  437. /// </summary>
  438. protected byte[] blockBuffer;
  439. /// <summary>
  440. /// 'Assembly' buffer used to assemble data before writing
  441. /// </summary>
  442. protected byte[] assemblyBuffer;
  443. /// <summary>
  444. /// TarBuffer used to provide correct blocking factor
  445. /// </summary>
  446. protected TarBuffer buffer;
  447. /// <summary>
  448. /// the destination stream for the archive contents
  449. /// </summary>
  450. protected Stream outputStream;
  451. #endregion
  452. }
  453. }
  454. /* The original Java file had this header:
  455. ** Authored by Timothy Gerard Endres
  456. ** <mailto:time@gjt.org> <http://www.trustice.com>
  457. **
  458. ** This work has been placed into the public domain.
  459. ** You may use this work in any way and for any purpose you wish.
  460. **
  461. ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
  462. ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
  463. ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
  464. ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
  465. ** REDISTRIBUTION OF THIS SOFTWARE.
  466. **
  467. */