public GzipUncompressionFilter() : base() { _crc = new PhpHash.HashPhpResource.CRC32B(); _state = UncompressionState.Header; _chunkQueue = new BinaryChunkQueue(); }
/// <summary> /// Changes state based on header flags. Is called by header-handling states only. /// </summary> private void UpdateHeaderState() { switch (_state) { case UncompressionState.Header: if (HeaderFlag(Zlib.GZIP_HEADER_EXTRAFIELD)) _state = UncompressionState.HeaderExtraField; else if (HeaderFlag(Zlib.GZIP_HEADER_FILENAME)) _state = UncompressionState.HeaderFilename; else if (HeaderFlag(Zlib.GZIP_HEADER_COMMENT)) _state = UncompressionState.HeaderComment; else if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) _state = UncompressionState.HeaderCRC; else _state = UncompressionState.Data; break; case UncompressionState.HeaderExtraField: if (HeaderFlag(Zlib.GZIP_HEADER_FILENAME)) _state = UncompressionState.HeaderFilename; else if (HeaderFlag(Zlib.GZIP_HEADER_COMMENT)) _state = UncompressionState.HeaderComment; else if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) _state = UncompressionState.HeaderCRC; else _state = UncompressionState.Data; break; case UncompressionState.HeaderFilename: if (HeaderFlag(Zlib.GZIP_HEADER_COMMENT)) _state = UncompressionState.HeaderComment; else if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) _state = UncompressionState.HeaderCRC; else _state = UncompressionState.Data; break; case UncompressionState.HeaderComment: if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) _state = UncompressionState.HeaderCRC; else _state = UncompressionState.Data; break; case UncompressionState.HeaderCRC: _state = UncompressionState.Data; break; } }
public override object Filter(object input, bool closing) { // TODO: not the most efficient method - after the filters are upgraded to bucket lists, update this PhpBytes bInput = Core.Convert.ObjectToPhpBytes(input); if (bInput != null) { if (_state == UncompressionState.Failed) { // failed filter should not get any more calls PhpException.Throw(PhpError.Warning, "using filter in failed state"); return null; } if (_state == UncompressionState.PostTrailer) { // post trailer - ignore everything if (closing) { _state = UncompressionState.Finished; } return new PhpBytes(); } if (_state == UncompressionState.Finished) { // finished filter should not get any more data PhpException.Throw(PhpError.Warning, "using filter in finished state"); return null; } if (_state == UncompressionState.Passthrough) { // this is not gzip data format - pass the data through return new PhpBytes(bInput); } // enqueue the block _chunkQueue.EnqueueByteBlock(bInput.ReadonlyData, 0, bInput.Length); if (_state == UncompressionState.Header) { #region Header handling //beginning of the stream byte[] beginning = _chunkQueue.DequeueByteBlock(Zlib.GZIP_HEADER_LENGTH); if (beginning == null && !closing) { // we do not have enough data, but we know there would be more data ahead return new PhpBytes(); } else { //check the header format if (beginning.Length >= 2 && beginning[0] == Zlib.GZIP_HEADER[0] && beginning[1] == Zlib.GZIP_HEADER[1]) { //header magic bytes are OK if (beginning.Length < Zlib.GZIP_HEADER_LENGTH) { // header is too short -> this is an error PhpException.Throw(PhpError.Warning, "unexpected end of file"); return null; } else { // check the rest of the header if (beginning[2] != Zlib.Z_DEFLATED) { PhpException.Throw(PhpError.Warning, "unknown compression method"); return null; } if ((beginning[3] & Zlib.GZIP_HEADER_RESERVED_FLAGS) != 0) { PhpException.Throw(PhpError.Warning, "unknown header flags set"); return null; } _headerFlags = beginning[3]; //change the header state based on the header flags UpdateHeaderState(); } } else { // this is not a gzip format -> passthrough the data _state = UncompressionState.Passthrough; return new PhpBytes(beginning); } } #endregion } if (_state == UncompressionState.HeaderExtraField) { #region Header Extra Field Handling if (_extraHeaderLength == null) { //length was not yet detected if (_chunkQueue.AvailableBytes < 2) { //wait for more input return new PhpBytes(); } else { //assemble length _extraHeaderLength = _chunkQueue.DequeueByte(); _extraHeaderLength &= (_chunkQueue.DequeueByte() << 8); } } if (_extraHeaderLength != null) { //length was already read if (_chunkQueue.AvailableBytes < _extraHeaderLength) { //wait for more input return new PhpBytes(); } else { Debug.Assert(_extraHeaderLength.HasValue); //skip the extra header _chunkQueue.SkipByteBlock(_extraHeaderLength.Value); UpdateHeaderState(); } } #endregion } if (_state == UncompressionState.HeaderFilename || _state == UncompressionState.HeaderComment) { #region Header Filename and Comment Handling // filename or comment // cycle until input ends or zero character is encountered while (true) { byte? nextByte = _chunkQueue.DequeueByte(); if (nextByte == null) { //wait for more input return new PhpBytes(); } if (nextByte == 0) { // end the cycle break; } } // go to the next state UpdateHeaderState(); #endregion } if (_state == UncompressionState.HeaderCRC) { #region CRC Handling // header CRC if (_chunkQueue.AvailableBytes < 2) { //wait for more input return new PhpBytes(); } else { //skip the CRC _chunkQueue.DequeueByte(); _chunkQueue.DequeueByte(); UpdateHeaderState(); } #endregion } //filled by data handling and sometimes returned by trailer handling byte[] output = null; if (_state == UncompressionState.Data) { #region Deflated Data Handling //get all available bytes byte[] inputBytes = _chunkQueue.DequeueByteBlock(_chunkQueue.AvailableBytes); int inputOffset = 0; // perform the inner operation try { output = FilterInner(inputBytes, ref inputOffset, closing); } catch { // exception was thrown _state = UncompressionState.Failed; throw; } if (output == null) { // error happened and exception was not thrown _state = UncompressionState.Failed; return null; } // update the hash algorithm _crc.Update(output); if (inputOffset != inputBytes.Length) { // push the rest of the data into the chunk queue _chunkQueue.PushByteBlock(inputBytes, inputOffset, inputBytes.Length - inputOffset); // end of deflated block reached _state = UncompressionState.Trailer; // pass through to Trailer handling } else { //normal decompressed block - return it return new PhpBytes(output); } #endregion } if (_state == UncompressionState.Trailer) { #region Trailer Handling // the deflate block has already ended, we are processing trailer if (closing || _chunkQueue.AvailableBytes >= Zlib.GZIP_FOOTER_LENGTH) { byte[] trailer; trailer = _chunkQueue.DequeueByteBlock(_chunkQueue.AvailableBytes); if (trailer.Length >= Zlib.GZIP_FOOTER_LENGTH) { byte[] crc = _crc.Final(); if (crc[3] != trailer[0] || crc[2] != trailer[1] || crc[1] != trailer[2] || crc[0] != trailer[3]) { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "incorrect data check"); return null; } if (BitConverter.ToInt32(trailer, 4) != _stream.total_out) { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "incorrect length check"); return null; } _state = closing ? UncompressionState.Finished : UncompressionState.PostTrailer; // everything is fine, return the output if available return output != null ? new PhpBytes(output) : new PhpBytes(); } else { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "unexpected end of file"); return null; } } else { //stream is not closing yet - return the remaining output, otherwise empty return output != null ? new PhpBytes(output) : new PhpBytes(); } #endregion } //this should not happen Debug.Fail(); return null; } else { Debug.Fail("GzipUncompressionFilter expects chunks to be convertible to PhpBytes."); return null; } }
/// <summary> /// Changes state based on header flags. Is called by header-handling states only. /// </summary> private void UpdateHeaderState() { switch (_state) { case UncompressionState.Header: if (HeaderFlag(Zlib.GZIP_HEADER_EXTRAFIELD)) { _state = UncompressionState.HeaderExtraField; } else if (HeaderFlag(Zlib.GZIP_HEADER_FILENAME)) { _state = UncompressionState.HeaderFilename; } else if (HeaderFlag(Zlib.GZIP_HEADER_COMMENT)) { _state = UncompressionState.HeaderComment; } else if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) { _state = UncompressionState.HeaderCRC; } else { _state = UncompressionState.Data; } break; case UncompressionState.HeaderExtraField: if (HeaderFlag(Zlib.GZIP_HEADER_FILENAME)) { _state = UncompressionState.HeaderFilename; } else if (HeaderFlag(Zlib.GZIP_HEADER_COMMENT)) { _state = UncompressionState.HeaderComment; } else if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) { _state = UncompressionState.HeaderCRC; } else { _state = UncompressionState.Data; } break; case UncompressionState.HeaderFilename: if (HeaderFlag(Zlib.GZIP_HEADER_COMMENT)) { _state = UncompressionState.HeaderComment; } else if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) { _state = UncompressionState.HeaderCRC; } else { _state = UncompressionState.Data; } break; case UncompressionState.HeaderComment: if (HeaderFlag(Zlib.GZIP_HEADER_CRC)) { _state = UncompressionState.HeaderCRC; } else { _state = UncompressionState.Data; } break; case UncompressionState.HeaderCRC: _state = UncompressionState.Data; break; } }
public override object Filter(object input, bool closing) { // TODO: not the most efficient method - after the filters are upgraded to bucket lists, update this PhpBytes bInput = Core.Convert.ObjectToPhpBytes(input); if (bInput != null) { if (_state == UncompressionState.Failed) { // failed filter should not get any more calls PhpException.Throw(PhpError.Warning, "using filter in failed state"); return(null); } if (_state == UncompressionState.PostTrailer) { // post trailer - ignore everything if (closing) { _state = UncompressionState.Finished; } return(new PhpBytes()); } if (_state == UncompressionState.Finished) { // finished filter should not get any more data PhpException.Throw(PhpError.Warning, "using filter in finished state"); return(null); } if (_state == UncompressionState.Passthrough) { // this is not gzip data format - pass the data through return(new PhpBytes(bInput)); } // enqueue the block _chunkQueue.EnqueueByteBlock(bInput.ReadonlyData, 0, bInput.Length); if (_state == UncompressionState.Header) { #region Header handling //beginning of the stream byte[] beginning = _chunkQueue.DequeueByteBlock(Zlib.GZIP_HEADER_LENGTH); if (beginning == null && !closing) { // we do not have enough data, but we know there would be more data ahead return(new PhpBytes()); } else { //check the header format if (beginning.Length >= 2 && beginning[0] == Zlib.GZIP_HEADER[0] && beginning[1] == Zlib.GZIP_HEADER[1]) { //header magic bytes are OK if (beginning.Length < Zlib.GZIP_HEADER_LENGTH) { // header is too short -> this is an error PhpException.Throw(PhpError.Warning, "unexpected end of file"); return(null); } else { // check the rest of the header if (beginning[2] != Zlib.Z_DEFLATED) { PhpException.Throw(PhpError.Warning, "unknown compression method"); return(null); } if ((beginning[3] & Zlib.GZIP_HEADER_RESERVED_FLAGS) != 0) { PhpException.Throw(PhpError.Warning, "unknown header flags set"); return(null); } _headerFlags = beginning[3]; //change the header state based on the header flags UpdateHeaderState(); } } else { // this is not a gzip format -> passthrough the data _state = UncompressionState.Passthrough; return(new PhpBytes(beginning)); } } #endregion } if (_state == UncompressionState.HeaderExtraField) { #region Header Extra Field Handling if (_extraHeaderLength == null) { //length was not yet detected if (_chunkQueue.AvailableBytes < 2) { //wait for more input return(new PhpBytes()); } else { //assemble length _extraHeaderLength = _chunkQueue.DequeueByte(); _extraHeaderLength &= (_chunkQueue.DequeueByte() << 8); } } if (_extraHeaderLength != null) { //length was already read if (_chunkQueue.AvailableBytes < _extraHeaderLength) { //wait for more input return(new PhpBytes()); } else { Debug.Assert(_extraHeaderLength.HasValue); //skip the extra header _chunkQueue.SkipByteBlock(_extraHeaderLength.Value); UpdateHeaderState(); } } #endregion } if (_state == UncompressionState.HeaderFilename || _state == UncompressionState.HeaderComment) { #region Header Filename and Comment Handling // filename or comment // cycle until input ends or zero character is encountered while (true) { byte?nextByte = _chunkQueue.DequeueByte(); if (nextByte == null) { //wait for more input return(new PhpBytes()); } if (nextByte == 0) { // end the cycle break; } } // go to the next state UpdateHeaderState(); #endregion } if (_state == UncompressionState.HeaderCRC) { #region CRC Handling // header CRC if (_chunkQueue.AvailableBytes < 2) { //wait for more input return(new PhpBytes()); } else { //skip the CRC _chunkQueue.DequeueByte(); _chunkQueue.DequeueByte(); UpdateHeaderState(); } #endregion } //filled by data handling and sometimes returned by trailer handling byte[] output = null; if (_state == UncompressionState.Data) { #region Deflated Data Handling //get all available bytes byte[] inputBytes = _chunkQueue.DequeueByteBlock(_chunkQueue.AvailableBytes); int inputOffset = 0; // perform the inner operation try { output = FilterInner(inputBytes, ref inputOffset, closing); } catch { // exception was thrown _state = UncompressionState.Failed; throw; } if (output == null) { // error happened and exception was not thrown _state = UncompressionState.Failed; return(null); } // update the hash algorithm _crc.Update(output); if (inputOffset != inputBytes.Length) { // push the rest of the data into the chunk queue _chunkQueue.PushByteBlock(inputBytes, inputOffset, inputBytes.Length - inputOffset); // end of deflated block reached _state = UncompressionState.Trailer; // pass through to Trailer handling } else { //normal decompressed block - return it return(new PhpBytes(output)); } #endregion } if (_state == UncompressionState.Trailer) { #region Trailer Handling // the deflate block has already ended, we are processing trailer if (closing || _chunkQueue.AvailableBytes >= Zlib.GZIP_FOOTER_LENGTH) { byte[] trailer; trailer = _chunkQueue.DequeueByteBlock(_chunkQueue.AvailableBytes); if (trailer.Length >= Zlib.GZIP_FOOTER_LENGTH) { byte[] crc = _crc.Final(); if (crc[3] != trailer[0] || crc[2] != trailer[1] || crc[1] != trailer[2] || crc[0] != trailer[3]) { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "incorrect data check"); return(null); } if (BitConverter.ToInt32(trailer, 4) != _stream.total_out) { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "incorrect length check"); return(null); } _state = closing ? UncompressionState.Finished : UncompressionState.PostTrailer; // everything is fine, return the output if available return(output != null ? new PhpBytes(output) : new PhpBytes()); } else { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "unexpected end of file"); return(null); } } else { //stream is not closing yet - return the remaining output, otherwise empty return(output != null ? new PhpBytes(output) : new PhpBytes()); } #endregion } //this should not happen Debug.Fail(null); return(null); } else { Debug.Fail("GzipUncompressionFilter expects chunks to be convertible to PhpBytes."); return(null); } }