Summary

Class:ICSharpCode.SharpZipLib.Zip.ZipInputStream
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipInputStream.cs
Covered lines:162
Uncovered lines:44
Coverable lines:206
Total lines:610
Line coverage:78.6%
Branch coverage:67.3%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
.ctor(...)1100100
.ctor(...)100
GetNextEntry()2491.3875.56
ReadDataDescriptor()37560
CompleteCloseEntry(...)690.9181.82
CloseEntry()1047.8342.11
ReadByte()27566.67
ReadingNotAvailable(...)100
ReadingNotSupported(...)100
InitialRead(...)118480.95
Read(...)5100100
BodyRead(...)2282.8669.23
Close()1100100

File(s)

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

#LineLine coverage
 1using System;
 2using System.IO;
 3using ICSharpCode.SharpZipLib.Checksum;
 4using ICSharpCode.SharpZipLib.Encryption;
 5using ICSharpCode.SharpZipLib.Zip.Compression;
 6using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
 7
 8namespace ICSharpCode.SharpZipLib.Zip
 9{
 10  /// <summary>
 11  /// This is an InflaterInputStream that reads the files baseInputStream an zip archive
 12  /// one after another.  It has a special method to get the zip entry of
 13  /// the next file.  The zip entry contains information about the file name
 14  /// size, compressed size, Crc, etc.
 15  /// It includes support for Stored and Deflated entries.
 16  /// <br/>
 17  /// <br/>Author of the original java version : Jochen Hoenicke
 18  /// </summary>
 19  ///
 20  /// <example> This sample shows how to read a zip file
 21  /// <code lang="C#">
 22  /// using System;
 23  /// using System.Text;
 24  /// using System.IO;
 25  ///
 26  /// using ICSharpCode.SharpZipLib.Zip;
 27  ///
 28  /// class MainClass
 29  /// {
 30  ///   public static void Main(string[] args)
 31  ///   {
 32  ///     using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) {
 33  ///
 34  ///       ZipEntry theEntry;
 35  ///       const int size = 2048;
 36  ///       byte[] data = new byte[2048];
 37  ///
 38  ///       while ((theEntry = s.GetNextEntry()) != null) {
 39  ///                 if ( entry.IsFile ) {
 40  ///             Console.Write("Show contents (y/n) ?");
 41  ///             if (Console.ReadLine() == "y") {
 42  ///               while (true) {
 43  ///                 size = s.Read(data, 0, data.Length);
 44  ///                 if (size > 0) {
 45  ///                   Console.Write(new ASCIIEncoding().GetString(data, 0, size));
 46  ///                 } else {
 47  ///                   break;
 48  ///                 }
 49  ///               }
 50  ///             }
 51  ///         }
 52  ///       }
 53  ///     }
 54  ///   }
 55  /// }
 56  /// </code>
 57  /// </example>
 58  public class ZipInputStream : InflaterInputStream
 59  {
 60    #region Instance Fields
 61
 62    /// <summary>
 63    /// Delegate for reading bytes from a stream.
 64    /// </summary>
 65    delegate int ReadDataHandler(byte[] b, int offset, int length);
 66
 67    /// <summary>
 68    /// The current reader this instance.
 69    /// </summary>
 70    ReadDataHandler internalReader;
 71
 5772    Crc32 crc = new Crc32();
 73    ZipEntry entry;
 74
 75    long size;
 76    int method;
 77    int flags;
 78    string password;
 79    #endregion
 80
 81    #region Constructors
 82    /// <summary>
 83    /// Creates a new Zip input stream, for reading a zip archive.
 84    /// </summary>
 85    /// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param>
 86    public ZipInputStream(Stream baseInputStream)
 5787      : base(baseInputStream, new Inflater(true))
 88    {
 5789      internalReader = new ReadDataHandler(ReadingNotAvailable);
 5790    }
 91
 92    /// <summary>
 93    /// Creates a new Zip input stream, for reading a zip archive.
 94    /// </summary>
 95    /// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param>
 96    /// <param name="bufferSize">Size of the buffer.</param>
 97    public ZipInputStream(Stream baseInputStream, int bufferSize)
 098      : base(baseInputStream, new Inflater(true), bufferSize)
 99    {
 0100      internalReader = new ReadDataHandler(ReadingNotAvailable);
 0101    }
 102    #endregion
 103
 104    /// <summary>
 105    /// Optional password used for encryption when non-null
 106    /// </summary>
 107    /// <value>A password for all encrypted <see cref="ZipEntry">entries </see> in this <see cref="ZipInputStream"/></va
 108    public string Password {
 109      get {
 0110        return password;
 111      }
 112      set {
 23113        password = value;
 23114      }
 115    }
 116
 117
 118    /// <summary>
 119    /// Gets a value indicating if there is a current entry and it can be decompressed
 120    /// </summary>
 121    /// <remarks>
 122    /// The entry can only be decompressed if the library supports the zip features required to extract it.
 123    /// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details.
 124    /// </remarks>
 125    public bool CanDecompressEntry {
 126      get {
 70127        return (entry != null) && entry.CanDecompress;
 128      }
 129    }
 130
 131    /// <summary>
 132    /// Advances to the next entry in the archive
 133    /// </summary>
 134    /// <returns>
 135    /// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries.
 136    /// </returns>
 137    /// <remarks>
 138    /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called.
 139    /// </remarks>
 140    /// <exception cref="InvalidOperationException">
 141    /// Input stream is closed
 142    /// </exception>
 143    /// <exception cref="ZipException">
 144    /// Password is not set, password is invalid, compression method is invalid,
 145    /// version required to extract is not supported
 146    /// </exception>
 147    public ZipEntry GetNextEntry()
 148    {
 87149       if (crc == null) {
 0150        throw new InvalidOperationException("Closed.");
 151      }
 152
 87153       if (entry != null) {
 14154        CloseEntry();
 155      }
 156
 87157      int header = inputBuffer.ReadLeInt();
 158
 87159       if (header == ZipConstants.CentralHeaderSignature ||
 87160        header == ZipConstants.EndOfCentralDirectorySignature ||
 87161        header == ZipConstants.CentralHeaderDigitalSignature ||
 87162        header == ZipConstants.ArchiveExtraDataSignature ||
 87163        header == ZipConstants.Zip64CentralFileHeaderSignature) {
 164        // No more individual entries exist
 6165        Close();
 6166        return null;
 167      }
 168
 169      // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
 170      // Spanning signature is same as descriptor signature and is untested as yet.
 81171       if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature)) {
 0172        header = inputBuffer.ReadLeInt();
 173      }
 174
 81175       if (header != ZipConstants.LocalHeaderSignature) {
 0176        throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
 177      }
 178
 81179      var versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
 180
 81181      flags = inputBuffer.ReadLeShort();
 81182      method = inputBuffer.ReadLeShort();
 81183      var dostime = (uint)inputBuffer.ReadLeInt();
 81184      int crc2 = inputBuffer.ReadLeInt();
 81185      csize = inputBuffer.ReadLeInt();
 81186      size = inputBuffer.ReadLeInt();
 81187      int nameLen = inputBuffer.ReadLeShort();
 81188      int extraLen = inputBuffer.ReadLeShort();
 189
 81190      bool isCrypted = (flags & 1) == 1;
 191
 81192      byte[] buffer = new byte[nameLen];
 81193      inputBuffer.ReadRawBuffer(buffer);
 194
 81195      string name = ZipConstants.ConvertToStringExt(flags, buffer);
 196
 81197      entry = new ZipEntry(name, versionRequiredToExtract);
 81198      entry.Flags = flags;
 199
 81200      entry.CompressionMethod = (CompressionMethod)method;
 201
 81202       if ((flags & 8) == 0) {
 44203        entry.Crc = crc2 & 0xFFFFFFFFL;
 44204        entry.Size = size & 0xFFFFFFFFL;
 44205        entry.CompressedSize = csize & 0xFFFFFFFFL;
 206
 44207        entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff);
 208
 44209      } else {
 210
 211        // This allows for GNU, WinZip and possibly other archives, the PKZIP spec
 212        // says these values are zero under these circumstances.
 37213         if (crc2 != 0) {
 12214          entry.Crc = crc2 & 0xFFFFFFFFL;
 215        }
 216
 37217         if (size != 0) {
 37218          entry.Size = size & 0xFFFFFFFFL;
 219        }
 220
 37221         if (csize != 0) {
 37222          entry.CompressedSize = csize & 0xFFFFFFFFL;
 223        }
 224
 37225        entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
 226      }
 227
 81228      entry.DosTime = dostime;
 229
 230      // If local header requires Zip64 is true then the extended header should contain
 231      // both values.
 232
 233      // Handle extra data if present.  This can set/alter some fields of the entry.
 81234       if (extraLen > 0) {
 78235        byte[] extra = new byte[extraLen];
 78236        inputBuffer.ReadRawBuffer(extra);
 78237        entry.ExtraData = extra;
 238      }
 239
 81240      entry.ProcessExtraData(true);
 81241       if (entry.CompressedSize >= 0) {
 57242        csize = entry.CompressedSize;
 243      }
 244
 81245       if (entry.Size >= 0) {
 57246        size = entry.Size;
 247      }
 248
 81249       if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.
 0250        throw new ZipException("Stored, but compressed != uncompressed");
 251      }
 252
 253      // Determine how to handle reading of data if this is attempted.
 81254       if (entry.IsCompressionMethodSupported()) {
 81255        internalReader = new ReadDataHandler(InitialRead);
 81256      } else {
 0257        internalReader = new ReadDataHandler(ReadingNotSupported);
 258      }
 259
 81260      return entry;
 261    }
 262
 263    /// <summary>
 264    /// Read data descriptor at the end of compressed data.
 265    /// </summary>
 266    void ReadDataDescriptor()
 267    {
 5268       if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature) {
 0269        throw new ZipException("Data descriptor signature not found");
 270      }
 271
 5272      entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
 273
 5274       if (entry.LocalHeaderRequiresZip64) {
 5275        csize = inputBuffer.ReadLeLong();
 5276        size = inputBuffer.ReadLeLong();
 5277      } else {
 0278        csize = inputBuffer.ReadLeInt();
 0279        size = inputBuffer.ReadLeInt();
 280      }
 5281      entry.CompressedSize = csize;
 5282      entry.Size = size;
 5283    }
 284
 285    /// <summary>
 286    /// Complete cleanup as the final part of closing.
 287    /// </summary>
 288    /// <param name="testCrc">True if the crc value should be tested</param>
 289    void CompleteCloseEntry(bool testCrc)
 290    {
 32291      StopDecrypting();
 292
 32293       if ((flags & 8) != 0) {
 5294        ReadDataDescriptor();
 295      }
 296
 32297      size = 0;
 298
 32299       if (testCrc &&
 32300        ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) {
 0301        throw new ZipException("CRC mismatch");
 302      }
 303
 32304      crc.Reset();
 305
 32306       if (method == (int)CompressionMethod.Deflated) {
 25307        inf.Reset();
 308      }
 32309      entry = null;
 32310    }
 311
 312    /// <summary>
 313    /// Closes the current zip entry and moves to the next one.
 314    /// </summary>
 315    /// <exception cref="InvalidOperationException">
 316    /// The stream is closed
 317    /// </exception>
 318    /// <exception cref="ZipException">
 319    /// The Zip stream ends early
 320    /// </exception>
 321    public void CloseEntry()
 322    {
 14323       if (crc == null) {
 0324        throw new InvalidOperationException("Closed");
 325      }
 326
 14327       if (entry == null) {
 0328        return;
 329      }
 330
 14331       if (method == (int)CompressionMethod.Deflated) {
 9332         if ((flags & 8) != 0) {
 333          // We don't know how much we must skip, read until end.
 0334          byte[] tmp = new byte[4096];
 335
 336          // Read will close this entry
 0337           while (Read(tmp, 0, tmp.Length) > 0) {
 338          }
 0339          return;
 340        }
 341
 9342        csize -= inf.TotalIn;
 9343        inputBuffer.Available += inf.RemainingInput;
 344      }
 345
 14346       if ((inputBuffer.Available > csize) && (csize >= 0)) {
 14347        inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
 14348      } else {
 0349        csize -= inputBuffer.Available;
 0350        inputBuffer.Available = 0;
 0351         while (csize != 0) {
 0352          long skipped = Skip(csize);
 353
 0354           if (skipped <= 0) {
 0355            throw new ZipException("Zip archive ends early.");
 356          }
 357
 0358          csize -= skipped;
 359        }
 360      }
 361
 14362      CompleteCloseEntry(false);
 14363    }
 364
 365    /// <summary>
 366    /// Returns 1 if there is an entry available
 367    /// Otherwise returns 0.
 368    /// </summary>
 369    public override int Available {
 370      get {
 0371         return entry != null ? 1 : 0;
 372      }
 373    }
 374
 375    /// <summary>
 376    /// Returns the current size that can be read from the current entry if available
 377    /// </summary>
 378    /// <exception cref="ZipException">Thrown if the entry size is not known.</exception>
 379    /// <exception cref="InvalidOperationException">Thrown if no entry is currently available.</exception>
 380    public override long Length {
 381      get {
 0382         if (entry != null) {
 0383           if (entry.Size >= 0) {
 0384            return entry.Size;
 385          } else {
 0386            throw new ZipException("Length not available for the current entry");
 387          }
 388        } else {
 0389          throw new InvalidOperationException("No current entry");
 390        }
 391      }
 392
 393    }
 394
 395    /// <summary>
 396    /// Reads a byte from the current zip entry.
 397    /// </summary>
 398    /// <returns>
 399    /// The byte or -1 if end of stream is reached.
 400    /// </returns>
 401    public override int ReadByte()
 402    {
 12403      byte[] b = new byte[1];
 12404       if (Read(b, 0, 1) <= 0) {
 0405        return -1;
 406      }
 12407      return b[0] & 0xff;
 408    }
 409
 410    /// <summary>
 411    /// Handle attempts to read by throwing an <see cref="InvalidOperationException"/>.
 412    /// </summary>
 413    /// <param name="destination">The destination array to store data in.</param>
 414    /// <param name="offset">The offset at which data read should be stored.</param>
 415    /// <param name="count">The maximum number of bytes to read.</param>
 416    /// <returns>Returns the number of bytes actually read.</returns>
 417    int ReadingNotAvailable(byte[] destination, int offset, int count)
 418    {
 0419      throw new InvalidOperationException("Unable to read from this stream");
 420    }
 421
 422    /// <summary>
 423    /// Handle attempts to read from this entry by throwing an exception
 424    /// </summary>
 425    int ReadingNotSupported(byte[] destination, int offset, int count)
 426    {
 0427      throw new ZipException("The compression method for this entry is not supported");
 428    }
 429
 430    /// <summary>
 431    /// Perform the initial read on an entry which may include
 432    /// reading encryption headers and setting up inflation.
 433    /// </summary>
 434    /// <param name="destination">The destination to fill with data read.</param>
 435    /// <param name="offset">The offset to start reading at.</param>
 436    /// <param name="count">The maximum number of bytes to read.</param>
 437    /// <returns>The actual number of bytes read.</returns>
 438    int InitialRead(byte[] destination, int offset, int count)
 439    {
 70440       if (!CanDecompressEntry) {
 0441        throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version + ")");
 442      }
 443
 444      // Handle encryption if required.
 70445       if (entry.IsCrypted) {
 24446         if (password == null) {
 0447          throw new ZipException("No password set.");
 448        }
 449
 450        // Generate and set crypto transform...
 24451        var managed = new PkzipClassicManaged();
 24452        byte[] key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password));
 453
 24454        inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
 455
 24456        byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
 24457        inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
 458
 24459         if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) {
 0460          throw new ZipException("Invalid password");
 461        }
 462
 24463         if (csize >= ZipConstants.CryptoHeaderSize) {
 13464          csize -= ZipConstants.CryptoHeaderSize;
 24465         } else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0) {
 0466          throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
 467        }
 468      } else {
 46469        inputBuffer.CryptoTransform = null;
 470      }
 471
 70472       if ((csize > 0) || ((flags & (int)GeneralBitFlags.Descriptor) != 0)) {
 69473         if ((method == (int)CompressionMethod.Deflated) && (inputBuffer.Available > 0)) {
 66474          inputBuffer.SetInflaterInput(inf);
 475        }
 476
 69477        internalReader = new ReadDataHandler(BodyRead);
 69478        return BodyRead(destination, offset, count);
 479      } else {
 1480        internalReader = new ReadDataHandler(ReadingNotAvailable);
 1481        return 0;
 482      }
 483    }
 484
 485    /// <summary>
 486    /// Read a block of bytes from the stream.
 487    /// </summary>
 488    /// <param name="buffer">The destination for the bytes.</param>
 489    /// <param name="offset">The index to start storing data.</param>
 490    /// <param name="count">The number of bytes to attempt to read.</param>
 491    /// <returns>Returns the number of bytes read.</returns>
 492    /// <remarks>Zero bytes read means end of stream.</remarks>
 493    public override int Read(byte[] buffer, int offset, int count)
 494    {
 148495       if (buffer == null) {
 1496        throw new ArgumentNullException(nameof(buffer));
 497      }
 498
 147499       if (offset < 0) {
 1500        throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
 501      }
 502
 146503       if (count < 0) {
 1504        throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
 505      }
 506
 145507       if ((buffer.Length - offset) < count) {
 3508        throw new ArgumentException("Invalid offset/count combination");
 509      }
 510
 142511      return internalReader(buffer, offset, count);
 512    }
 513
 514    /// <summary>
 515    /// Reads a block of bytes from the current zip entry.
 516    /// </summary>
 517    /// <returns>
 518    /// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on en
 519    /// </returns>
 520    /// <exception name="IOException">
 521    /// An i/o error occured.
 522    /// </exception>
 523    /// <exception cref="ZipException">
 524    /// The deflated stream is corrupted.
 525    /// </exception>
 526    /// <exception cref="InvalidOperationException">
 527    /// The stream is not open.
 528    /// </exception>
 529    int BodyRead(byte[] buffer, int offset, int count)
 530    {
 141531       if (crc == null) {
 0532        throw new InvalidOperationException("Closed");
 533      }
 534
 141535       if ((entry == null) || (count <= 0)) {
 46536        return 0;
 537      }
 538
 95539       if (offset + count > buffer.Length) {
 0540        throw new ArgumentException("Offset + count exceeds buffer size");
 541      }
 542
 95543      bool finished = false;
 544
 95545       switch (method) {
 546        case (int)CompressionMethod.Deflated:
 92547          count = base.Read(buffer, offset, count);
 92548           if (count <= 0) {
 16549             if (!inf.IsFinished) {
 0550              throw new ZipException("Inflater not finished!");
 551            }
 16552            inputBuffer.Available = inf.RemainingInput;
 553
 554            // A csize of -1 is from an unpatched local header
 16555             if ((flags & 8) == 0 &&
 16556              (inf.TotalIn != csize && csize != 0xFFFFFFFF && csize != -1 || inf.TotalOut != size)) {
 0557              throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut
 558            }
 16559            inf.Reset();
 16560            finished = true;
 561          }
 16562          break;
 563
 564        case (int)CompressionMethod.Stored:
 3565           if ((count > csize) && (csize >= 0)) {
 0566            count = (int)csize;
 567          }
 568
 3569           if (count > 0) {
 3570            count = inputBuffer.ReadClearTextBuffer(buffer, offset, count);
 3571             if (count > 0) {
 3572              csize -= count;
 3573              size -= count;
 574            }
 575          }
 576
 3577           if (csize == 0) {
 2578            finished = true;
 2579          } else {
 1580             if (count < 0) {
 0581              throw new ZipException("EOF in stored block");
 582            }
 583          }
 584          break;
 585      }
 586
 95587       if (count > 0) {
 79588        crc.Update(buffer, offset, count);
 589      }
 590
 95591       if (finished) {
 18592        CompleteCloseEntry(true);
 593      }
 594
 95595      return count;
 596    }
 597
 598    /// <summary>
 599    /// Closes the zip input stream
 600    /// </summary>
 601    public override void Close()
 602    {
 61603      internalReader = new ReadDataHandler(ReadingNotAvailable);
 61604      crc = null;
 61605      entry = null;
 606
 61607      base.Close();
 61608    }
 609  }
 610}