diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 8bfc86cea..c979b7000 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using SharpCompress.Common; using SharpCompress.Factories; +using SharpCompress.IO; using SharpCompress.Readers; namespace SharpCompress.Archives; @@ -19,7 +20,7 @@ public static class ArchiveFactory public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) { readerOptions ??= new ReaderOptions(); - + stream = new SharpCompressStream(stream, bufferSize: readerOptions.BufferSize); return FindFactory(stream).Open(stream, readerOptions); } @@ -165,14 +166,22 @@ private static T FindFactory(Stream stream) ); } - public static bool IsArchive(string filePath, out ArchiveType? type) + public static bool IsArchive( + string filePath, + out ArchiveType? type, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { filePath.CheckNotNullOrEmpty(nameof(filePath)); using Stream s = File.OpenRead(filePath); - return IsArchive(s, out type); + return IsArchive(s, out type, bufferSize); } - public static bool IsArchive(Stream stream, out ArchiveType? type) + public static bool IsArchive( + Stream stream, + out ArchiveType? type, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { type = null; stream.CheckNotNull(nameof(stream)); diff --git a/src/SharpCompress/Archives/AutoArchiveFactory.cs b/src/SharpCompress/Archives/AutoArchiveFactory.cs index 14038ea19..78313df54 100644 --- a/src/SharpCompress/Archives/AutoArchiveFactory.cs +++ b/src/SharpCompress/Archives/AutoArchiveFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using SharpCompress.Common; @@ -14,8 +14,11 @@ class AutoArchiveFactory : IArchiveFactory public IEnumerable GetSupportedExtensions() => throw new NotSupportedException(); - public bool IsArchive(Stream stream, string? password = null) => - throw new NotSupportedException(); + public bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => throw new NotSupportedException(); public FileInfo? GetFilePart(int index, FileInfo part1) => throw new NotSupportedException(); diff --git a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs index 90f6f16f8..7983bca4b 100644 --- a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs @@ -58,7 +58,7 @@ public override Stream OpenEntryStream() { //ensure new stream is at the start, this could be reset stream.Seek(0, SeekOrigin.Begin); - return NonDisposingStream.Create(stream); + return SharpCompressStream.Create(stream, leaveOpen: true); } internal override void Close() diff --git a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs index 32be7d9b4..147d187fe 100644 --- a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs @@ -58,7 +58,7 @@ public override Stream OpenEntryStream() { //ensure new stream is at the start, this could be reset stream.Seek(0, SeekOrigin.Begin); - return NonDisposingStream.Create(stream); + return SharpCompressStream.Create(stream, leaveOpen: true); } internal override void Close() diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 79cdcf3ea..d877626b7 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -122,24 +122,40 @@ public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null ); } - public static bool IsZipFile(string filePath, string? password = null) => - IsZipFile(new FileInfo(filePath), password); + public static bool IsZipFile( + string filePath, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => IsZipFile(new FileInfo(filePath), password, bufferSize); - public static bool IsZipFile(FileInfo fileInfo, string? password = null) + public static bool IsZipFile( + FileInfo fileInfo, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { if (!fileInfo.Exists) { return false; } using Stream stream = fileInfo.OpenRead(); - return IsZipFile(stream, password); + return IsZipFile(stream, password, bufferSize); } - public static bool IsZipFile(Stream stream, string? password = null) + public static bool IsZipFile( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); try { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + var header = headerFactory .ReadStreamHeader(stream) .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); @@ -159,11 +175,20 @@ public static bool IsZipFile(Stream stream, string? password = null) } } - public static bool IsZipMulti(Stream stream, string? password = null) + public static bool IsZipMulti( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); try { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + var header = headerFactory .ReadStreamHeader(stream) .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); @@ -202,7 +227,7 @@ protected override IEnumerable LoadVolumes(SourceStream stream) if (streams.Count() > 1) //test part 2 - true = multipart not split { streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception - var isZip = IsZipFile(streams[1], ReaderOptions.Password); + var isZip = IsZipFile(streams[1], ReaderOptions.Password, ReaderOptions.BufferSize); streams[1].Position -= 4; if (isZip) { diff --git a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs index 628e505fa..c01776e67 100644 --- a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs @@ -59,7 +59,7 @@ public override Stream OpenEntryStream() { //ensure new stream is at the start, this could be reset stream.Seek(0, SeekOrigin.Begin); - return NonDisposingStream.Create(stream); + return SharpCompressStream.Create(stream, leaveOpen: true); } internal override void Close() diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs index b3e7edea2..a0fe736a4 100644 --- a/src/SharpCompress/Common/EntryStream.cs +++ b/src/SharpCompress/Common/EntryStream.cs @@ -1,11 +1,33 @@ using System; using System.IO; +using System.IO.Compression; +using SharpCompress.IO; using SharpCompress.Readers; namespace SharpCompress.Common; -public class EntryStream : Stream +public class EntryStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly IReader _reader; private readonly Stream _stream; private bool _completed; @@ -15,6 +37,9 @@ internal EntryStream(IReader reader, Stream stream) { _reader = reader; _stream = stream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(EntryStream)); +#endif } /// @@ -32,11 +57,28 @@ protected override void Dispose(bool disposing) { SkipEntry(); } + + //Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised + if (_stream is IStreamStack ss) + { + if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream) + { + deflateStream.Flush(); //Deflate over reads. Knock it back + } + else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream) + { + lzmaStream.Flush(); //Lzma over reads. Knock it back + } + } + if (_isDisposed) { return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(EntryStream)); +#endif base.Dispose(disposing); _stream.Dispose(); } @@ -53,7 +95,7 @@ public override void Flush() { } public override long Position { - get => throw new NotSupportedException(); + get => _stream.Position; //throw new NotSupportedException(); set => throw new NotSupportedException(); } diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs index 09a2fdb8f..46e8120a8 100644 --- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs +++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs @@ -4,13 +4,38 @@ namespace SharpCompress.Common.Tar; -internal class TarReadOnlySubStream : NonDisposingStream +internal class TarReadOnlySubStream : SharpCompressStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => base.Stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private bool _isDisposed; private long _amountRead; public TarReadOnlySubStream(Stream stream, long bytesToRead) - : base(stream, throwOnDispose: false) => BytesLeftToRead = bytesToRead; + : base(stream, leaveOpen: true, throwOnDispose: false) + { + BytesLeftToRead = bytesToRead; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(TarReadOnlySubStream)); +#endif + } protected override void Dispose(bool disposing) { @@ -20,7 +45,9 @@ protected override void Dispose(bool disposing) } _isDisposed = true; - +#if DEBUG_STREAMS + this.DebugDispose(typeof(TarReadOnlySubStream)); +#endif if (disposing) { // Ensure we read all remaining blocks for this entry. diff --git a/src/SharpCompress/Common/Volume.cs b/src/SharpCompress/Common/Volume.cs index 3b48d1c10..54dc49953 100644 --- a/src/SharpCompress/Common/Volume.cs +++ b/src/SharpCompress/Common/Volume.cs @@ -17,8 +17,12 @@ internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0) _baseStream = stream; if (ReaderOptions.LeaveStreamOpen) { - stream = NonDisposingStream.Create(stream); + stream = SharpCompressStream.Create(stream, leaveOpen: true); } + + if (stream is IStreamStack ss) + ss.SetBuffer(ReaderOptions.BufferSize, true); + _actualStream = stream; } diff --git a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs index f8914c68c..50b94c1eb 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Common.Zip; @@ -9,8 +10,28 @@ internal enum CryptoMode Decrypt, } -internal class PkwareTraditionalCryptoStream : Stream +internal class PkwareTraditionalCryptoStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { return; } + } + int IStreamStack.BufferPosition + { + get => 0; + set { return; } + } + + void IStreamStack.SetPosition(long position) { } + private readonly PkwareTraditionalEncryptionData _encryptor; private readonly CryptoMode _mode; private readonly Stream _stream; @@ -25,6 +46,10 @@ CryptoMode mode _encryptor = encryptor; _stream = stream; _mode = mode; + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(PkwareTraditionalCryptoStream)); +#endif } public override bool CanRead => (_mode == CryptoMode.Decrypt); @@ -100,6 +125,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(PkwareTraditionalCryptoStream)); +#endif base.Dispose(disposing); _stream.Dispose(); } diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs index a3fa3caab..543d78d94 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs @@ -26,12 +26,12 @@ internal override Stream GetCompressedStream() ); if (LeaveStreamOpen) { - return NonDisposingStream.Create(_decompressionStream); + return SharpCompressStream.Create(_decompressionStream, leaveOpen: true); } return _decompressionStream; } - internal BinaryReader FixStreamedFileLocation(ref RewindableStream rewindableStream) + internal BinaryReader FixStreamedFileLocation(ref SharpCompressStream rewindableStream) { if (Header.IsDirectory) { @@ -49,7 +49,8 @@ internal BinaryReader FixStreamedFileLocation(ref RewindableStream rewindableStr if (_decompressionStream is DeflateStream deflateStream) { - rewindableStream.Rewind(deflateStream.InputBuffer); + ((IStreamStack)rewindableStream).StackSeek(0); + //rewindableStream.Rewind(deflateStream.InputBuffer); } Skipped = true; diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs index 508dfd1a3..723b59088 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -19,16 +20,12 @@ internal StreamingZipHeaderFactory( internal IEnumerable ReadStreamHeader(Stream stream) { - RewindableStream rewindableStream; - - if (stream is RewindableStream rs) - { - rewindableStream = rs; - } - else + if (stream is not SharpCompressStream) //ensure the stream is already a SharpCompressStream. So the buffer/size will already be set { - rewindableStream = new RewindableStream(stream); + throw new ArgumentException("Stream must be a SharpCompressStream", nameof(stream)); } + SharpCompressStream rewindableStream = (SharpCompressStream)stream; + while (true) { ZipHeader? header; @@ -43,9 +40,8 @@ internal IEnumerable ReadStreamHeader(Stream stream) { continue; } - reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( - ref rewindableStream - ); + + // removed requirement for FixStreamedFileLocation() var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; @@ -56,26 +52,32 @@ ref rewindableStream } _lastEntryHeader.Crc = crc; - // The DataDescriptor can be either 64bit or 32bit - var compressed_size = reader.ReadUInt32(); - var uncompressed_size = reader.ReadUInt32(); - - // Check if we have header or 64bit DataDescriptor + //attempt 32bit read + ulong compSize = reader.ReadUInt32(); + ulong uncompSize = reader.ReadUInt32(); headerBytes = reader.ReadUInt32(); - var test_header = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50); - var test_64bit = ((long)uncompressed_size << 32) | compressed_size; - if (test_64bit == _lastEntryHeader.CompressedSize && test_header) + //check for zip64 sentinel or unexpected header + bool isSentinel = compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF; + bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50; + + if (!isHeader && !isSentinel) { - _lastEntryHeader.UncompressedSize = - ((long)reader.ReadUInt32() << 32) | headerBytes; + //reshuffle into 64-bit values + compSize = (uncompSize << 32) | compSize; + uncompSize = ((ulong)headerBytes << 32) | reader.ReadUInt32(); headerBytes = reader.ReadUInt32(); } - else + else if (isSentinel) { - _lastEntryHeader.UncompressedSize = uncompressed_size; + //standards-compliant zip64 descriptor + compSize = reader.ReadUInt64(); + uncompSize = reader.ReadUInt64(); } + _lastEntryHeader.CompressedSize = (long)compSize; + _lastEntryHeader.UncompressedSize = (long)uncompSize; + if (pos.HasValue) { _lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize; @@ -86,9 +88,9 @@ ref rewindableStream if (_lastEntryHeader.Part is null) continue; - reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( - ref rewindableStream - ); + //reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( + // ref rewindableStream + //); var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; @@ -173,16 +175,11 @@ ref rewindableStream } // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor ) else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor)) { - var isRecording = rewindableStream.IsRecording; - if (!isRecording) - { - rewindableStream.StartRecording(); - } var nextHeaderBytes = reader.ReadUInt32(); + ((IStreamStack)rewindableStream).Rewind(sizeof(uint)); // Check if next data is PostDataDescriptor, streamed file with 0 length header.HasData = !IsHeader(nextHeaderBytes); - rewindableStream.Rewind(!isRecording); } else // We are not streaming and compressed size is 0, we have no data { diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs index 5742458a7..24de58a17 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs @@ -2,11 +2,32 @@ using System.Buffers.Binary; using System.IO; using System.Security.Cryptography; +using SharpCompress.IO; namespace SharpCompress.Common.Zip; -internal class WinzipAesCryptoStream : Stream +internal class WinzipAesCryptoStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private const int BLOCK_SIZE_IN_BYTES = 16; private readonly SymmetricAlgorithm _cipher; private readonly byte[] _counter = new byte[BLOCK_SIZE_IN_BYTES]; @@ -27,6 +48,10 @@ long length _stream = stream; _totalBytesLeftToRead = length; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(WinzipAesCryptoStream)); +#endif + _cipher = CreateCipher(winzipAesEncryptionData); var iv = new byte[BLOCK_SIZE_IN_BYTES]; @@ -64,6 +89,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(WinzipAesCryptoStream)); +#endif if (disposing) { //read out last 10 auth bytes diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 85b566d97..b6be47848 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -45,7 +45,7 @@ internal override Stream GetCompressedStream() ); if (LeaveStreamOpen) { - return NonDisposingStream.Create(decompressionStream); + return SharpCompressStream.Create(decompressionStream, leaveOpen: true); } return decompressionStream; } @@ -218,7 +218,7 @@ protected Stream GetCryptoStream(Stream plainStream) ) || Header.IsZip64 ) { - plainStream = NonDisposingStream.Create(plainStream); //make sure AES doesn't close + plainStream = SharpCompressStream.Create(plainStream, leaveOpen: true); //make sure AES doesn't close } else { diff --git a/src/SharpCompress/Compressors/ADC/ADCStream.cs b/src/SharpCompress/Compressors/ADC/ADCStream.cs index 0660d11f9..70e8fd8b3 100644 --- a/src/SharpCompress/Compressors/ADC/ADCStream.cs +++ b/src/SharpCompress/Compressors/ADC/ADCStream.cs @@ -1,4 +1,4 @@ -// +// // ADC.cs // // Author: @@ -28,14 +28,35 @@ using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Compressors.ADC; /// /// Provides a forward readable only stream that decompresses ADC data /// -public sealed class ADCStream : Stream +public sealed class ADCStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + /// /// This stream holds the compressed data /// @@ -74,6 +95,9 @@ public ADCStream(Stream stream, CompressionMode compressionMode = CompressionMod } _stream = stream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ADCStream)); +#endif } public override bool CanRead => _stream.CanRead; @@ -99,6 +123,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(ADCStream)); +#endif base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs b/src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs index 85a129bcb..be19b3fec 100644 --- a/src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs +++ b/src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs @@ -4,9 +4,30 @@ using System.Linq; using SharpCompress.Compressors.RLE90; using SharpCompress.Compressors.Squeezed; +using SharpCompress.IO; -public partial class ArcLzwStream : Stream +public partial class ArcLzwStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private Stream _stream; private bool _processed; private bool _useCrunched; @@ -33,6 +54,9 @@ public partial class ArcLzwStream : Stream public ArcLzwStream(Stream stream, int compressedSize, bool useCrunched = true) { _stream = stream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ArcLzwStream)); +#endif _useCrunched = useCrunched; _compressedSize = compressedSize; @@ -196,4 +220,12 @@ public override long Seek(long offset, SeekOrigin origin) => public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ArcLzwStream)); +#endif + base.Dispose(disposing); + } } diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs index 2c00ea717..5b9884cad 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs @@ -1,10 +1,31 @@ -using System; +using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Compressors.BZip2; -public sealed class BZip2Stream : Stream +public sealed class BZip2Stream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream stream; private bool isDisposed; @@ -16,6 +37,9 @@ public sealed class BZip2Stream : Stream /// Decompress Concatenated public BZip2Stream(Stream stream, CompressionMode compressionMode, bool decompressConcatenated) { +#if DEBUG_STREAMS + this.DebugConstruct(typeof(BZip2Stream)); +#endif Mode = compressionMode; if (Mode == CompressionMode.Compress) { @@ -36,6 +60,9 @@ protected override void Dispose(bool disposing) return; } isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(BZip2Stream)); +#endif if (disposing) { stream.Dispose(); diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs index cf006dbcf..abff49735 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs @@ -2,6 +2,7 @@ using System; using System.IO; +using SharpCompress.IO; /* * Copyright 2001,2004-2005 The Apache Software Foundation @@ -37,8 +38,28 @@ namespace SharpCompress.Compressors.BZip2; * start of the BZIP2 stream to make it compatible with other PGP programs. */ -internal class CBZip2InputStream : Stream +internal class CBZip2InputStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => bsStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private static void Cadvise() { //System.out.Println("CRC Error"); @@ -164,6 +185,10 @@ public CBZip2InputStream(Stream zStream, bool decompressConcatenated) ll8 = null; tt = null; BsSetStream(zStream); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(CBZip2InputStream)); +#endif + Initialize(true); InitBlock(); SetupBlock(); @@ -176,6 +201,9 @@ protected override void Dispose(bool disposing) return; } isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(CBZip2InputStream)); +#endif base.Dispose(disposing); bsStream?.Dispose(); } diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs index 5e78c5fa4..555c6fcb2 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using SharpCompress.IO; /* * Copyright 2001,2004-2005 The Apache Software Foundation @@ -38,8 +39,28 @@ namespace SharpCompress.Compressors.BZip2; * start of the BZIP2 stream to make it compatible with other PGP programs. */ -internal sealed class CBZip2OutputStream : Stream +internal sealed class CBZip2OutputStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => bsStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private const int SETMASK = (1 << 21); private const int CLEARMASK = (~SETMASK); private const int GREATER_ICOST = 15; @@ -334,6 +355,10 @@ public CBZip2OutputStream(Stream inStream, int inBlockSize) BsSetStream(inStream); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(CBZip2OutputStream)); +#endif + workFactor = 50; if (inBlockSize > 9) { @@ -450,6 +475,9 @@ protected override void Dispose(bool disposing) Finish(); disposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(CBZip2OutputStream)); +#endif Dispose(); bsStream?.Dispose(); bsStream = null; diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs index 5566e817d..8f268f004 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs @@ -27,11 +27,33 @@ using System; using System.IO; using System.Text; +using System.Threading; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate; -public class DeflateStream : Stream +public class DeflateStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _baseStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly ZlibBaseStream _baseStream; private bool _disposed; @@ -40,7 +62,8 @@ public DeflateStream( CompressionMode mode, CompressionLevel level = CompressionLevel.Default, Encoding? forceEncoding = null - ) => + ) + { _baseStream = new ZlibBaseStream( stream, mode, @@ -49,6 +72,11 @@ public DeflateStream( forceEncoding ); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(DeflateStream)); +#endif + } + #region Zlib properties /// @@ -233,6 +261,9 @@ protected override void Dispose(bool disposing) { if (!_disposed) { +#if DEBUG_STREAMS + this.DebugDispose(typeof(DeflateStream)); +#endif if (disposing) { _baseStream?.Dispose(); @@ -290,6 +321,7 @@ public override int Read(byte[] buffer, int offset, int count) { throw new ObjectDisposedException("DeflateStream"); } + return _baseStream.Read(buffer, offset, count); } diff --git a/src/SharpCompress/Compressors/Deflate/GZipStream.cs b/src/SharpCompress/Compressors/Deflate/GZipStream.cs index 0f7beca8d..2f6510408 100644 --- a/src/SharpCompress/Compressors/Deflate/GZipStream.cs +++ b/src/SharpCompress/Compressors/Deflate/GZipStream.cs @@ -30,11 +30,32 @@ using System.Buffers.Binary; using System.IO; using System.Text; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate; -public class GZipStream : Stream +public class GZipStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => BaseStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + internal static readonly DateTime UNIX_EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private string? _comment; @@ -62,6 +83,9 @@ Encoding encoding ) { BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(GZipStream)); +#endif _encoding = encoding; } @@ -210,6 +234,9 @@ protected override void Dispose(bool disposing) Crc32 = BaseStream.Crc32; } _disposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(GZipStream)); +#endif } } finally diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index f6372dff3..155a3556a 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -32,6 +32,7 @@ using System.IO; using System.Text; using SharpCompress.Common.Tar.Headers; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate; @@ -42,8 +43,28 @@ internal enum ZlibStreamFlavor GZIP = 1952, } -internal class ZlibBaseStream : Stream +internal class ZlibBaseStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + protected internal ZlibCodec _z; // deferred init... new ZlibCodec(); protected internal StreamMode _streamMode = StreamMode.Undefined; @@ -87,6 +108,10 @@ Encoding encoding _encoding = encoding; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ZlibBaseStream)); +#endif + // workitem 7159 if (flavor == ZlibStreamFlavor.GZIP) { @@ -334,6 +359,9 @@ protected override void Dispose(bool disposing) return; } isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(ZlibBaseStream)); +#endif base.Dispose(disposing); if (disposing) { @@ -354,7 +382,13 @@ protected override void Dispose(bool disposing) } } - public override void Flush() => _stream.Flush(); + public override void Flush() + { + _stream.Flush(); + //rewind the buffer + ((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused + z.AvailableBytesIn = 0; + } public override Int64 Seek(Int64 offset, SeekOrigin origin) => throw new NotSupportedException(); @@ -634,6 +668,13 @@ public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) crc.SlurpBlock(buffer, offset, rc); } + if (rc == ZlibConstants.Z_STREAM_END && z.AvailableBytesIn != 0 && !_wantCompress) + { + //rewind the buffer + ((IStreamStack)this).Rewind(z.AvailableBytesIn); //unused + z.AvailableBytesIn = 0; + } + return rc; } diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs index e2c69b535..d20516499 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs @@ -28,11 +28,32 @@ using System; using System.IO; using System.Text; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate; -public class ZlibStream : Stream +public class ZlibStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _baseStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly ZlibBaseStream _baseStream; private bool _disposed; @@ -47,7 +68,13 @@ public ZlibStream( CompressionMode mode, CompressionLevel level, Encoding encoding - ) => _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding); + ) + { + _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ZlibStream)); +#endif + } #region Zlib properties @@ -216,6 +243,9 @@ protected override void Dispose(bool disposing) _baseStream?.Dispose(); } _disposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(ZlibStream)); +#endif } } finally diff --git a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs index e6a3fdfd0..8ef83ec41 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -10,11 +10,32 @@ using System.Runtime.CompilerServices; using SharpCompress.Common; using SharpCompress.Common.Zip; +using SharpCompress.IO; namespace SharpCompress.Compressors.Deflate64; -public sealed class Deflate64Stream : Stream +public sealed class Deflate64Stream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private const int DEFAULT_BUFFER_SIZE = 8192; private Stream _stream; @@ -42,6 +63,9 @@ public Deflate64Stream(Stream stream, CompressionMode mode) } InitializeInflater(stream, ZipCompressionMethod.Deflate64); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(Deflate64Stream)); +#endif } /// @@ -252,6 +276,9 @@ protected override void Dispose(bool disposing) // In this case, we still need to clean up internal resources, hence the inner finally blocks. try { +#if DEBUG_STREAMS + this.DebugDispose(typeof(Deflate64Stream)); +#endif if (disposing) { _stream?.Dispose(); diff --git a/src/SharpCompress/Compressors/Explode/ExplodeStream.cs b/src/SharpCompress/Compressors/Explode/ExplodeStream.cs index 11a016e04..4b34b5055 100644 --- a/src/SharpCompress/Compressors/Explode/ExplodeStream.cs +++ b/src/SharpCompress/Compressors/Explode/ExplodeStream.cs @@ -1,11 +1,32 @@ using System; using System.IO; using SharpCompress.Common.Zip.Headers; +using SharpCompress.IO; namespace SharpCompress.Compressors.Explode; -public class ExplodeStream : Stream +public class ExplodeStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => inStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private const int INVALID_CODE = 99; private const int WSIZE = 64 * 1024; @@ -45,6 +66,9 @@ HeaderFlags generalPurposeBitFlag ) { inStream = inStr; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ExplodeStream)); +#endif this.compressedSize = (int)compressedSize; unCompressedSize = (long)uncompressedSize; this.generalPurposeBitFlag = generalPurposeBitFlag; @@ -54,6 +78,14 @@ HeaderFlags generalPurposeBitFlag explode_var_init(); } + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ExplodeStream)); +#endif + base.Dispose(disposing); + } + public override void Flush() { throw new NotImplementedException(); diff --git a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs index e89d705a6..a7a8583d2 100644 --- a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs @@ -3,11 +3,32 @@ using System.Security.Cryptography; using System.Text; using SharpCompress.Compressors.LZMA.Utilites; +using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA; -internal sealed class AesDecoderStream : DecoderStream2 +internal sealed class AesDecoderStream : DecoderStream2, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => mStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream mStream; private readonly ICryptoTransform mDecoder; private readonly byte[] mBuffer; @@ -31,6 +52,10 @@ public AesDecoderStream(Stream input, byte[] info, IPasswordProvider pass, long mStream = input; mLimit = limit; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(AesDecoderStream)); +#endif + if (((uint)input.Length & 15) != 0) { throw new NotSupportedException("AES decoder does not support padding."); @@ -64,6 +89,9 @@ protected override void Dispose(bool disposing) return; } isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(AesDecoderStream)); +#endif if (disposing) { mStream.Dispose(); diff --git a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs index fd18b16c4..de2d284f8 100644 --- a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs @@ -1,11 +1,32 @@ -using System; +using System; using System.Collections.Generic; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA; -internal class Bcj2DecoderStream : DecoderStream2 +internal class Bcj2DecoderStream : DecoderStream2, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _mMainStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private const int K_NUM_TOP_BITS = 24; private const uint K_TOP_VALUE = (1 << K_NUM_TOP_BITS); @@ -109,6 +130,10 @@ public Bcj2DecoderStream(Stream[] streams, byte[] info, long limit) _mStatusDecoder[i] = new StatusDecoder(); } +#if DEBUG_STREAMS + this.DebugConstruct(typeof(Bcj2DecoderStream)); +#endif + _mIter = Run().GetEnumerator(); } @@ -119,6 +144,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(Bcj2DecoderStream)); +#endif base.Dispose(disposing); _mMainStream.Dispose(); _mCallStream.Dispose(); diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.cs index d1bb72466..26d34d3a1 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -15,10 +15,30 @@ namespace SharpCompress.Compressors.LZMA; /// /// Stream supporting the LZIP format, as documented at http://www.nongnu.org/lzip/manual/lzip_manual.html /// -public sealed class LZipStream : Stream +public sealed class LZipStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream _stream; - private readonly CountingWritableSubStream? _countingWritableSubStream; + private readonly SharpCompressStream? _countingWritableSubStream; private bool _disposed; private bool _finished; @@ -44,7 +64,7 @@ public LZipStream(Stream stream, CompressionMode mode) var dSize = 104 * 1024; WriteHeaderSize(stream); - _countingWritableSubStream = new CountingWritableSubStream(stream); + _countingWritableSubStream = new SharpCompressStream(stream, leaveOpen: true); _stream = new Crc32Stream( new LzmaStream( new LzmaEncoderProperties(true, dSize), @@ -52,6 +72,9 @@ public LZipStream(Stream stream, CompressionMode mode) _countingWritableSubStream ) ); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(LZipStream)); +#endif } } @@ -64,7 +87,7 @@ public void Finish() var crc32Stream = (Crc32Stream)_stream; crc32Stream.WrappedStream.Dispose(); crc32Stream.Dispose(); - var compressedCount = _countingWritableSubStream.NotNull().Count; + var compressedCount = _countingWritableSubStream.NotNull().InternalPosition; Span intBuf = stackalloc byte[8]; BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc); @@ -74,7 +97,10 @@ public void Finish() _countingWritableSubStream?.Write(intBuf); //total with headers - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, compressedCount + 6 + 20); + BinaryPrimitives.WriteUInt64LittleEndian( + intBuf, + (ulong)compressedCount + (ulong)(6 + 20) + ); _countingWritableSubStream?.Write(intBuf); } _finished = true; @@ -90,6 +116,9 @@ protected override void Dispose(bool disposing) return; } _disposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(LZipStream)); +#endif if (disposing) { Finish(); diff --git a/src/SharpCompress/Compressors/LZMA/Log.cs b/src/SharpCompress/Compressors/LZMA/Log.cs index 1431966c3..c2b5651e4 100644 --- a/src/SharpCompress/Compressors/LZMA/Log.cs +++ b/src/SharpCompress/Compressors/LZMA/Log.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; @@ -28,52 +28,68 @@ private static void EnsureIndent() if (NEEDS_INDENT) { NEEDS_INDENT = false; +#if DEBUG_LZMA Debug.Write(INDENT.Peek()); +#endif } } public static void Write(object value) { EnsureIndent(); +#if DEBUG_LZMA Debug.Write(value); +#endif } public static void Write(string text) { EnsureIndent(); +#if DEBUG_LZMA Debug.Write(text); +#endif } public static void Write(string format, params object[] args) { EnsureIndent(); +#if DEBUG_LZMA Debug.Write(string.Format(format, args)); +#endif } public static void WriteLine() { +#if DEBUG_LZMA Debug.WriteLine(""); +#endif NEEDS_INDENT = true; } public static void WriteLine(object value) { EnsureIndent(); +#if DEBUG_LZMA Debug.WriteLine(value); +#endif NEEDS_INDENT = true; } public static void WriteLine(string text) { EnsureIndent(); +#if DEBUG_LZMA Debug.WriteLine(text); +#endif NEEDS_INDENT = true; } public static void WriteLine(string format, params object[] args) { EnsureIndent(); +#if DEBUG_LZMA Debug.WriteLine(string.Format(format, args)); +#endif NEEDS_INDENT = true; } } diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs index d2b565e9e..ae6d1c63c 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -4,11 +4,32 @@ using System.Buffers.Binary; using System.IO; using SharpCompress.Compressors.LZMA.LZ; +using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA; -public class LzmaStream : Stream +public class LzmaStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _inputStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream _inputStream; private readonly long _inputSize; private readonly long _outputSize; @@ -56,6 +77,10 @@ bool isLzma2 _outputSize = outputSize; _isLzma2 = isLzma2; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(LzmaStream)); +#endif + if (!isLzma2) { _dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1)); @@ -117,6 +142,11 @@ Stream outputStream Properties = prop; _encoder.SetStreams(null, outputStream, -1, -1); + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(LzmaStream)); +#endif + if (presetDictionary != null) { _encoder.Train(presetDictionary); @@ -138,6 +168,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(LzmaStream)); +#endif if (disposing) { if (_encoder != null) diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs index 344924be4..6b3797921 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs @@ -1,10 +1,31 @@ -using System; +using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA.Utilites; -internal class CrcBuilderStream : Stream +internal class CrcBuilderStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _mTarget; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream _mTarget; private uint _mCrc; private bool _mFinished; @@ -13,6 +34,9 @@ internal class CrcBuilderStream : Stream public CrcBuilderStream(Stream target) { _mTarget = target; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(CrcBuilderStream)); +#endif _mCrc = Crc.INIT_CRC; } @@ -23,6 +47,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(CrcBuilderStream)); +#endif _mTarget.Dispose(); base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/Lzw/LzwStream.cs b/src/SharpCompress/Compressors/Lzw/LzwStream.cs index 488ba1aa1..71132dac0 100644 --- a/src/SharpCompress/Compressors/Lzw/LzwStream.cs +++ b/src/SharpCompress/Compressors/Lzw/LzwStream.cs @@ -1,6 +1,7 @@ using System; using System.IO; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Compressors.Lzw { @@ -42,8 +43,28 @@ namespace SharpCompress.Compressors.Lzw /// } /// /// - public class LzwStream : Stream + public class LzwStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => baseInputStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + public static bool IsLzwStream(Stream stream) { try @@ -90,6 +111,9 @@ public static bool IsLzwStream(Stream stream) public LzwStream(Stream baseInputStream) { this.baseInputStream = baseInputStream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(LzwStream)); +#endif } /// @@ -539,6 +563,9 @@ protected override void Dispose(bool disposing) if (!isClosed) { isClosed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(LzwStream)); +#endif if (IsStreamOwner) { baseInputStream.Dispose(); diff --git a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs index bdc4d65a1..143aaf7ea 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs @@ -1,15 +1,36 @@ -#nullable disable +#nullable disable using System; using System.IO; using SharpCompress.Compressors.LZMA.RangeCoder; using SharpCompress.Compressors.PPMd.H; using SharpCompress.Compressors.PPMd.I1; +using SharpCompress.IO; namespace SharpCompress.Compressors.PPMd; -public class PpmdStream : Stream +public class PpmdStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly PpmdProperties _properties; private readonly Stream _stream; private readonly bool _compress; @@ -25,6 +46,10 @@ public PpmdStream(PpmdProperties properties, Stream stream, bool compress) _stream = stream; _compress = compress; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(PpmdStream)); +#endif + if (properties.Version == PpmdVersion.I1) { _model = new Model(); @@ -74,6 +99,9 @@ protected override void Dispose(bool isDisposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(PpmdStream)); +#endif if (isDisposing) { if (_compress) diff --git a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs index c499c79c2..09034040a 100644 --- a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs +++ b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs @@ -4,11 +4,32 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Compressors.RLE90 { - public class RunLength90Stream : Stream + public class RunLength90Stream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream _stream; private const byte DLE = 0x90; private int _compressedSize; @@ -18,6 +39,17 @@ public RunLength90Stream(Stream stream, int compressedSize) { _stream = stream; _compressedSize = compressedSize; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(RunLength90Stream)); +#endif + } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(RunLength90Stream)); +#endif + base.Dispose(disposing); } public override bool CanRead => true; diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index eda9c7657..b96533ce3 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -5,11 +5,32 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar; +using SharpCompress.IO; namespace SharpCompress.Compressors.Rar; -internal sealed class MultiVolumeReadOnlyStream : Stream +internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => currentStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private long currentPosition; private long maxPosition; @@ -31,6 +52,9 @@ IExtractionListener streamListener filePartEnumerator = parts.GetEnumerator(); filePartEnumerator.MoveNext(); InitializeNextFilePart(); +#if DEBUG_STREAMS + this.DebugConstruct(typeof(MultiVolumeReadOnlyStream)); +#endif } protected override void Dispose(bool disposing) @@ -38,6 +62,10 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); if (disposing) { +#if DEBUG_STREAMS + this.DebugDispose(typeof(MultiVolumeReadOnlyStream)); +#endif + if (filePartEnumerator != null) { filePartEnumerator.Dispose(); diff --git a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs index 0cf7c85cd..c0aead0db 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs @@ -3,11 +3,31 @@ using System.Linq; using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; +using SharpCompress.IO; namespace SharpCompress.Compressors.Rar; -internal class RarBLAKE2spStream : RarStream +internal class RarBLAKE2spStream : RarStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => readStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly MultiVolumeReadOnlyStream readStream; private readonly bool disableCRCCheck; @@ -91,12 +111,24 @@ MultiVolumeReadOnlyStream readStream : base(unpack, fileHeader, readStream) { this.readStream = readStream; + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(RarBLAKE2spStream)); +#endif disableCRCCheck = fileHeader.IsEncrypted; _hash = fileHeader.FileCrc.NotNull(); _blake2sp = new BLAKE2SP(); ResetCrc(); } + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(RarBLAKE2spStream)); +#endif + base.Dispose(disposing); + } + public byte[] GetCrc() => _hash; internal void ResetCrc(BLAKE2S hash) diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index ce42a4bd3..301910359 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -1,11 +1,32 @@ using System; +using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; +using SharpCompress.IO; namespace SharpCompress.Compressors.Rar; -internal class RarCrcStream : RarStream +internal class RarCrcStream : RarStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => readStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly MultiVolumeReadOnlyStream readStream; private uint currentCrc; private readonly bool disableCRC; @@ -18,10 +39,21 @@ MultiVolumeReadOnlyStream readStream : base(unpack, fileHeader, readStream) { this.readStream = readStream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(RarCrcStream)); +#endif disableCRC = fileHeader.IsEncrypted; ResetCrc(); } + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(RarCrcStream)); +#endif + base.Dispose(disposing); + } + public uint GetCrc() => ~currentCrc; public void ResetCrc() => currentCrc = 0xffffffff; diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index 073fcaa05..f2f4b2198 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -4,11 +4,32 @@ using System.Buffers; using System.IO; using SharpCompress.Common.Rar.Headers; +using SharpCompress.IO; namespace SharpCompress.Compressors.Rar; -internal class RarStream : Stream +internal class RarStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => readStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly IRarUnpack unpack; private readonly FileHeader fileHeader; private readonly Stream readStream; @@ -31,6 +52,11 @@ public RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream) this.unpack = unpack; this.fileHeader = fileHeader; this.readStream = readStream; + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(RarStream)); +#endif + fetch = true; unpack.DoUnpack(fileHeader, readStream, this); fetch = false; @@ -43,6 +69,9 @@ protected override void Dispose(bool disposing) { if (disposing) { +#if DEBUG_STREAMS + this.DebugDispose(typeof(RarStream)); +#endif ArrayPool.Shared.Return(this.tmpBuffer); this.tmpBuffer = null; } diff --git a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs index 1cb67f444..8ea0cd3d2 100644 --- a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs +++ b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs @@ -1,10 +1,31 @@ using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Compressors.Reduce; -public class ReduceStream : Stream +public class ReduceStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => inStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly long unCompressedSize; private readonly long compressedSize; private readonly Stream inStream; @@ -31,6 +52,10 @@ public ReduceStream(Stream inStr, long compsize, long unCompSize, int factor) inByteCount = 0; outBytesCount = 0; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ReduceStream)); +#endif + this.factor = factor; distanceMask = (int)mask_bits[factor] << 8; lengthMask = 0xff >> factor; @@ -47,6 +72,14 @@ public ReduceStream(Stream inStr, long compsize, long unCompSize, int factor) LoadNextByteTable(); } + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ReduceStream)); +#endif + base.Dispose(disposing); + } + public override void Flush() { throw new NotImplementedException(); diff --git a/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs b/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs index 258f2c42b..8e304896f 100644 --- a/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs +++ b/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs @@ -1,10 +1,31 @@ using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Compressors.Shrink; -internal class ShrinkStream : Stream +internal class ShrinkStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => inStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private Stream inStream; private CompressionMode _compressionMode; @@ -23,12 +44,24 @@ long uncompressedSize inStream = stream; _compressionMode = compressionMode; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ShrinkStream)); +#endif + _compressedSize = (ulong)compressedSize; _uncompressedSize = uncompressedSize; _byteOut = new byte[_uncompressedSize]; _outBytesCount = 0L; } + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ShrinkStream)); +#endif + base.Dispose(disposing); + } + public override bool CanRead => true; public override bool CanSeek => true; diff --git a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs index b35616f31..bd9760df9 100644 --- a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs +++ b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs @@ -5,12 +5,32 @@ using System.Text; using System.Threading.Tasks; using SharpCompress.Compressors.RLE90; -using ZstdSharp.Unsafe; +using SharpCompress.IO; namespace SharpCompress.Compressors.Squeezed { - public class SqueezeStream : Stream + public class SqueezeStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream _stream; private readonly int _compressedSize; private const int NUMVALS = 257; @@ -21,6 +41,17 @@ public SqueezeStream(Stream stream, int compressedSize) { _stream = stream; _compressedSize = compressedSize; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(SqueezeStream)); +#endif + } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(SqueezeStream)); +#endif + base.Dispose(disposing); } public override bool CanRead => true; diff --git a/src/SharpCompress/Compressors/Xz/XZFooter.cs b/src/SharpCompress/Compressors/Xz/XZFooter.cs index 9b2dd6e28..2610ed69b 100644 --- a/src/SharpCompress/Compressors/Xz/XZFooter.cs +++ b/src/SharpCompress/Compressors/Xz/XZFooter.cs @@ -23,7 +23,7 @@ public XZFooter(BinaryReader reader) public static XZFooter FromStream(Stream stream) { var footer = new XZFooter( - new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8) + new BinaryReader(SharpCompressStream.Create(stream, leaveOpen: true), Encoding.UTF8) ); footer.Process(); return footer; diff --git a/src/SharpCompress/Compressors/Xz/XZHeader.cs b/src/SharpCompress/Compressors/Xz/XZHeader.cs index 945449fa3..5df5447dc 100644 --- a/src/SharpCompress/Compressors/Xz/XZHeader.cs +++ b/src/SharpCompress/Compressors/Xz/XZHeader.cs @@ -19,7 +19,7 @@ public class XZHeader public static XZHeader FromStream(Stream stream) { var header = new XZHeader( - new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8) + new BinaryReader(SharpCompressStream.Create(stream, leaveOpen: true), Encoding.UTF8) ); header.Process(); return header; diff --git a/src/SharpCompress/Compressors/Xz/XZIndex.cs b/src/SharpCompress/Compressors/Xz/XZIndex.cs index 386c15da4..95ad2c619 100644 --- a/src/SharpCompress/Compressors/Xz/XZIndex.cs +++ b/src/SharpCompress/Compressors/Xz/XZIndex.cs @@ -32,7 +32,7 @@ public XZIndex(BinaryReader reader, bool indexMarkerAlreadyVerified) public static XZIndex FromStream(Stream stream, bool indexMarkerAlreadyVerified) { var index = new XZIndex( - new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8), + new BinaryReader(SharpCompressStream.Create(stream, leaveOpen: true), Encoding.UTF8), indexMarkerAlreadyVerified ); index.Process(); diff --git a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs index fd947cc9d..210478d61 100644 --- a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs @@ -1,10 +1,31 @@ using System.IO; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Compressors.Xz; -public abstract class XZReadOnlyStream : ReadOnlyStream +public abstract class XZReadOnlyStream : ReadOnlyStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => base.BaseStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + public XZReadOnlyStream(Stream stream) { BaseStream = stream; @@ -12,5 +33,16 @@ public XZReadOnlyStream(Stream stream) { throw new InvalidFormatException("Must be able to read from stream"); } +#if DEBUG_STREAMS + this.DebugConstruct(typeof(XZReadOnlyStream)); +#endif + } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(XZReadOnlyStream)); +#endif + base.Dispose(disposing); } } diff --git a/src/SharpCompress/Compressors/Xz/XZStream.cs b/src/SharpCompress/Compressors/Xz/XZStream.cs index 4cac8136f..96f70dca5 100644 --- a/src/SharpCompress/Compressors/Xz/XZStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZStream.cs @@ -3,12 +3,49 @@ using System; using System.IO; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Compressors.Xz; [CLSCompliant(false)] -public sealed class XZStream : XZReadOnlyStream +public sealed class XZStream : XZReadOnlyStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => _baseStream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + + public XZStream(Stream baseStream) + : base(baseStream) + { + _baseStream = baseStream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(XZStream)); +#endif + } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(XZStream)); +#endif + base.Dispose(disposing); + } + public static bool IsXZStream(Stream stream) { try @@ -35,6 +72,7 @@ private void AssertBlockCheckTypeIsSupported() } } + private readonly Stream _baseStream; public XZHeader Header { get; private set; } public XZIndex Index { get; private set; } public XZFooter Footer { get; private set; } @@ -43,9 +81,6 @@ private void AssertBlockCheckTypeIsSupported() private bool _endOfStream; - public XZStream(Stream stream) - : base(stream) { } - public override int Read(byte[] buffer, int offset, int count) { var bytesRead = 0; diff --git a/src/SharpCompress/Crypto/Crc32Stream.cs b/src/SharpCompress/Crypto/Crc32Stream.cs index 5f5ccd871..dfbcfac8b 100644 --- a/src/SharpCompress/Crypto/Crc32Stream.cs +++ b/src/SharpCompress/Crypto/Crc32Stream.cs @@ -2,22 +2,63 @@ using System; using System.IO; +using SharpCompress.IO; namespace SharpCompress.Crypto; [CLSCompliant(false)] -public sealed class Crc32Stream(Stream stream, uint polynomial, uint seed) : Stream +public sealed class Crc32Stream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + + private readonly Stream stream; + private readonly uint[] _table; + private uint seed; + public const uint DEFAULT_POLYNOMIAL = 0xedb88320u; public const uint DEFAULT_SEED = 0xffffffffu; private static uint[] _defaultTable; - private readonly uint[] _table = InitializeTable(polynomial); - public Crc32Stream(Stream stream) : this(stream, DEFAULT_POLYNOMIAL, DEFAULT_SEED) { } + public Crc32Stream(Stream stream, uint polynomial, uint seed) + { + this.stream = stream; + _table = InitializeTable(polynomial); + this.seed = seed; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(Crc32Stream)); +#endif + } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(Crc32Stream)); +#endif + base.Dispose(disposing); + } + public Stream WrappedStream => stream; public override void Flush() => stream.Flush(); diff --git a/src/SharpCompress/Factories/ArcFactory.cs b/src/SharpCompress/Factories/ArcFactory.cs index 469ee1368..b5180afae 100644 --- a/src/SharpCompress/Factories/ArcFactory.cs +++ b/src/SharpCompress/Factories/ArcFactory.cs @@ -23,7 +23,11 @@ public override IEnumerable GetSupportedExtensions() yield return "arc"; } - public override bool IsArchive(Stream stream, string? password = null) + public override bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { //You may have to use some(paranoid) checks to ensure that you actually are //processing an ARC file, since other archivers also adopted the idea of putting diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs index 426528d12..1b9e23e96 100644 --- a/src/SharpCompress/Factories/Factory.cs +++ b/src/SharpCompress/Factories/Factory.cs @@ -49,23 +49,27 @@ public static void RegisterFactory(Factory factory) public abstract IEnumerable GetSupportedExtensions(); /// - public abstract bool IsArchive(Stream stream, string? password = null); + public abstract bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ); /// public virtual FileInfo? GetFilePart(int index, FileInfo part1) => null; /// - /// Tries to open an from a . + /// Tries to open an from a . /// /// /// This method provides extra insight to support loading compressed TAR files. /// - /// + /// /// /// /// internal virtual bool TryOpenReader( - RewindableStream rewindableStream, + SharpCompressStream stream, ReaderOptions options, out IReader? reader ) @@ -74,11 +78,12 @@ out IReader? reader if (this is IReaderFactory readerFactory) { - rewindableStream.Rewind(false); - if (IsArchive(rewindableStream, options.Password)) + long pos = ((IStreamStack)stream).GetPosition(); + + if (IsArchive(stream, options.Password, options.BufferSize)) { - rewindableStream.Rewind(true); - reader = readerFactory.OpenReader(rewindableStream, options); + ((IStreamStack)stream).StackSeek(pos); + reader = readerFactory.OpenReader(stream, options); return true; } } diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index be1b49315..17f344cf0 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -40,8 +40,11 @@ public override IEnumerable GetSupportedExtensions() } /// - public override bool IsArchive(Stream stream, string? password = null) => - GZipArchive.IsGZipFile(stream); + public override bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => GZipArchive.IsGZipFile(stream); #endregion @@ -73,26 +76,27 @@ public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOpt /// internal override bool TryOpenReader( - RewindableStream rewindableStream, + SharpCompressStream rewindableStream, ReaderOptions options, out IReader? reader ) { reader = null; - rewindableStream.Rewind(false); + long pos = ((IStreamStack)rewindableStream).GetPosition(); + if (GZipArchive.IsGZipFile(rewindableStream)) { - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = new TarReader(rewindableStream, options, CompressionType.GZip); return true; } - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = OpenReader(rewindableStream, options); return true; } diff --git a/src/SharpCompress/Factories/IFactory.cs b/src/SharpCompress/Factories/IFactory.cs index 08bedd509..63d5eeec6 100644 --- a/src/SharpCompress/Factories/IFactory.cs +++ b/src/SharpCompress/Factories/IFactory.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using SharpCompress.Readers; namespace SharpCompress.Factories; @@ -35,7 +36,11 @@ public interface IFactory /// /// A stream, pointing to the beginning of the archive. /// optional password - bool IsArchive(Stream stream, string? password = null); + bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ); /// /// From a passed in archive (zip, rar, 7z, 001), return all parts. diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs index cfdb7ff24..610999057 100644 --- a/src/SharpCompress/Factories/RarFactory.cs +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -29,8 +29,11 @@ public override IEnumerable GetSupportedExtensions() } /// - public override bool IsArchive(Stream stream, string? password = null) => - RarArchive.IsRarFile(stream); + public override bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => RarArchive.IsRarFile(stream); /// public override FileInfo? GetFilePart(int index, FileInfo part1) => diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index cfc99d572..18dedbfdd 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -28,8 +28,11 @@ public override IEnumerable GetSupportedExtensions() } /// - public override bool IsArchive(Stream stream, string? password = null) => - SevenZipArchive.IsSevenZipFile(stream); + public override bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => SevenZipArchive.IsSevenZipFile(stream); #endregion @@ -60,7 +63,7 @@ public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOpt #region reader internal override bool TryOpenReader( - RewindableStream rewindableStream, + SharpCompressStream rewindableStream, ReaderOptions options, out IReader? reader ) diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 715a05d2f..50840302f 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -67,8 +67,11 @@ public override IEnumerable GetSupportedExtensions() } /// - public override bool IsArchive(Stream stream, string? password = null) => - TarArchive.IsTarFile(stream); + public override bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => TarArchive.IsTarFile(stream); #endregion @@ -100,75 +103,75 @@ public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOpt /// internal override bool TryOpenReader( - RewindableStream rewindableStream, + SharpCompressStream rewindableStream, ReaderOptions options, out IReader? reader ) { reader = null; + long pos = ((IStreamStack)rewindableStream).GetPosition(); - rewindableStream.Rewind(false); if (TarArchive.IsTarFile(rewindableStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = OpenReader(rewindableStream, options); return true; } - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); if (BZip2Stream.IsBZip2(rewindableStream)) { - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new BZip2Stream( - NonDisposingStream.Create(rewindableStream), + SharpCompressStream.Create(rewindableStream, leaveOpen: true), CompressionMode.Decompress, false ); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = new TarReader(rewindableStream, options, CompressionType.BZip2); return true; } } - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); if (LZipStream.IsLZipFile(rewindableStream)) { - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new LZipStream( - NonDisposingStream.Create(rewindableStream), + SharpCompressStream.Create(rewindableStream, leaveOpen: true), CompressionMode.Decompress ); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = new TarReader(rewindableStream, options, CompressionType.LZip); return true; } } - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); if (XZStream.IsXZStream(rewindableStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new XZStream(rewindableStream); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = new TarReader(rewindableStream, options, CompressionType.Xz); return true; } } - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); if (LzwStream.IsLzwStream(rewindableStream)) { - rewindableStream.Rewind(false); var testStream = new LzwStream(rewindableStream); + ((IStreamStack)rewindableStream).StackSeek(pos); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); reader = new TarReader(rewindableStream, options, CompressionType.Lzw); return true; } diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index 780d2a9c0..5c2fcad8d 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -3,6 +3,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; +using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Zip; using SharpCompress.Writers; @@ -38,13 +39,22 @@ public override IEnumerable GetSupportedExtensions() } /// - public override bool IsArchive(Stream stream, string? password = null) + public override bool IsArchive( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) { var startPosition = stream.CanSeek ? stream.Position : -1; // probe for single volume zip - if (ZipArchive.IsZipFile(stream, password)) + if (stream is not SharpCompressStream) // wrap to provide buffer bef + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + if (ZipArchive.IsZipFile(stream, password, bufferSize)) { return true; } @@ -59,11 +69,13 @@ public override bool IsArchive(Stream stream, string? password = null) stream.Position = startPosition; //test the zip (last) file of a multipart zip - if (ZipArchive.IsZipMulti(stream)) + if (ZipArchive.IsZipMulti(stream, password, bufferSize)) { return true; } + stream.Position = startPosition; + return false; } diff --git a/src/SharpCompress/IO/BufferedSubStream.cs b/src/SharpCompress/IO/BufferedSubStream.cs index e4c3def0b..d719fd907 100755 --- a/src/SharpCompress/IO/BufferedSubStream.cs +++ b/src/SharpCompress/IO/BufferedSubStream.cs @@ -3,14 +3,39 @@ namespace SharpCompress.IO; -internal class BufferedSubStream(Stream stream, long origin, long bytesToRead) - : NonDisposingStream(stream, throwOnDispose: false) +internal class BufferedSubStream : SharpCompressStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => base.Stream; + + public BufferedSubStream(Stream stream, long origin, long bytesToRead) + : base(stream, leaveOpen: true, throwOnDispose: false) + { +#if DEBUG_STREAMS + this.DebugConstruct(typeof(BufferedSubStream)); +#endif + this.origin = origin; + this.BytesLeftToRead = bytesToRead; + } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(BufferedSubStream)); +#endif + if (disposing) { } + base.Dispose(disposing); + } + private int _cacheOffset; private int _cacheLength; private readonly byte[] _cache = new byte[32 << 10]; + private long origin; - private long BytesLeftToRead { get; set; } = bytesToRead; + private long BytesLeftToRead { get; set; } public override bool CanRead => true; diff --git a/src/SharpCompress/IO/CountingWritableSubStream.cs b/src/SharpCompress/IO/CountingWritableSubStream.cs deleted file mode 100644 index ce9ace713..000000000 --- a/src/SharpCompress/IO/CountingWritableSubStream.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.IO; - -namespace SharpCompress.IO; - -internal class CountingWritableSubStream : NonDisposingStream -{ - internal CountingWritableSubStream(Stream stream) - : base(stream, throwOnDispose: false) { } - - public ulong Count { get; private set; } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override void Flush() => Stream.Flush(); - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); - - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) - { - Stream.Write(buffer, offset, count); - Count += (uint)count; - } - - public override void WriteByte(byte value) - { - Stream.WriteByte(value); - ++Count; - } -} diff --git a/src/SharpCompress/IO/DataDescriptorStream.cs b/src/SharpCompress/IO/DataDescriptorStream.cs index 801f9fa7a..235c7a30f 100644 --- a/src/SharpCompress/IO/DataDescriptorStream.cs +++ b/src/SharpCompress/IO/DataDescriptorStream.cs @@ -3,8 +3,28 @@ namespace SharpCompress.IO; -public class DataDescriptorStream : Stream +public class DataDescriptorStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _stream; + + int IStreamStack.BufferSize + { + get => 0; + set { return; } + } + int IStreamStack.BufferPosition + { + get => 0; + set { return; } + } + + void IStreamStack.SetPosition(long position) { } + private readonly Stream _stream; private long _start; private int _searchPosition; @@ -20,6 +40,10 @@ public DataDescriptorStream(Stream stream) _start = _stream.Position; _searchPosition = 0; _done = false; + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(DataDescriptorStream)); +#endif } internal bool IsRecording { get; private set; } @@ -31,6 +55,9 @@ protected override void Dispose(bool disposing) return; } _isDisposed = true; +#if DEBUG_STREAMS + this.DebugDispose(typeof(DataDescriptorStream)); +#endif base.Dispose(disposing); if (disposing) { diff --git a/src/SharpCompress/IO/IStreamStack.cs b/src/SharpCompress/IO/IStreamStack.cs new file mode 100644 index 000000000..56c020c8f --- /dev/null +++ b/src/SharpCompress/IO/IStreamStack.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace SharpCompress.IO +{ + public interface IStreamStack + { + /// + /// Gets or sets the default buffer size to be applied when buffering is enabled for this stream stack. + /// This value is used by the SetBuffer extension method to configure buffering on the appropriate stream + /// in the stack hierarchy. A value of 0 indicates no default buffer size is set. + /// + int DefaultBufferSize { get; set; } + + /// + /// Returns the immediate underlying stream in the stack. + /// + Stream BaseStream(); + + /// + /// Gets or sets the size of the buffer if the stream supports buffering; otherwise, returns 0. + /// This property must not throw. + /// + int BufferSize { get; set; } + + /// + /// Gets or sets the current position within the buffer if the stream supports buffering; otherwise, returns 0. + /// This property must not throw. + /// + int BufferPosition { get; set; } + + /// + /// Updates the internal position state of the stream. This should not perform seeking on the underlying stream, + /// but should update any internal position or buffer state as appropriate for the stream implementation. + /// + /// The absolute position to set within the stream stack. + void SetPosition(long position); + +#if DEBUG_STREAMS + /// + /// Gets or sets the unique instance identifier for debugging purposes. + /// + long InstanceId { get; set; } +#endif + } + + internal static class StackStreamExtensions + { + /// + /// Gets the logical position of the first buffering stream in the stack, or 0 if none exist. + /// + /// The most derived (outermost) stream in the stack. + /// The position of the first buffering stream, or 0 if not found. + internal static long GetPosition(this IStreamStack stream) + { + IStreamStack? current = stream; + + while (current != null) + { + if (current.BufferSize != 0 && current is Stream st) + { + return st.Position; + } + current = current?.BaseStream() as IStreamStack; + } + return 0; + } + + /// + /// Rewinds the buffer of the outermost buffering stream in the stack by the specified count, if supported. + /// Only the most derived buffering stream is affected. + /// + /// The most derived (outermost) stream in the stack. + /// The number of bytes to rewind within the buffer. + internal static void Rewind(this IStreamStack stream, int count) + { + Stream baseStream = stream.BaseStream(); + Stream thisStream = (Stream)stream; + IStreamStack? buffStream = null; + IStreamStack? current = stream; + + while (buffStream == null && current != null) + { + if (current.BufferSize != 0) + { + buffStream = current; + buffStream.BufferPosition -= Math.Min(buffStream.BufferPosition, count); + } + current = current?.BaseStream() as IStreamStack; + } + } + + /// + /// Sets the buffer size on the first buffering stream in the stack, or on the outermost stream if none exist. + /// If is true, sets the buffer size regardless of current value. + /// + /// The most derived (outermost) stream in the stack. + /// The buffer size to set. + /// If true, forces the buffer size to be set even if already set. + internal static void SetBuffer(this IStreamStack stream, int bufferSize, bool force) + { + if (bufferSize == 0 || stream == null) + return; + + IStreamStack? current = stream; + IStreamStack defaultBuffer = stream; + IStreamStack? buffer = null; + + // First pass: find the deepest IStreamStack + while (current != null) + { + defaultBuffer = current; + if (buffer == null && ((current.BufferSize != 0 && bufferSize != 0) || force)) + buffer = current; + if (defaultBuffer.DefaultBufferSize != 0) + break; + current = current.BaseStream() as IStreamStack; + } + if (defaultBuffer.DefaultBufferSize == 0) + defaultBuffer.DefaultBufferSize = bufferSize; + (buffer ?? stream).BufferSize = bufferSize; + } + + /// + /// Attempts to set the position in the stream stack. If a buffering stream is present and the position is within its buffer, + /// BufferPosition is set on the outermost buffering stream and all intermediate streams update their internal state via SetPosition. + /// If no buffering stream is present, seeks as close to the root stream as possible and updates all intermediate streams' state via SetPosition. + /// Seeking is never performed if any intermediate stream in the stack is buffering. + /// Throws if the position cannot be set. + /// + /// + /// The most derived (outermost) stream in the stack. The method traverses up the stack via BaseStream() until a stream can satisfy the buffer or seek request. + /// + /// The absolute position to set. + /// The position that was set. + internal static long StackSeek(this IStreamStack stream, long position) + { + var stack = new List(); + Stream? current = stream as Stream; + int lastBufferingIndex = -1; + int firstSeekableIndex = -1; + Stream? firstSeekableStream = null; + + // Traverse the stack, collecting info + while (current is IStreamStack stackStream) + { + stack.Add(stackStream); + if (stackStream.BufferSize > 0) + { + lastBufferingIndex = stack.Count - 1; + break; + } + current = stackStream.BaseStream(); + } + + // Find the first seekable stream (closest to the root) + if (current != null && current.CanSeek) + { + firstSeekableIndex = stack.Count; + firstSeekableStream = current; + } + + // If any buffering stream exists, try to set BufferPosition on the outermost one + if (lastBufferingIndex != -1) + { + var bufferingStream = stack[lastBufferingIndex]; + if (position >= 0 && position < bufferingStream.BufferSize) + { + bufferingStream.BufferPosition = (int)position; + return position; + } + else + { + // If position is not in buffer, reset buffer and proceed as non-buffering + bufferingStream.BufferPosition = 0; + } + // Continue to seek as if no buffer is present + } + + // If no buffering, or buffer was reset, seek at the first seekable stream (closest to the root) + if (firstSeekableStream != null) + { + firstSeekableStream.Seek(position, SeekOrigin.Begin); + return firstSeekableStream.Position; + } + + throw new NotSupportedException( + "Cannot set position on this stream stack (no seekable or buffering stream supports the requested position)." + ); + } + + /// + /// Reads bytes from the stream, using the position to observe how much was actually consumed and rewind the buffer to ensure further reads are correct. + /// This is required to prevent buffered reads from skipping data, while also benefiting from buffering and reduced stream IO reads. + /// + /// The stream to read from. + /// The buffer to read data into. + /// The offset in the buffer to start writing data. + /// The maximum number of bytes to read. + /// Returns the buffering stream found in the stack, or null if none exists. + /// Returns the number of bytes actually read from the base stream, or -1 if no buffering stream was found. + /// The number of bytes read into the buffer. + internal static int Read( + this IStreamStack stream, + byte[] buffer, + int offset, + int count, + out IStreamStack? buffStream, + out int baseReadCount + ) + { + Stream baseStream = stream.BaseStream(); + Stream thisStream = (Stream)stream; + IStreamStack? current = stream; + buffStream = null; + baseReadCount = -1; + + while (buffStream == null && (current = current?.BaseStream() as IStreamStack) != null) + { + if (current.BufferSize != 0) + { + buffStream = current; + } + } + + long buffPos = buffStream == null ? -1 : ((Stream)buffStream).Position; + + int read = baseStream.Read(buffer, offset, count); //amount read in to buffer + + if (buffPos != -1) + { + baseReadCount = (int)(((Stream)buffStream!).Position - buffPos); + } + return read; + } + +#if DEBUG_STREAMS + private static long _instanceCounter = 0; + + private static string cleansePos(long pos) + { + if (pos < 0) + return ""; + return "Px" + pos.ToString("x"); + } + + /// + /// Gets or creates a unique instance ID for the stream stack for debugging purposes. + /// + /// The stream stack. + /// Reference to the instance ID field. + /// Whether this is being called during construction. + /// The instance ID. + public static long GetInstanceId( + this IStreamStack stream, + ref long instanceId, + bool construct + ) + { + if (instanceId == 0) //will not be equal to 0 when inherited IStackStream types are being used + instanceId = System.Threading.Interlocked.Increment(ref _instanceCounter); + return instanceId; + } + + /// + /// Writes a debug message for stream construction. + /// + /// The stream stack. + /// The type being constructed. + public static void DebugConstruct(this IStreamStack stream, Type constructing) + { + long id = stream.InstanceId; + stream.InstanceId = GetInstanceId(stream, ref id, true); + var frame = (new StackTrace()).GetFrame(3); + string parentInfo = + frame != null + ? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()" + : "Unknown"; + if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types + Debug.WriteLine( + $"{GetStreamStackString(stream, true)} : Constructed by [{parentInfo}]" + ); + } + + /// + /// Writes a debug message for stream disposal. + /// + /// The stream stack. + /// The type being disposed. + public static void DebugDispose(this IStreamStack stream, Type constructing) + { + var frame = (new StackTrace()).GetFrame(3); + string parentInfo = + frame != null + ? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()" + : "Unknown"; + if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types + Debug.WriteLine($"{GetStreamStackString(stream, false)} : Disposed by [{parentInfo}]"); + } + + /// + /// Writes a debug trace message for the stream. + /// + /// The stream stack. + /// The debug message to write. + public static void DebugTrace(this IStreamStack stream, string message) + { + Debug.WriteLine( + $"{GetStreamStackString(stream, false)} : [{stream.GetType().Name}]{message}" + ); + } + + /// + /// Returns the full stream chain as a string, including instance IDs and positions. + /// + /// The stream stack to represent. + /// Whether this is being called during construction. + /// A string representation of the entire stream stack. + public static string GetStreamStackString(this IStreamStack stream, bool construct) + { + var sb = new StringBuilder(); + Stream? current = stream as Stream; + while (current != null) + { + IStreamStack? sStack = current as IStreamStack; + string id = sStack != null ? "#" + sStack.InstanceId.ToString() : ""; + string buffSize = sStack != null ? "Bx" + sStack.BufferSize.ToString("x") : ""; + string defBuffSize = + sStack != null ? "Dx" + sStack.DefaultBufferSize.ToString("x") : ""; + + if (sb.Length > 0) + sb.Insert(0, "/"); + try + { + sb.Insert( + 0, + $"{current.GetType().Name}{id}[{cleansePos(current.Position)}:{buffSize}:{defBuffSize}]" + ); + } + catch + { + if (current is SharpCompressStream scs) + sb.Insert( + 0, + $"{current.GetType().Name}{id}[{cleansePos(scs.InternalPosition)}:{buffSize}:{defBuffSize}]" + ); + else + sb.Insert(0, $"{current.GetType().Name}{id}[:{buffSize}]"); + } + if (sStack != null) + current = sStack.BaseStream(); //current may not be a IStreamStack, allow one more loop + else + break; + } + return sb.ToString(); + } +#endif + } +} diff --git a/src/SharpCompress/IO/ListeningStream.cs b/src/SharpCompress/IO/ListeningStream.cs index 0acf1056f..fafe12b57 100644 --- a/src/SharpCompress/IO/ListeningStream.cs +++ b/src/SharpCompress/IO/ListeningStream.cs @@ -1,10 +1,30 @@ -using System.IO; +using System.IO; using SharpCompress.Common; namespace SharpCompress.IO; -internal class ListeningStream : Stream +internal class ListeningStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => Stream; + + int IStreamStack.BufferSize + { + get => 0; + set { return; } + } + int IStreamStack.BufferPosition + { + get => 0; + set { return; } + } + + void IStreamStack.SetPosition(long position) { } + private long _currentEntryTotalReadBytes; private readonly IExtractionListener _listener; @@ -12,10 +32,16 @@ public ListeningStream(IExtractionListener listener, Stream stream) { Stream = stream; this._listener = listener; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ListeningStream)); +#endif } protected override void Dispose(bool disposing) { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ListeningStream)); +#endif if (disposing) { Stream.Dispose(); diff --git a/src/SharpCompress/IO/NonDisposingStream.cs b/src/SharpCompress/IO/NonDisposingStream.cs deleted file mode 100644 index 334296d72..000000000 --- a/src/SharpCompress/IO/NonDisposingStream.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.IO; - -namespace SharpCompress.IO; - -public class NonDisposingStream : Stream -{ - public static NonDisposingStream Create(Stream stream, bool throwOnDispose = false) - { - if ( - stream is NonDisposingStream nonDisposingStream - && nonDisposingStream.ThrowOnDispose == throwOnDispose - ) - { - return nonDisposingStream; - } - return new NonDisposingStream(stream, throwOnDispose); - } - - protected NonDisposingStream(Stream stream, bool throwOnDispose = false) - { - Stream = stream; - ThrowOnDispose = throwOnDispose; - } - - public bool ThrowOnDispose { get; set; } - - protected override void Dispose(bool disposing) - { - if (ThrowOnDispose) - { - throw new InvalidOperationException( - $"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}" - ); - } - } - - protected Stream Stream { get; } - - public override bool CanRead => Stream.CanRead; - - public override bool CanSeek => Stream.CanSeek; - - public override bool CanWrite => Stream.CanWrite; - - public override void Flush() => Stream.Flush(); - - public override long Length => Stream.Length; - - public override long Position - { - get => Stream.Position; - set => Stream.Position = value; - } - - public override int Read(byte[] buffer, int offset, int count) => - Stream.Read(buffer, offset, count); - - public override long Seek(long offset, SeekOrigin origin) => Stream.Seek(offset, origin); - - public override void SetLength(long value) => Stream.SetLength(value); - - public override void Write(byte[] buffer, int offset, int count) => - Stream.Write(buffer, offset, count); - -#if !NETFRAMEWORK && !NETSTANDARD2_0 - - public override int Read(Span buffer) => Stream.Read(buffer); - - public override void Write(ReadOnlySpan buffer) => Stream.Write(buffer); - -#endif -} diff --git a/src/SharpCompress/IO/ReadOnlySubStream.cs b/src/SharpCompress/IO/ReadOnlySubStream.cs index 632d350f5..ffcd55fd7 100644 --- a/src/SharpCompress/IO/ReadOnlySubStream.cs +++ b/src/SharpCompress/IO/ReadOnlySubStream.cs @@ -1,17 +1,24 @@ using System; +using System.Diagnostics; using System.IO; namespace SharpCompress.IO; -internal class ReadOnlySubStream : NonDisposingStream +internal class ReadOnlySubStream : SharpCompressStream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => base.Stream; + private long _position; public ReadOnlySubStream(Stream stream, long bytesToRead) : this(stream, null, bytesToRead) { } public ReadOnlySubStream(Stream stream, long? origin, long bytesToRead) - : base(stream, throwOnDispose: false) + : base(stream, leaveOpen: true, throwOnDispose: false) { if (origin != null && stream.Position != origin.Value) { @@ -19,6 +26,9 @@ public ReadOnlySubStream(Stream stream, long? origin, long bytesToRead) } BytesLeftToRead = bytesToRead; _position = 0; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ReadOnlySubStream)); +#endif } private long BytesLeftToRead { get; set; } @@ -89,4 +99,12 @@ public override int Read(Span buffer) public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ReadOnlySubStream)); +#endif + base.Dispose(disposing); + } } diff --git a/src/SharpCompress/IO/RewindableStream.cs b/src/SharpCompress/IO/RewindableStream.cs deleted file mode 100644 index c7febd1da..000000000 --- a/src/SharpCompress/IO/RewindableStream.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.IO; - -namespace SharpCompress.IO; - -public class RewindableStream : Stream -{ - private readonly Stream _stream; - private MemoryStream _bufferStream = new(); - private bool _isRewound; - private bool _isDisposed; - - public RewindableStream(Stream stream) => this._stream = stream; - - internal bool IsRecording { get; private set; } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - base.Dispose(disposing); - if (disposing) - { - _stream.Dispose(); - } - } - - public void Rewind(bool stopRecording) - { - _isRewound = true; - IsRecording = !stopRecording; - _bufferStream.Position = 0; - } - - public void Rewind(MemoryStream buffer) - { - if (_bufferStream.Position >= buffer.Length) - { - _bufferStream.Position -= buffer.Length; - } - else - { - _bufferStream.TransferTo(buffer); - //create new memorystream to allow proper resizing as memorystream could be a user provided buffer - //https://github.com/adamhathcock/sharpcompress/issues/306 - _bufferStream = new MemoryStream(); - buffer.Position = 0; - buffer.TransferTo(_bufferStream); - _bufferStream.Position = 0; - } - _isRewound = true; - } - - public void StartRecording() - { - //if (isRewound && bufferStream.Position != 0) - // throw new System.NotImplementedException(); - if (_bufferStream.Position != 0) - { - var data = _bufferStream.ToArray(); - var position = _bufferStream.Position; - _bufferStream.SetLength(0); - _bufferStream.Write(data, (int)position, data.Length - (int)position); - _bufferStream.Position = 0; - } - IsRecording = true; - } - - public override bool CanRead => true; - - public override bool CanSeek => _stream.CanSeek; - - public override bool CanWrite => false; - - public override void Flush() { } - - public override long Length => _stream.Length; - - public override long Position - { - get => _stream.Position + _bufferStream.Position - _bufferStream.Length; - set - { - if (!_isRewound) - { - _stream.Position = value; - } - else if (value < _stream.Position - _bufferStream.Length || value >= _stream.Position) - { - _stream.Position = value; - _isRewound = false; - _bufferStream.SetLength(0); - } - else - { - _bufferStream.Position = value - _stream.Position + _bufferStream.Length; - } - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - //don't actually read if we don't really want to read anything - //currently a network stream bug on Windows for .NET Core - if (count == 0) - { - return 0; - } - int read; - if (_isRewound && _bufferStream.Position != _bufferStream.Length) - { - // don't read more than left - var readCount = Math.Min(count, (int)(_bufferStream.Length - _bufferStream.Position)); - read = _bufferStream.Read(buffer, offset, readCount); - if (read < readCount) - { - var tempRead = _stream.Read(buffer, offset + read, count - read); - if (IsRecording) - { - _bufferStream.Write(buffer, offset + read, tempRead); - } - read += tempRead; - } - if (_bufferStream.Position == _bufferStream.Length && !IsRecording) - { - _isRewound = false; - _bufferStream.SetLength(0); - } - return read; - } - - read = _stream.Read(buffer, offset, count); - if (IsRecording) - { - _bufferStream.Write(buffer, offset, read); - } - return read; - } - - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); -} diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs new file mode 100644 index 000000000..6d7d5372e --- /dev/null +++ b/src/SharpCompress/IO/SharpCompressStream.cs @@ -0,0 +1,335 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; + +namespace SharpCompress.IO; + +public class SharpCompressStream : Stream, IStreamStack +{ +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => Stream; + + // Buffering fields + private int _bufferSize; + private byte[]? _buffer; + private int _bufferPosition; + private int _bufferedLength; + private bool _bufferingEnabled; + private long _baseInitialPos; + + private void ValidateBufferState() + { + if (_bufferPosition < 0 || _bufferPosition > _bufferedLength) + { + throw new InvalidOperationException( + "Buffer state is inconsistent: _bufferPosition is out of range." + ); + } + } + + int IStreamStack.BufferSize + { + get => _bufferingEnabled ? _bufferSize : 0; + set //need to adjust an already existing buffer + { + if (_bufferSize != value) + { + _bufferSize = value; + _bufferingEnabled = _bufferSize > 0; + if (_bufferingEnabled) + { + _buffer = new byte[_bufferSize]; + _bufferPosition = 0; + _bufferedLength = 0; + if (_bufferingEnabled) + { + ValidateBufferState(); // Add here + } + try + { + _internalPosition = Stream.Position; + } + catch + { + _internalPosition = 0; + } + } + } + } + } + + int IStreamStack.BufferPosition + { + get => _bufferingEnabled ? _bufferPosition : 0; + set + { + if (_bufferingEnabled) + { + if (value < 0 || value > _bufferedLength) + throw new ArgumentOutOfRangeException(nameof(value)); + _internalPosition = value; + _bufferPosition = value; + ValidateBufferState(); // Add here + } + } + } + + void IStreamStack.SetPosition(long position) { } + + public Stream Stream { get; } + + //private MemoryStream _bufferStream = new(); + + private bool _readOnly; //some archive detection requires seek to be disabled to cause it to exception to try the next arc type + + //private bool _isRewound; + private bool _isDisposed; + private long _internalPosition = 0; + + public bool ThrowOnDispose { get; set; } + public bool LeaveOpen { get; set; } + + public long InternalPosition => _internalPosition; + + public static SharpCompressStream Create( + Stream stream, + bool leaveOpen = false, + bool throwOnDispose = false, + int bufferSize = 0, + bool forceBuffer = false + ) + { + if ( + stream is SharpCompressStream sc + && sc.LeaveOpen == leaveOpen + && sc.ThrowOnDispose == throwOnDispose + ) + { + if (bufferSize != 0) + ((IStreamStack)stream).SetBuffer(bufferSize, forceBuffer); + return sc; + } + return new SharpCompressStream(stream, leaveOpen, throwOnDispose, bufferSize, forceBuffer); + } + + public SharpCompressStream( + Stream stream, + bool leaveOpen = false, + bool throwOnDispose = false, + int bufferSize = 0, + bool forceBuffer = false + ) + { + Stream = stream; + this.LeaveOpen = leaveOpen; + this.ThrowOnDispose = throwOnDispose; + _readOnly = !Stream.CanSeek; + + ((IStreamStack)this).SetBuffer(bufferSize, forceBuffer); + try + { + _baseInitialPos = stream.Position; + } + catch + { + _baseInitialPos = 0; + } + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(SharpCompressStream)); +#endif + } + + internal bool IsRecording { get; private set; } + + protected override void Dispose(bool disposing) + { +#if DEBUG_STREAMS + this.DebugDispose(typeof(SharpCompressStream)); +#endif + if (_isDisposed) + { + return; + } + _isDisposed = true; + base.Dispose(disposing); + + if (this.LeaveOpen) + { + return; + } + if (ThrowOnDispose) + { + throw new InvalidOperationException( + $"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}" + ); + } + if (disposing) + { + Stream.Dispose(); + } + } + + public override bool CanRead => Stream.CanRead; + + public override bool CanSeek => !_readOnly && Stream.CanSeek; + + public override bool CanWrite => !_readOnly && Stream.CanWrite; + + public override void Flush() + { + Stream.Flush(); + } + + public override long Length + { + get { return Stream.Length; } + } + + public override long Position + { + get + { + long pos = _internalPosition; // Stream.Position + _bufferStream.Position - _bufferStream.Length; + return pos; + } + set { Seek(value, SeekOrigin.Begin); } + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count == 0) + return 0; + + if (_bufferingEnabled) + { + ValidateBufferState(); + + // Fill buffer if needed + if (_bufferedLength == 0) + { + _bufferedLength = Stream.Read(_buffer!, 0, _bufferSize); + _bufferPosition = 0; + } + int available = _bufferedLength - _bufferPosition; + int toRead = Math.Min(count, available); + if (toRead > 0) + { + Array.Copy(_buffer!, _bufferPosition, buffer, offset, toRead); + _bufferPosition += toRead; + _internalPosition += toRead; + return toRead; + } + // If buffer exhausted, refill + int r = Stream.Read(_buffer!, 0, _bufferSize); + if (r == 0) + return 0; + _bufferedLength = r; + _bufferPosition = 0; + if (_bufferedLength == 0) + { + return 0; + } + toRead = Math.Min(count, _bufferedLength); + Array.Copy(_buffer!, 0, buffer, offset, toRead); + _bufferPosition = toRead; + _internalPosition += toRead; + return toRead; + } + else + { + if (count == 0) + { + return 0; + } + int read; + read = Stream.Read(buffer, offset, count); + _internalPosition += read; + return read; + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (_bufferingEnabled) + { + ValidateBufferState(); + } + + long orig = _internalPosition; + long targetPos; + // Calculate the absolute target position based on origin + switch (origin) + { + case SeekOrigin.Begin: + targetPos = offset; + break; + case SeekOrigin.Current: + targetPos = _internalPosition + offset; + break; + case SeekOrigin.End: + targetPos = this.Length + offset; + break; + default: + throw new ArgumentOutOfRangeException(nameof(origin), origin, null); + } + + long bufferPos = _internalPosition - _bufferPosition; + + if (targetPos >= bufferPos && targetPos < bufferPos + _bufferedLength) + { + _bufferPosition = (int)(targetPos - bufferPos); //repoint within the buffer + _internalPosition = targetPos; + } + else + { + long newStreamPos = + Stream.Seek(targetPos + _baseInitialPos, SeekOrigin.Begin) - _baseInitialPos; + _internalPosition = newStreamPos; + _bufferPosition = 0; + _bufferedLength = 0; + } + + return _internalPosition; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void WriteByte(byte value) + { + Stream.WriteByte(value); + ++_internalPosition; + } + + public override void Write(byte[] buffer, int offset, int count) + { + Stream.Write(buffer, offset, count); + _internalPosition += count; + } + +#if !NETFRAMEWORK && !NETSTANDARD2_0 + + //public override int Read(Span buffer) + //{ + // int bytesRead = Stream.Read(buffer); + // _internalPosition += bytesRead; + // return bytesRead; + //} + + // public override void Write(ReadOnlySpan buffer) + // { + // Stream.Write(buffer); + // _internalPosition += buffer.Length; + // } + +#endif +} diff --git a/src/SharpCompress/IO/SourceStream.cs b/src/SharpCompress/IO/SourceStream.cs index 46c0a3f67..74eddec86 100644 --- a/src/SharpCompress/IO/SourceStream.cs +++ b/src/SharpCompress/IO/SourceStream.cs @@ -6,8 +6,28 @@ namespace SharpCompress.IO; -public class SourceStream : Stream +public class SourceStream : Stream, IStreamStack { +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + int IStreamStack.DefaultBufferSize { get; set; } + + Stream IStreamStack.BaseStream() => _streams[_stream]; + + int IStreamStack.BufferSize + { + get => 0; + set { return; } + } + int IStreamStack.BufferPosition + { + get => 0; + set { return; } + } + + void IStreamStack.SetPosition(long position) { } + private long _prevSize; private readonly List _files; private readonly List _streams; @@ -54,6 +74,10 @@ ReaderOptions options } _stream = 0; _prevSize = 0; + +#if DEBUG_STREAMS + this.DebugConstruct(typeof(SourceStream)); +#endif } public void LoadAllParts() @@ -236,6 +260,9 @@ public override void Close() protected override void Dispose(bool disposing) { +#if DEBUG_STREAMS + this.DebugDispose(typeof(SourceStream)); +#endif Close(); base.Dispose(disposing); } diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 82e3d7761..b6b76b4d3 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -18,12 +18,14 @@ public static IReader Open(Stream stream, ReaderOptions? options = null) stream.CheckNotNull(nameof(stream)); options ??= new ReaderOptions() { LeaveStreamOpen = false }; - var rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); + var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize); + + long pos = ((IStreamStack)bStream).GetPosition(); foreach (var factory in Factories.Factory.Factories.OfType()) { - if (factory.TryOpenReader(rewindableStream, options, out var reader) && reader != null) + ((IStreamStack)bStream).StackSeek(pos); + if (factory.TryOpenReader(bStream, options, out var reader) && reader != null) { return reader; } diff --git a/src/SharpCompress/Readers/ReaderOptions.cs b/src/SharpCompress/Readers/ReaderOptions.cs index 706829cb6..a581d51a5 100644 --- a/src/SharpCompress/Readers/ReaderOptions.cs +++ b/src/SharpCompress/Readers/ReaderOptions.cs @@ -4,6 +4,8 @@ namespace SharpCompress.Readers; public class ReaderOptions : OptionsBase { + public const int DefaultBufferSize = 0x10000; + /// /// Look for RarArchive (Check for self-extracting archives or cases where RarArchive isn't at the start of the file) /// @@ -12,4 +14,6 @@ public class ReaderOptions : OptionsBase public string? Password { get; set; } public bool DisableCheckIncomplete { get; set; } + + public int BufferSize { get; set; } = DefaultBufferSize; } diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index 464934461..cfa9c0082 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -55,46 +55,49 @@ public static TarReader Open(Stream stream, ReaderOptions? options = null) { stream.CheckNotNull(nameof(stream)); options = options ?? new ReaderOptions(); - var rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); + var rewindableStream = new SharpCompressStream(stream); + + long pos = ((IStreamStack)rewindableStream).GetPosition(); + if (GZipArchive.IsGZipFile(rewindableStream)) { - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); return new TarReader(rewindableStream, options, CompressionType.GZip); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); if (BZip2Stream.IsBZip2(rewindableStream)) { - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new BZip2Stream(rewindableStream, CompressionMode.Decompress, false); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); return new TarReader(rewindableStream, options, CompressionType.BZip2); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); if (LZipStream.IsLZipFile(rewindableStream)) { - rewindableStream.Rewind(false); + ((IStreamStack)rewindableStream).StackSeek(pos); var testStream = new LZipStream(rewindableStream, CompressionMode.Decompress); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(true); + ((IStreamStack)rewindableStream).StackSeek(pos); return new TarReader(rewindableStream, options, CompressionType.LZip); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Rewind(true); + + ((IStreamStack)rewindableStream).StackSeek(pos); return new TarReader(rewindableStream, options, CompressionType.None); } diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index bde07dbb8..d76f1e586 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -1,4 +1,4 @@ - + SharpCompress - Pure C# Decompression/Compression en-US @@ -31,6 +31,18 @@ true + + + + + + + $(DefineConstants);DEBUG_STREAMS + + + + + diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.cs b/src/SharpCompress/Writers/GZip/GZipWriter.cs index f30fdb559..95c69a3ee 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriter.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriter.cs @@ -16,7 +16,7 @@ public GZipWriter(Stream destination, GZipWriterOptions? options = null) { if (WriterOptions.LeaveStreamOpen) { - destination = NonDisposingStream.Create(destination); + destination = SharpCompressStream.Create(destination, leaveOpen: true); } InitializeStream( new GZipStream( diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index a3e572f1b..e4b207862 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -25,7 +25,7 @@ public TarWriter(Stream destination, TarWriterOptions options) } if (WriterOptions.LeaveStreamOpen) { - destination = NonDisposingStream.Create(destination); + destination = SharpCompressStream.Create(destination, leaveOpen: true); } switch (options.CompressionType) { diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 8e89da643..82def7a4d 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -40,7 +40,7 @@ public ZipWriter(Stream destination, ZipWriterOptions zipWriterOptions) if (WriterOptions.LeaveStreamOpen) { - destination = NonDisposingStream.Create(destination); + destination = SharpCompressStream.Create(destination, leaveOpen: true); } InitializeStream(destination); } @@ -313,7 +313,7 @@ internal class ZipWritingStream : Stream private readonly ZipWriter writer; private readonly ZipCompressionMethod zipCompressionMethod; private readonly CompressionLevel compressionLevel; - private CountingWritableSubStream? counting; + private SharpCompressStream? counting; private ulong decompressed; // Flag to prevent throwing exceptions on Dispose @@ -353,7 +353,7 @@ public override long Position private Stream GetWriteStream(Stream writeStream) { - counting = new CountingWritableSubStream(writeStream); + counting = new SharpCompressStream(writeStream, leaveOpen: true); Stream output = counting; switch (zipCompressionMethod) { @@ -419,9 +419,9 @@ protected override void Dispose(bool disposing) return; } - var countingCount = counting?.Count ?? 0; + var countingCount = counting?.InternalPosition ?? 0; entry.Crc = (uint)crc.Crc32Result; - entry.Compressed = countingCount; + entry.Compressed = (ulong)countingCount; entry.Decompressed = decompressed; var zip64 = @@ -521,7 +521,7 @@ public override void Write(byte[] buffer, int offset, int count) // if we can prevent the writes from happening if (entry.Zip64HeaderOffset == 0) { - var countingCount = counting?.Count ?? 0; + var countingCount = counting?.InternalPosition ?? 0; // Pre-check, the counting.Count is not exact, as we do not know the size before having actually compressed it if ( limitsExceeded @@ -541,7 +541,7 @@ public override void Write(byte[] buffer, int offset, int count) if (entry.Zip64HeaderOffset == 0) { - var countingCount = counting?.Count ?? 0; + var countingCount = counting?.InternalPosition ?? 0; // Post-check, this is accurate if ((decompressed > uint.MaxValue) || countingCount > uint.MaxValue) { diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 116a11903..b2ae3ccf1 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -307,9 +307,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.16, )", - "resolved": "8.0.16", - "contentHash": "0H1QaKpVibe++Zx6EYJQGhrpfz2bBPGiQ7Rpsmx8I3+oKv+ZRRIfVfmcj50KuZlhhRE6V02y5bUjP+V2oPM2ng==" + "requested": "[8.0.18, )", + "resolved": "8.0.18", + "contentHash": "OiXqr2YIBEV9dsAWEtasK470ALyJ0VxJ9k4MotOxlWV6HeEgrJKYMW4HHj1OCCXvqE0/A25wEKPkpfiBARgDZA==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 94f402c9e..15deb061b 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -36,7 +36,13 @@ CompressionType compression { foreach (var path in testArchives) { - using (var stream = NonDisposingStream.Create(File.OpenRead(path), true)) + using ( + var stream = SharpCompressStream.Create( + File.OpenRead(path), + leaveOpen: true, + throwOnDispose: true + ) + ) { try { @@ -110,7 +116,13 @@ IEnumerable testArchives { foreach (var path in testArchives) { - using (var stream = NonDisposingStream.Create(File.OpenRead(path), true)) + using ( + var stream = SharpCompressStream.Create( + File.OpenRead(path), + leaveOpen: true, + throwOnDispose: true + ) + ) using (var archive = archiveFactory.Open(stream, readerOptions)) { try diff --git a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs index 9d475fd58..187cd80c0 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs @@ -18,7 +18,7 @@ public void GZip_Reader_Generic2() { //read only as GZip itme using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var reader = GZipReader.Open(new RewindableStream(stream)); + using var reader = GZipReader.Open(new SharpCompressStream(stream)); while (reader.MoveToNextEntry()) // Crash here { Assert.NotEqual(0, reader.Entry.Size); diff --git a/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs b/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs index ae91533c2..4c279a3c8 100644 --- a/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs +++ b/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs @@ -1,18 +1,52 @@ using System; using System.IO; +using SharpCompress.IO; +using SharpCompress.Readers; namespace SharpCompress.Test.Mocks; -public class ForwardOnlyStream(Stream stream) : Stream +public class ForwardOnlyStream : SharpCompressStream, IStreamStack { + private readonly Stream stream; +#if DEBUG_STREAMS + long IStreamStack.InstanceId { get; set; } +#endif + + Stream IStreamStack.BaseStream() => stream; + + int IStreamStack.BufferSize + { + get => 0; + set { } + } + int IStreamStack.BufferPosition + { + get => 0; + set { } + } + + void IStreamStack.SetPosition(long position) { } + public bool IsDisposed { get; private set; } + public ForwardOnlyStream(Stream stream, int bufferSize = ReaderOptions.DefaultBufferSize) + : base(stream, bufferSize: bufferSize) + { + this.stream = stream; +#if DEBUG_STREAMS + this.DebugConstruct(typeof(ForwardOnlyStream)); +#endif + } + protected override void Dispose(bool disposing) { if (!IsDisposed) { if (disposing) { +#if DEBUG_STREAMS + this.DebugDispose(typeof(ForwardOnlyStream)); +#endif stream.Dispose(); IsDisposed = true; base.Dispose(disposing); diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index 7ea1d7b79..b43183487 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -19,7 +19,7 @@ protected void Read( { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - options ??= new ReaderOptions(); + options ??= new ReaderOptions() { BufferSize = 0x20000 }; //test larger buffer size (need test rather than eyeballing debug logs :P) options.LeaveStreamOpen = true; ReadImpl(testArchive, expectedCompression, options); @@ -36,9 +36,11 @@ ReaderOptions options ) { using var file = File.OpenRead(testArchive); - using var protectedStream = NonDisposingStream.Create( - new ForwardOnlyStream(file), - throwOnDispose: true + using var protectedStream = SharpCompressStream.Create( + new ForwardOnlyStream(file, options.BufferSize), + leaveOpen: true, + throwOnDispose: true, + bufferSize: options.BufferSize ); using var testStream = new TestStream(protectedStream); using (var reader = ReaderFactory.Open(testStream, options)) diff --git a/tests/SharpCompress.Test/SharpCompress.Test.csproj b/tests/SharpCompress.Test/SharpCompress.Test.csproj index e262903fc..ce97ff0df 100644 --- a/tests/SharpCompress.Test/SharpCompress.Test.csproj +++ b/tests/SharpCompress.Test/SharpCompress.Test.csproj @@ -6,6 +6,9 @@ SharpCompress.Test.snk true + + $(DefineConstants);DEBUG_STREAMS + diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs b/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs index ab18da451..d83ed86c2 100644 --- a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs @@ -20,15 +20,17 @@ public void TestRewind() bw.Write(7); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); - stream.StartRecording(); + var stream = new SharpCompressStream(ms, bufferSize: 0x10000); + //stream.StartRecording(); var br = new BinaryReader(stream); Assert.Equal(1, br.ReadInt32()); Assert.Equal(2, br.ReadInt32()); Assert.Equal(3, br.ReadInt32()); Assert.Equal(4, br.ReadInt32()); - stream.Rewind(true); - stream.StartRecording(); + //stream.Rewind(true); + ((IStreamStack)stream).StackSeek(0); + //stream.StartRecording(); + long pos = stream.Position; Assert.Equal(1, br.ReadInt32()); Assert.Equal(2, br.ReadInt32()); Assert.Equal(3, br.ReadInt32()); @@ -36,8 +38,9 @@ public void TestRewind() Assert.Equal(5, br.ReadInt32()); Assert.Equal(6, br.ReadInt32()); Assert.Equal(7, br.ReadInt32()); - stream.Rewind(true); - stream.StartRecording(); + //stream.Rewind(true); + //stream.StartRecording(); + ((IStreamStack)stream).StackSeek(pos); Assert.Equal(1, br.ReadInt32()); Assert.Equal(2, br.ReadInt32()); Assert.Equal(3, br.ReadInt32()); @@ -58,21 +61,24 @@ public void TestIncompleteRewind() bw.Write(7); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); - stream.StartRecording(); + var stream = new SharpCompressStream(ms, bufferSize: 0x10000); + //stream.StartRecording(); var br = new BinaryReader(stream); Assert.Equal(1, br.ReadInt32()); Assert.Equal(2, br.ReadInt32()); Assert.Equal(3, br.ReadInt32()); Assert.Equal(4, br.ReadInt32()); - stream.Rewind(true); + ((IStreamStack)stream).StackSeek(0); + //stream.Rewind(true); Assert.Equal(1, br.ReadInt32()); Assert.Equal(2, br.ReadInt32()); - stream.StartRecording(); + long pos = stream.Position; + //stream.StartRecording(); Assert.Equal(3, br.ReadInt32()); Assert.Equal(4, br.ReadInt32()); Assert.Equal(5, br.ReadInt32()); - stream.Rewind(true); + ((IStreamStack)stream).StackSeek(pos); + //stream.Rewind(true); Assert.Equal(3, br.ReadInt32()); Assert.Equal(4, br.ReadInt32()); Assert.Equal(5, br.ReadInt32()); diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs new file mode 100644 index 000000000..e664231c4 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs @@ -0,0 +1,98 @@ +using System; +using System.Buffers; +using System.IO; +using System.Linq; +using System.Text; +using SharpCompress.Compressors.LZMA; +using SharpCompress.IO; +using Xunit; + +namespace SharpCompress.Test.Streams; + +public class SharpCompressStreamTests +{ + private static void createData(MemoryStream ms) + { + using (BinaryWriter bw = new BinaryWriter(ms, Encoding.UTF8, true)) + { + //write offset every 4 bytes - easy to test position + for (int i = 0; i < ms.Length; i += 4) + { + bw.Write(i); + } + } + ms.Position = 0; + } + + [Fact] + public void BufferReadTest() + { + byte[] data = ArrayPool.Shared.Rent(0x100000); + byte[] test = ArrayPool.Shared.Rent(0x1000); + using (MemoryStream ms = new MemoryStream(data)) + { + createData(ms); + + using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + { + IStreamStack stack = (IStreamStack)scs; + + scs.Seek(0x1000, SeekOrigin.Begin); + Assert.Equal(0x1000, scs.Position); //position in the SharpCompressionStream (with 0xf000 remaining in the buffer) + Assert.Equal(0x1000, ms.Position); //initial seek + full buffer read + + scs.Read(test, 0, test.Length); //read bytes 0x1000 to 0x2000 + Assert.Equal(0x2000, scs.Position); //stream has correct position + Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); //is the data correct + Assert.Equal(0x11000, ms.Position); //seek plus read bytes + + scs.Seek(0x500, SeekOrigin.Begin); //seek before the buffer start + scs.Read(test, 0, test.Length); //read bytes 0x500 to 0x1500 + Assert.Equal(0x1500, scs.Position); //stream has correct position + Assert.True(data.Skip(0x500).Take(test.Length).SequenceEqual(test)); //is the data correct + Assert.Equal(0x10500, ms.Position); //seek plus read bytes + } + } + + ArrayPool.Shared.Return(data); + ArrayPool.Shared.Return(test); + } + + [Fact] + public void BufferReadAndSeekTest() + { + byte[] data = ArrayPool.Shared.Rent(0x100000); + byte[] test = ArrayPool.Shared.Rent(0x1000); + using (MemoryStream ms = new MemoryStream(data)) + { + createData(ms); + + using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + { + IStreamStack stack = (IStreamStack)scs; + + scs.Read(test, 0, test.Length); //read bytes 0 to 0x1000 + Assert.True(data.Take(test.Length).SequenceEqual(test)); //is the data correct + Assert.Equal(0x1000, scs.Position); //stream has correct position + Assert.Equal(0x10000, ms.Position); //moved the base stream on by the size of the buffer not what was requested + + scs.Read(test, 0, test.Length); //read bytes 0x1000 to 0x2000 + Assert.Equal(0x2000, scs.Position); //stream has correct position + Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); //is the data correct + Assert.Equal(0x10000, ms.Position); //the base stream has not moved + + //rewind the buffer + stack.Rewind(0x1000); //rewind buffer back by 0x1000 bytes + + //repeat the previous test + scs.Read(test, 0, test.Length); //read bytes 0x1000 to 0x2000 + Assert.Equal(0x2000, scs.Position); //stream has correct position + Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); //is the data correct + Assert.Equal(0x10000, ms.Position); //the base stream has not moved + } + } + + ArrayPool.Shared.Return(data); + ArrayPool.Shared.Return(test); + } +} diff --git a/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs b/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs index 8d9db0910..5808078c9 100644 --- a/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs +++ b/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs @@ -80,7 +80,7 @@ public void Zlib_should_read_the_previously_written_message() private void Compress(Stream input, Stream output, int compressionLevel) { using var zlibStream = new ZlibStream( - NonDisposingStream.Create(output), + SharpCompressStream.Create(output, leaveOpen: true), CompressionMode.Compress, (CompressionLevel)compressionLevel ); @@ -91,7 +91,7 @@ private void Compress(Stream input, Stream output, int compressionLevel) private void Decompress(Stream input, Stream output) { using var zlibStream = new ZlibStream( - NonDisposingStream.Create(input), + SharpCompressStream.Create(input, leaveOpen: true), CompressionMode.Decompress ); zlibStream.CopyTo(output); diff --git a/tests/SharpCompress.Test/Tar/TarWriterTests.cs b/tests/SharpCompress.Test/Tar/TarWriterTests.cs index 2ef629674..3f81e0c2f 100644 --- a/tests/SharpCompress.Test/Tar/TarWriterTests.cs +++ b/tests/SharpCompress.Test/Tar/TarWriterTests.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Text; using SharpCompress.Common; using SharpCompress.Writers.Tar; @@ -8,6 +8,14 @@ namespace SharpCompress.Test.Tar; public class TarWriterTests : WriterTests { + static TarWriterTests() + { +#if !NETFRAMEWORK + //fix issue where these tests could not be ran in isolation + System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); +#endif + } + public TarWriterTests() : base(ArchiveType.Tar) => UseExtensionInsteadOfNameToVerify = true; diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index ba08b3d29..7a367e6e1 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -40,7 +40,10 @@ protected void Write( readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using var reader = ReaderFactory.Open(NonDisposingStream.Create(stream), readerOptions); + using var reader = ReaderFactory.Open( + SharpCompressStream.Create(stream, leaveOpen: true), + readerOptions + ); reader.WriteAllToDirectory( SCRATCH_FILES_PATH, new ExtractionOptions { ExtractFullPath = true } diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index d65deb7d0..f8d69d6dc 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -263,7 +263,9 @@ public void TestSharpCompressWithEmptyStream() stream = new MemoryStream(memory.ToArray()); File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), memory.ToArray()); - using IReader zipReader = ZipReader.Open(NonDisposingStream.Create(stream, true)); + using IReader zipReader = ZipReader.Open( + SharpCompressStream.Create(stream, leaveOpen: true, throwOnDispose: true) + ); var i = 0; while (zipReader.MoveToNextEntry()) {