Summary

Class:ICSharpCode.SharpZipLib.Tar.TarInputStream
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarInputStream.cs
Covered lines:40
Uncovered lines:118
Coverable lines:158
Total lines:626
Line coverage:25.3%
Branch coverage:22%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
.ctor(...)1100100
.ctor(...)1100100
Flush()100
Seek(...)100
SetLength(...)100
Write(...)100
WriteByte(...)100
ReadByte()200
Read(...)1000
Close()1100100
SetEntryFactory(...)100
GetRecordSize()100
Skip(...)400
Mark(...)100
Reset()100
GetNextEntry()2042.8641.03
CopyEntryContents(...)200
SkipToNextEntry()200
CreateEntry(...)100
CreateEntryFromFile(...)100
CreateEntry(...)100

File(s)

C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarInputStream.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3using System.Text;
 4
 5namespace ICSharpCode.SharpZipLib.Tar
 6{
 7  /// <summary>
 8  /// The TarInputStream reads a UNIX tar archive as an InputStream.
 9  /// methods are provided to position at each successive entry in
 10  /// the archive, and the read each entry as a normal input stream
 11  /// using read().
 12  /// </summary>
 13  public class TarInputStream : Stream
 14  {
 15    #region Constructors
 16    /// <summary>
 17    /// Construct a TarInputStream with default block factor
 18    /// </summary>
 19    /// <param name="inputStream">stream to source data from</param>
 20    public TarInputStream(Stream inputStream)
 421      : this(inputStream, TarBuffer.DefaultBlockFactor)
 22    {
 423    }
 24
 25    /// <summary>
 26    /// Construct a TarInputStream with user specified block factor
 27    /// </summary>
 28    /// <param name="inputStream">stream to source data from</param>
 29    /// <param name="blockFactor">block factor to apply to archive</param>
 530    public TarInputStream(Stream inputStream, int blockFactor)
 31    {
 532      this.inputStream = inputStream;
 533      tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
 534    }
 35
 36    #endregion
 37
 38    /// <summary>
 39    /// Get/set flag indicating ownership of the underlying stream.
 40    /// When the flag is true <see cref="Close"></see> will close the underlying stream also.
 41    /// </summary>
 42    public bool IsStreamOwner {
 043      get { return tarBuffer.IsStreamOwner; }
 244      set { tarBuffer.IsStreamOwner = value; }
 45    }
 46
 47    #region Stream Overrides
 48    /// <summary>
 49    /// Gets a value indicating whether the current stream supports reading
 50    /// </summary>
 51    public override bool CanRead {
 52      get {
 053        return inputStream.CanRead;
 54      }
 55    }
 56
 57    /// <summary>
 58    /// Gets a value indicating whether the current stream supports seeking
 59    /// This property always returns false.
 60    /// </summary>
 61    public override bool CanSeek {
 62      get {
 063        return false;
 64      }
 65    }
 66
 67    /// <summary>
 68    /// Gets a value indicating if the stream supports writing.
 69    /// This property always returns false.
 70    /// </summary>
 71    public override bool CanWrite {
 72      get {
 073        return false;
 74      }
 75    }
 76
 77    /// <summary>
 78    /// The length in bytes of the stream
 79    /// </summary>
 80    public override long Length {
 81      get {
 082        return inputStream.Length;
 83      }
 84    }
 85
 86    /// <summary>
 87    /// Gets or sets the position within the stream.
 88    /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException
 89    /// </summary>
 90    /// <exception cref="NotSupportedException">Any attempt to set position</exception>
 91    public override long Position {
 92      get {
 093        return inputStream.Position;
 94      }
 95      set {
 096        throw new NotSupportedException("TarInputStream Seek not supported");
 97      }
 98    }
 99
 100    /// <summary>
 101    /// Flushes the baseInputStream
 102    /// </summary>
 103    public override void Flush()
 104    {
 0105      inputStream.Flush();
 0106    }
 107
 108    /// <summary>
 109    /// Set the streams position.  This operation is not supported and will throw a NotSupportedException
 110    /// </summary>
 111    /// <param name="offset">The offset relative to the origin to seek to.</param>
 112    /// <param name="origin">The <see cref="SeekOrigin"/> to start seeking from.</param>
 113    /// <returns>The new position in the stream.</returns>
 114    /// <exception cref="NotSupportedException">Any access</exception>
 115    public override long Seek(long offset, SeekOrigin origin)
 116    {
 0117      throw new NotSupportedException("TarInputStream Seek not supported");
 118    }
 119
 120    /// <summary>
 121    /// Sets the length of the stream
 122    /// This operation is not supported and will throw a NotSupportedException
 123    /// </summary>
 124    /// <param name="value">The new stream length.</param>
 125    /// <exception cref="NotSupportedException">Any access</exception>
 126    public override void SetLength(long value)
 127    {
 0128      throw new NotSupportedException("TarInputStream SetLength not supported");
 129    }
 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    {
 0141      throw new NotSupportedException("TarInputStream Write not supported");
 142    }
 143
 144    /// <summary>
 145    /// Writes a byte to the current position in the file stream.
 146    /// This operation is not supported and will throw a NotSupportedException
 147    /// </summary>
 148    /// <param name="value">The byte value to write.</param>
 149    /// <exception cref="NotSupportedException">Any access</exception>
 150    public override void WriteByte(byte value)
 151    {
 0152      throw new NotSupportedException("TarInputStream WriteByte not supported");
 153    }
 154    /// <summary>
 155    /// Reads a byte from the current tar archive entry.
 156    /// </summary>
 157    /// <returns>A byte cast to an int; -1 if the at the end of the stream.</returns>
 158    public override int ReadByte()
 159    {
 0160      byte[] oneByteBuffer = new byte[1];
 0161      int num = Read(oneByteBuffer, 0, 1);
 0162       if (num <= 0) {
 163        // return -1 to indicate that no byte was read.
 0164        return -1;
 165      }
 0166      return oneByteBuffer[0];
 167    }
 168
 169    /// <summary>
 170    /// Reads bytes from the current tar archive entry.
 171    ///
 172    /// This method is aware of the boundaries of the current
 173    /// entry in the archive and will deal with them appropriately
 174    /// </summary>
 175    /// <param name="buffer">
 176    /// The buffer into which to place bytes read.
 177    /// </param>
 178    /// <param name="offset">
 179    /// The offset at which to place bytes read.
 180    /// </param>
 181    /// <param name="count">
 182    /// The number of bytes to read.
 183    /// </param>
 184    /// <returns>
 185    /// The number of bytes read, or 0 at end of stream/EOF.
 186    /// </returns>
 187    public override int Read(byte[] buffer, int offset, int count)
 188    {
 0189       if (buffer == null) {
 0190        throw new ArgumentNullException(nameof(buffer));
 191      }
 192
 0193      int totalRead = 0;
 194
 0195       if (entryOffset >= entrySize) {
 0196        return 0;
 197      }
 198
 0199      long numToRead = count;
 200
 0201       if ((numToRead + entryOffset) > entrySize) {
 0202        numToRead = entrySize - entryOffset;
 203      }
 204
 0205       if (readBuffer != null) {
 0206         int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead;
 207
 0208        Array.Copy(readBuffer, 0, buffer, offset, sz);
 209
 0210         if (sz >= readBuffer.Length) {
 0211          readBuffer = null;
 0212        } else {
 0213          int newLen = readBuffer.Length - sz;
 0214          byte[] newBuf = new byte[newLen];
 0215          Array.Copy(readBuffer, sz, newBuf, 0, newLen);
 0216          readBuffer = newBuf;
 217        }
 218
 0219        totalRead += sz;
 0220        numToRead -= sz;
 0221        offset += sz;
 222      }
 223
 0224       while (numToRead > 0) {
 0225        byte[] rec = tarBuffer.ReadBlock();
 0226         if (rec == null) {
 227          // Unexpected EOF!
 0228          throw new TarException("unexpected EOF with " + numToRead + " bytes unread");
 229        }
 230
 0231        var sz = (int)numToRead;
 0232        int recLen = rec.Length;
 233
 0234         if (recLen > sz) {
 0235          Array.Copy(rec, 0, buffer, offset, sz);
 0236          readBuffer = new byte[recLen - sz];
 0237          Array.Copy(rec, sz, readBuffer, 0, recLen - sz);
 0238        } else {
 0239          sz = recLen;
 0240          Array.Copy(rec, 0, buffer, offset, recLen);
 241        }
 242
 0243        totalRead += sz;
 0244        numToRead -= sz;
 0245        offset += sz;
 246      }
 247
 0248      entryOffset += totalRead;
 249
 0250      return totalRead;
 251    }
 252
 253    /// <summary>
 254    /// Closes this stream. Calls the TarBuffer's close() method.
 255    /// The underlying stream is closed by the TarBuffer.
 256    /// </summary>
 257    public override void Close()
 258    {
 5259      tarBuffer.Close();
 5260    }
 261
 262    #endregion
 263
 264    /// <summary>
 265    /// Set the entry factory for this instance.
 266    /// </summary>
 267    /// <param name="factory">The factory for creating new entries</param>
 268    public void SetEntryFactory(IEntryFactory factory)
 269    {
 0270      entryFactory = factory;
 0271    }
 272
 273    /// <summary>
 274    /// Get the record size being used by this stream's TarBuffer.
 275    /// </summary>
 276    public int RecordSize {
 0277      get { return tarBuffer.RecordSize; }
 278    }
 279
 280    /// <summary>
 281    /// Get the record size being used by this stream's TarBuffer.
 282    /// </summary>
 283    /// <returns>
 284    /// TarBuffer record size.
 285    /// </returns>
 286    [Obsolete("Use RecordSize property instead")]
 287    public int GetRecordSize()
 288    {
 0289      return tarBuffer.RecordSize;
 290    }
 291
 292    /// <summary>
 293    /// Get the available data that can be read from the current
 294    /// entry in the archive. This does not indicate how much data
 295    /// is left in the entire archive, only in the current entry.
 296    /// This value is determined from the entry's size header field
 297    /// and the amount of data already read from the current entry.
 298    /// </summary>
 299    /// <returns>
 300    /// The number of available bytes for the current entry.
 301    /// </returns>
 302    public long Available {
 303      get {
 0304        return entrySize - entryOffset;
 305      }
 306    }
 307
 308    /// <summary>
 309    /// Skip bytes in the input buffer. This skips bytes in the
 310    /// current entry's data, not the entire archive, and will
 311    /// stop at the end of the current entry's data if the number
 312    /// to skip extends beyond that point.
 313    /// </summary>
 314    /// <param name="skipCount">
 315    /// The number of bytes to skip.
 316    /// </param>
 317    public void Skip(long skipCount)
 318    {
 319      // TODO: REVIEW efficiency of TarInputStream.Skip
 320      // This is horribly inefficient, but it ensures that we
 321      // properly skip over bytes via the TarBuffer...
 322      //
 0323      byte[] skipBuf = new byte[8 * 1024];
 324
 0325       for (long num = skipCount; num > 0;) {
 0326         int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num;
 0327        int numRead = Read(skipBuf, 0, toRead);
 328
 0329         if (numRead == -1) {
 330          break;
 331        }
 332
 0333        num -= numRead;
 334      }
 0335    }
 336
 337    /// <summary>
 338    /// Return a value of true if marking is supported; false otherwise.
 339    /// </summary>
 340    /// <remarks>Currently marking is not supported, the return value is always false.</remarks>
 341    public bool IsMarkSupported {
 342      get {
 0343        return false;
 344      }
 345    }
 346
 347    /// <summary>
 348    /// Since we do not support marking just yet, we do nothing.
 349    /// </summary>
 350    /// <param name ="markLimit">
 351    /// The limit to mark.
 352    /// </param>
 353    public void Mark(int markLimit)
 354    {
 0355    }
 356
 357    /// <summary>
 358    /// Since we do not support marking just yet, we do nothing.
 359    /// </summary>
 360    public void Reset()
 361    {
 0362    }
 363
 364    /// <summary>
 365    /// Get the next entry in this tar archive. This will skip
 366    /// over any remaining data in the current entry, if there
 367    /// is one, and place the input stream at the header of the
 368    /// next entry, and read the header and instantiate a new
 369    /// TarEntry from the header bytes and return that entry.
 370    /// If there are no more entries in the archive, null will
 371    /// be returned to indicate that the end of the archive has
 372    /// been reached.
 373    /// </summary>
 374    /// <returns>
 375    /// The next TarEntry in the archive, or null.
 376    /// </returns>
 377    public TarEntry GetNextEntry()
 378    {
 3379       if (hasHitEOF) {
 0380        return null;
 381      }
 382
 3383       if (currentEntry != null) {
 0384        SkipToNextEntry();
 385      }
 386
 3387      byte[] headerBuf = tarBuffer.ReadBlock();
 388
 3389       if (headerBuf == null) {
 0390        hasHitEOF = true;
 0391      } else
 3392        hasHitEOF |= TarBuffer.IsEndOfArchiveBlock(headerBuf);
 393
 3394       if (hasHitEOF) {
 1395        currentEntry = null;
 1396      } else {
 397        try {
 2398          var header = new TarHeader();
 2399          header.ParseBuffer(headerBuf);
 2400           if (!header.IsChecksumValid) {
 1401            throw new TarException("Header checksum is invalid");
 402          }
 1403          this.entryOffset = 0;
 1404          this.entrySize = header.Size;
 405
 1406          StringBuilder longName = null;
 407
 1408           if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) {
 409
 0410            byte[] nameBuffer = new byte[TarBuffer.BlockSize];
 0411            long numToRead = this.entrySize;
 412
 0413            longName = new StringBuilder();
 414
 0415             while (numToRead > 0) {
 0416               int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead
 417
 0418               if (numRead == -1) {
 0419                throw new InvalidHeaderException("Failed to read long name entry");
 420              }
 421
 0422              longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString());
 0423              numToRead -= numRead;
 424            }
 425
 0426            SkipToNextEntry();
 0427            headerBuf = this.tarBuffer.ReadBlock();
 1428           } else if (header.TypeFlag == TarHeader.LF_GHDR) {  // POSIX global extended header
 429                                    // Ignore things we dont understand completely for now
 0430            SkipToNextEntry();
 0431            headerBuf = this.tarBuffer.ReadBlock();
 1432           } else if (header.TypeFlag == TarHeader.LF_XHDR) {  // POSIX extended header
 433                                    // Ignore things we dont understand completely for now
 0434            SkipToNextEntry();
 0435            headerBuf = this.tarBuffer.ReadBlock();
 1436           } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) {
 437            // TODO: could show volume name when verbose
 0438            SkipToNextEntry();
 0439            headerBuf = this.tarBuffer.ReadBlock();
 1440           } else if (header.TypeFlag != TarHeader.LF_NORMAL &&
 1441                 header.TypeFlag != TarHeader.LF_OLDNORM &&
 1442                 header.TypeFlag != TarHeader.LF_LINK &&
 1443                 header.TypeFlag != TarHeader.LF_SYMLINK &&
 1444                 header.TypeFlag != TarHeader.LF_DIR) {
 445            // Ignore things we dont understand completely for now
 0446            SkipToNextEntry();
 0447            headerBuf = tarBuffer.ReadBlock();
 448          }
 449
 1450           if (entryFactory == null) {
 1451            currentEntry = new TarEntry(headerBuf);
 1452             if (longName != null) {
 0453              currentEntry.Name = longName.ToString();
 454            }
 0455          } else {
 0456            currentEntry = entryFactory.CreateEntry(headerBuf);
 457          }
 458
 459          // Magic was checked here for 'ustar' but there are multiple valid possibilities
 460          // so this is not done anymore.
 461
 1462          entryOffset = 0;
 463
 464          // TODO: Review How do we resolve this discrepancy?!
 1465          entrySize = this.currentEntry.Size;
 1466        } catch (InvalidHeaderException ex) {
 0467          entrySize = 0;
 0468          entryOffset = 0;
 0469          currentEntry = null;
 0470          string errorText = string.Format("Bad header in record {0} block {1} {2}",
 0471            tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message);
 0472          throw new InvalidHeaderException(errorText);
 473        }
 474      }
 2475      return currentEntry;
 476    }
 477
 478    /// <summary>
 479    /// Copies the contents of the current tar archive entry directly into
 480    /// an output stream.
 481    /// </summary>
 482    /// <param name="outputStream">
 483    /// The OutputStream into which to write the entry's data.
 484    /// </param>
 485    public void CopyEntryContents(Stream outputStream)
 486    {
 0487      byte[] tempBuffer = new byte[32 * 1024];
 488
 0489      while (true) {
 0490        int numRead = Read(tempBuffer, 0, tempBuffer.Length);
 0491         if (numRead <= 0) {
 492          break;
 493        }
 0494        outputStream.Write(tempBuffer, 0, numRead);
 495      }
 0496    }
 497
 498    void SkipToNextEntry()
 499    {
 0500      long numToSkip = entrySize - entryOffset;
 501
 0502       if (numToSkip > 0) {
 0503        Skip(numToSkip);
 504      }
 505
 0506      readBuffer = null;
 0507    }
 508
 509    /// <summary>
 510    /// This interface is provided, along with the method <see cref="SetEntryFactory"/>, to allow
 511    /// the programmer to have their own <see cref="TarEntry"/> subclass instantiated for the
 512    /// entries return from <see cref="GetNextEntry"/>.
 513    /// </summary>
 514    public interface IEntryFactory
 515    {
 516      /// <summary>
 517      /// Create an entry based on name alone
 518      /// </summary>
 519      /// <param name="name">
 520      /// Name of the new EntryPointNotFoundException to create
 521      /// </param>
 522      /// <returns>created TarEntry or descendant class</returns>
 523      TarEntry CreateEntry(string name);
 524
 525      /// <summary>
 526      /// Create an instance based on an actual file
 527      /// </summary>
 528      /// <param name="fileName">
 529      /// Name of file to represent in the entry
 530      /// </param>
 531      /// <returns>
 532      /// Created TarEntry or descendant class
 533      /// </returns>
 534      TarEntry CreateEntryFromFile(string fileName);
 535
 536      /// <summary>
 537      /// Create a tar entry based on the header information passed
 538      /// </summary>
 539      /// <param name="headerBuffer">
 540      /// Buffer containing header information to create an an entry from.
 541      /// </param>
 542      /// <returns>
 543      /// Created TarEntry or descendant class
 544      /// </returns>
 545      TarEntry CreateEntry(byte[] headerBuffer);
 546    }
 547
 548    /// <summary>
 549    /// Standard entry factory class creating instances of the class TarEntry
 550    /// </summary>
 551    public class EntryFactoryAdapter : IEntryFactory
 552    {
 553      /// <summary>
 554      /// Create a <see cref="TarEntry"/> based on named
 555      /// </summary>
 556      /// <param name="name">The name to use for the entry</param>
 557      /// <returns>A new <see cref="TarEntry"/></returns>
 558      public TarEntry CreateEntry(string name)
 559      {
 0560        return TarEntry.CreateTarEntry(name);
 561      }
 562
 563      /// <summary>
 564      /// Create a tar entry with details obtained from <paramref name="fileName">file</paramref>
 565      /// </summary>
 566      /// <param name="fileName">The name of the file to retrieve details from.</param>
 567      /// <returns>A new <see cref="TarEntry"/></returns>
 568      public TarEntry CreateEntryFromFile(string fileName)
 569      {
 0570        return TarEntry.CreateEntryFromFile(fileName);
 571      }
 572
 573      /// <summary>
 574      /// Create an entry based on details in <paramref name="headerBuffer">header</paramref>
 575      /// </summary>
 576      /// <param name="headerBuffer">The buffer containing entry details.</param>
 577      /// <returns>A new <see cref="TarEntry"/></returns>
 578      public TarEntry CreateEntry(byte[] headerBuffer)
 579      {
 0580        return new TarEntry(headerBuffer);
 581      }
 582    }
 583
 584    #region Instance Fields
 585    /// <summary>
 586    /// Flag set when last block has been read
 587    /// </summary>
 588    protected bool hasHitEOF;
 589
 590    /// <summary>
 591    /// Size of this entry as recorded in header
 592    /// </summary>
 593    protected long entrySize;
 594
 595    /// <summary>
 596    /// Number of bytes read for this entry so far
 597    /// </summary>
 598    protected long entryOffset;
 599
 600    /// <summary>
 601    /// Buffer used with calls to <code>Read()</code>
 602    /// </summary>
 603    protected byte[] readBuffer;
 604
 605    /// <summary>
 606    /// Working buffer
 607    /// </summary>
 608    protected TarBuffer tarBuffer;
 609
 610    /// <summary>
 611    /// Current entry being read
 612    /// </summary>
 613    TarEntry currentEntry;
 614
 615    /// <summary>
 616    /// Factory used to create TarEntry or descendant class instance
 617    /// </summary>
 618    protected IEntryFactory entryFactory;
 619
 620    /// <summary>
 621    /// Stream used as the source of input data.
 622    /// </summary>
 623    readonly Stream inputStream;
 624    #endregion
 625  }
 626}