/// <summary>Creates a new (pure Java) gzip decompressor.</summary> public BuiltInGzipDecompressor() { // if read as LE short int // 'true' (nowrap) => Inflater will handle raw deflate stream only state = BuiltInGzipDecompressor.GzipStateLabel.HeaderBasic; crc.Reset(); }
// switching to Inflater now /// <summary>Parse the gzip trailer (assuming we're in the appropriate state).</summary> /// <remarks> /// Parse the gzip trailer (assuming we're in the appropriate state). /// In order to deal with degenerate cases (e.g., user buffer is one byte /// long), we copy trailer bytes (all 8 of 'em) to a local buffer.</p> /// See http://www.ietf.org/rfc/rfc1952.txt for the gzip spec. /// </remarks> /// <exception cref="System.IO.IOException"/> private void ExecuteTrailerState() { if (userBufLen <= 0) { return; } // verify that the CRC-32 of the decompressed stream matches the value // stored in the gzip trailer if (state == BuiltInGzipDecompressor.GzipStateLabel.TrailerCrc) { // localBuf was empty before we handed off to Inflater, so we handle this // exactly like header fields System.Diagnostics.Debug.Assert((localBufOff < 4)); // initially 0, but may need multiple calls int n = Math.Min(userBufLen, 4 - localBufOff); CopyBytesToLocal(n); if (localBufOff >= 4) { long streamCRC = ReadUIntLE(localBuf, 0); if (streamCRC != crc.GetValue()) { throw new IOException("gzip stream CRC failure"); } localBufOff = 0; crc.Reset(); state = BuiltInGzipDecompressor.GzipStateLabel.TrailerSize; } } if (userBufLen <= 0) { return; } // verify that the mod-2^32 decompressed stream size matches the value // stored in the gzip trailer if (state == BuiltInGzipDecompressor.GzipStateLabel.TrailerSize) { System.Diagnostics.Debug.Assert((localBufOff < 4)); // initially 0, but may need multiple calls int n = Math.Min(userBufLen, 4 - localBufOff); CopyBytesToLocal(n); // modifies userBufLen, etc. if (localBufOff >= 4) { // should be strictly == long inputSize = ReadUIntLE(localBuf, 0); if (inputSize != (inflater.GetBytesWritten() & unchecked ((long)(0xffffffffL)))) { throw new IOException("stored gzip size doesn't match decompressed size"); } localBufOff = 0; state = BuiltInGzipDecompressor.GzipStateLabel.Finished; } } if (state == BuiltInGzipDecompressor.GzipStateLabel.Finished) { return; } }
/// <summary> /// Resets everything, including the input buffer, regardless of whether the /// current gzip substream is finished.</p> /// </summary> public virtual void Reset() { lock (this) { // could optionally emit INFO message if state != GzipStateLabel.FINISHED inflater.Reset(); state = BuiltInGzipDecompressor.GzipStateLabel.HeaderBasic; crc.Reset(); userBufOff = userBufLen = 0; localBufOff = 0; headerBytesRead = 0; trailerBytesRead = 0; numExtraFieldBytesRemaining = -1; hasExtraField = false; hasFilename = false; hasComment = false; hasHeaderCRC = false; } }
/// <summary>Parse the gzip header (assuming we're in the appropriate state).</summary> /// <remarks> /// Parse the gzip header (assuming we're in the appropriate state). /// In order to deal with degenerate cases (e.g., user buffer is one byte /// long), we copy (some) header bytes to another buffer. (Filename, /// comment, and extra-field bytes are simply skipped.)</p> /// See http://www.ietf.org/rfc/rfc1952.txt for the gzip spec. Note that /// no version of gzip to date (at least through 1.4.0, 2010-01-20) supports /// the FHCRC header-CRC16 flagbit; instead, the implementation treats it /// as a multi-file continuation flag (which it also doesn't support). :-( /// Sun's JDK v6 (1.6) supports the header CRC, however, and so do we. /// </remarks> /// <exception cref="System.IO.IOException"/> private void ExecuteHeaderState() { // this can happen because DecompressorStream's decompress() is written // to call decompress() first, setInput() second: if (userBufLen <= 0) { return; } // "basic"/required header: somewhere in first 10 bytes if (state == BuiltInGzipDecompressor.GzipStateLabel.HeaderBasic) { int n = Math.Min(userBufLen, 10 - localBufOff); // (or 10-headerBytesRead) CheckAndCopyBytesToLocal(n); // modifies userBufLen, etc. if (localBufOff >= 10) { // should be strictly == ProcessBasicHeader(); // sig, compression method, flagbits localBufOff = 0; // no further need for basic header state = BuiltInGzipDecompressor.GzipStateLabel.HeaderExtraField; } } if (userBufLen <= 0) { return; } // optional header stuff (extra field, filename, comment, header CRC) if (state == BuiltInGzipDecompressor.GzipStateLabel.HeaderExtraField) { if (hasExtraField) { // 2 substates: waiting for 2 bytes => get numExtraFieldBytesRemaining, // or already have 2 bytes & waiting to finish skipping specified length if (numExtraFieldBytesRemaining < 0) { int n = Math.Min(userBufLen, 2 - localBufOff); CheckAndCopyBytesToLocal(n); if (localBufOff >= 2) { numExtraFieldBytesRemaining = ReadUShortLE(localBuf, 0); localBufOff = 0; } } if (numExtraFieldBytesRemaining > 0 && userBufLen > 0) { int n = Math.Min(userBufLen, numExtraFieldBytesRemaining); CheckAndSkipBytes(n); // modifies userBufLen, etc. numExtraFieldBytesRemaining -= n; } if (numExtraFieldBytesRemaining == 0) { state = BuiltInGzipDecompressor.GzipStateLabel.HeaderFilename; } } else { state = BuiltInGzipDecompressor.GzipStateLabel.HeaderFilename; } } if (userBufLen <= 0) { return; } if (state == BuiltInGzipDecompressor.GzipStateLabel.HeaderFilename) { if (hasFilename) { bool doneWithFilename = CheckAndSkipBytesUntilNull(); if (!doneWithFilename) { return; } } // exit early: used up entire buffer without hitting NULL state = BuiltInGzipDecompressor.GzipStateLabel.HeaderComment; } if (userBufLen <= 0) { return; } if (state == BuiltInGzipDecompressor.GzipStateLabel.HeaderComment) { if (hasComment) { bool doneWithComment = CheckAndSkipBytesUntilNull(); if (!doneWithComment) { return; } } // exit early: used up entire buffer state = BuiltInGzipDecompressor.GzipStateLabel.HeaderCrc; } if (userBufLen <= 0) { return; } if (state == BuiltInGzipDecompressor.GzipStateLabel.HeaderCrc) { if (hasHeaderCRC) { System.Diagnostics.Debug.Assert((localBufOff < 2)); int n = Math.Min(userBufLen, 2 - localBufOff); CopyBytesToLocal(n); if (localBufOff >= 2) { long headerCRC = ReadUShortLE(localBuf, 0); if (headerCRC != (crc.GetValue() & unchecked ((int)(0xffff)))) { throw new IOException("gzip header CRC failure"); } localBufOff = 0; crc.Reset(); state = BuiltInGzipDecompressor.GzipStateLabel.DeflateStream; } } else { crc.Reset(); // will reuse for CRC-32 of uncompressed data state = BuiltInGzipDecompressor.GzipStateLabel.DeflateStream; } } }
// note: might be zero /// <summary> /// Decompress the data (gzip header, deflate stream, gzip trailer) in the /// provided buffer. /// </summary> /// <returns>the number of decompressed bytes placed into b</returns> /// <exception cref="System.IO.IOException"/> public virtual int Decompress(byte[] b, int off, int len) { lock (this) { /* From the caller's perspective, this is where the state machine lives. * The code is written such that we never return from decompress() with * data remaining in userBuf unless we're in FINISHED state and there was * data beyond the current gzip member (e.g., we're within a concatenated * gzip stream). If this ever changes, {@link #needsInput()} will also * need to be modified (i.e., uncomment the userBufLen condition). * * The actual deflate-stream processing (decompression) is handled by * Java's Inflater class. Unlike the gzip header/trailer code (execute* * methods below), the deflate stream is never copied; Inflater operates * directly on the user's buffer. */ int numAvailBytes = 0; if (state != BuiltInGzipDecompressor.GzipStateLabel.DeflateStream) { ExecuteHeaderState(); if (userBufLen <= 0) { return(numAvailBytes); } } // "executeDeflateStreamState()" if (state == BuiltInGzipDecompressor.GzipStateLabel.DeflateStream) { // hand off user data (or what's left of it) to Inflater--but note that // Inflater may not have consumed all of previous bufferload (e.g., if // data highly compressed or output buffer very small), in which case // userBufLen will be zero if (userBufLen > 0) { inflater.SetInput(userBuf, userBufOff, userBufLen); userBufOff += userBufLen; userBufLen = 0; } // now decompress it into b[] try { numAvailBytes = inflater.Inflate(b, off, len); } catch (SharpZipBaseException dfe) { throw new IOException(dfe.Message); } crc.Update(b, off, numAvailBytes); // CRC-32 is on _uncompressed_ data if (inflater.IsFinished) { state = BuiltInGzipDecompressor.GzipStateLabel.TrailerCrc; int bytesRemaining = inflater.RemainingInput; System.Diagnostics.Debug.Assert((bytesRemaining >= 0), "logic error: Inflater finished; byte-count is inconsistent" ); // could save a copy of userBufLen at call to inflater.setInput() and // verify that bytesRemaining <= origUserBufLen, but would have to // be a (class) member variable...seems excessive for a sanity check userBufOff -= bytesRemaining; userBufLen = bytesRemaining; } else { // or "+=", but guaranteed 0 coming in return(numAvailBytes); } } // minor optimization ExecuteTrailerState(); return(numAvailBytes); } }