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