//------------------------------------------------------ // // IDeflateTransform Interface // //------------------------------------------------------ /// <summary> /// Decompress delegate - invoke ZLib in a manner consistent with RMA/Office /// </summary> /// <param name="source">stream to read from</param> /// <param name="sink">stream to write to</param> public void Decompress(Stream source, Stream sink) { if (source == null) { throw new ArgumentNullException("source"); } if (sink == null) { throw new ArgumentNullException("sink"); } Invariant.Assert(source.CanRead); Invariant.Assert(sink.CanWrite, "Logic Error - Cannot decompress into a read-only stream"); // remember this for later long storedPosition = -1; try { if (source.CanSeek) { storedPosition = source.Position; source.Position = 0; } if (sink.CanSeek) { sink.Position = 0; } // zlib state ZLibNative.ZLibStreamHandle zStream; // initialize the zlib library ZLibNative.ErrorCode retVal = ZLibNative.CreateZLibStreamForInflate(out zStream, DEFAULT_WINDOW_BITS); ThrowIfZLibError(retVal); byte[] sourceBuf = null; // source buffer byte[] sinkBuf = null; // destination buffer - where to write data GCHandle gcSourceBuf = new GCHandle(); // Preallocate these so we can safely access them GCHandle gcSinkBuf = new GCHandle(); // in the next finally block. try { // read all available data // each block is preceded by a header that is 3 ulongs int uncompressedSize, compressedSize; long destStreamLength = 0; // keep track of decompressed size while (ReadBlockHeader(source, out uncompressedSize, out compressedSize)) { // ensure we have space AllocOrRealloc(compressedSize, ref sourceBuf, ref gcSourceBuf); AllocOrRealloc(uncompressedSize, ref sinkBuf, ref gcSinkBuf); // read the data into the sourceBuf int bytesRead = PackagingUtilities.ReliableRead(source, sourceBuf, 0, compressedSize); if (bytesRead > 0) { if (compressedSize != bytesRead) { throw new FileFormatException(SR.Get(SRID.CorruptStream)); } // prepare structure // The buffer pointers must be reset for every call // because ZLibNative.Inflate modifies them zStream.NextIn = gcSourceBuf.AddrOfPinnedObject(); zStream.NextOut = gcSinkBuf.AddrOfPinnedObject(); zStream.AvailIn = (uint)bytesRead; // this is number of bytes available for decompression at pInBuf and is updated by ums_deflate call zStream.AvailOut = (uint)sinkBuf.Length; // this is the number of bytes free in pOutBuf and is updated by ums_deflate call // InvokeZLib does the actual interop. It updates zStream, and sinkBuf (sourceBuf passed by ref to avoid copying) // and leaves the decompressed data in sinkBuf. // int decompressedSize = InvokeZLib(bytesRead, ref zStream, ref sourceBuf, ref sinkBuf, pSource, pSink, false); retVal = zStream.Inflate(ZLibNative.FlushCode.SyncFlush); ThrowIfZLibError(retVal); checked { int decompressedSize = sinkBuf.Length - (int)zStream.AvailOut; // verify that data matches header if (decompressedSize != uncompressedSize) { throw new FileFormatException(SR.Get(SRID.CorruptStream)); } destStreamLength += decompressedSize; // write to the base stream sink.Write(sinkBuf, 0, decompressedSize); } } else { // block header but no block data if (compressedSize != 0) { throw new FileFormatException(SR.Get(SRID.CorruptStream)); } } } // make sure we truncate if the destination stream was longer than this current decompress if (sink.CanSeek) { sink.SetLength(destStreamLength); } } finally { if (gcSourceBuf.IsAllocated) { gcSourceBuf.Free(); } if (gcSinkBuf.IsAllocated) { gcSinkBuf.Free(); } } } finally { // seek to the current logical position before returning if (source.CanSeek) { source.Position = storedPosition; } } }
private ZLibNative.ErrorCode Inflate(ZLibNative.FlushCode flushCode) { ZLibNative.ErrorCode errC; try { errC = _zlibStream.Inflate(flushCode); } catch (Exception cause) // could not load the Zlib DLL correctly { throw new ZLibException("SR.ZLibErrorDLLLoadError", cause); } switch (errC) { case ZLibNative.ErrorCode.Ok: // progress has been made inflating case ZLibNative.ErrorCode.StreamEnd: // The end of the input stream has been reached return errC; case ZLibNative.ErrorCode.BufError: // No room in the output buffer - inflate() can be called again with more space to continue return errC; case ZLibNative.ErrorCode.MemError: // Not enough memory to complete the operation throw new ZLibException("SR.ZLibErrorNotEnoughMemory", "inflate_", (int)errC, _zlibStream.GetErrorMessage()); case ZLibNative.ErrorCode.DataError: // The input data was corrupted (input stream not conforming to the zlib format or incorrect check value) throw new InvalidDataException("SR.UnsupportedCompression"); case ZLibNative.ErrorCode.StreamError: //the stream structure was inconsistent (for example if next_in or next_out was NULL), throw new ZLibException("SR.ZLibErrorInconsistentStream", "inflate_", (int)errC, _zlibStream.GetErrorMessage()); default: throw new ZLibException("SR.ZLibErrorUnexpected", "inflate_", (int)errC, _zlibStream.GetErrorMessage()); } }
/// <summary> /// Compress delegate - invoke ZLib in a manner consistent with RMA/Office /// </summary> /// <param name="source"></param> /// <param name="sink"></param> /// <remarks>We are careful to avoid use of Position, Length or SetLength on non-seekable streams. If /// source or sink are non-seekable, it is assumed that positions are correctly set upon entry and that /// they need not be restored. We also assume that destination stream length need not be truncated.</remarks> public void Compress(Stream source, Stream sink) { if (source == null) { throw new ArgumentNullException("source"); } if (sink == null) { throw new ArgumentNullException("sink"); } Invariant.Assert(source.CanRead); Invariant.Assert(sink.CanWrite, "Logic Error - Cannot compress into a read-only stream"); // remember this for later if possible long storedPosition = -1; // default to illegal value to catch any logic errors try { int sourceBufferSize; // don't allocate 4k for really tiny source streams if (source.CanSeek) { storedPosition = source.Position; source.Position = 0; // Casting result to int is safe because _defaultBlockSize is very small and the result // of Math.Min(x, _defaultBlockSize) must be no larger than _defaultBlockSize. sourceBufferSize = (int)(Math.Min(source.Length, (long)_defaultBlockSize)); } else { sourceBufferSize = _defaultBlockSize; // can't call Length so fallback to default } if (sink.CanSeek) { sink.Position = 0; } // zlib state ZLibNative.ZLibStreamHandle zStream; // initialize the zlib library ZLibNative.ErrorCode retVal = ZLibNative.CreateZLibStreamForDeflate( out zStream, ZLibNative.CompressionLevel.DefaultCompression, DEFAULT_WINDOW_BITS, DEFAULT_MEM_LEVEL, ZLibNative.CompressionStrategy.DefaultStrategy); ThrowIfZLibError(retVal); // where to write data - can actually grow if data is uncompressible long destStreamLength = 0; byte[] sourceBuf = null; // source buffer byte[] sinkBuf = null; // destination buffer GCHandle gcSourceBuf = new GCHandle(); GCHandle gcSinkBuf = new GCHandle(); try { // allocate managed buffers AllocOrRealloc(sourceBufferSize, ref sourceBuf, ref gcSourceBuf); AllocOrRealloc(_defaultBlockSize + (_defaultBlockSize >> 1), ref sinkBuf, ref gcSinkBuf); // while (more data is available) // - read into the sourceBuf // - compress into the sinkBuf // - emit the header // - write out to the _baseStream // Suppress 6518 Local IDisposable object not disposed: // Reason: The stream is not owned by us, therefore we cannot // close the BinaryWriter as it will Close the stream underneath. #pragma warning disable 6518 BinaryWriter writer = new BinaryWriter(sink); int bytesRead; while ((bytesRead = PackagingUtilities.ReliableRead(source, sourceBuf, 0, sourceBuf.Length)) > 0) { Invariant.Assert(bytesRead <= sourceBufferSize); // prepare structure // these pointers must be re-assigned for each loop because // ums_deflate modifies them zStream.NextIn = gcSourceBuf.AddrOfPinnedObject(); zStream.NextOut = gcSinkBuf.AddrOfPinnedObject(); zStream.AvailIn = (uint)bytesRead; // this is number of bytes available for compression at pInBuf and is updated by ums_deflate call zStream.AvailOut = (uint)sinkBuf.Length; // this is the number of bytes free in pOutBuf and is updated by ums_deflate call // cast is safe because SyncFlush is a constant retVal = zStream.Deflate(ZLibNative.FlushCode.SyncFlush); ThrowIfZLibError(retVal); checked { int compressedSize = sinkBuf.Length - (int)zStream.AvailOut; Invariant.Assert(compressedSize > 0, "compressing non-zero bytes creates a non-empty block"); // This should never happen because our destination buffer // is twice as large as our source buffer Invariant.Assert(zStream.AvailIn == 0, "Expecting all data to be compressed!"); // write the header writer.Write(_blockHeaderToken); // token writer.Write((UInt32)bytesRead); writer.Write((UInt32)compressedSize); destStreamLength += _headerBuf.Length; // write to the base stream sink.Write(sinkBuf, 0, compressedSize); destStreamLength += compressedSize; } } // post-compression // truncate if necessary if (sink.CanSeek) { sink.SetLength(destStreamLength); } } finally { if (gcSourceBuf.IsAllocated) { gcSourceBuf.Free(); } if (gcSinkBuf.IsAllocated) { gcSinkBuf.Free(); } } #pragma warning restore 6518 } finally { // seek to the current logical position before returning if (sink.CanSeek) { source.Position = storedPosition; } } }
/// <summary> /// Wrapper around the ZLib inflate function, configuring the stream appropriately. /// </summary> private unsafe ZLibNative.ErrorCode ReadInflateOutput(IntPtr bufPtr, int length, ZLibNative.FlushCode flushCode, out int bytesRead) { lock (_syncLock) { _zlibStream.NextOut = bufPtr; _zlibStream.AvailOut = (uint)length; ZLibNative.ErrorCode errC = Inflate(flushCode); bytesRead = length - (int)_zlibStream.AvailOut; return errC; } }