diff --git a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs index 48abea7d5..729ad529d 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 new NonDisposingStream(stream); + return SharpCompressStream.CreateNonDisposing(stream); } internal override void Close() diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index d61d03da2..be352cdb7 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -46,7 +46,13 @@ public async ValueTask WriteToAsync( throw new ExtractionException("Entry is a file directory and cannot be extracted."); } +#if LEGACY_DOTNET using var entryStream = await archiveEntry.OpenEntryStreamAsync(cancellationToken); +#else + await using var entryStream = await archiveEntry.OpenEntryStreamAsync( + cancellationToken + ); +#endif var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress); await sourceStream .CopyToAsync(streamToWriteTo, Constants.BufferSize, cancellationToken) diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 3f766cd87..e9b27d6ad 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -153,12 +153,12 @@ protected override IEnumerable GetEntries(Stream stream) } } - protected override EntryStream GetEntryStream(bool useSyncOverAsyncDispose) + protected override EntryStream GetEntryStream() { var entry = _currentEntry.NotNull("currentEntry is not null"); if (entry.IsDirectory) { - return CreateEntryStream(Stream.Null, false); + return CreateEntryStream(Stream.Null); } var folder = entry.FilePart.Folder; @@ -186,8 +186,7 @@ protected override EntryStream GetEntryStream(bool useSyncOverAsyncDispose) return CreateEntryStream( new SyncOnlyStream( new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true) - ), - useSyncOverAsyncDispose + ) ); } diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index a1b834e89..959b4ab44 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -176,7 +176,11 @@ public static async ValueTask IsTarFileAsync( try { var tarHeader = new TarHeader(new ArchiveEncoding()); - var reader = new AsyncBinaryReader(stream, false); +#if NET8_0_OR_GREATER + await using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#else + using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#endif var readSucceeded = await tarHeader.ReadAsync(reader); var isEmptyArchive = tarHeader.Name?.Length == 0 diff --git a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs index b09876e98..921452e9e 100644 --- a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs @@ -14,8 +14,9 @@ internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull(); - public ValueTask OpenEntryStreamAsync(CancellationToken cancellationToken = default) => - new(OpenEntryStream()); + public async ValueTask OpenEntryStreamAsync( + CancellationToken cancellationToken = default + ) => (await Parts.Single().GetCompressedStreamAsync(cancellationToken)).NotNull(); #region IArchiveEntry Members diff --git a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs index e27a603c7..99254369c 100644 --- a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs @@ -79,7 +79,7 @@ public override Stream OpenEntryStream() } //ensure new stream is at the start, this could be reset stream.Seek(0, SeekOrigin.Begin); - return new NonDisposingStream(stream); + return SharpCompressStream.CreateNonDisposing(stream); } internal override void Close() diff --git a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs index 6d58ac995..168f3a4fd 100644 --- a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs @@ -80,7 +80,7 @@ public override Stream OpenEntryStream() } //ensure new stream is at the start, this could be reset stream.Seek(0, SeekOrigin.Begin); - return new NonDisposingStream(stream); + return SharpCompressStream.CreateNonDisposing(stream); } internal override void Close() diff --git a/src/SharpCompress/Common/Ace/AceCrc.cs b/src/SharpCompress/Common/Ace/AceCrc.cs index f075c6a06..bbd51d923 100644 --- a/src/SharpCompress/Common/Ace/AceCrc.cs +++ b/src/SharpCompress/Common/Ace/AceCrc.cs @@ -4,62 +4,61 @@ using System.Text; using System.Threading.Tasks; -namespace SharpCompress.Common.Ace +namespace SharpCompress.Common.Ace; + +public class AceCrc { - public class AceCrc + // CRC-32 lookup table (standard polynomial 0xEDB88320, reflected) + private static readonly uint[] Crc32Table = GenerateTable(); + + private static uint[] GenerateTable() { - // CRC-32 lookup table (standard polynomial 0xEDB88320, reflected) - private static readonly uint[] Crc32Table = GenerateTable(); + var table = new uint[256]; - private static uint[] GenerateTable() + for (int i = 0; i < 256; i++) { - var table = new uint[256]; + uint crc = (uint)i; - for (int i = 0; i < 256; i++) + for (int j = 0; j < 8; j++) { - uint crc = (uint)i; - - for (int j = 0; j < 8; j++) + if ((crc & 1) != 0) { - if ((crc & 1) != 0) - { - crc = (crc >> 1) ^ 0xEDB88320u; - } - else - { - crc >>= 1; - } + crc = (crc >> 1) ^ 0xEDB88320u; + } + else + { + crc >>= 1; } - - table[i] = crc; } - return table; + table[i] = crc; } - /// - /// Calculate ACE CRC-32 checksum. - /// ACE CRC-32 uses standard CRC-32 polynomial (0xEDB88320, reflected) - /// with init=0xFFFFFFFF but NO final XOR. - /// - public static uint AceCrc32(ReadOnlySpan data) - { - uint crc = 0xFFFFFFFFu; - - foreach (byte b in data) - { - crc = (crc >> 8) ^ Crc32Table[(crc ^ b) & 0xFF]; - } + return table; + } - return crc; // No final XOR for ACE - } + /// + /// Calculate ACE CRC-32 checksum. + /// ACE CRC-32 uses standard CRC-32 polynomial (0xEDB88320, reflected) + /// with init=0xFFFFFFFF but NO final XOR. + /// + public static uint AceCrc32(ReadOnlySpan data) + { + uint crc = 0xFFFFFFFFu; - /// - /// ACE CRC-16 is the lower 16 bits of the ACE CRC-32. - /// - public static ushort AceCrc16(ReadOnlySpan data) + foreach (byte b in data) { - return (ushort)(AceCrc32(data) & 0xFFFF); + crc = (crc >> 8) ^ Crc32Table[(crc ^ b) & 0xFF]; } + + return crc; // No final XOR for ACE + } + + /// + /// ACE CRC-16 is the lower 16 bits of the ACE CRC-32. + /// + public static ushort AceCrc16(ReadOnlySpan data) + { + return (ushort)(AceCrc32(data) & 0xFFFF); } } diff --git a/src/SharpCompress/Common/Ace/AceEntry.cs b/src/SharpCompress/Common/Ace/AceEntry.cs index a3dde1e7f..5aa94fb4d 100644 --- a/src/SharpCompress/Common/Ace/AceEntry.cs +++ b/src/SharpCompress/Common/Ace/AceEntry.cs @@ -6,63 +6,62 @@ using System.Threading.Tasks; using SharpCompress.Common.Ace.Headers; -namespace SharpCompress.Common.Ace +namespace SharpCompress.Common.Ace; + +public class AceEntry : Entry { - public class AceEntry : Entry - { - private readonly AceFilePart _filePart; + private readonly AceFilePart _filePart; - internal AceEntry(AceFilePart filePart) - { - _filePart = filePart; - } + internal AceEntry(AceFilePart filePart) + { + _filePart = filePart; + } - public override long Crc + public override long Crc + { + get { - get + if (_filePart == null) { - if (_filePart == null) - { - return 0; - } - return _filePart.Header.Crc32; + return 0; } + return _filePart.Header.Crc32; } + } - public override string? Key => _filePart?.Header.Filename; + public override string? Key => _filePart?.Header.Filename; - public override string? LinkTarget => null; + public override string? LinkTarget => null; - public override long CompressedSize => _filePart?.Header.PackedSize ?? 0; + public override long CompressedSize => _filePart?.Header.PackedSize ?? 0; - public override CompressionType CompressionType + public override CompressionType CompressionType + { + get { - get + if (_filePart.Header.CompressionType == Headers.CompressionType.Stored) { - if (_filePart.Header.CompressionType == Headers.CompressionType.Stored) - { - return CompressionType.None; - } - return CompressionType.AceLZ77; + return CompressionType.None; } + return CompressionType.AceLZ77; } + } - public override long Size => _filePart?.Header.OriginalSize ?? 0; + public override long Size => _filePart?.Header.OriginalSize ?? 0; - public override DateTime? LastModifiedTime => _filePart.Header.DateTime; + public override DateTime? LastModifiedTime => _filePart.Header.DateTime; - public override DateTime? CreatedTime => null; + public override DateTime? CreatedTime => null; - public override DateTime? LastAccessedTime => null; + public override DateTime? LastAccessedTime => null; - public override DateTime? ArchivedTime => null; + public override DateTime? ArchivedTime => null; - public override bool IsEncrypted => _filePart.Header.IsFileEncrypted; + public override bool IsEncrypted => _filePart.Header.IsFileEncrypted; - public override bool IsDirectory => _filePart.Header.IsDirectory; + public override bool IsDirectory => _filePart.Header.IsDirectory; - public override bool IsSplitAfter => false; + public override bool IsSplitAfter => false; - internal override IEnumerable Parts => _filePart.Empty(); - } + internal override IEnumerable Parts => _filePart.Empty(); } diff --git a/src/SharpCompress/Common/Ace/AceFilePart.cs b/src/SharpCompress/Common/Ace/AceFilePart.cs index 6efefaf56..a56c17f60 100644 --- a/src/SharpCompress/Common/Ace/AceFilePart.cs +++ b/src/SharpCompress/Common/Ace/AceFilePart.cs @@ -7,46 +7,45 @@ using SharpCompress.Common.Ace.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Ace +namespace SharpCompress.Common.Ace; + +public class AceFilePart : FilePart { - public class AceFilePart : FilePart - { - private readonly Stream _stream; - internal AceFileHeader Header { get; set; } + private readonly Stream _stream; + internal AceFileHeader Header { get; set; } - internal AceFilePart(AceFileHeader localAceHeader, Stream seekableStream) - : base(localAceHeader.ArchiveEncoding) - { - _stream = seekableStream; - Header = localAceHeader; - } + internal AceFilePart(AceFileHeader localAceHeader, Stream seekableStream) + : base(localAceHeader.ArchiveEncoding) + { + _stream = seekableStream; + Header = localAceHeader; + } - internal override string? FilePartName => Header.Filename; + internal override string? FilePartName => Header.Filename; - internal override Stream GetCompressedStream() + internal override Stream GetCompressedStream() + { + if (_stream != null) { - if (_stream != null) + Stream compressedStream; + switch (Header.CompressionType) { - Stream compressedStream; - switch (Header.CompressionType) - { - case Headers.CompressionType.Stored: - compressedStream = new ReadOnlySubStream( - _stream, - Header.DataStartPosition, - Header.PackedSize - ); - break; - default: - throw new NotSupportedException( - "CompressionMethod: " + Header.CompressionQuality - ); - } - return compressedStream; + case Headers.CompressionType.Stored: + compressedStream = new ReadOnlySubStream( + _stream, + Header.DataStartPosition, + Header.PackedSize + ); + break; + default: + throw new NotSupportedException( + "CompressionMethod: " + Header.CompressionQuality + ); } - return _stream.NotNull(); + return compressedStream; } - - internal override Stream? GetRawStream() => _stream; + return _stream.NotNull(); } + + internal override Stream? GetRawStream() => _stream; } diff --git a/src/SharpCompress/Common/Ace/AceVolume.cs b/src/SharpCompress/Common/Ace/AceVolume.cs index 28456866b..f3931d2c6 100644 --- a/src/SharpCompress/Common/Ace/AceVolume.cs +++ b/src/SharpCompress/Common/Ace/AceVolume.cs @@ -7,29 +7,28 @@ using SharpCompress.Common.Arj; using SharpCompress.Readers; -namespace SharpCompress.Common.Ace +namespace SharpCompress.Common.Ace; + +public class AceVolume : Volume { - public class AceVolume : Volume - { - public AceVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) { } + public AceVolume(Stream stream, ReaderOptions readerOptions, int index = 0) + : base(stream, readerOptions, index) { } - public override bool IsFirstVolume - { - get { return true; } - } + public override bool IsFirstVolume + { + get { return true; } + } - /// - /// ArjArchive is part of a multi-part archive. - /// - public override bool IsMultiVolume - { - get { return false; } - } + /// + /// ArjArchive is part of a multi-part archive. + /// + public override bool IsMultiVolume + { + get { return false; } + } - internal IEnumerable GetVolumeFileParts() - { - return new List(); - } + internal IEnumerable GetVolumeFileParts() + { + return new List(); } } diff --git a/src/SharpCompress/Common/Ace/Headers/AceFileHeader.cs b/src/SharpCompress/Common/Ace/Headers/AceFileHeader.cs index 16aa1e3b9..9a9688108 100644 --- a/src/SharpCompress/Common/Ace/Headers/AceFileHeader.cs +++ b/src/SharpCompress/Common/Ace/Headers/AceFileHeader.cs @@ -7,169 +7,168 @@ using System.Xml.Linq; using SharpCompress.Common.Arc; -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// ACE file entry header +/// +public sealed partial class AceFileHeader : AceHeader { + public long DataStartPosition { get; private set; } + public long PackedSize { get; set; } + public long OriginalSize { get; set; } + public DateTime DateTime { get; set; } + public int Attributes { get; set; } + public uint Crc32 { get; set; } + public CompressionType CompressionType { get; set; } + public CompressionQuality CompressionQuality { get; set; } + public ushort Parameters { get; set; } + public string Filename { get; set; } = string.Empty; + public List Comment { get; set; } = new(); + /// - /// ACE file entry header + /// File data offset in the archive /// - public sealed partial class AceFileHeader : AceHeader + public ulong DataOffset { get; set; } + + public bool IsDirectory => (Attributes & 0x10) != 0; + + public bool IsContinuedFromPrev => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.CONTINUED_PREV) != 0; + + public bool IsContinuedToNext => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.CONTINUED_NEXT) != 0; + + public int DictionarySize { - public long DataStartPosition { get; private set; } - public long PackedSize { get; set; } - public long OriginalSize { get; set; } - public DateTime DateTime { get; set; } - public int Attributes { get; set; } - public uint Crc32 { get; set; } - public CompressionType CompressionType { get; set; } - public CompressionQuality CompressionQuality { get; set; } - public ushort Parameters { get; set; } - public string Filename { get; set; } = string.Empty; - public List Comment { get; set; } = new(); - - /// - /// File data offset in the archive - /// - public ulong DataOffset { get; set; } - - public bool IsDirectory => (Attributes & 0x10) != 0; - - public bool IsContinuedFromPrev => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.CONTINUED_PREV) != 0; - - public bool IsContinuedToNext => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.CONTINUED_NEXT) != 0; - - public int DictionarySize + get { - get - { - int bits = Parameters & 0x0F; - return bits < 10 ? 1024 : 1 << bits; - } + int bits = Parameters & 0x0F; + return bits < 10 ? 1024 : 1 << bits; } + } - public AceFileHeader(IArchiveEncoding archiveEncoding) - : base(archiveEncoding, AceHeaderType.FILE) { } + public AceFileHeader(IArchiveEncoding archiveEncoding) + : base(archiveEncoding, AceHeaderType.FILE) { } - /// - /// Reads the next file entry header from the stream. - /// Returns null if no more entries or end of archive. - /// Supports both ACE 1.0 and ACE 2.0 formats. - /// - public override AceHeader? Read(Stream stream) + /// + /// Reads the next file entry header from the stream. + /// Returns null if no more entries or end of archive. + /// Supports both ACE 1.0 and ACE 2.0 formats. + /// + public override AceHeader? Read(Stream stream) + { + var headerData = ReadHeader(stream); + if (headerData.Length == 0) { - var headerData = ReadHeader(stream); - if (headerData.Length == 0) - { - return null; - } - int offset = 0; + return null; + } + int offset = 0; - // Header type (1 byte) - HeaderType = headerData[offset++]; + // Header type (1 byte) + HeaderType = headerData[offset++]; - // Skip recovery record headers (ACE 2.0 feature) - if (HeaderType == (byte)SharpCompress.Common.Ace.Headers.AceHeaderType.RECOVERY32) - { - // Skip to next header - return null; - } + // Skip recovery record headers (ACE 2.0 feature) + if (HeaderType == (byte)SharpCompress.Common.Ace.Headers.AceHeaderType.RECOVERY32) + { + // Skip to next header + return null; + } - if (HeaderType != (byte)SharpCompress.Common.Ace.Headers.AceHeaderType.FILE) - { - // Unknown header type - skip - return null; - } + if (HeaderType != (byte)SharpCompress.Common.Ace.Headers.AceHeaderType.FILE) + { + // Unknown header type - skip + return null; + } - // Header flags (2 bytes) - HeaderFlags = BitConverter.ToUInt16(headerData, offset); - offset += 2; + // Header flags (2 bytes) + HeaderFlags = BitConverter.ToUInt16(headerData, offset); + offset += 2; - // Packed size (4 bytes) - PackedSize = BitConverter.ToUInt32(headerData, offset); - offset += 4; + // Packed size (4 bytes) + PackedSize = BitConverter.ToUInt32(headerData, offset); + offset += 4; - // Original size (4 bytes) - OriginalSize = BitConverter.ToUInt32(headerData, offset); - offset += 4; + // Original size (4 bytes) + OriginalSize = BitConverter.ToUInt32(headerData, offset); + offset += 4; - // File date/time in DOS format (4 bytes) - var dosDateTime = BitConverter.ToUInt32(headerData, offset); - DateTime = ConvertDosDateTime(dosDateTime); - offset += 4; + // File date/time in DOS format (4 bytes) + var dosDateTime = BitConverter.ToUInt32(headerData, offset); + DateTime = ConvertDosDateTime(dosDateTime); + offset += 4; - // File attributes (4 bytes) - Attributes = (int)BitConverter.ToUInt32(headerData, offset); - offset += 4; + // File attributes (4 bytes) + Attributes = (int)BitConverter.ToUInt32(headerData, offset); + offset += 4; - // CRC32 (4 bytes) - Crc32 = BitConverter.ToUInt32(headerData, offset); - offset += 4; + // CRC32 (4 bytes) + Crc32 = BitConverter.ToUInt32(headerData, offset); + offset += 4; - // Compression type (1 byte) - byte compressionType = headerData[offset++]; - CompressionType = GetCompressionType(compressionType); + // Compression type (1 byte) + byte compressionType = headerData[offset++]; + CompressionType = GetCompressionType(compressionType); - // Compression quality/parameter (1 byte) - byte compressionQuality = headerData[offset++]; - CompressionQuality = GetCompressionQuality(compressionQuality); + // Compression quality/parameter (1 byte) + byte compressionQuality = headerData[offset++]; + CompressionQuality = GetCompressionQuality(compressionQuality); - // Parameters (2 bytes) - Parameters = BitConverter.ToUInt16(headerData, offset); - offset += 2; + // Parameters (2 bytes) + Parameters = BitConverter.ToUInt16(headerData, offset); + offset += 2; - // Reserved (2 bytes) - skip - offset += 2; + // Reserved (2 bytes) - skip + offset += 2; - // Filename length (2 bytes) - var filenameLength = BitConverter.ToUInt16(headerData, offset); - offset += 2; + // Filename length (2 bytes) + var filenameLength = BitConverter.ToUInt16(headerData, offset); + offset += 2; - // Filename - if (offset + filenameLength <= headerData.Length) - { - Filename = ArchiveEncoding.Decode(headerData, offset, filenameLength); - offset += filenameLength; - } + // Filename + if (offset + filenameLength <= headerData.Length) + { + Filename = ArchiveEncoding.Decode(headerData, offset, filenameLength); + offset += filenameLength; + } - // Handle comment if present - if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) + // Handle comment if present + if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) + { + // Comment length (2 bytes) + if (offset + 2 <= headerData.Length) { - // Comment length (2 bytes) - if (offset + 2 <= headerData.Length) - { - ushort commentLength = BitConverter.ToUInt16(headerData, offset); - offset += 2 + commentLength; // Skip comment - } + ushort commentLength = BitConverter.ToUInt16(headerData, offset); + offset += 2 + commentLength; // Skip comment } - - // Store the data start position - DataStartPosition = stream.Position; - - return this; } - // ReadAsync moved to AceFileHeader.Async.cs + // Store the data start position + DataStartPosition = stream.Position; - public CompressionType GetCompressionType(byte value) => - value switch - { - 0 => CompressionType.Stored, - 1 => CompressionType.Lz77, - 2 => CompressionType.Blocked, - _ => CompressionType.Unknown, - }; - - public CompressionQuality GetCompressionQuality(byte value) => - value switch - { - 0 => CompressionQuality.None, - 1 => CompressionQuality.Fastest, - 2 => CompressionQuality.Fast, - 3 => CompressionQuality.Normal, - 4 => CompressionQuality.Good, - 5 => CompressionQuality.Best, - _ => CompressionQuality.Unknown, - }; + return this; } + + // ReadAsync moved to AceFileHeader.Async.cs + + public CompressionType GetCompressionType(byte value) => + value switch + { + 0 => CompressionType.Stored, + 1 => CompressionType.Lz77, + 2 => CompressionType.Blocked, + _ => CompressionType.Unknown, + }; + + public CompressionQuality GetCompressionQuality(byte value) => + value switch + { + 0 => CompressionQuality.None, + 1 => CompressionQuality.Fastest, + 2 => CompressionQuality.Fast, + 3 => CompressionQuality.Normal, + 4 => CompressionQuality.Good, + 5 => CompressionQuality.Best, + _ => CompressionQuality.Unknown, + }; } diff --git a/src/SharpCompress/Common/Ace/Headers/AceHeader.cs b/src/SharpCompress/Common/Ace/Headers/AceHeader.cs index eb4bf5b70..5e3657bcb 100644 --- a/src/SharpCompress/Common/Ace/Headers/AceHeader.cs +++ b/src/SharpCompress/Common/Ace/Headers/AceHeader.cs @@ -5,153 +5,152 @@ using SharpCompress.Common.Arj.Headers; using SharpCompress.Crypto; -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// Header type constants +/// +public enum AceHeaderType { - /// - /// Header type constants - /// - public enum AceHeaderType - { - MAIN = 0, - FILE = 1, - RECOVERY32 = 2, - RECOVERY64A = 3, - RECOVERY64B = 4, - } + MAIN = 0, + FILE = 1, + RECOVERY32 = 2, + RECOVERY64A = 3, + RECOVERY64B = 4, +} - public abstract partial class AceHeader +public abstract partial class AceHeader +{ + // ACE signature: bytes at offset 7 should be "**ACE**" + private static readonly byte[] AceSignature = + [ + (byte)'*', + (byte)'*', + (byte)'A', + (byte)'C', + (byte)'E', + (byte)'*', + (byte)'*', + ]; + + public AceHeader(IArchiveEncoding archiveEncoding, AceHeaderType type) { - // ACE signature: bytes at offset 7 should be "**ACE**" - private static readonly byte[] AceSignature = - [ - (byte)'*', - (byte)'*', - (byte)'A', - (byte)'C', - (byte)'E', - (byte)'*', - (byte)'*', - ]; - - public AceHeader(IArchiveEncoding archiveEncoding, AceHeaderType type) - { - AceHeaderType = type; - ArchiveEncoding = archiveEncoding; - } + AceHeaderType = type; + ArchiveEncoding = archiveEncoding; + } - public IArchiveEncoding ArchiveEncoding { get; } - public AceHeaderType AceHeaderType { get; } + public IArchiveEncoding ArchiveEncoding { get; } + public AceHeaderType AceHeaderType { get; } - public ushort HeaderFlags { get; set; } - public ushort HeaderCrc { get; set; } - public ushort HeaderSize { get; set; } - public byte HeaderType { get; set; } + public ushort HeaderFlags { get; set; } + public ushort HeaderCrc { get; set; } + public ushort HeaderSize { get; set; } + public byte HeaderType { get; set; } - public bool IsFileEncrypted => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.FILE_ENCRYPTED) != 0; - public bool Is64Bit => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.MEMORY_64BIT) != 0; + public bool IsFileEncrypted => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.FILE_ENCRYPTED) != 0; + public bool Is64Bit => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.MEMORY_64BIT) != 0; - public bool IsSolid => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.SOLID_MAIN) != 0; + public bool IsSolid => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.SOLID_MAIN) != 0; - public bool IsMultiVolume => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.MULTIVOLUME) != 0; + public bool IsMultiVolume => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.MULTIVOLUME) != 0; - public abstract AceHeader? Read(Stream reader); + public abstract AceHeader? Read(Stream reader); - // Async methods moved to AceHeader.Async.cs + // Async methods moved to AceHeader.Async.cs - public byte[] ReadHeader(Stream stream) + public byte[] ReadHeader(Stream stream) + { + // Read header CRC (2 bytes) and header size (2 bytes) + var headerBytes = new byte[4]; + if (!stream.ReadFully(headerBytes)) { - // Read header CRC (2 bytes) and header size (2 bytes) - var headerBytes = new byte[4]; - if (!stream.ReadFully(headerBytes)) - { - return Array.Empty(); - } - - HeaderCrc = BitConverter.ToUInt16(headerBytes, 0); // CRC for validation - HeaderSize = BitConverter.ToUInt16(headerBytes, 2); - if (HeaderSize == 0) - { - return Array.Empty(); - } + return Array.Empty(); + } - // Read the header data - var body = new byte[HeaderSize]; - if (!stream.ReadFully(body)) - { - return Array.Empty(); - } + HeaderCrc = BitConverter.ToUInt16(headerBytes, 0); // CRC for validation + HeaderSize = BitConverter.ToUInt16(headerBytes, 2); + if (HeaderSize == 0) + { + return Array.Empty(); + } - // Verify crc - var checksum = AceCrc.AceCrc16(body); - if (checksum != HeaderCrc) - { - throw new InvalidDataException("Header checksum is invalid"); - } - return body; + // Read the header data + var body = new byte[HeaderSize]; + if (!stream.ReadFully(body)) + { + return Array.Empty(); } - public static bool IsArchive(Stream stream) + // Verify crc + var checksum = AceCrc.AceCrc16(body); + if (checksum != HeaderCrc) { - // ACE files have a specific signature - // First two bytes are typically 0x60 0xEA (signature bytes) - // At offset 7, there should be "**ACE**" (7 bytes) - var bytes = new byte[14]; - if (stream.Read(bytes, 0, 14) != 14) - { - return false; - } + throw new InvalidDataException("Header checksum is invalid"); + } + return body; + } - // Check for "**ACE**" at offset 7 - return CheckMagicBytes(bytes, 7); + public static bool IsArchive(Stream stream) + { + // ACE files have a specific signature + // First two bytes are typically 0x60 0xEA (signature bytes) + // At offset 7, there should be "**ACE**" (7 bytes) + var bytes = new byte[14]; + if (stream.Read(bytes, 0, 14) != 14) + { + return false; } - protected static bool CheckMagicBytes(byte[] headerBytes, int offset) + // Check for "**ACE**" at offset 7 + return CheckMagicBytes(bytes, 7); + } + + protected static bool CheckMagicBytes(byte[] headerBytes, int offset) + { + // Check for "**ACE**" at specified offset + for (int i = 0; i < AceSignature.Length; i++) { - // Check for "**ACE**" at specified offset - for (int i = 0; i < AceSignature.Length; i++) + if (headerBytes[offset + i] != AceSignature[i]) { - if (headerBytes[offset + i] != AceSignature[i]) - { - return false; - } + return false; } - return true; } + return true; + } - protected DateTime ConvertDosDateTime(uint dosDateTime) + protected DateTime ConvertDosDateTime(uint dosDateTime) + { + try { - try - { - int second = (int)(dosDateTime & 0x1F) * 2; - int minute = (int)((dosDateTime >> 5) & 0x3F); - int hour = (int)((dosDateTime >> 11) & 0x1F); - int day = (int)((dosDateTime >> 16) & 0x1F); - int month = (int)((dosDateTime >> 21) & 0x0F); - int year = (int)((dosDateTime >> 25) & 0x7F) + 1980; - - if ( - day < 1 - || day > 31 - || month < 1 - || month > 12 - || hour > 23 - || minute > 59 - || second > 59 - ) - { - return DateTime.MinValue; - } - - return new DateTime(year, month, day, hour, minute, second); - } - catch + int second = (int)(dosDateTime & 0x1F) * 2; + int minute = (int)((dosDateTime >> 5) & 0x3F); + int hour = (int)((dosDateTime >> 11) & 0x1F); + int day = (int)((dosDateTime >> 16) & 0x1F); + int month = (int)((dosDateTime >> 21) & 0x0F); + int year = (int)((dosDateTime >> 25) & 0x7F) + 1980; + + if ( + day < 1 + || day > 31 + || month < 1 + || month > 12 + || hour > 23 + || minute > 59 + || second > 59 + ) { return DateTime.MinValue; } + + return new DateTime(year, month, day, hour, minute, second); + } + catch + { + return DateTime.MinValue; } } } diff --git a/src/SharpCompress/Common/Ace/Headers/AceMainHeader.cs b/src/SharpCompress/Common/Ace/Headers/AceMainHeader.cs index 7b189cd60..fd74354f9 100644 --- a/src/SharpCompress/Common/Ace/Headers/AceMainHeader.cs +++ b/src/SharpCompress/Common/Ace/Headers/AceMainHeader.cs @@ -8,94 +8,93 @@ using SharpCompress.Common.Zip.Headers; using SharpCompress.Crypto; -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// ACE main archive header +/// +public sealed partial class AceMainHeader : AceHeader { + public byte ExtractVersion { get; set; } + public byte CreatorVersion { get; set; } + public HostOS HostOS { get; set; } + public byte VolumeNumber { get; set; } + public DateTime DateTime { get; set; } + public string Advert { get; set; } = string.Empty; + public List Comment { get; set; } = new(); + public byte AceVersion { get; private set; } + + public AceMainHeader(IArchiveEncoding archiveEncoding) + : base(archiveEncoding, AceHeaderType.MAIN) { } + /// - /// ACE main archive header + /// Reads the main archive header from the stream. + /// Returns header if this is a valid ACE archive. + /// Supports both ACE 1.0 and ACE 2.0 formats. /// - public sealed partial class AceMainHeader : AceHeader + public override AceHeader? Read(Stream stream) { - public byte ExtractVersion { get; set; } - public byte CreatorVersion { get; set; } - public HostOS HostOS { get; set; } - public byte VolumeNumber { get; set; } - public DateTime DateTime { get; set; } - public string Advert { get; set; } = string.Empty; - public List Comment { get; set; } = new(); - public byte AceVersion { get; private set; } - - public AceMainHeader(IArchiveEncoding archiveEncoding) - : base(archiveEncoding, AceHeaderType.MAIN) { } - - /// - /// Reads the main archive header from the stream. - /// Returns header if this is a valid ACE archive. - /// Supports both ACE 1.0 and ACE 2.0 formats. - /// - public override AceHeader? Read(Stream stream) + var headerData = ReadHeader(stream); + if (headerData.Length == 0) { - var headerData = ReadHeader(stream); - if (headerData.Length == 0) - { - return null; - } - int offset = 0; + return null; + } + int offset = 0; - // Header type should be 0 for main header - if (headerData[offset++] != HeaderType) - { - return null; - } + // Header type should be 0 for main header + if (headerData[offset++] != HeaderType) + { + return null; + } - // Header flags (2 bytes) - HeaderFlags = BitConverter.ToUInt16(headerData, offset); - offset += 2; + // Header flags (2 bytes) + HeaderFlags = BitConverter.ToUInt16(headerData, offset); + offset += 2; - // Skip signature "**ACE**" (7 bytes) - if (!CheckMagicBytes(headerData, offset)) - { - throw new InvalidDataException("Invalid ACE archive signature."); - } - offset += 7; + // Skip signature "**ACE**" (7 bytes) + if (!CheckMagicBytes(headerData, offset)) + { + throw new InvalidDataException("Invalid ACE archive signature."); + } + offset += 7; - // ACE version (1 byte) - 10 for ACE 1.0, 20 for ACE 2.0 - AceVersion = headerData[offset++]; - ExtractVersion = headerData[offset++]; + // ACE version (1 byte) - 10 for ACE 1.0, 20 for ACE 2.0 + AceVersion = headerData[offset++]; + ExtractVersion = headerData[offset++]; - // Host OS (1 byte) - if (offset < headerData.Length) - { - var hostOsByte = headerData[offset++]; - HostOS = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown; - } - // Volume number (1 byte) - VolumeNumber = headerData[offset++]; + // Host OS (1 byte) + if (offset < headerData.Length) + { + var hostOsByte = headerData[offset++]; + HostOS = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown; + } + // Volume number (1 byte) + VolumeNumber = headerData[offset++]; - // Creation date/time (4 bytes) - var dosDateTime = BitConverter.ToUInt32(headerData, offset); - DateTime = ConvertDosDateTime(dosDateTime); - offset += 4; + // Creation date/time (4 bytes) + var dosDateTime = BitConverter.ToUInt32(headerData, offset); + DateTime = ConvertDosDateTime(dosDateTime); + offset += 4; - // Reserved fields (8 bytes) - if (offset + 8 <= headerData.Length) - { - offset += 8; - } + // Reserved fields (8 bytes) + if (offset + 8 <= headerData.Length) + { + offset += 8; + } - // Skip additional fields based on flags - // Handle comment if present - if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) + // Skip additional fields based on flags + // Handle comment if present + if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) + { + if (offset + 2 <= headerData.Length) { - if (offset + 2 <= headerData.Length) - { - ushort commentLength = BitConverter.ToUInt16(headerData, offset); - offset += 2 + commentLength; - } + ushort commentLength = BitConverter.ToUInt16(headerData, offset); + offset += 2 + commentLength; } - - return this; } - // ReadAsync moved to AceMainHeader.Async.cs + return this; } + + // ReadAsync moved to AceMainHeader.Async.cs } diff --git a/src/SharpCompress/Common/Ace/Headers/CompressionQuality.cs b/src/SharpCompress/Common/Ace/Headers/CompressionQuality.cs index 57017b55f..eb53d639e 100644 --- a/src/SharpCompress/Common/Ace/Headers/CompressionQuality.cs +++ b/src/SharpCompress/Common/Ace/Headers/CompressionQuality.cs @@ -1,16 +1,15 @@ -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// Compression quality +/// +public enum CompressionQuality { - /// - /// Compression quality - /// - public enum CompressionQuality - { - None, - Fastest, - Fast, - Normal, - Good, - Best, - Unknown, - } + None, + Fastest, + Fast, + Normal, + Good, + Best, + Unknown, } diff --git a/src/SharpCompress/Common/Ace/Headers/CompressionType.cs b/src/SharpCompress/Common/Ace/Headers/CompressionType.cs index 799e7929b..f86fa080b 100644 --- a/src/SharpCompress/Common/Ace/Headers/CompressionType.cs +++ b/src/SharpCompress/Common/Ace/Headers/CompressionType.cs @@ -1,13 +1,12 @@ -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// Compression types +/// +public enum CompressionType { - /// - /// Compression types - /// - public enum CompressionType - { - Stored, - Lz77, - Blocked, - Unknown, - } + Stored, + Lz77, + Blocked, + Unknown, } diff --git a/src/SharpCompress/Common/Ace/Headers/HeaderFlags.cs b/src/SharpCompress/Common/Ace/Headers/HeaderFlags.cs index 6a5f8926f..b3d67898a 100644 --- a/src/SharpCompress/Common/Ace/Headers/HeaderFlags.cs +++ b/src/SharpCompress/Common/Ace/Headers/HeaderFlags.cs @@ -1,33 +1,32 @@ -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// Header flags (main + file, overlapping meanings) +/// +public static class HeaderFlags { - /// - /// Header flags (main + file, overlapping meanings) - /// - public static class HeaderFlags - { - // Shared / low bits - public const ushort ADDSIZE = 0x0001; // extra size field present - public const ushort COMMENT = 0x0002; // comment present - public const ushort MEMORY_64BIT = 0x0004; - public const ushort AV_STRING = 0x0008; // AV string present - public const ushort SOLID = 0x0010; // solid file - public const ushort LOCKED = 0x0020; - public const ushort PROTECTED = 0x0040; + // Shared / low bits + public const ushort ADDSIZE = 0x0001; // extra size field present + public const ushort COMMENT = 0x0002; // comment present + public const ushort MEMORY_64BIT = 0x0004; + public const ushort AV_STRING = 0x0008; // AV string present + public const ushort SOLID = 0x0010; // solid file + public const ushort LOCKED = 0x0020; + public const ushort PROTECTED = 0x0040; - // Main header specific - public const ushort V20FORMAT = 0x0100; - public const ushort SFX = 0x0200; - public const ushort LIMITSFXJR = 0x0400; - public const ushort MULTIVOLUME = 0x0800; - public const ushort ADVERT = 0x1000; - public const ushort RECOVERY = 0x2000; - public const ushort LOCKED_MAIN = 0x4000; - public const ushort SOLID_MAIN = 0x8000; + // Main header specific + public const ushort V20FORMAT = 0x0100; + public const ushort SFX = 0x0200; + public const ushort LIMITSFXJR = 0x0400; + public const ushort MULTIVOLUME = 0x0800; + public const ushort ADVERT = 0x1000; + public const ushort RECOVERY = 0x2000; + public const ushort LOCKED_MAIN = 0x4000; + public const ushort SOLID_MAIN = 0x8000; - // File header specific (same bits, different meaning) - public const ushort NTSECURITY = 0x0400; - public const ushort CONTINUED_PREV = 0x1000; - public const ushort CONTINUED_NEXT = 0x2000; - public const ushort FILE_ENCRYPTED = 0x4000; // file encrypted (file header) - } + // File header specific (same bits, different meaning) + public const ushort NTSECURITY = 0x0400; + public const ushort CONTINUED_PREV = 0x1000; + public const ushort CONTINUED_NEXT = 0x2000; + public const ushort FILE_ENCRYPTED = 0x4000; // file encrypted (file header) } diff --git a/src/SharpCompress/Common/Ace/Headers/HostOS.cs b/src/SharpCompress/Common/Ace/Headers/HostOS.cs index 173d56eb8..d58d30c05 100644 --- a/src/SharpCompress/Common/Ace/Headers/HostOS.cs +++ b/src/SharpCompress/Common/Ace/Headers/HostOS.cs @@ -1,22 +1,21 @@ -namespace SharpCompress.Common.Ace.Headers +namespace SharpCompress.Common.Ace.Headers; + +/// +/// Host OS type +/// +public enum HostOS { - /// - /// Host OS type - /// - public enum HostOS - { - MsDos = 0, - Os2, - Windows, - Unix, - MacOs, - WinNt, - Primos, - AppleGs, - Atari, - Vax, - Amiga, - Next, - Unknown, - } + MsDos = 0, + Os2, + Windows, + Unix, + MacOs, + WinNt, + Primos, + AppleGs, + Atari, + Vax, + Amiga, + Next, + Unknown, } diff --git a/src/SharpCompress/Common/Arc/ArcEntry.cs b/src/SharpCompress/Common/Arc/ArcEntry.cs index a67f10d11..0a94ae0c8 100644 --- a/src/SharpCompress/Common/Arc/ArcEntry.cs +++ b/src/SharpCompress/Common/Arc/ArcEntry.cs @@ -7,54 +7,53 @@ using SharpCompress.Common.GZip; using SharpCompress.Common.Tar; -namespace SharpCompress.Common.Arc +namespace SharpCompress.Common.Arc; + +public class ArcEntry : Entry { - public class ArcEntry : Entry - { - private readonly ArcFilePart? _filePart; + private readonly ArcFilePart? _filePart; - internal ArcEntry(ArcFilePart? filePart) - { - _filePart = filePart; - } + internal ArcEntry(ArcFilePart? filePart) + { + _filePart = filePart; + } - public override long Crc + public override long Crc + { + get { - get + if (_filePart == null) { - if (_filePart == null) - { - return 0; - } - return _filePart.Header.Crc16; + return 0; } + return _filePart.Header.Crc16; } + } - public override string? Key => _filePart?.Header.Name; + public override string? Key => _filePart?.Header.Name; - public override string? LinkTarget => null; + public override string? LinkTarget => null; - public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; + public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; - public override CompressionType CompressionType => - _filePart?.Header.CompressionMethod ?? CompressionType.Unknown; + public override CompressionType CompressionType => + _filePart?.Header.CompressionMethod ?? CompressionType.Unknown; - public override long Size => throw new NotImplementedException(); + public override long Size => throw new NotImplementedException(); - public override DateTime? LastModifiedTime => null; + public override DateTime? LastModifiedTime => null; - public override DateTime? CreatedTime => null; + public override DateTime? CreatedTime => null; - public override DateTime? LastAccessedTime => null; + public override DateTime? LastAccessedTime => null; - public override DateTime? ArchivedTime => null; + public override DateTime? ArchivedTime => null; - public override bool IsEncrypted => false; + public override bool IsEncrypted => false; - public override bool IsDirectory => false; + public override bool IsDirectory => false; - public override bool IsSplitAfter => false; + public override bool IsSplitAfter => false; - internal override IEnumerable Parts => _filePart.Empty(); - } + internal override IEnumerable Parts => _filePart.Empty(); } diff --git a/src/SharpCompress/Common/Arc/ArcEntryHeader.cs b/src/SharpCompress/Common/Arc/ArcEntryHeader.cs index d2c0479f6..3645020d0 100644 --- a/src/SharpCompress/Common/Arc/ArcEntryHeader.cs +++ b/src/SharpCompress/Common/Arc/ArcEntryHeader.cs @@ -5,91 +5,90 @@ using System.Threading; using System.Threading.Tasks; -namespace SharpCompress.Common.Arc +namespace SharpCompress.Common.Arc; + +public class ArcEntryHeader { - public class ArcEntryHeader - { - public IArchiveEncoding ArchiveEncoding { get; } - public CompressionType CompressionMethod { get; private set; } - public string? Name { get; private set; } - public long CompressedSize { get; private set; } - public DateTime DateTime { get; private set; } - public int Crc16 { get; private set; } - public long OriginalSize { get; private set; } - public long DataStartPosition { get; private set; } + public IArchiveEncoding ArchiveEncoding { get; } + public CompressionType CompressionMethod { get; private set; } + public string? Name { get; private set; } + public long CompressedSize { get; private set; } + public DateTime DateTime { get; private set; } + public int Crc16 { get; private set; } + public long OriginalSize { get; private set; } + public long DataStartPosition { get; private set; } - public ArcEntryHeader(IArchiveEncoding archiveEncoding) - { - this.ArchiveEncoding = archiveEncoding; - } + public ArcEntryHeader(IArchiveEncoding archiveEncoding) + { + this.ArchiveEncoding = archiveEncoding; + } - public ArcEntryHeader? ReadHeader(Stream stream) + public ArcEntryHeader? ReadHeader(Stream stream) + { + byte[] headerBytes = new byte[29]; + if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length) { - byte[] headerBytes = new byte[29]; - if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length) - { - return null; - } - DataStartPosition = stream.Position; - return LoadFrom(headerBytes); + return null; } + DataStartPosition = stream.Position; + return LoadFrom(headerBytes); + } - public async ValueTask ReadHeaderAsync( - Stream stream, - CancellationToken cancellationToken = default + public async ValueTask ReadHeaderAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + byte[] headerBytes = new byte[29]; + if ( + await stream.ReadAsync(headerBytes, 0, headerBytes.Length, cancellationToken) + != headerBytes.Length ) { - byte[] headerBytes = new byte[29]; - if ( - await stream.ReadAsync(headerBytes, 0, headerBytes.Length, cancellationToken) - != headerBytes.Length - ) - { - return null; - } - DataStartPosition = stream.Position; - return LoadFrom(headerBytes); + return null; } + DataStartPosition = stream.Position; + return LoadFrom(headerBytes); + } - public ArcEntryHeader LoadFrom(byte[] headerBytes) - { - CompressionMethod = GetCompressionType(headerBytes[1]); + public ArcEntryHeader LoadFrom(byte[] headerBytes) + { + CompressionMethod = GetCompressionType(headerBytes[1]); - // Read name - int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator - Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12); + // Read name + int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator + Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12); - int offset = 15; - CompressedSize = BitConverter.ToUInt32(headerBytes, offset); - offset += 4; - uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset); - DateTime = ConvertToDateTime(rawDateTime); - offset += 4; - Crc16 = BitConverter.ToUInt16(headerBytes, offset); - offset += 2; - OriginalSize = BitConverter.ToUInt32(headerBytes, offset); - return this; - } + int offset = 15; + CompressedSize = BitConverter.ToUInt32(headerBytes, offset); + offset += 4; + uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset); + DateTime = ConvertToDateTime(rawDateTime); + offset += 4; + Crc16 = BitConverter.ToUInt16(headerBytes, offset); + offset += 2; + OriginalSize = BitConverter.ToUInt32(headerBytes, offset); + return this; + } - private CompressionType GetCompressionType(byte value) + private CompressionType GetCompressionType(byte value) + { + return value switch { - return value switch - { - 1 or 2 => CompressionType.None, - 3 => CompressionType.Packed, - 4 => CompressionType.Squeezed, - 5 or 6 or 7 or 8 => CompressionType.Crunched, - 9 => CompressionType.Squashed, - 10 => CompressionType.Crushed, - 11 => CompressionType.Distilled, - _ => CompressionType.Unknown, - }; - } + 1 or 2 => CompressionType.None, + 3 => CompressionType.Packed, + 4 => CompressionType.Squeezed, + 5 or 6 or 7 or 8 => CompressionType.Crunched, + 9 => CompressionType.Squashed, + 10 => CompressionType.Crushed, + 11 => CompressionType.Distilled, + _ => CompressionType.Unknown, + }; + } - public static DateTime ConvertToDateTime(long rawDateTime) - { - // Convert Unix timestamp to DateTime (UTC) - return DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime; - } + public static DateTime ConvertToDateTime(long rawDateTime) + { + // Convert Unix timestamp to DateTime (UTC) + return DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime; } } diff --git a/src/SharpCompress/Common/Arc/ArcFilePart.Async.cs b/src/SharpCompress/Common/Arc/ArcFilePart.Async.cs index 60f6b697a..0ae8004db 100644 --- a/src/SharpCompress/Common/Arc/ArcFilePart.Async.cs +++ b/src/SharpCompress/Common/Arc/ArcFilePart.Async.cs @@ -7,62 +7,52 @@ using SharpCompress.Compressors.Squeezed; using SharpCompress.IO; -namespace SharpCompress.Common.Arc +namespace SharpCompress.Common.Arc; + +public partial class ArcFilePart { - public partial class ArcFilePart + internal override async ValueTask GetCompressedStreamAsync( + CancellationToken cancellationToken = default + ) { - internal override async ValueTask GetCompressedStreamAsync( - CancellationToken cancellationToken = default - ) + if (_stream != null) { - if (_stream != null) + Stream compressedStream; + switch (Header.CompressionMethod) { - Stream compressedStream; - switch (Header.CompressionMethod) - { - case CompressionType.None: - compressedStream = new ReadOnlySubStream( - _stream, - Header.DataStartPosition, - Header.CompressedSize - ); - break; - case CompressionType.Packed: - compressedStream = new RunLength90Stream( - _stream, - (int)Header.CompressedSize - ); - break; - case CompressionType.Squeezed: - compressedStream = await SqueezeStream.CreateAsync( - _stream, - (int)Header.CompressedSize, - cancellationToken - ); - break; - case CompressionType.Crunched: - if (Header.OriginalSize > 128 * 1024) - { - throw new NotSupportedException( - "CompressionMethod: " - + Header.CompressionMethod - + " with size > 128KB" - ); - } - compressedStream = new ArcLzwStream( - _stream, - (int)Header.CompressedSize, - true - ); - break; - default: + case CompressionType.None: + compressedStream = new ReadOnlySubStream( + _stream, + Header.DataStartPosition, + Header.CompressedSize + ); + break; + case CompressionType.Packed: + compressedStream = new RunLength90Stream(_stream, (int)Header.CompressedSize); + break; + case CompressionType.Squeezed: + compressedStream = await SqueezeStream.CreateAsync( + _stream, + (int)Header.CompressedSize, + cancellationToken + ); + break; + case CompressionType.Crunched: + if (Header.OriginalSize > 128 * 1024) + { throw new NotSupportedException( - "CompressionMethod: " + Header.CompressionMethod + "CompressionMethod: " + Header.CompressionMethod + " with size > 128KB" ); - } - return compressedStream; + } + compressedStream = new ArcLzwStream(_stream, (int)Header.CompressedSize, true); + break; + default: + throw new NotSupportedException( + "CompressionMethod: " + Header.CompressionMethod + ); } - return _stream; + return compressedStream; } + return _stream; } } diff --git a/src/SharpCompress/Common/Arc/ArcFilePart.cs b/src/SharpCompress/Common/Arc/ArcFilePart.cs index 0c4474ffa..4a5231a18 100644 --- a/src/SharpCompress/Common/Arc/ArcFilePart.cs +++ b/src/SharpCompress/Common/Arc/ArcFilePart.cs @@ -13,74 +13,61 @@ using SharpCompress.Compressors.Squeezed; using SharpCompress.IO; -namespace SharpCompress.Common.Arc +namespace SharpCompress.Common.Arc; + +public partial class ArcFilePart : FilePart { - public partial class ArcFilePart : FilePart - { - private readonly Stream? _stream; + private readonly Stream? _stream; - internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream) - : base(localArcHeader.ArchiveEncoding) - { - _stream = seekableStream; - Header = localArcHeader; - } + internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream) + : base(localArcHeader.ArchiveEncoding) + { + _stream = seekableStream; + Header = localArcHeader; + } - internal ArcEntryHeader Header { get; set; } + internal ArcEntryHeader Header { get; set; } - internal override string? FilePartName => Header.Name; + internal override string? FilePartName => Header.Name; - internal override Stream GetCompressedStream() + internal override Stream GetCompressedStream() + { + if (_stream != null) { - if (_stream != null) + Stream compressedStream; + switch (Header.CompressionMethod) { - Stream compressedStream; - switch (Header.CompressionMethod) - { - case CompressionType.None: - compressedStream = new ReadOnlySubStream( - _stream, - Header.DataStartPosition, - Header.CompressedSize - ); - break; - case CompressionType.Packed: - compressedStream = new RunLength90Stream( - _stream, - (int)Header.CompressedSize - ); - break; - case CompressionType.Squeezed: - compressedStream = SqueezeStream.Create( - _stream, - (int)Header.CompressedSize - ); - break; - case CompressionType.Crunched: - if (Header.OriginalSize > 128 * 1024) - { - throw new NotSupportedException( - "CompressionMethod: " - + Header.CompressionMethod - + " with size > 128KB" - ); - } - compressedStream = new ArcLzwStream( - _stream, - (int)Header.CompressedSize, - true - ); - break; - default: + case CompressionType.None: + compressedStream = new ReadOnlySubStream( + _stream, + Header.DataStartPosition, + Header.CompressedSize + ); + break; + case CompressionType.Packed: + compressedStream = new RunLength90Stream(_stream, (int)Header.CompressedSize); + break; + case CompressionType.Squeezed: + compressedStream = SqueezeStream.Create(_stream, (int)Header.CompressedSize); + break; + case CompressionType.Crunched: + if (Header.OriginalSize > 128 * 1024) + { throw new NotSupportedException( - "CompressionMethod: " + Header.CompressionMethod + "CompressionMethod: " + Header.CompressionMethod + " with size > 128KB" ); - } - return compressedStream; + } + compressedStream = new ArcLzwStream(_stream, (int)Header.CompressedSize, true); + break; + default: + throw new NotSupportedException( + "CompressionMethod: " + Header.CompressionMethod + ); } - return _stream.NotNull(); + return compressedStream; } - - internal override Stream? GetRawStream() => _stream; + return _stream.NotNull(); } + + internal override Stream? GetRawStream() => _stream; } diff --git a/src/SharpCompress/Common/Arc/ArcVolume.cs b/src/SharpCompress/Common/Arc/ArcVolume.cs index 8ebd11ea9..99fb56eee 100644 --- a/src/SharpCompress/Common/Arc/ArcVolume.cs +++ b/src/SharpCompress/Common/Arc/ArcVolume.cs @@ -6,11 +6,10 @@ using System.Threading.Tasks; using SharpCompress.Readers; -namespace SharpCompress.Common.Arc +namespace SharpCompress.Common.Arc; + +public class ArcVolume : Volume { - public class ArcVolume : Volume - { - public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) { } - } + public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0) + : base(stream, readerOptions, index) { } } diff --git a/src/SharpCompress/Common/Arj/ArjEntry.cs b/src/SharpCompress/Common/Arj/ArjEntry.cs index 8dd84e872..fe4a525b4 100644 --- a/src/SharpCompress/Common/Arj/ArjEntry.cs +++ b/src/SharpCompress/Common/Arj/ArjEntry.cs @@ -6,53 +6,52 @@ using SharpCompress.Common.Arc; using SharpCompress.Common.Arj.Headers; -namespace SharpCompress.Common.Arj +namespace SharpCompress.Common.Arj; + +public class ArjEntry : Entry { - public class ArjEntry : Entry - { - private readonly ArjFilePart _filePart; + private readonly ArjFilePart _filePart; - internal ArjEntry(ArjFilePart filePart) - { - _filePart = filePart; - } + internal ArjEntry(ArjFilePart filePart) + { + _filePart = filePart; + } - public override long Crc => _filePart.Header.OriginalCrc32; + public override long Crc => _filePart.Header.OriginalCrc32; - public override string? Key => _filePart?.Header.Name; + public override string? Key => _filePart?.Header.Name; - public override string? LinkTarget => null; + public override string? LinkTarget => null; - public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; + public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; - public override CompressionType CompressionType + public override CompressionType CompressionType + { + get { - get + if (_filePart.Header.CompressionMethod == CompressionMethod.Stored) { - if (_filePart.Header.CompressionMethod == CompressionMethod.Stored) - { - return CompressionType.None; - } - return CompressionType.ArjLZ77; + return CompressionType.None; } + return CompressionType.ArjLZ77; } + } - public override long Size => _filePart?.Header.OriginalSize ?? 0; + public override long Size => _filePart?.Header.OriginalSize ?? 0; - public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime; + public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime; - public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime; + public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime; - public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime; + public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime; - public override DateTime? ArchivedTime => null; + public override DateTime? ArchivedTime => null; - public override bool IsEncrypted => false; + public override bool IsEncrypted => false; - public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory; + public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory; - public override bool IsSplitAfter => false; + public override bool IsSplitAfter => false; - internal override IEnumerable Parts => _filePart.Empty(); - } + internal override IEnumerable Parts => _filePart.Empty(); } diff --git a/src/SharpCompress/Common/Arj/ArjFilePart.cs b/src/SharpCompress/Common/Arj/ArjFilePart.cs index 83246d22a..f8bb2311a 100644 --- a/src/SharpCompress/Common/Arj/ArjFilePart.cs +++ b/src/SharpCompress/Common/Arj/ArjFilePart.cs @@ -8,65 +8,62 @@ using SharpCompress.Compressors.Arj; using SharpCompress.IO; -namespace SharpCompress.Common.Arj +namespace SharpCompress.Common.Arj; + +public class ArjFilePart : FilePart { - public class ArjFilePart : FilePart - { - private readonly Stream _stream; - internal ArjLocalHeader Header { get; set; } + private readonly Stream _stream; + internal ArjLocalHeader Header { get; set; } - internal ArjFilePart(ArjLocalHeader localArjHeader, Stream seekableStream) - : base(localArjHeader.ArchiveEncoding) - { - _stream = seekableStream; - Header = localArjHeader; - } + internal ArjFilePart(ArjLocalHeader localArjHeader, Stream seekableStream) + : base(localArjHeader.ArchiveEncoding) + { + _stream = seekableStream; + Header = localArjHeader; + } - internal override string? FilePartName => Header.Name; + internal override string? FilePartName => Header.Name; - internal override Stream GetCompressedStream() + internal override Stream GetCompressedStream() + { + if (_stream != null) { - if (_stream != null) + Stream compressedStream; + switch (Header.CompressionMethod) { - Stream compressedStream; - switch (Header.CompressionMethod) - { - case CompressionMethod.Stored: - compressedStream = new ReadOnlySubStream( - _stream, - Header.DataStartPosition, - Header.CompressedSize - ); - break; - case CompressionMethod.CompressedMost: - case CompressionMethod.Compressed: - case CompressionMethod.CompressedFaster: - if (Header.OriginalSize > 128 * 1024) - { - throw new NotSupportedException( - "CompressionMethod: " - + Header.CompressionMethod - + " with size > 128KB" - ); - } - compressedStream = new LhaStream( - _stream, - (int)Header.OriginalSize - ); - break; - case CompressionMethod.CompressedFastest: - compressedStream = new LHDecoderStream(_stream, (int)Header.OriginalSize); - break; - default: + case CompressionMethod.Stored: + compressedStream = new ReadOnlySubStream( + _stream, + Header.DataStartPosition, + Header.CompressedSize + ); + break; + case CompressionMethod.CompressedMost: + case CompressionMethod.Compressed: + case CompressionMethod.CompressedFaster: + if (Header.OriginalSize > 128 * 1024) + { throw new NotSupportedException( - "CompressionMethod: " + Header.CompressionMethod + "CompressionMethod: " + Header.CompressionMethod + " with size > 128KB" ); - } - return compressedStream; + } + compressedStream = new LhaStream( + _stream, + (int)Header.OriginalSize + ); + break; + case CompressionMethod.CompressedFastest: + compressedStream = new LHDecoderStream(_stream, (int)Header.OriginalSize); + break; + default: + throw new NotSupportedException( + "CompressionMethod: " + Header.CompressionMethod + ); } - return _stream.NotNull(); + return compressedStream; } - - internal override Stream GetRawStream() => _stream; + return _stream.NotNull(); } + + internal override Stream GetRawStream() => _stream; } diff --git a/src/SharpCompress/Common/Arj/ArjVolume.cs b/src/SharpCompress/Common/Arj/ArjVolume.cs index 872da93a3..d65d56c92 100644 --- a/src/SharpCompress/Common/Arj/ArjVolume.cs +++ b/src/SharpCompress/Common/Arj/ArjVolume.cs @@ -8,29 +8,28 @@ using SharpCompress.Common.Rar.Headers; using SharpCompress.Readers; -namespace SharpCompress.Common.Arj +namespace SharpCompress.Common.Arj; + +public class ArjVolume : Volume { - public class ArjVolume : Volume - { - public ArjVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) { } + public ArjVolume(Stream stream, ReaderOptions readerOptions, int index = 0) + : base(stream, readerOptions, index) { } - public override bool IsFirstVolume - { - get { return true; } - } + public override bool IsFirstVolume + { + get { return true; } + } - /// - /// ArjArchive is part of a multi-part archive. - /// - public override bool IsMultiVolume - { - get { return false; } - } + /// + /// ArjArchive is part of a multi-part archive. + /// + public override bool IsMultiVolume + { + get { return false; } + } - internal IEnumerable GetVolumeFileParts() - { - return new List(); - } + internal IEnumerable GetVolumeFileParts() + { + return new List(); } } diff --git a/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs index dd7ca4026..a12eb01d6 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs @@ -8,156 +8,153 @@ using SharpCompress.Common.Zip.Headers; using SharpCompress.Crypto; -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public enum ArjHeaderType +{ + MainHeader, + LocalHeader, +} + +public abstract partial class ArjHeader { - public enum ArjHeaderType + private const int FIRST_HDR_SIZE = 34; + private const ushort ARJ_MAGIC = 0xEA60; + + public ArjHeader(ArjHeaderType type) { - MainHeader, - LocalHeader, + ArjHeaderType = type; } - public abstract partial class ArjHeader + public ArjHeaderType ArjHeaderType { get; } + public byte Flags { get; set; } + public FileType FileType { get; set; } + + public abstract ArjHeader? Read(Stream reader); + + // Async methods moved to ArjHeader.Async.cs + + public byte[] ReadHeader(Stream stream) { - private const int FIRST_HDR_SIZE = 34; - private const ushort ARJ_MAGIC = 0xEA60; + // check for magic bytes + var magic = new byte[2]; + if (stream.Read(magic) != 2) + { + return Array.Empty(); + } - public ArjHeader(ArjHeaderType type) + if (!CheckMagicBytes(magic)) { - ArjHeaderType = type; + throw new InvalidDataException("Not an ARJ file (wrong magic bytes)"); } - public ArjHeaderType ArjHeaderType { get; } - public byte Flags { get; set; } - public FileType FileType { get; set; } + // read header_size + byte[] headerBytes = new byte[2]; + stream.Read(headerBytes, 0, 2); + var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8); + if (headerSize < 1) + { + return Array.Empty(); + } + + var body = new byte[headerSize]; + var read = stream.Read(body, 0, headerSize); + if (read < headerSize) + { + return Array.Empty(); + } + + byte[] crc = new byte[4]; + read = stream.Read(crc, 0, 4); + var checksum = Crc32Stream.Compute(body); + // Compute the hash value + if (checksum != BitConverter.ToUInt32(crc, 0)) + { + throw new InvalidDataException("Header checksum is invalid"); + } + return body; + } - public abstract ArjHeader? Read(Stream reader); + // ReadHeaderAsync moved to ArjHeader.Async.cs - // Async methods moved to ArjHeader.Async.cs + protected List ReadExtendedHeaders(Stream reader) + { + List extendedHeader = new List(); + byte[] buffer = new byte[2]; - public byte[] ReadHeader(Stream stream) + while (true) { - // check for magic bytes - var magic = new byte[2]; - if (stream.Read(magic) != 2) + int bytesRead = reader.Read(buffer, 0, 2); + if (bytesRead < 2) { - return Array.Empty(); + throw new EndOfStreamException( + "Unexpected end of stream while reading extended header size." + ); } - if (!CheckMagicBytes(magic)) + var extHeaderSize = (ushort)(buffer[0] | (buffer[1] << 8)); + if (extHeaderSize == 0) { - throw new InvalidDataException("Not an ARJ file (wrong magic bytes)"); + return extendedHeader; } - // read header_size - byte[] headerBytes = new byte[2]; - stream.Read(headerBytes, 0, 2); - var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8); - if (headerSize < 1) + byte[] header = new byte[extHeaderSize]; + bytesRead = reader.Read(header, 0, extHeaderSize); + if (bytesRead < extHeaderSize) { - return Array.Empty(); + throw new EndOfStreamException( + "Unexpected end of stream while reading extended header data." + ); } - var body = new byte[headerSize]; - var read = stream.Read(body, 0, headerSize); - if (read < headerSize) + byte[] crc = new byte[4]; + bytesRead = reader.Read(crc, 0, 4); + if (bytesRead < 4) { - return Array.Empty(); + throw new EndOfStreamException( + "Unexpected end of stream while reading extended header CRC." + ); } - byte[] crc = new byte[4]; - read = stream.Read(crc, 0, 4); - var checksum = Crc32Stream.Compute(body); - // Compute the hash value + var checksum = Crc32Stream.Compute(header); if (checksum != BitConverter.ToUInt32(crc, 0)) { - throw new InvalidDataException("Header checksum is invalid"); + throw new InvalidDataException("Extended header checksum is invalid"); } - return body; - } - - // ReadHeaderAsync moved to ArjHeader.Async.cs - protected List ReadExtendedHeaders(Stream reader) - { - List extendedHeader = new List(); - byte[] buffer = new byte[2]; - - while (true) - { - int bytesRead = reader.Read(buffer, 0, 2); - if (bytesRead < 2) - { - throw new EndOfStreamException( - "Unexpected end of stream while reading extended header size." - ); - } - - var extHeaderSize = (ushort)(buffer[0] | (buffer[1] << 8)); - if (extHeaderSize == 0) - { - return extendedHeader; - } - - byte[] header = new byte[extHeaderSize]; - bytesRead = reader.Read(header, 0, extHeaderSize); - if (bytesRead < extHeaderSize) - { - throw new EndOfStreamException( - "Unexpected end of stream while reading extended header data." - ); - } - - byte[] crc = new byte[4]; - bytesRead = reader.Read(crc, 0, 4); - if (bytesRead < 4) - { - throw new EndOfStreamException( - "Unexpected end of stream while reading extended header CRC." - ); - } - - var checksum = Crc32Stream.Compute(header); - if (checksum != BitConverter.ToUInt32(crc, 0)) - { - throw new InvalidDataException("Extended header checksum is invalid"); - } - - extendedHeader.Add(header); - } + extendedHeader.Add(header); } + } - // Flag helpers - public bool IsGabled => (Flags & 0x01) != 0; - public bool IsAnsiPage => (Flags & 0x02) != 0; - public bool IsVolume => (Flags & 0x04) != 0; - public bool IsArjProtected => (Flags & 0x08) != 0; - public bool IsPathSym => (Flags & 0x10) != 0; - public bool IsBackup => (Flags & 0x20) != 0; - public bool IsSecured => (Flags & 0x40) != 0; - public bool IsAltName => (Flags & 0x80) != 0; - - public static FileType FileTypeFromByte(byte value) - { - return Enum.IsDefined(typeof(FileType), value) - ? (FileType)value - : Headers.FileType.Unknown; - } + // Flag helpers + public bool IsGabled => (Flags & 0x01) != 0; + public bool IsAnsiPage => (Flags & 0x02) != 0; + public bool IsVolume => (Flags & 0x04) != 0; + public bool IsArjProtected => (Flags & 0x08) != 0; + public bool IsPathSym => (Flags & 0x10) != 0; + public bool IsBackup => (Flags & 0x20) != 0; + public bool IsSecured => (Flags & 0x40) != 0; + public bool IsAltName => (Flags & 0x80) != 0; + + public static FileType FileTypeFromByte(byte value) + { + return Enum.IsDefined(typeof(FileType), value) ? (FileType)value : Headers.FileType.Unknown; + } - public static bool IsArchive(Stream stream) + public static bool IsArchive(Stream stream) + { + var bytes = new byte[2]; + if (stream.Read(bytes, 0, 2) != 2) { - var bytes = new byte[2]; - if (stream.Read(bytes, 0, 2) != 2) - { - return false; - } - - return CheckMagicBytes(bytes); + return false; } - protected static bool CheckMagicBytes(byte[] headerBytes) - { - var magicValue = (ushort)(headerBytes[0] | headerBytes[1] << 8); - return magicValue == ARJ_MAGIC; - } + return CheckMagicBytes(bytes); + } + + protected static bool CheckMagicBytes(byte[] headerBytes) + { + var magicValue = (ushort)(headerBytes[0] | headerBytes[1] << 8); + return magicValue == ARJ_MAGIC; } } diff --git a/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs index a16596dfb..4ace325de 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs @@ -7,158 +7,156 @@ using System.Threading; using System.Threading.Tasks; -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public partial class ArjLocalHeader : ArjHeader { - public partial class ArjLocalHeader : ArjHeader + public ArchiveEncoding ArchiveEncoding { get; } + public long DataStartPosition { get; protected set; } + + public byte ArchiverVersionNumber { get; set; } + public byte MinVersionToExtract { get; set; } + public HostOS HostOS { get; set; } + public CompressionMethod CompressionMethod { get; set; } + public DosDateTime DateTimeModified { get; set; } = new DosDateTime(0); + public long CompressedSize { get; set; } + public long OriginalSize { get; set; } + public long OriginalCrc32 { get; set; } + public int FileSpecPosition { get; set; } + public int FileAccessMode { get; set; } + public byte FirstChapter { get; set; } + public byte LastChapter { get; set; } + public long ExtendedFilePosition { get; set; } + public DosDateTime DateTimeAccessed { get; set; } = new DosDateTime(0); + public DosDateTime DateTimeCreated { get; set; } = new DosDateTime(0); + public long OriginalSizeEvenForVolumes { get; set; } + public string Name { get; set; } = string.Empty; + public string Comment { get; set; } = string.Empty; + + private const byte StdHdrSize = 30; + private const byte R9HdrSize = 46; + + public ArjLocalHeader(ArchiveEncoding archiveEncoding) + : base(ArjHeaderType.LocalHeader) { - public ArchiveEncoding ArchiveEncoding { get; } - public long DataStartPosition { get; protected set; } - - public byte ArchiverVersionNumber { get; set; } - public byte MinVersionToExtract { get; set; } - public HostOS HostOS { get; set; } - public CompressionMethod CompressionMethod { get; set; } - public DosDateTime DateTimeModified { get; set; } = new DosDateTime(0); - public long CompressedSize { get; set; } - public long OriginalSize { get; set; } - public long OriginalCrc32 { get; set; } - public int FileSpecPosition { get; set; } - public int FileAccessMode { get; set; } - public byte FirstChapter { get; set; } - public byte LastChapter { get; set; } - public long ExtendedFilePosition { get; set; } - public DosDateTime DateTimeAccessed { get; set; } = new DosDateTime(0); - public DosDateTime DateTimeCreated { get; set; } = new DosDateTime(0); - public long OriginalSizeEvenForVolumes { get; set; } - public string Name { get; set; } = string.Empty; - public string Comment { get; set; } = string.Empty; - - private const byte StdHdrSize = 30; - private const byte R9HdrSize = 46; - - public ArjLocalHeader(ArchiveEncoding archiveEncoding) - : base(ArjHeaderType.LocalHeader) - { - ArchiveEncoding = - archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); - } + ArchiveEncoding = + archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); + } - public override ArjHeader? Read(Stream stream) + public override ArjHeader? Read(Stream stream) + { + var body = ReadHeader(stream); + if (body.Length > 0) { - var body = ReadHeader(stream); - if (body.Length > 0) - { - ReadExtendedHeaders(stream); - var header = LoadFrom(body); - header.DataStartPosition = stream.Position; - return header; - } - return null; + ReadExtendedHeaders(stream); + var header = LoadFrom(body); + header.DataStartPosition = stream.Position; + return header; } + return null; + } - // ReadAsync moved to ArjLocalHeader.Async.cs + // ReadAsync moved to ArjLocalHeader.Async.cs - public ArjLocalHeader LoadFrom(byte[] headerBytes) - { - int offset = 0; + public ArjLocalHeader LoadFrom(byte[] headerBytes) + { + int offset = 0; - int ReadInt16() + int ReadInt16() + { + if (offset + 1 >= headerBytes.Length) { - if (offset + 1 >= headerBytes.Length) - { - throw new EndOfStreamException(); - } - var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8; - offset += 2; - return v; + throw new EndOfStreamException(); } - long ReadInt32() + var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8; + offset += 2; + return v; + } + long ReadInt32() + { + if (offset + 3 >= headerBytes.Length) { - if (offset + 3 >= headerBytes.Length) - { - throw new EndOfStreamException(); - } - long v = - headerBytes[offset] & 0xFF - | (headerBytes[offset + 1] & 0xFF) << 8 - | (headerBytes[offset + 2] & 0xFF) << 16 - | (headerBytes[offset + 3] & 0xFF) << 24; - offset += 4; - return v; + throw new EndOfStreamException(); } + long v = + headerBytes[offset] & 0xFF + | (headerBytes[offset + 1] & 0xFF) << 8 + | (headerBytes[offset + 2] & 0xFF) << 16 + | (headerBytes[offset + 3] & 0xFF) << 24; + offset += 4; + return v; + } - byte headerSize = headerBytes[offset++]; - ArchiverVersionNumber = headerBytes[offset++]; - MinVersionToExtract = headerBytes[offset++]; - HostOS hostOS = (HostOS)headerBytes[offset++]; - Flags = headerBytes[offset++]; - CompressionMethod = CompressionMethodFromByte(headerBytes[offset++]); - FileType = FileTypeFromByte(headerBytes[offset++]); + byte headerSize = headerBytes[offset++]; + ArchiverVersionNumber = headerBytes[offset++]; + MinVersionToExtract = headerBytes[offset++]; + HostOS hostOS = (HostOS)headerBytes[offset++]; + Flags = headerBytes[offset++]; + CompressionMethod = CompressionMethodFromByte(headerBytes[offset++]); + FileType = FileTypeFromByte(headerBytes[offset++]); - offset++; // Skip 1 byte + offset++; // Skip 1 byte - var rawTimestamp = ReadInt32(); - DateTimeModified = - rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); + var rawTimestamp = ReadInt32(); + DateTimeModified = rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); - CompressedSize = ReadInt32(); - OriginalSize = ReadInt32(); - OriginalCrc32 = ReadInt32(); - FileSpecPosition = ReadInt16(); - FileAccessMode = ReadInt16(); + CompressedSize = ReadInt32(); + OriginalSize = ReadInt32(); + OriginalCrc32 = ReadInt32(); + FileSpecPosition = ReadInt16(); + FileAccessMode = ReadInt16(); - FirstChapter = headerBytes[offset++]; - LastChapter = headerBytes[offset++]; + FirstChapter = headerBytes[offset++]; + LastChapter = headerBytes[offset++]; - ExtendedFilePosition = 0; - OriginalSizeEvenForVolumes = 0; + ExtendedFilePosition = 0; + OriginalSizeEvenForVolumes = 0; - if (headerSize > StdHdrSize) + if (headerSize > StdHdrSize) + { + ExtendedFilePosition = ReadInt32(); + + if (headerSize >= R9HdrSize) { - ExtendedFilePosition = ReadInt32(); - - if (headerSize >= R9HdrSize) - { - rawTimestamp = ReadInt32(); - DateTimeAccessed = - rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); - rawTimestamp = ReadInt32(); - DateTimeCreated = - rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); - OriginalSizeEvenForVolumes = ReadInt32(); - } + rawTimestamp = ReadInt32(); + DateTimeAccessed = + rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); + rawTimestamp = ReadInt32(); + DateTimeCreated = + rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); + OriginalSizeEvenForVolumes = ReadInt32(); } - - Name = Encoding.ASCII.GetString( - headerBytes, - offset, - Array.IndexOf(headerBytes, (byte)0, offset) - offset - ); - offset += Name.Length + 1; - - Comment = Encoding.ASCII.GetString( - headerBytes, - offset, - Array.IndexOf(headerBytes, (byte)0, offset) - offset - ); - offset += Comment.Length + 1; - - return this; } - public static CompressionMethod CompressionMethodFromByte(byte value) + Name = Encoding.ASCII.GetString( + headerBytes, + offset, + Array.IndexOf(headerBytes, (byte)0, offset) - offset + ); + offset += Name.Length + 1; + + Comment = Encoding.ASCII.GetString( + headerBytes, + offset, + Array.IndexOf(headerBytes, (byte)0, offset) - offset + ); + offset += Comment.Length + 1; + + return this; + } + + public static CompressionMethod CompressionMethodFromByte(byte value) + { + return value switch { - return value switch - { - 0 => CompressionMethod.Stored, - 1 => CompressionMethod.CompressedMost, - 2 => CompressionMethod.Compressed, - 3 => CompressionMethod.CompressedFaster, - 4 => CompressionMethod.CompressedFastest, - 8 => CompressionMethod.NoDataNoCrc, - 9 => CompressionMethod.NoData, - _ => CompressionMethod.Unknown, - }; - } + 0 => CompressionMethod.Stored, + 1 => CompressionMethod.CompressedMost, + 2 => CompressionMethod.Compressed, + 3 => CompressionMethod.CompressedFaster, + 4 => CompressionMethod.CompressedFastest, + 8 => CompressionMethod.NoDataNoCrc, + 9 => CompressionMethod.NoData, + _ => CompressionMethod.Unknown, + }; } } diff --git a/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs index 6fb138962..3f4586131 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs @@ -6,137 +6,136 @@ using SharpCompress.Compressors.Deflate; using SharpCompress.Crypto; -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public partial class ArjMainHeader : ArjHeader { - public partial class ArjMainHeader : ArjHeader + private const int FIRST_HDR_SIZE = 34; + private const ushort ARJ_MAGIC = 0xEA60; + + public ArchiveEncoding ArchiveEncoding { get; } + + public int ArchiverVersionNumber { get; private set; } + public int MinVersionToExtract { get; private set; } + public HostOS HostOs { get; private set; } + public int SecurityVersion { get; private set; } + public DosDateTime CreationDateTime { get; private set; } = new DosDateTime(0); + public long CompressedSize { get; private set; } + public long ArchiveSize { get; private set; } + public long SecurityEnvelope { get; private set; } + public int FileSpecPosition { get; private set; } + public int SecurityEnvelopeLength { get; private set; } + public int EncryptionVersion { get; private set; } + public int LastChapter { get; private set; } + + public int ArjProtectionFactor { get; private set; } + public int Flags2 { get; private set; } + public string Name { get; private set; } = string.Empty; + public string Comment { get; private set; } = string.Empty; + + public ArjMainHeader(ArchiveEncoding archiveEncoding) + : base(ArjHeaderType.MainHeader) + { + ArchiveEncoding = + archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); + } + + public override ArjHeader? Read(Stream stream) + { + var body = ReadHeader(stream); + ReadExtendedHeaders(stream); + return LoadFrom(body); + } + + // ReadAsync moved to ArjMainHeader.Async.cs + + public ArjMainHeader LoadFrom(byte[] headerBytes) { - private const int FIRST_HDR_SIZE = 34; - private const ushort ARJ_MAGIC = 0xEA60; - - public ArchiveEncoding ArchiveEncoding { get; } - - public int ArchiverVersionNumber { get; private set; } - public int MinVersionToExtract { get; private set; } - public HostOS HostOs { get; private set; } - public int SecurityVersion { get; private set; } - public DosDateTime CreationDateTime { get; private set; } = new DosDateTime(0); - public long CompressedSize { get; private set; } - public long ArchiveSize { get; private set; } - public long SecurityEnvelope { get; private set; } - public int FileSpecPosition { get; private set; } - public int SecurityEnvelopeLength { get; private set; } - public int EncryptionVersion { get; private set; } - public int LastChapter { get; private set; } - - public int ArjProtectionFactor { get; private set; } - public int Flags2 { get; private set; } - public string Name { get; private set; } = string.Empty; - public string Comment { get; private set; } = string.Empty; - - public ArjMainHeader(ArchiveEncoding archiveEncoding) - : base(ArjHeaderType.MainHeader) + var offset = 1; + + byte ReadByte() { - ArchiveEncoding = - archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); + if (offset >= headerBytes.Length) + { + throw new EndOfStreamException(); + } + return (byte)(headerBytes[offset++] & 0xFF); } - public override ArjHeader? Read(Stream stream) + int ReadInt16() { - var body = ReadHeader(stream); - ReadExtendedHeaders(stream); - return LoadFrom(body); + if (offset + 1 >= headerBytes.Length) + { + throw new EndOfStreamException(); + } + var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8; + offset += 2; + return v; } - // ReadAsync moved to ArjMainHeader.Async.cs - - public ArjMainHeader LoadFrom(byte[] headerBytes) + long ReadInt32() { - var offset = 1; - - byte ReadByte() + if (offset + 3 >= headerBytes.Length) { - if (offset >= headerBytes.Length) - { - throw new EndOfStreamException(); - } - return (byte)(headerBytes[offset++] & 0xFF); + throw new EndOfStreamException(); } + long v = + headerBytes[offset] & 0xFF + | (headerBytes[offset + 1] & 0xFF) << 8 + | (headerBytes[offset + 2] & 0xFF) << 16 + | (headerBytes[offset + 3] & 0xFF) << 24; + offset += 4; + return v; + } + string ReadNullTerminatedString(byte[] x, int startIndex) + { + var result = new StringBuilder(); + int i = startIndex; - int ReadInt16() + while (i < x.Length && x[i] != 0) { - if (offset + 1 >= headerBytes.Length) - { - throw new EndOfStreamException(); - } - var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8; - offset += 2; - return v; + result.Append((char)x[i]); + i++; } - long ReadInt32() + // Skip the null terminator + i++; + if (i < x.Length) { - if (offset + 3 >= headerBytes.Length) - { - throw new EndOfStreamException(); - } - long v = - headerBytes[offset] & 0xFF - | (headerBytes[offset + 1] & 0xFF) << 8 - | (headerBytes[offset + 2] & 0xFF) << 16 - | (headerBytes[offset + 3] & 0xFF) << 24; - offset += 4; - return v; + byte[] remainder = new byte[x.Length - i]; + Array.Copy(x, i, remainder, 0, remainder.Length); + x = remainder; } - string ReadNullTerminatedString(byte[] x, int startIndex) - { - var result = new StringBuilder(); - int i = startIndex; - - while (i < x.Length && x[i] != 0) - { - result.Append((char)x[i]); - i++; - } - // Skip the null terminator - i++; - if (i < x.Length) - { - byte[] remainder = new byte[x.Length - i]; - Array.Copy(x, i, remainder, 0, remainder.Length); - x = remainder; - } - - return result.ToString(); - } + return result.ToString(); + } - ArchiverVersionNumber = ReadByte(); - MinVersionToExtract = ReadByte(); + ArchiverVersionNumber = ReadByte(); + MinVersionToExtract = ReadByte(); - var hostOsByte = ReadByte(); - HostOs = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown; + var hostOsByte = ReadByte(); + HostOs = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown; - Flags = ReadByte(); - SecurityVersion = ReadByte(); - FileType = FileTypeFromByte(ReadByte()); + Flags = ReadByte(); + SecurityVersion = ReadByte(); + FileType = FileTypeFromByte(ReadByte()); - offset++; // skip reserved + offset++; // skip reserved - CreationDateTime = new DosDateTime((int)ReadInt32()); - CompressedSize = ReadInt32(); - ArchiveSize = ReadInt32(); + CreationDateTime = new DosDateTime((int)ReadInt32()); + CompressedSize = ReadInt32(); + ArchiveSize = ReadInt32(); - SecurityEnvelope = ReadInt32(); - FileSpecPosition = ReadInt16(); - SecurityEnvelopeLength = ReadInt16(); + SecurityEnvelope = ReadInt32(); + FileSpecPosition = ReadInt16(); + SecurityEnvelopeLength = ReadInt16(); - EncryptionVersion = ReadByte(); - LastChapter = ReadByte(); + EncryptionVersion = ReadByte(); + LastChapter = ReadByte(); - Name = ReadNullTerminatedString(headerBytes, offset); - Comment = ReadNullTerminatedString(headerBytes, offset + 1 + Name.Length); + Name = ReadNullTerminatedString(headerBytes, offset); + Comment = ReadNullTerminatedString(headerBytes, offset + 1 + Name.Length); - return this; - } + return this; } } diff --git a/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs b/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs index acdf2ce92..e4423e2a8 100644 --- a/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs +++ b/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs @@ -4,17 +4,16 @@ using System.Text; using System.Threading.Tasks; -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public enum CompressionMethod { - public enum CompressionMethod - { - Stored = 0, - CompressedMost = 1, - Compressed = 2, - CompressedFaster = 3, - CompressedFastest = 4, - NoDataNoCrc = 8, - NoData = 9, - Unknown, - } + Stored = 0, + CompressedMost = 1, + Compressed = 2, + CompressedFaster = 3, + CompressedFastest = 4, + NoDataNoCrc = 8, + NoData = 9, + Unknown, } diff --git a/src/SharpCompress/Common/Arj/Headers/DosDateTime.cs b/src/SharpCompress/Common/Arj/Headers/DosDateTime.cs index d51045ad3..c882e2126 100644 --- a/src/SharpCompress/Common/Arj/Headers/DosDateTime.cs +++ b/src/SharpCompress/Common/Arj/Headers/DosDateTime.cs @@ -1,37 +1,36 @@ using System; -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public class DosDateTime { - public class DosDateTime + public DateTime DateTime { get; } + + public DosDateTime(long dosValue) { - public DateTime DateTime { get; } + // Ensure only the lower 32 bits are used + int value = unchecked((int)(dosValue & 0xFFFFFFFF)); + + var date = (value >> 16) & 0xFFFF; + var time = value & 0xFFFF; + + var day = date & 0x1F; + var month = (date >> 5) & 0x0F; + var year = ((date >> 9) & 0x7F) + 1980; + + var second = (time & 0x1F) * 2; + var minute = (time >> 5) & 0x3F; + var hour = (time >> 11) & 0x1F; - public DosDateTime(long dosValue) + try { - // Ensure only the lower 32 bits are used - int value = unchecked((int)(dosValue & 0xFFFFFFFF)); - - var date = (value >> 16) & 0xFFFF; - var time = value & 0xFFFF; - - var day = date & 0x1F; - var month = (date >> 5) & 0x0F; - var year = ((date >> 9) & 0x7F) + 1980; - - var second = (time & 0x1F) * 2; - var minute = (time >> 5) & 0x3F; - var hour = (time >> 11) & 0x1F; - - try - { - DateTime = new DateTime(year, month, day, hour, minute, second); - } - catch - { - DateTime = DateTime.MinValue; - } + DateTime = new DateTime(year, month, day, hour, minute, second); + } + catch + { + DateTime = DateTime.MinValue; } - - public override string ToString() => DateTime.ToString("yyyy-MM-dd HH:mm:ss"); } + + public override string ToString() => DateTime.ToString("yyyy-MM-dd HH:mm:ss"); } diff --git a/src/SharpCompress/Common/Arj/Headers/FileType.cs b/src/SharpCompress/Common/Arj/Headers/FileType.cs index 39c529f03..18b4535bd 100644 --- a/src/SharpCompress/Common/Arj/Headers/FileType.cs +++ b/src/SharpCompress/Common/Arj/Headers/FileType.cs @@ -1,13 +1,12 @@ -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public enum FileType : byte { - public enum FileType : byte - { - Binary = 0, - Text7Bit = 1, - CommentHeader = 2, - Directory = 3, - VolumeLabel = 4, - ChapterLabel = 5, - Unknown = 255, - } + Binary = 0, + Text7Bit = 1, + CommentHeader = 2, + Directory = 3, + VolumeLabel = 4, + ChapterLabel = 5, + Unknown = 255, } diff --git a/src/SharpCompress/Common/Arj/Headers/HostOS.cs b/src/SharpCompress/Common/Arj/Headers/HostOS.cs index a74e7b0f9..ded53a94c 100644 --- a/src/SharpCompress/Common/Arj/Headers/HostOS.cs +++ b/src/SharpCompress/Common/Arj/Headers/HostOS.cs @@ -1,19 +1,18 @@ -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public enum HostOS { - public enum HostOS - { - MsDos = 0, - PrimOS = 1, - Unix = 2, - Amiga = 3, - MacOs = 4, - OS2 = 5, - AppleGS = 6, - AtariST = 7, - NeXT = 8, - VaxVMS = 9, - Win95 = 10, - Win32 = 11, - Unknown = 255, - } + MsDos = 0, + PrimOS = 1, + Unix = 2, + Amiga = 3, + MacOs = 4, + OS2 = 5, + AppleGS = 6, + AtariST = 7, + NeXT = 8, + VaxVMS = 9, + Win95 = 10, + Win32 = 11, + Unknown = 255, } diff --git a/src/SharpCompress/Common/AsyncBinaryReader.cs b/src/SharpCompress/Common/AsyncBinaryReader.cs deleted file mode 100644 index efcaf9e41..000000000 --- a/src/SharpCompress/Common/AsyncBinaryReader.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Buffers.Binary; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SharpCompress.Common -{ - public sealed class AsyncBinaryReader : IDisposable - { - private readonly Stream _stream; - private readonly Stream _originalStream; - private readonly bool _leaveOpen; - private readonly byte[] _buffer = new byte[8]; - private bool _disposed; - - public AsyncBinaryReader(Stream stream, bool leaveOpen = false, int bufferSize = 4096) - { - if (!stream.CanRead) - { - throw new ArgumentException("Stream must be readable."); - } - - _originalStream = stream ?? throw new ArgumentNullException(nameof(stream)); - _leaveOpen = leaveOpen; - - // Use the stream directly without wrapping in BufferedStream - // BufferedStream uses synchronous Read internally which doesn't work with async-only streams - _stream = stream; - } - - public Stream BaseStream => _stream; - - public async ValueTask ReadByteAsync(CancellationToken ct = default) - { - await _stream.ReadExactAsync(_buffer, 0, 1, ct).ConfigureAwait(false); - return _buffer[0]; - } - - public async ValueTask ReadUInt16Async(CancellationToken ct = default) - { - await _stream.ReadExactAsync(_buffer, 0, 2, ct).ConfigureAwait(false); - return BinaryPrimitives.ReadUInt16LittleEndian(_buffer); - } - - public async ValueTask ReadUInt32Async(CancellationToken ct = default) - { - await _stream.ReadExactAsync(_buffer, 0, 4, ct).ConfigureAwait(false); - return BinaryPrimitives.ReadUInt32LittleEndian(_buffer); - } - - public async ValueTask ReadUInt64Async(CancellationToken ct = default) - { - await _stream.ReadExactAsync(_buffer, 0, 8, ct).ConfigureAwait(false); - return BinaryPrimitives.ReadUInt64LittleEndian(_buffer); - } - - public async ValueTask ReadBytesAsync( - byte[] bytes, - int offset, - int count, - CancellationToken ct = default - ) - { - await _stream.ReadExactAsync(bytes, offset, count, ct).ConfigureAwait(false); - } - - public async ValueTask SkipAsync(int count, CancellationToken ct = default) - { - await _stream.SkipAsync(count, ct).ConfigureAwait(false); - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - _disposed = true; - - // Dispose the original stream if we own it - if (!_leaveOpen) - { - _originalStream.Dispose(); - } - } - -#if NET8_0_OR_GREATER - public async ValueTask DisposeAsync() - { - if (_disposed) - { - return; - } - - _disposed = true; - - // Dispose the original stream if we own it - if (!_leaveOpen) - { - await _originalStream.DisposeAsync().ConfigureAwait(false); - } - } -#endif - } -} diff --git a/src/SharpCompress/Common/Constants.cs b/src/SharpCompress/Common/Constants.cs index 8b8f20b2c..b9ffcbe37 100644 --- a/src/SharpCompress/Common/Constants.cs +++ b/src/SharpCompress/Common/Constants.cs @@ -8,5 +8,34 @@ public static class Constants /// public static int BufferSize { get; set; } = 81920; + /// + /// The default size for rewindable buffers in SharpCompressStream. + /// Used for format detection on non-seekable streams. + /// + /// + /// + /// When opening archives from non-seekable streams (network streams, pipes, + /// compressed streams), SharpCompress uses a ring buffer to enable format + /// auto-detection. This buffer allows the library to try multiple decoders + /// by rewinding and re-reading the same data. + /// + /// + /// Default: 81920 bytes (81KB) - sufficient for typical format detection. + /// + /// + /// Typical usage: 500-1000 bytes for most archives + /// + /// + /// Can be overridden per-stream via ReaderOptions.RewindableBufferSize. + /// + /// + /// Increase if: + /// + /// Handling self-extracting archives (may need 512KB+) + /// Format detection fails with buffer overflow errors + /// Using custom formats with large headers + /// + /// + /// public static int RewindableBufferSize { get; set; } = 81920; } diff --git a/src/SharpCompress/Common/EntryStream.Async.cs b/src/SharpCompress/Common/EntryStream.Async.cs index 21708a893..a58afe70e 100644 --- a/src/SharpCompress/Common/EntryStream.Async.cs +++ b/src/SharpCompress/Common/EntryStream.Async.cs @@ -42,9 +42,6 @@ public override async ValueTask DisposeAsync() await lzmaStream.FlushAsync().ConfigureAwait(false); } } -#if DEBUG_STREAMS - this.DebugDispose(typeof(EntryStream)); -#endif await base.DisposeAsync().ConfigureAwait(false); await _stream.DisposeAsync().ConfigureAwait(false); } diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs index 537edabba..4ebd100ab 100644 --- a/src/SharpCompress/Common/EntryStream.cs +++ b/src/SharpCompress/Common/EntryStream.cs @@ -14,16 +14,11 @@ public partial class EntryStream : Stream private readonly Stream _stream; private bool _completed; private bool _isDisposed; - private readonly bool _useSyncOverAsyncDispose; - internal EntryStream(IReader reader, Stream stream, bool useSyncOverAsyncDispose) + internal EntryStream(IReader reader, Stream stream) { _reader = reader; _stream = stream; - _useSyncOverAsyncDispose = useSyncOverAsyncDispose; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(EntryStream)); -#endif } /// @@ -44,7 +39,7 @@ protected override void Dispose(bool disposing) _isDisposed = true; if (!(_completed || _reader.Cancelled)) { - if (_useSyncOverAsyncDispose) + if (Utility.UseSyncOverAsyncDispose()) { SkipEntryAsync().GetAwaiter().GetResult(); } @@ -72,9 +67,6 @@ is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream lzmaStream.Flush(); //Lzma over reads. Knock it back } } -#if DEBUG_STREAMS - this.DebugDispose(typeof(EntryStream)); -#endif base.Dispose(disposing); _stream.Dispose(); } diff --git a/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs b/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs index 57837f84d..5b58e0a22 100644 --- a/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Common.Rar; diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.Async.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.Async.cs index f753eb031..a43ad55b5 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.Async.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.Async.cs @@ -6,13 +6,7 @@ using System.Threading.Tasks; using SharpCompress.Common.Rar; using SharpCompress.IO; -#if !Rar2017_64bit using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif namespace SharpCompress.Common.Rar.Headers; diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index f0b7a53c6..966dc2bc9 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -6,13 +6,7 @@ using System.Threading.Tasks; using SharpCompress.Common.Rar; using SharpCompress.IO; -#if !Rar2017_64bit using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif namespace SharpCompress.Common.Rar.Headers; diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeader.Async.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.Async.cs index e3ca687fc..f5d796a40 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.Async.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.Async.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Tar.Headers; diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs index ad6a357e3..59e0fc6b5 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs @@ -13,12 +13,17 @@ internal static partial class TarHeaderFactory IArchiveEncoding archiveEncoding ) { +#if NET8_0_OR_GREATER + await using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#else + using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#endif + while (true) { TarHeader? header = null; try { - var reader = new AsyncBinaryReader(stream, false); header = new TarHeader(archiveEncoding); if (!await header.ReadAsync(reader)) { diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs index 7edbed2ff..d6c178898 100644 --- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs +++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs @@ -16,9 +16,6 @@ public TarReadOnlySubStream(Stream stream, long bytesToRead, bool useSyncOverAsy _stream = stream; _useSyncOverAsyncDispose = useSyncOverAsyncDispose; BytesLeftToRead = bytesToRead; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(TarReadOnlySubStream)); -#endif } protected override void Dispose(bool disposing) @@ -29,9 +26,6 @@ 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. @@ -43,7 +37,7 @@ protected override void Dispose(bool disposing) if (bytesInLastBlock != 0) { - if (_useSyncOverAsyncDispose) + if (Utility.UseSyncOverAsyncDispose()) { _stream.SkipAsync(512 - bytesInLastBlock).GetAwaiter().GetResult(); } @@ -64,9 +58,6 @@ public override async System.Threading.Tasks.ValueTask DisposeAsync() } _isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(TarReadOnlySubStream)); -#endif // Ensure we read all remaining blocks for this entry. await _stream.SkipAsync(BytesLeftToRead).ConfigureAwait(false); _amountRead += BytesLeftToRead; diff --git a/src/SharpCompress/Common/Volume.cs b/src/SharpCompress/Common/Volume.cs index 849f90b7e..d00ed4f0b 100644 --- a/src/SharpCompress/Common/Volume.cs +++ b/src/SharpCompress/Common/Volume.cs @@ -17,13 +17,14 @@ internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0) ReaderOptions = readerOptions ?? new ReaderOptions(); _baseStream = stream; - if (stream is RewindableStream ss) + // Only rewind if it's a buffered SharpCompressStream (not passthrough) + if (stream is SharpCompressStream ss && !ss.IsPassthrough) { ss.Rewind(); } if (ReaderOptions.LeaveStreamOpen) { - stream = new NonDisposingStream(stream); + stream = SharpCompressStream.CreateNonDisposing(stream); } _actualStream = stream; diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.Async.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.Async.cs index 811beeff2..86e0f771e 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.Async.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.Async.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.Async.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.Async.cs index e723f1e50..6cc356d6f 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.Async.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.Async.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs b/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs index 9c648baf3..86b66a810 100644 --- a/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.Async.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.Async.cs index 3a7426a10..9a8a991e2 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.Async.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.Async.cs @@ -1,6 +1,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs b/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs index 29aaabaae..d5e68feea 100644 --- a/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.Async.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.Async.cs index 386db4159..9bbfe4f8c 100644 --- a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.Async.cs +++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.Async.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.Async.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.Async.cs index 804f05daf..c4188c8b3 100644 --- a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.Async.cs +++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.Async.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs b/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs index 9ce1caa3a..5daf0560d 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using SharpCompress.IO; namespace SharpCompress.Common.Zip.Headers; diff --git a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs index 618f91753..65e233478 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs @@ -25,10 +25,6 @@ CryptoMode mode _encryptor = encryptor; _stream = stream; _mode = mode; - -#if DEBUG_STREAMS - this.DebugConstruct(typeof(PkwareTraditionalCryptoStream)); -#endif } public override bool CanRead => (_mode == CryptoMode.Decrypt); @@ -104,9 +100,6 @@ 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/SeekableZipHeaderFactory.Async.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs index 4eaf7c1f9..23422ae4f 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading.Tasks; using SharpCompress.Common.Zip.Headers; +using SharpCompress.IO; namespace SharpCompress.Common.Zip; @@ -11,7 +12,11 @@ internal sealed partial class SeekableZipHeaderFactory { internal async IAsyncEnumerable ReadSeekableHeaderAsync(Stream stream) { - var reader = new AsyncBinaryReader(stream); +#if NET8_0_OR_GREATER + await using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#else + using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#endif await SeekBackToHeaderAsync(stream, reader); @@ -127,7 +132,11 @@ DirectoryEntryHeader directoryEntryHeader ) { stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin); - var reader = new AsyncBinaryReader(stream); +#if NET8_0_OR_GREATER + await using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#else + using var reader = new AsyncBinaryReader(stream, leaveOpen: true); +#endif var signature = await reader.ReadUInt32Async(); if (await ReadHeader(signature, reader, _zip64) is not LocalEntryHeader localEntryHeader) { diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs index 9e7651c63..dbd628413 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs @@ -24,7 +24,7 @@ await GetCryptoStreamAsync(CreateBaseStream(), cancellationToken) .ConfigureAwait(false); if (LeaveStreamOpen) { - return new NonDisposingStream(_decompressionStream); + return SharpCompressStream.CreateNonDisposing(_decompressionStream); } return _decompressionStream; } diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs index 9c5875c8f..5368469f9 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs @@ -26,16 +26,16 @@ internal override Stream GetCompressedStream() ); if (LeaveStreamOpen) { - return new NonDisposingStream(_decompressionStream); + return SharpCompressStream.CreateNonDisposing(_decompressionStream); } return _decompressionStream; } - internal BinaryReader FixStreamedFileLocation(ref Stream rewindableStream) + internal BinaryReader FixStreamedFileLocation(ref Stream stream) { if (Header.IsDirectory) { - return new BinaryReader(rewindableStream); + return new BinaryReader(stream, System.Text.Encoding.Default, leaveOpen: true); } if (Header.HasData && !Skipped) @@ -49,12 +49,12 @@ internal BinaryReader FixStreamedFileLocation(ref Stream rewindableStream) if (_decompressionStream is DeflateStream deflateStream) { - rewindableStream.Position = 0; + stream.Position = 0; } Skipped = true; } - var reader = new BinaryReader(rewindableStream); + var reader = new BinaryReader(stream, System.Text.Encoding.Default, leaveOpen: true); _decompressionStream = null; return reader; } diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs index 71004b77e..b1ddb576b 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs @@ -60,7 +60,7 @@ public IAsyncEnumerator GetAsyncEnumerator( private sealed class StreamHeaderAsyncEnumerator : IAsyncEnumerator, IDisposable { private readonly StreamingZipHeaderFactory _headerFactory; - private readonly RewindableStream _rewindableStream; + private readonly SharpCompressStream _sharpCompressStream; private readonly AsyncBinaryReader _reader; private readonly CancellationToken _cancellationToken; private bool _completed; @@ -72,10 +72,10 @@ CancellationToken cancellationToken ) { _headerFactory = headerFactory; - // Use EnsureSeekable to avoid double-wrapping if stream is already a RewindableStream, + // Use EnsureSeekable to avoid double-wrapping if stream is already a SharpCompressStream, // and to preserve seekability for DataDescriptorStream which needs to seek backward - _rewindableStream = RewindableStream.EnsureSeekable(stream); - _reader = new AsyncBinaryReader(_rewindableStream, leaveOpen: true); + _sharpCompressStream = SharpCompressStream.EnsureSeekable(stream); + _reader = new AsyncBinaryReader(_sharpCompressStream, leaveOpen: true); _cancellationToken = cancellationToken; } @@ -110,7 +110,9 @@ public async ValueTask MoveNextAsync() continue; } - var pos = _rewindableStream.CanSeek ? (long?)_rewindableStream.Position : null; + var pos = _sharpCompressStream.CanSeek + ? (long?)_sharpCompressStream.Position + : null; var crc = await _reader .ReadUInt32Async(_cancellationToken) @@ -178,7 +180,9 @@ public async ValueTask MoveNextAsync() continue; } - var pos = _rewindableStream.CanSeek ? (long?)_rewindableStream.Position : null; + var pos = _sharpCompressStream.CanSeek + ? (long?)_sharpCompressStream.Position + : null; headerBytes = await _reader .ReadUInt32Async(_cancellationToken) @@ -236,12 +240,12 @@ await _reader { lastEntryHeader.DataStartPosition = pos - lastEntryHeader.CompressedSize; - // For SeekableRewindableStream, seek back to just after the local header signature. - // Plain RewindableStream cannot seek to arbitrary positions, so we skip this. + // For SeekableSharpCompressStream, seek back to just after the local header signature. + // Plain SharpCompressStream cannot seek to arbitrary positions, so we skip this. // 4 = First 4 bytes of the entry header (i.e. 50 4B 03 04) - if (_rewindableStream is SeekableRewindableStream) + if (_sharpCompressStream is SeekableSharpCompressStream) { - _rewindableStream.Position = pos.Value + 4; + _sharpCompressStream.Position = pos.Value + 4; } } } @@ -293,7 +297,7 @@ await _reader var nextHeaderBytes = await _reader .ReadUInt32Async(_cancellationToken) .ConfigureAwait(false); - ((IStreamStack)_rewindableStream).Rewind(sizeof(uint)); + _sharpCompressStream.Rewind(sizeof(uint)); // Check if next data is PostDataDescriptor, streamed file with 0 length header.HasData = !IsHeader(nextHeaderBytes); diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs index e7091d42a..cc91e1845 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs @@ -20,173 +20,192 @@ internal StreamingZipHeaderFactory( internal IEnumerable ReadStreamHeader(Stream stream) { - // Use EnsureSeekable to avoid double-wrapping if stream is already a RewindableStream, + // Use EnsureSeekable to avoid double-wrapping if stream is already a SharpCompressStream, // and to preserve seekability for DataDescriptorStream which needs to seek backward - var rewindableStream = RewindableStream.EnsureSeekable(stream); - while (true) + var sharpCompressStream = SharpCompressStream.EnsureSeekable(stream); + var reader = new BinaryReader( + sharpCompressStream, + System.Text.Encoding.Default, + leaveOpen: true + ); + + try { - var reader = new BinaryReader(rewindableStream); - uint headerBytes = 0; - if ( - _lastEntryHeader != null - && FlagUtility.HasFlag(_lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor) - ) + while (true) { - if (_lastEntryHeader.Part is null) + uint headerBytes = 0; + if ( + _lastEntryHeader != null + && FlagUtility.HasFlag( + _lastEntryHeader.Flags, + HeaderFlags.UsePostDataDescriptor + ) + ) { - continue; - } - - // removed requirement for FixStreamedFileLocation() - - var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; - - var crc = reader.ReadUInt32(); - if (crc == POST_DATA_DESCRIPTOR) - { - crc = reader.ReadUInt32(); - } - _lastEntryHeader.Crc = crc; - - //attempt 32bit read - ulong compSize = reader.ReadUInt32(); - ulong uncompSize = reader.ReadUInt32(); - headerBytes = reader.ReadUInt32(); - - //check for zip64 sentinel or unexpected header - bool isSentinel = compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF; - bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50; - - if (!isHeader && !isSentinel) - { - //reshuffle into 64-bit values - compSize = (uncompSize << 32) | compSize; - uncompSize = ((ulong)headerBytes << 32) | reader.ReadUInt32(); + if (_lastEntryHeader.Part is null) + { + continue; + } + + // removed requirement for FixStreamedFileLocation() + + var pos = sharpCompressStream.CanSeek + ? (long?)sharpCompressStream.Position + : null; + + var crc = reader.ReadUInt32(); + if (crc == POST_DATA_DESCRIPTOR) + { + crc = reader.ReadUInt32(); + } + _lastEntryHeader.Crc = crc; + + //attempt 32bit read + ulong compSize = reader.ReadUInt32(); + ulong uncompSize = reader.ReadUInt32(); headerBytes = reader.ReadUInt32(); - } - else if (isSentinel) - { - //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; + //check for zip64 sentinel or unexpected header + bool isSentinel = compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF; + bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50; + + if (!isHeader && !isSentinel) + { + //reshuffle into 64-bit values + compSize = (uncompSize << 32) | compSize; + uncompSize = ((ulong)headerBytes << 32) | reader.ReadUInt32(); + headerBytes = reader.ReadUInt32(); + } + else if (isSentinel) + { + //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; + } } - } - else if (_lastEntryHeader != null && _lastEntryHeader.IsZip64) - { - if (_lastEntryHeader.Part is null) - { - continue; - } - - //reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( - // ref rewindableStream - //); - - var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; - - headerBytes = reader.ReadUInt32(); - - var version = reader.ReadUInt16(); - var flags = (HeaderFlags)reader.ReadUInt16(); - var compressionMethod = (ZipCompressionMethod)reader.ReadUInt16(); - var lastModifiedDate = reader.ReadUInt16(); - var lastModifiedTime = reader.ReadUInt16(); - - var crc = reader.ReadUInt32(); - - if (crc == POST_DATA_DESCRIPTOR) + else if (_lastEntryHeader != null && _lastEntryHeader.IsZip64) { - crc = reader.ReadUInt32(); - } - _lastEntryHeader.Crc = crc; + if (_lastEntryHeader.Part is null) + { + continue; + } - // The DataDescriptor can be either 64bit or 32bit - var compressed_size = reader.ReadUInt32(); - var uncompressed_size = reader.ReadUInt32(); + //reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( + // ref sharpCompressStream + //); - // Check if we have header or 64bit DataDescriptor - var test_header = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50); + var pos = sharpCompressStream.CanSeek + ? (long?)sharpCompressStream.Position + : null; - var test_64bit = ((long)uncompressed_size << 32) | compressed_size; - if (test_64bit == _lastEntryHeader.CompressedSize && test_header) - { - _lastEntryHeader.UncompressedSize = - ((long)reader.ReadUInt32() << 32) | headerBytes; headerBytes = reader.ReadUInt32(); + + var version = reader.ReadUInt16(); + var flags = (HeaderFlags)reader.ReadUInt16(); + var compressionMethod = (ZipCompressionMethod)reader.ReadUInt16(); + var lastModifiedDate = reader.ReadUInt16(); + var lastModifiedTime = reader.ReadUInt16(); + + var crc = reader.ReadUInt32(); + + if (crc == POST_DATA_DESCRIPTOR) + { + crc = reader.ReadUInt32(); + } + _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 + var test_header = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50); + + var test_64bit = ((long)uncompressed_size << 32) | compressed_size; + if (test_64bit == _lastEntryHeader.CompressedSize && test_header) + { + _lastEntryHeader.UncompressedSize = + ((long)reader.ReadUInt32() << 32) | headerBytes; + headerBytes = reader.ReadUInt32(); + } + else + { + _lastEntryHeader.UncompressedSize = uncompressed_size; + } + + if (pos.HasValue) + { + _lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize; + + // 4 = First 4 bytes of the entry header (i.e. 50 4B 03 04) + sharpCompressStream.Position = pos.Value + 4; + } } else { - _lastEntryHeader.UncompressedSize = uncompressed_size; + headerBytes = reader.ReadUInt32(); } - if (pos.HasValue) + _lastEntryHeader = null; + var header = ReadHeader(headerBytes, reader); + if (header is null) { - _lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize; - - // 4 = First 4 bytes of the entry header (i.e. 50 4B 03 04) - rewindableStream.Position = pos.Value + 4; + yield break; } - } - else - { - headerBytes = reader.ReadUInt32(); - } - _lastEntryHeader = null; - var header = ReadHeader(headerBytes, reader); - if (header is null) - { - yield break; - } - - //entry could be zero bytes so we need to know that. - if (header.ZipHeaderType == ZipHeaderType.LocalEntry) - { - var local_header = ((LocalEntryHeader)header); - var dir_header = _entries?.FirstOrDefault(entry => - entry.Key == local_header.Name - && local_header.CompressedSize == 0 - && local_header.UncompressedSize == 0 - && local_header.Crc == 0 - && local_header.IsDirectory == false - ); - - if (dir_header != null) + //entry could be zero bytes so we need to know that. + if (header.ZipHeaderType == ZipHeaderType.LocalEntry) { - local_header.UncompressedSize = dir_header.Size; - local_header.CompressedSize = dir_header.CompressedSize; - local_header.Crc = (uint)dir_header.Crc; - } - - // If we have CompressedSize, there is data to be read - if (local_header.CompressedSize > 0) - { - header.HasData = true; - } // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor ) - else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor)) - { - // Peek ahead to check if next data is a header or file data. - // Use the IStreamStack.Rewind mechanism to give back the peeked bytes. - var nextHeaderBytes = reader.ReadUInt32(); - ((IStreamStack)rewindableStream).Rewind(sizeof(uint)); - - // Check if next data is PostDataDescriptor, streamed file with 0 length - header.HasData = !IsHeader(nextHeaderBytes); - } - else // We are not streaming and compressed size is 0, we have no data - { - header.HasData = false; + var local_header = ((LocalEntryHeader)header); + var dir_header = _entries?.FirstOrDefault(entry => + entry.Key == local_header.Name + && local_header.CompressedSize == 0 + && local_header.UncompressedSize == 0 + && local_header.Crc == 0 + && local_header.IsDirectory == false + ); + + if (dir_header != null) + { + local_header.UncompressedSize = dir_header.Size; + local_header.CompressedSize = dir_header.CompressedSize; + local_header.Crc = (uint)dir_header.Crc; + } + + // If we have CompressedSize, there is data to be read + if (local_header.CompressedSize > 0) + { + header.HasData = true; + } // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor ) + else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor)) + { + // Peek ahead to check if next data is a header or file data. + // Use the IStreamStack.Rewind mechanism to give back the peeked bytes. + var nextHeaderBytes = reader.ReadUInt32(); + sharpCompressStream.Rewind(sizeof(uint)); + + // Check if next data is PostDataDescriptor, streamed file with 0 length + header.HasData = !IsHeader(nextHeaderBytes); + } + else // We are not streaming and compressed size is 0, we have no data + { + header.HasData = false; + } } + yield return header; } - yield return header; + } + finally + { + reader.Dispose(); } } } diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs index cffb83862..deb4dd10a 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs @@ -16,9 +16,6 @@ public override async ValueTask DisposeAsync() return; } _isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(WinzipAesCryptoStream)); -#endif // Read out last 10 auth bytes asynchronously byte[] authBytes = ArrayPool.Shared.Rent(10); try diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs index 5434c58b0..167f14616 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs @@ -19,22 +19,15 @@ internal partial class WinzipAesCryptoStream : Stream private bool _isFinalBlock; private long _totalBytesLeftToRead; private bool _isDisposed; - private bool _useSyncOverAsyncDispose; internal WinzipAesCryptoStream( Stream stream, WinzipAesEncryptionData winzipAesEncryptionData, - long length, - bool useSyncOverAsyncDispose + long length ) { _stream = stream; _totalBytesLeftToRead = length; - _useSyncOverAsyncDispose = useSyncOverAsyncDispose; - -#if DEBUG_STREAMS - this.DebugConstruct(typeof(WinzipAesCryptoStream)); -#endif _cipher = CreateCipher(winzipAesEncryptionData); @@ -73,13 +66,10 @@ 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 - catch exceptions for async-only streams - if (_useSyncOverAsyncDispose) + if (Utility.UseSyncOverAsyncDispose()) { var ten = ArrayPool.Shared.Rent(10); try diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs index dbb9b3220..bcd5650f1 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs @@ -39,7 +39,7 @@ await GetCryptoStreamAsync(CreateBaseStream(), cancellationToken) .ConfigureAwait(false); if (LeaveStreamOpen) { - return new NonDisposingStream(decompressionStream); + return SharpCompressStream.CreateNonDisposing(decompressionStream); } return decompressionStream; } @@ -63,7 +63,7 @@ protected async ValueTask GetCryptoStreamAsync( ) || Header.IsZip64 ) { - plainStream = new NonDisposingStream(plainStream); //make sure AES doesn't close + plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close } else { @@ -99,15 +99,10 @@ await Header { if (Header.WinzipAesEncryptionData != null) { - var useSyncOverAsync = false; -#if LEGACY_DOTNET - useSyncOverAsync = true; -#endif return new WinzipAesCryptoStream( plainStream, Header.WinzipAesEncryptionData, - Header.CompressedSize - 10, - useSyncOverAsync + Header.CompressedSize - 10 ); } return plainStream; diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 43eac529a..7dd860e76 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 new NonDisposingStream(decompressionStream); + return SharpCompressStream.CreateNonDisposing(decompressionStream); } return decompressionStream; } @@ -150,18 +150,26 @@ protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod m { throw new NotSupportedException("LZMA with pkware encryption."); } - var reader = new BinaryReader(stream); - reader.ReadUInt16(); //LZMA version - var props = new byte[reader.ReadUInt16()]; - reader.Read(props, 0, props.Length); - return LzmaStream.Create( - props, - stream, - Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1, - FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) - ? -1 - : Header.UncompressedSize - ); + using ( + var reader = new BinaryReader( + stream, + System.Text.Encoding.Default, + leaveOpen: true + ) + ) + { + reader.ReadUInt16(); //LZMA version + var props = new byte[reader.ReadUInt16()]; + reader.Read(props, 0, props.Length); + return LzmaStream.Create( + props, + stream, + Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1, + FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) + ? -1 + : Header.UncompressedSize + ); + } } case ZipCompressionMethod.Xz: { @@ -233,7 +241,7 @@ protected Stream GetCryptoStream(Stream plainStream) ) || Header.IsZip64 ) { - plainStream = new NonDisposingStream(plainStream); //make sure AES doesn't close + plainStream = SharpCompressStream.CreateNonDisposing(plainStream); //make sure AES doesn't close } else { @@ -270,8 +278,7 @@ protected Stream GetCryptoStream(Stream plainStream) return new WinzipAesCryptoStream( plainStream, Header.WinzipAesEncryptionData, - Header.CompressedSize - 10, - false + Header.CompressedSize - 10 ); } return plainStream; diff --git a/src/SharpCompress/Compressors/ADC/ADCStream.cs b/src/SharpCompress/Compressors/ADC/ADCStream.cs index 742111342..cb67d9d22 100644 --- a/src/SharpCompress/Compressors/ADC/ADCStream.cs +++ b/src/SharpCompress/Compressors/ADC/ADCStream.cs @@ -76,9 +76,6 @@ public ADCStream(Stream stream, CompressionMode compressionMode = CompressionMod } _stream = stream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ADCStream)); -#endif } public override bool CanRead => _stream.CanRead; @@ -104,9 +101,6 @@ 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 111106908..f9308228c 100644 --- a/src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs +++ b/src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs @@ -33,9 +33,6 @@ 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; @@ -202,9 +199,6 @@ public override void Write(byte[] buffer, int offset, int count) => protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(ArcLzwStream)); -#endif base.Dispose(disposing); } } diff --git a/src/SharpCompress/Compressors/Arj/BitReader.cs b/src/SharpCompress/Compressors/Arj/BitReader.cs index feb8cb944..efe5bf470 100644 --- a/src/SharpCompress/Compressors/Arj/BitReader.cs +++ b/src/SharpCompress/Compressors/Arj/BitReader.cs @@ -1,72 +1,68 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +[CLSCompliant(true)] +public partial class BitReader { - [CLSCompliant(true)] - public partial class BitReader - { - private readonly Stream _input; - private int _bitBuffer; // currently buffered bits - private int _bitCount; // number of bits in buffer + private readonly Stream _input; + private int _bitBuffer; // currently buffered bits + private int _bitCount; // number of bits in buffer - public BitReader(Stream input) - { - _input = input ?? throw new ArgumentNullException(nameof(input)); - _bitBuffer = 0; - _bitCount = 0; - } + public BitReader(Stream input) + { + _input = input ?? throw new ArgumentNullException(nameof(input)); + _bitBuffer = 0; + _bitCount = 0; + } - /// - /// Reads a single bit from the stream. Returns 0 or 1. - /// - public int ReadBit() + /// + /// Reads a single bit from the stream. Returns 0 or 1. + /// + public int ReadBit() + { + if (_bitCount == 0) { - if (_bitCount == 0) + int nextByte = _input.ReadByte(); + if (nextByte < 0) { - int nextByte = _input.ReadByte(); - if (nextByte < 0) - { - throw new EndOfStreamException("No more data available in BitReader."); - } - - _bitBuffer = nextByte; - _bitCount = 8; + throw new EndOfStreamException("No more data available in BitReader."); } - int bit = (_bitBuffer >> (_bitCount - 1)) & 1; - _bitCount--; - return bit; + _bitBuffer = nextByte; + _bitCount = 8; } - /// - /// Reads n bits (up to 32) from the stream. - /// - public int ReadBits(int count) - { - if (count < 0 || count > 32) - { - throw new ArgumentOutOfRangeException( - nameof(count), - "Count must be between 0 and 32." - ); - } + int bit = (_bitBuffer >> (_bitCount - 1)) & 1; + _bitCount--; + return bit; + } - int result = 0; - for (int i = 0; i < count; i++) - { - result = (result << 1) | ReadBit(); - } - return result; + /// + /// Reads n bits (up to 32) from the stream. + /// + public int ReadBits(int count) + { + if (count < 0 || count > 32) + { + throw new ArgumentOutOfRangeException(nameof(count), "Count must be between 0 and 32."); } - /// - /// Resets any buffered bits. - /// - public void AlignToByte() + int result = 0; + for (int i = 0; i < count; i++) { - _bitCount = 0; - _bitBuffer = 0; + result = (result << 1) | ReadBit(); } + return result; + } + + /// + /// Resets any buffered bits. + /// + public void AlignToByte() + { + _bitCount = 0; + _bitBuffer = 0; } } diff --git a/src/SharpCompress/Compressors/Arj/HistoryIterator.cs b/src/SharpCompress/Compressors/Arj/HistoryIterator.cs index e38d12960..2341be558 100644 --- a/src/SharpCompress/Compressors/Arj/HistoryIterator.cs +++ b/src/SharpCompress/Compressors/Arj/HistoryIterator.cs @@ -2,42 +2,41 @@ using System.Collections; using System.Collections.Generic; -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +/// +/// Iterator that reads & pushes values back into the ring buffer. +/// +public class HistoryIterator : IEnumerator { - /// - /// Iterator that reads & pushes values back into the ring buffer. - /// - public class HistoryIterator : IEnumerator - { - private int _index; - private readonly IRingBuffer _ring; + private int _index; + private readonly IRingBuffer _ring; - public HistoryIterator(IRingBuffer ring, int startIndex) - { - _ring = ring; - _index = startIndex; - } + public HistoryIterator(IRingBuffer ring, int startIndex) + { + _ring = ring; + _index = startIndex; + } - public bool MoveNext() - { - Current = _ring[_index]; - _index = unchecked(_index + 1); + public bool MoveNext() + { + Current = _ring[_index]; + _index = unchecked(_index + 1); - // Push value back into the ring buffer - _ring.Push(Current); + // Push value back into the ring buffer + _ring.Push(Current); - return true; // iterator is infinite - } + return true; // iterator is infinite + } - public void Reset() - { - throw new NotSupportedException(); - } + public void Reset() + { + throw new NotSupportedException(); + } - public byte Current { get; private set; } + public byte Current { get; private set; } - object IEnumerator.Current => Current; + object IEnumerator.Current => Current; - public void Dispose() { } - } + public void Dispose() { } } diff --git a/src/SharpCompress/Compressors/Arj/HuffmanTree.cs b/src/SharpCompress/Compressors/Arj/HuffmanTree.cs index 6d4c7e869..63bf89682 100644 --- a/src/SharpCompress/Compressors/Arj/HuffmanTree.cs +++ b/src/SharpCompress/Compressors/Arj/HuffmanTree.cs @@ -3,216 +3,212 @@ using System.IO; using System.Text; -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +[CLSCompliant(true)] +public enum NodeType { - [CLSCompliant(true)] - public enum NodeType + Leaf, + Branch, +} + +[CLSCompliant(true)] +public sealed class TreeEntry +{ + public readonly NodeType Type; + public readonly int LeafValue; + public readonly int BranchIndex; + + public const int MAX_INDEX = 4096; + + private TreeEntry(NodeType type, int leafValue, int branchIndex) { - Leaf, - Branch, + Type = type; + LeafValue = leafValue; + BranchIndex = branchIndex; } - [CLSCompliant(true)] - public sealed class TreeEntry + public static TreeEntry Leaf(int value) { - public readonly NodeType Type; - public readonly int LeafValue; - public readonly int BranchIndex; - - public const int MAX_INDEX = 4096; + return new TreeEntry(NodeType.Leaf, value, -1); + } - private TreeEntry(NodeType type, int leafValue, int branchIndex) + public static TreeEntry Branch(int index) + { + if (index >= MAX_INDEX) { - Type = type; - LeafValue = leafValue; - BranchIndex = branchIndex; + throw new ArgumentOutOfRangeException(nameof(index), "Branch index exceeds MAX_INDEX"); } + return new TreeEntry(NodeType.Branch, 0, index); + } +} - public static TreeEntry Leaf(int value) - { - return new TreeEntry(NodeType.Leaf, value, -1); - } +[CLSCompliant(true)] +public sealed partial class HuffTree +{ + private readonly List _tree; - public static TreeEntry Branch(int index) - { - if (index >= MAX_INDEX) - { - throw new ArgumentOutOfRangeException( - nameof(index), - "Branch index exceeds MAX_INDEX" - ); - } - return new TreeEntry(NodeType.Branch, 0, index); - } + public HuffTree(int capacity = 0) + { + _tree = new List(capacity); } - [CLSCompliant(true)] - public sealed partial class HuffTree + public void SetSingle(int value) { - private readonly List _tree; + _tree.Clear(); + _tree.Add(TreeEntry.Leaf(value)); + } - public HuffTree(int capacity = 0) + public void BuildTree(byte[] lengths, int count) + { + if (lengths == null) { - _tree = new List(capacity); + throw new ArgumentNullException(nameof(lengths)); } - public void SetSingle(int value) + if (count < 0 || count > lengths.Length) { - _tree.Clear(); - _tree.Add(TreeEntry.Leaf(value)); + throw new ArgumentOutOfRangeException(nameof(count)); } - public void BuildTree(byte[] lengths, int count) + if (count > TreeEntry.MAX_INDEX / 2) { - if (lengths == null) - { - throw new ArgumentNullException(nameof(lengths)); - } - - if (count < 0 || count > lengths.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } + throw new ArgumentException( + $"Count exceeds maximum allowed: {TreeEntry.MAX_INDEX / 2}" + ); + } + byte[] slice = new byte[count]; + Array.Copy(lengths, slice, count); - if (count > TreeEntry.MAX_INDEX / 2) - { - throw new ArgumentException( - $"Count exceeds maximum allowed: {TreeEntry.MAX_INDEX / 2}" - ); - } - byte[] slice = new byte[count]; - Array.Copy(lengths, slice, count); + BuildTree(slice); + } - BuildTree(slice); + public void BuildTree(byte[] valueLengths) + { + if (valueLengths == null) + { + throw new ArgumentNullException(nameof(valueLengths)); } - public void BuildTree(byte[] valueLengths) + if (valueLengths.Length > TreeEntry.MAX_INDEX / 2) { - if (valueLengths == null) - { - throw new ArgumentNullException(nameof(valueLengths)); - } + throw new InvalidOperationException("Too many code lengths"); + } - if (valueLengths.Length > TreeEntry.MAX_INDEX / 2) - { - throw new InvalidOperationException("Too many code lengths"); - } + _tree.Clear(); - _tree.Clear(); + int maxAllocated = 1; // start with a single (root) node - int maxAllocated = 1; // start with a single (root) node + for (byte currentLen = 1; ; currentLen++) + { + // add missing branches up to current limit + int maxLimit = maxAllocated; - for (byte currentLen = 1; ; currentLen++) + for (int i = _tree.Count; i < maxLimit; i++) { - // add missing branches up to current limit - int maxLimit = maxAllocated; - - for (int i = _tree.Count; i < maxLimit; i++) + // TreeEntry.Branch may throw if index too large + try { - // TreeEntry.Branch may throw if index too large - try - { - _tree.Add(TreeEntry.Branch(maxAllocated)); - } - catch (ArgumentOutOfRangeException e) - { - _tree.Clear(); - throw new InvalidOperationException("Branch index exceeds limit", e); - } - - // each branch node allocates two children - maxAllocated += 2; + _tree.Add(TreeEntry.Branch(maxAllocated)); } - - // fill tree with leaves found in the lengths table at the current length - bool moreLeaves = false; - - for (int value = 0; value < valueLengths.Length; value++) + catch (ArgumentOutOfRangeException e) { - byte len = valueLengths[value]; - if (len == currentLen) - { - _tree.Add(TreeEntry.Leaf(value)); - } - else if (len > currentLen) - { - moreLeaves = true; // there are more leaves to process - } + _tree.Clear(); + throw new InvalidOperationException("Branch index exceeds limit", e); } - // sanity check (too many leaves) - if (_tree.Count > maxAllocated) + // each branch node allocates two children + maxAllocated += 2; + } + + // fill tree with leaves found in the lengths table at the current length + bool moreLeaves = false; + + for (int value = 0; value < valueLengths.Length; value++) + { + byte len = valueLengths[value]; + if (len == currentLen) { - throw new InvalidOperationException("Too many leaves"); + _tree.Add(TreeEntry.Leaf(value)); } - - // stop when no longer finding longer codes - if (!moreLeaves) + else if (len > currentLen) { - break; + moreLeaves = true; // there are more leaves to process } } - // ensure tree is complete - if (_tree.Count != maxAllocated) + // sanity check (too many leaves) + if (_tree.Count > maxAllocated) { - throw new InvalidOperationException( - $"Missing some leaves: tree count = {_tree.Count}, expected = {maxAllocated}" - ); + throw new InvalidOperationException("Too many leaves"); } - } - public int ReadEntry(BitReader reader) - { - if (_tree.Count == 0) + // stop when no longer finding longer codes + if (!moreLeaves) { - throw new InvalidOperationException("Tree not initialized"); + break; } + } - TreeEntry node = _tree[0]; - while (true) - { - if (node.Type == NodeType.Leaf) - { - return node.LeafValue; - } + // ensure tree is complete + if (_tree.Count != maxAllocated) + { + throw new InvalidOperationException( + $"Missing some leaves: tree count = {_tree.Count}, expected = {maxAllocated}" + ); + } + } - int bit = reader.ReadBit(); - int index = node.BranchIndex + bit; + public int ReadEntry(BitReader reader) + { + if (_tree.Count == 0) + { + throw new InvalidOperationException("Tree not initialized"); + } - if (index >= _tree.Count) - { - throw new InvalidOperationException("Invalid branch index during read"); - } + TreeEntry node = _tree[0]; + while (true) + { + if (node.Type == NodeType.Leaf) + { + return node.LeafValue; + } + + int bit = reader.ReadBit(); + int index = node.BranchIndex + bit; - node = _tree[index]; + if (index >= _tree.Count) + { + throw new InvalidOperationException("Invalid branch index during read"); } + + node = _tree[index]; } + } - public override string ToString() - { - var result = new StringBuilder(); + public override string ToString() + { + var result = new StringBuilder(); - void FormatStep(int index, string prefix) + void FormatStep(int index, string prefix) + { + var node = _tree[index]; + if (node.Type == NodeType.Leaf) { - var node = _tree[index]; - if (node.Type == NodeType.Leaf) - { - result.AppendLine($"{prefix} -> {node.LeafValue}"); - } - else - { - FormatStep(node.BranchIndex, prefix + "0"); - FormatStep(node.BranchIndex + 1, prefix + "1"); - } + result.AppendLine($"{prefix} -> {node.LeafValue}"); } - - if (_tree.Count > 0) + else { - FormatStep(0, ""); + FormatStep(node.BranchIndex, prefix + "0"); + FormatStep(node.BranchIndex + 1, prefix + "1"); } + } - return result.ToString(); + if (_tree.Count > 0) + { + FormatStep(0, ""); } + + return result.ToString(); } } diff --git a/src/SharpCompress/Compressors/Arj/ILhaDecoderConfig.cs b/src/SharpCompress/Compressors/Arj/ILhaDecoderConfig.cs index 8a82b978e..b546e14f6 100644 --- a/src/SharpCompress/Compressors/Arj/ILhaDecoderConfig.cs +++ b/src/SharpCompress/Compressors/Arj/ILhaDecoderConfig.cs @@ -1,9 +1,8 @@ -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +public interface ILhaDecoderConfig { - public interface ILhaDecoderConfig - { - int HistoryBits { get; } - int OffsetBits { get; } - RingBuffer RingBuffer { get; } - } + int HistoryBits { get; } + int OffsetBits { get; } + RingBuffer RingBuffer { get; } } diff --git a/src/SharpCompress/Compressors/Arj/IRingBuffer.cs b/src/SharpCompress/Compressors/Arj/IRingBuffer.cs index 40585b1d9..dab0f3a67 100644 --- a/src/SharpCompress/Compressors/Arj/IRingBuffer.cs +++ b/src/SharpCompress/Compressors/Arj/IRingBuffer.cs @@ -1,17 +1,16 @@ -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +public interface IRingBuffer { - public interface IRingBuffer - { - int BufferSize { get; } + int BufferSize { get; } - int Cursor { get; } - void SetCursor(int pos); + int Cursor { get; } + void SetCursor(int pos); - void Push(byte value); + void Push(byte value); - HistoryIterator IterFromOffset(int offset); - HistoryIterator IterFromPos(int pos); + HistoryIterator IterFromOffset(int offset); + HistoryIterator IterFromPos(int pos); - byte this[int index] { get; } - } + byte this[int index] { get; } } diff --git a/src/SharpCompress/Compressors/Arj/LHDecoderStream.cs b/src/SharpCompress/Compressors/Arj/LHDecoderStream.cs index 4ada97675..52b51a734 100644 --- a/src/SharpCompress/Compressors/Arj/LHDecoderStream.cs +++ b/src/SharpCompress/Compressors/Arj/LHDecoderStream.cs @@ -2,183 +2,181 @@ using System.Collections.Generic; using System.IO; -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +[CLSCompliant(true)] +public sealed partial class LHDecoderStream : Stream { - [CLSCompliant(true)] - public sealed partial class LHDecoderStream : Stream - { - private readonly BitReader _bitReader; - private readonly Stream _stream; + private readonly BitReader _bitReader; + private readonly Stream _stream; - // Buffer containing *all* bytes decoded so far. - private readonly List _buffer = new(); + // Buffer containing *all* bytes decoded so far. + private readonly List _buffer = new(); - private long _readPosition; - private readonly int _originalSize; - private bool _finishedDecoding; - private bool _disposed; + private long _readPosition; + private readonly int _originalSize; + private bool _finishedDecoding; + private bool _disposed; - private const int THRESHOLD = 3; + private const int THRESHOLD = 3; - public LHDecoderStream(Stream compressedStream, int originalSize) + public LHDecoderStream(Stream compressedStream, int originalSize) + { + _stream = compressedStream ?? throw new ArgumentNullException(nameof(compressedStream)); + if (!compressedStream.CanRead) { - _stream = compressedStream ?? throw new ArgumentNullException(nameof(compressedStream)); - if (!compressedStream.CanRead) - { - throw new ArgumentException( - "compressedStream must be readable.", - nameof(compressedStream) - ); - } - - _bitReader = new BitReader(compressedStream); - _originalSize = originalSize; - _readPosition = 0; - _finishedDecoding = (originalSize == 0); + throw new ArgumentException( + "compressedStream must be readable.", + nameof(compressedStream) + ); } - public Stream BaseStream => _stream; + _bitReader = new BitReader(compressedStream); + _originalSize = originalSize; + _readPosition = 0; + _finishedDecoding = (originalSize == 0); + } + + public Stream BaseStream => _stream; - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; - public override long Length => _originalSize; + public override long Length => _originalSize; - public override long Position + public override long Position + { + get => _readPosition; + set => throw new NotSupportedException(); + } + + /// + /// Decodes a single element (literal or back-reference) and appends it to _buffer. + /// Returns true if data was added, or false if all input has already been decoded. + /// + private bool DecodeNext() + { + if (_buffer.Count >= _originalSize) { - get => _readPosition; - set => throw new NotSupportedException(); + _finishedDecoding = true; + return false; } - /// - /// Decodes a single element (literal or back-reference) and appends it to _buffer. - /// Returns true if data was added, or false if all input has already been decoded. - /// - private bool DecodeNext() + int len = DecodeVal(0, 7); + if (len == 0) { - if (_buffer.Count >= _originalSize) - { - _finishedDecoding = true; - return false; - } + byte nextChar = (byte)_bitReader.ReadBits(8); + _buffer.Add(nextChar); + } + else + { + int repCount = len + THRESHOLD - 1; + int backPtr = DecodeVal(9, 13); - int len = DecodeVal(0, 7); - if (len == 0) + if (backPtr >= _buffer.Count) { - byte nextChar = (byte)_bitReader.ReadBits(8); - _buffer.Add(nextChar); - } - else - { - int repCount = len + THRESHOLD - 1; - int backPtr = DecodeVal(9, 13); - - if (backPtr >= _buffer.Count) - { - throw new InvalidDataException("Invalid back_ptr in LH stream"); - } - - int srcIndex = _buffer.Count - 1 - backPtr; - for (int j = 0; j < repCount && _buffer.Count < _originalSize; j++) - { - byte b = _buffer[srcIndex]; - _buffer.Add(b); - srcIndex++; - // srcIndex may grow; it's allowed (source region can overlap destination) - } + throw new InvalidDataException("Invalid back_ptr in LH stream"); } - if (_buffer.Count >= _originalSize) + int srcIndex = _buffer.Count - 1 - backPtr; + for (int j = 0; j < repCount && _buffer.Count < _originalSize; j++) { - _finishedDecoding = true; + byte b = _buffer[srcIndex]; + _buffer.Add(b); + srcIndex++; + // srcIndex may grow; it's allowed (source region can overlap destination) } - - return true; } - private int DecodeVal(int from, int to) + if (_buffer.Count >= _originalSize) { - int add = 0; - int bit = from; + _finishedDecoding = true; + } - while (bit < to && _bitReader.ReadBits(1) == 1) - { - add |= 1 << bit; - bit++; - } + return true; + } + + private int DecodeVal(int from, int to) + { + int add = 0; + int bit = from; - int res = bit > 0 ? _bitReader.ReadBits(bit) : 0; - return res + add; + while (bit < to && _bitReader.ReadBits(1) == 1) + { + add |= 1 << bit; + bit++; } - /// - /// Reads decompressed bytes into buffer[offset..offset+count]. - /// The method decodes additional data on demand when needed. - /// - public override int Read(byte[] buffer, int offset, int count) + int res = bit > 0 ? _bitReader.ReadBits(bit) : 0; + return res + add; + } + + /// + /// Reads decompressed bytes into buffer[offset..offset+count]. + /// The method decodes additional data on demand when needed. + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) { - if (_disposed) - { - throw new ObjectDisposedException(nameof(LHDecoderStream)); - } + throw new ObjectDisposedException(nameof(LHDecoderStream)); + } - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - if (offset < 0 || count < 0 || offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException("offset/count"); - } + if (offset < 0 || count < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException("offset/count"); + } - if (_readPosition >= _originalSize) - { - return 0; // EOF - } + if (_readPosition >= _originalSize) + { + return 0; // EOF + } - int totalRead = 0; + int totalRead = 0; - while (totalRead < count && _readPosition < _originalSize) + while (totalRead < count && _readPosition < _originalSize) + { + if (_readPosition >= _buffer.Count) { - if (_readPosition >= _buffer.Count) + bool had = DecodeNext(); + if (!had) { - bool had = DecodeNext(); - if (!had) - { - break; - } + break; } + } - int available = _buffer.Count - (int)_readPosition; - if (available <= 0) + int available = _buffer.Count - (int)_readPosition; + if (available <= 0) + { + if (!_finishedDecoding) { - if (!_finishedDecoding) - { - continue; - } - break; + continue; } - - int toCopy = Math.Min(available, count - totalRead); - _buffer.CopyTo((int)_readPosition, buffer, offset + totalRead, toCopy); - - _readPosition += toCopy; - totalRead += toCopy; + break; } - return totalRead; + int toCopy = Math.Min(available, count - totalRead); + _buffer.CopyTo((int)_readPosition, buffer, offset + totalRead, toCopy); + + _readPosition += toCopy; + totalRead += toCopy; } - public override void Flush() => throw new NotSupportedException(); + return totalRead; + } - public override long Seek(long offset, SeekOrigin origin) => - throw new NotSupportedException(); + public override void Flush() => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => - 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/Compressors/Arj/Lh5DecoderCfg.cs b/src/SharpCompress/Compressors/Arj/Lh5DecoderCfg.cs index ebff12b8b..81f025a97 100644 --- a/src/SharpCompress/Compressors/Arj/Lh5DecoderCfg.cs +++ b/src/SharpCompress/Compressors/Arj/Lh5DecoderCfg.cs @@ -1,9 +1,8 @@ -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +public class Lh5DecoderCfg : ILhaDecoderConfig { - public class Lh5DecoderCfg : ILhaDecoderConfig - { - public int HistoryBits => 14; - public int OffsetBits => 4; - public RingBuffer RingBuffer { get; } = new RingBuffer(1 << 14); - } + public int HistoryBits => 14; + public int OffsetBits => 4; + public RingBuffer RingBuffer { get; } = new RingBuffer(1 << 14); } diff --git a/src/SharpCompress/Compressors/Arj/Lh7DecoderCfg.cs b/src/SharpCompress/Compressors/Arj/Lh7DecoderCfg.cs index 88c7f2bad..a73934547 100644 --- a/src/SharpCompress/Compressors/Arj/Lh7DecoderCfg.cs +++ b/src/SharpCompress/Compressors/Arj/Lh7DecoderCfg.cs @@ -1,9 +1,8 @@ -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +public class Lh7DecoderCfg : ILhaDecoderConfig { - public class Lh7DecoderCfg : ILhaDecoderConfig - { - public int HistoryBits => 17; - public int OffsetBits => 5; - public RingBuffer RingBuffer { get; } = new RingBuffer(1 << 17); - } + public int HistoryBits => 17; + public int OffsetBits => 5; + public RingBuffer RingBuffer { get; } = new RingBuffer(1 << 17); } diff --git a/src/SharpCompress/Compressors/Arj/LhaStream.cs b/src/SharpCompress/Compressors/Arj/LhaStream.cs index 853e61031..76071b45c 100644 --- a/src/SharpCompress/Compressors/Arj/LhaStream.cs +++ b/src/SharpCompress/Compressors/Arj/LhaStream.cs @@ -3,340 +3,336 @@ using System.IO; using System.Linq; -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +[CLSCompliant(true)] +public sealed partial class LhaStream : Stream + where C : ILhaDecoderConfig, new() { - [CLSCompliant(true)] - public sealed partial class LhaStream : Stream - where C : ILhaDecoderConfig, new() - { - private readonly BitReader _bitReader; - private readonly Stream _stream; + private readonly BitReader _bitReader; + private readonly Stream _stream; - private readonly HuffTree _commandTree; - private readonly HuffTree _offsetTree; - private int _remainingCommands; - private (int offset, int count)? _copyProgress; - private readonly RingBuffer _ringBuffer; - private readonly C _config = new C(); + private readonly HuffTree _commandTree; + private readonly HuffTree _offsetTree; + private int _remainingCommands; + private (int offset, int count)? _copyProgress; + private readonly RingBuffer _ringBuffer; + private readonly C _config = new C(); - private const int NUM_COMMANDS = 510; - private const int NUM_TEMP_CODELEN = 20; + private const int NUM_COMMANDS = 510; + private const int NUM_TEMP_CODELEN = 20; - private readonly int _originalSize; - private int _producedBytes = 0; + private readonly int _originalSize; + private int _producedBytes = 0; - public LhaStream(Stream compressedStream, int originalSize) - { - _stream = compressedStream ?? throw new ArgumentNullException(nameof(compressedStream)); - _bitReader = new BitReader(compressedStream); - _ringBuffer = _config.RingBuffer; - _commandTree = new HuffTree(NUM_COMMANDS * 2); - _offsetTree = new HuffTree(NUM_TEMP_CODELEN * 2); - _remainingCommands = 0; - _copyProgress = null; - _originalSize = originalSize; - } + public LhaStream(Stream compressedStream, int originalSize) + { + _stream = compressedStream ?? throw new ArgumentNullException(nameof(compressedStream)); + _bitReader = new BitReader(compressedStream); + _ringBuffer = _config.RingBuffer; + _commandTree = new HuffTree(NUM_COMMANDS * 2); + _offsetTree = new HuffTree(NUM_TEMP_CODELEN * 2); + _remainingCommands = 0; + _copyProgress = null; + _originalSize = originalSize; + } - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; - public override long Length => throw new NotSupportedException(); - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } - public override void Flush() { } + public override void Flush() { } - public override long Seek(long offset, SeekOrigin origin) => - 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 SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); - public override int Read(byte[] buffer, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || count < 0 || (offset + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(); - } - - if (_producedBytes >= _originalSize) - { - return 0; // EOF - } - if (count == 0) - { - return 0; - } + throw new ArgumentNullException(nameof(buffer)); + } + if (offset < 0 || count < 0 || (offset + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } - int bytesRead = FillBuffer(buffer); - return bytesRead; + if (_producedBytes >= _originalSize) + { + return 0; // EOF + } + if (count == 0) + { + return 0; } - private byte ReadCodeLength() + int bytesRead = FillBuffer(buffer); + return bytesRead; + } + + private byte ReadCodeLength() + { + byte len = (byte)_bitReader.ReadBits(3); + if (len == 7) { - byte len = (byte)_bitReader.ReadBits(3); - if (len == 7) + while (_bitReader.ReadBit() != 0) { - while (_bitReader.ReadBit() != 0) + len++; + if (len > 255) { - len++; - if (len > 255) - { - throw new InvalidOperationException("Code length overflow"); - } + throw new InvalidOperationException("Code length overflow"); } } - return len; } + return len; + } + + private int ReadCodeSkip(int skipRange) + { + int bits; + int increment; - private int ReadCodeSkip(int skipRange) + switch (skipRange) { - int bits; - int increment; + case 0: + return 1; + case 1: + bits = 4; + increment = 3; // 3..=18 + break; + default: + bits = 9; + increment = 20; // 20..=531 + break; + } - switch (skipRange) - { - case 0: - return 1; - case 1: - bits = 4; - increment = 3; // 3..=18 - break; - default: - bits = 9; - increment = 20; // 20..=531 - break; - } + int skip = _bitReader.ReadBits(bits); + return skip + increment; + } + + private void ReadTempTree() + { + byte[] codeLengths = new byte[NUM_TEMP_CODELEN]; + + // number of codes to read (5 bits) + int numCodes = _bitReader.ReadBits(5); - int skip = _bitReader.ReadBits(bits); - return skip + increment; + // single code only + if (numCodes == 0) + { + int code = _bitReader.ReadBits(5); + _offsetTree.SetSingle((byte)code); + return; } - private void ReadTempTree() + if (numCodes > NUM_TEMP_CODELEN) { - byte[] codeLengths = new byte[NUM_TEMP_CODELEN]; + throw new Exception("temporary codelen table has invalid size"); + } - // number of codes to read (5 bits) - int numCodes = _bitReader.ReadBits(5); + // read actual lengths + int count = Math.Min(3, numCodes); + for (int i = 0; i < count; i++) + { + codeLengths[i] = (byte)ReadCodeLength(); + } - // single code only - if (numCodes == 0) - { - int code = _bitReader.ReadBits(5); - _offsetTree.SetSingle((byte)code); - return; - } + // 2-bit skip value follows + int skip = _bitReader.ReadBits(2); - if (numCodes > NUM_TEMP_CODELEN) - { - throw new Exception("temporary codelen table has invalid size"); - } + if (3 + skip > numCodes) + { + throw new Exception("temporary codelen table has invalid size"); + } - // read actual lengths - int count = Math.Min(3, numCodes); - for (int i = 0; i < count; i++) - { - codeLengths[i] = (byte)ReadCodeLength(); - } + for (int i = 3 + skip; i < numCodes; i++) + { + codeLengths[i] = (byte)ReadCodeLength(); + } - // 2-bit skip value follows - int skip = _bitReader.ReadBits(2); + _offsetTree.BuildTree(codeLengths, numCodes); + } - if (3 + skip > numCodes) - { - throw new Exception("temporary codelen table has invalid size"); - } + private void ReadCommandTree() + { + byte[] codeLengths = new byte[NUM_COMMANDS]; - for (int i = 3 + skip; i < numCodes; i++) - { - codeLengths[i] = (byte)ReadCodeLength(); - } + // number of codes to read (9 bits) + int numCodes = _bitReader.ReadBits(9); - _offsetTree.BuildTree(codeLengths, numCodes); + // single code only + if (numCodes == 0) + { + int code = _bitReader.ReadBits(9); + _commandTree.SetSingle((ushort)code); + return; } - private void ReadCommandTree() + if (numCodes > NUM_COMMANDS) { - byte[] codeLengths = new byte[NUM_COMMANDS]; - - // number of codes to read (9 bits) - int numCodes = _bitReader.ReadBits(9); + throw new Exception("commands codelen table has invalid size"); + } - // single code only - if (numCodes == 0) + int index = 0; + while (index < numCodes) + { + for (int n = 0; n < numCodes - index; n++) { - int code = _bitReader.ReadBits(9); - _commandTree.SetSingle((ushort)code); - return; - } + int code = _offsetTree.ReadEntry(_bitReader); - if (numCodes > NUM_COMMANDS) - { - throw new Exception("commands codelen table has invalid size"); - } - - int index = 0; - while (index < numCodes) - { - for (int n = 0; n < numCodes - index; n++) + if (code >= 0 && code <= 2) // skip range { - int code = _offsetTree.ReadEntry(_bitReader); - - if (code >= 0 && code <= 2) // skip range - { - int skipCount = ReadCodeSkip(code); - index += n + skipCount; - goto outerLoop; - } - else - { - codeLengths[index + n] = (byte)(code - 2); - } + int skipCount = ReadCodeSkip(code); + index += n + skipCount; + goto outerLoop; + } + else + { + codeLengths[index + n] = (byte)(code - 2); } - break; - - outerLoop: - ; } + break; - _commandTree.BuildTree(codeLengths, numCodes); + outerLoop: + ; } - private void ReadOffsetTree() - { - int numCodes = _bitReader.ReadBits(_config.OffsetBits); - if (numCodes == 0) - { - int code = _bitReader.ReadBits(_config.OffsetBits); - _offsetTree.SetSingle(code); - return; - } - - if (numCodes > _config.HistoryBits) - { - throw new InvalidDataException("Offset code table too large"); - } + _commandTree.BuildTree(codeLengths, numCodes); + } - byte[] codeLengths = new byte[NUM_TEMP_CODELEN]; - for (int i = 0; i < numCodes; i++) - { - codeLengths[i] = (byte)ReadCodeLength(); - } + private void ReadOffsetTree() + { + int numCodes = _bitReader.ReadBits(_config.OffsetBits); + if (numCodes == 0) + { + int code = _bitReader.ReadBits(_config.OffsetBits); + _offsetTree.SetSingle(code); + return; + } - _offsetTree.BuildTree(codeLengths, numCodes); + if (numCodes > _config.HistoryBits) + { + throw new InvalidDataException("Offset code table too large"); } - private void BeginNewBlock() + byte[] codeLengths = new byte[NUM_TEMP_CODELEN]; + for (int i = 0; i < numCodes; i++) { - ReadTempTree(); - ReadCommandTree(); - ReadOffsetTree(); + codeLengths[i] = (byte)ReadCodeLength(); } - private int ReadCommand() => _commandTree.ReadEntry(_bitReader); + _offsetTree.BuildTree(codeLengths, numCodes); + } + + private void BeginNewBlock() + { + ReadTempTree(); + ReadCommandTree(); + ReadOffsetTree(); + } - private int ReadOffset() + private int ReadCommand() => _commandTree.ReadEntry(_bitReader); + + private int ReadOffset() + { + int bits = _offsetTree.ReadEntry(_bitReader); + if (bits <= 1) { - int bits = _offsetTree.ReadEntry(_bitReader); - if (bits <= 1) - { - return bits; - } + return bits; + } + + int res = _bitReader.ReadBits(bits - 1); + return res | (1 << (bits - 1)); + } + + private int CopyFromHistory(byte[] target, int targetIndex, int offset, int count) + { + var historyIter = _ringBuffer.IterFromOffset(offset); + int copied = 0; - int res = _bitReader.ReadBits(bits - 1); - return res | (1 << (bits - 1)); + while (copied < count && historyIter.MoveNext() && (targetIndex + copied) < target.Length) + { + target[targetIndex + copied] = historyIter.Current; + copied++; } - private int CopyFromHistory(byte[] target, int targetIndex, int offset, int count) + if (copied < count) { - var historyIter = _ringBuffer.IterFromOffset(offset); - int copied = 0; + _copyProgress = (offset, count - copied); + } - while ( - copied < count && historyIter.MoveNext() && (targetIndex + copied) < target.Length - ) - { - target[targetIndex + copied] = historyIter.Current; - copied++; - } + return copied; + } - if (copied < count) - { - _copyProgress = (offset, count - copied); - } + public int FillBuffer(byte[] buffer) + { + int bufLen = buffer.Length; + int bufIndex = 0; - return copied; + // stop when we reached original size + if (_producedBytes >= _originalSize) + { + return 0; } - public int FillBuffer(byte[] buffer) - { - int bufLen = buffer.Length; - int bufIndex = 0; + // calculate limit, so that we don't go over the original size + int remaining = (int)Math.Min(bufLen, _originalSize - _producedBytes); - // stop when we reached original size - if (_producedBytes >= _originalSize) + while (bufIndex < remaining) + { + if (_copyProgress.HasValue) { - return 0; + var (offset, count) = _copyProgress.Value; + int copied = CopyFromHistory( + buffer, + bufIndex, + offset, + (int)Math.Min(count, remaining - bufIndex) + ); + bufIndex += copied; + _copyProgress = null; } - // calculate limit, so that we don't go over the original size - int remaining = (int)Math.Min(bufLen, _originalSize - _producedBytes); - - while (bufIndex < remaining) + if (_remainingCommands == 0) { - if (_copyProgress.HasValue) - { - var (offset, count) = _copyProgress.Value; - int copied = CopyFromHistory( - buffer, - bufIndex, - offset, - (int)Math.Min(count, remaining - bufIndex) - ); - bufIndex += copied; - _copyProgress = null; - } - - if (_remainingCommands == 0) + _remainingCommands = _bitReader.ReadBits(16); + if (bufIndex + _remainingCommands > remaining) { - _remainingCommands = _bitReader.ReadBits(16); - if (bufIndex + _remainingCommands > remaining) - { - break; - } - BeginNewBlock(); + break; } + BeginNewBlock(); + } - _remainingCommands--; + _remainingCommands--; - int command = ReadCommand(); + int command = ReadCommand(); - if (command >= 0 && command <= 0xFF) - { - byte value = (byte)command; - buffer[bufIndex++] = value; - _ringBuffer.Push(value); - } - else - { - int count = command - 0x100 + 3; - int offset = ReadOffset(); - int copyCount = (int)Math.Min(count, remaining - bufIndex); - bufIndex += CopyFromHistory(buffer, bufIndex, offset, copyCount); - } + if (command >= 0 && command <= 0xFF) + { + byte value = (byte)command; + buffer[bufIndex++] = value; + _ringBuffer.Push(value); + } + else + { + int count = command - 0x100 + 3; + int offset = ReadOffset(); + int copyCount = (int)Math.Min(count, remaining - bufIndex); + bufIndex += CopyFromHistory(buffer, bufIndex, offset, copyCount); } - - _producedBytes += bufIndex; - return bufIndex; } + + _producedBytes += bufIndex; + return bufIndex; } } diff --git a/src/SharpCompress/Compressors/Arj/RingBuffer.cs b/src/SharpCompress/Compressors/Arj/RingBuffer.cs index 9722f993f..39285b972 100644 --- a/src/SharpCompress/Compressors/Arj/RingBuffer.cs +++ b/src/SharpCompress/Compressors/Arj/RingBuffer.cs @@ -2,66 +2,65 @@ using System.Collections; using System.Collections.Generic; -namespace SharpCompress.Compressors.Arj +namespace SharpCompress.Compressors.Arj; + +/// +/// A fixed-size ring buffer where N must be a power of two. +/// +public class RingBuffer : IRingBuffer { - /// - /// A fixed-size ring buffer where N must be a power of two. - /// - public class RingBuffer : IRingBuffer - { - private readonly byte[] _buffer; - private int _cursor; + private readonly byte[] _buffer; + private int _cursor; - public int BufferSize { get; } + public int BufferSize { get; } - public int Cursor => _cursor; + public int Cursor => _cursor; - private readonly int _mask; + private readonly int _mask; - public RingBuffer(int size) + public RingBuffer(int size) + { + if ((size & (size - 1)) != 0) { - if ((size & (size - 1)) != 0) - { - throw new ArgumentException("RingArrayBuffer size must be a power of two"); - } - - BufferSize = size; - _buffer = new byte[size]; - _cursor = 0; - _mask = size - 1; - - // Fill with spaces - for (int i = 0; i < size; i++) - { - _buffer[i] = (byte)' '; - } + throw new ArgumentException("RingArrayBuffer size must be a power of two"); } - public void SetCursor(int pos) - { - _cursor = pos & _mask; - } + BufferSize = size; + _buffer = new byte[size]; + _cursor = 0; + _mask = size - 1; - public void Push(byte value) + // Fill with spaces + for (int i = 0; i < size; i++) { - int index = _cursor; - _buffer[index & _mask] = value; - _cursor = (index + 1) & _mask; + _buffer[i] = (byte)' '; } + } - public byte this[int index] => _buffer[index & _mask]; + public void SetCursor(int pos) + { + _cursor = pos & _mask; + } - public HistoryIterator IterFromOffset(int offset) - { - int masked = (offset & _mask) + 1; - int startIndex = _cursor + BufferSize - masked; - return new HistoryIterator(this, startIndex); - } + public void Push(byte value) + { + int index = _cursor; + _buffer[index & _mask] = value; + _cursor = (index + 1) & _mask; + } - public HistoryIterator IterFromPos(int pos) - { - int startIndex = pos & _mask; - return new HistoryIterator(this, startIndex); - } + public byte this[int index] => _buffer[index & _mask]; + + public HistoryIterator IterFromOffset(int offset) + { + int masked = (offset & _mask) + 1; + int startIndex = _cursor + BufferSize - masked; + return new HistoryIterator(this, startIndex); + } + + public HistoryIterator IterFromPos(int pos) + { + int startIndex = pos & _mask; + return new HistoryIterator(this, startIndex); } } diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs index 276a57e65..c9b0b0319 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs @@ -23,11 +23,7 @@ public static async ValueTask CreateAsync( CancellationToken cancellationToken = default ) { - var bZip2Stream = new BZip2Stream(); - bZip2Stream.leaveOpen = leaveOpen; -#if DEBUG_STREAMS - bZip2Stream.DebugConstruct(typeof(BZip2Stream)); -#endif + var bZip2Stream = new BZip2Stream(leaveOpen); bZip2Stream.Mode = compressionMode; if (bZip2Stream.Mode == CompressionMode.Compress) { @@ -38,6 +34,7 @@ public static async ValueTask CreateAsync( bZip2Stream.stream = await CBZip2InputStream.CreateAsync( stream, decompressConcatenated, + leaveOpen, cancellationToken ); } diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs index c145cb81d..0aa690f67 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -9,9 +10,12 @@ public sealed partial class BZip2Stream : Stream { private Stream stream = default!; private bool isDisposed; - private bool leaveOpen; + private readonly bool leaveOpen; - private BZip2Stream() { } + private BZip2Stream(bool leaveOpen) + { + this.leaveOpen = leaveOpen; + } /// /// Create a BZip2Stream @@ -26,11 +30,7 @@ public static BZip2Stream Create( bool leaveOpen = false ) { - var bZip2Stream = new BZip2Stream(); - bZip2Stream.leaveOpen = leaveOpen; -#if DEBUG_STREAMS - bZip2Stream.DebugConstruct(typeof(BZip2Stream)); -#endif + var bZip2Stream = new BZip2Stream(leaveOpen); bZip2Stream.Mode = compressionMode; if (bZip2Stream.Mode == CompressionMode.Compress) { @@ -57,9 +57,6 @@ protected override void Dispose(bool disposing) return; } isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(BZip2Stream)); -#endif if (disposing) { stream.Dispose(); @@ -111,7 +108,7 @@ public override void Write(byte[] buffer, int offset, int count) => /// public static bool IsBZip2(Stream stream) { - var br = new BinaryReader(stream); + using var br = new BinaryReader(stream, Encoding.Default, leaveOpen: true); var chars = br.ReadBytes(2); if (chars.Length < 2 || chars[0] != 'B' || chars[1] != 'Z') { diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs index 7252a8027..6a5992e81 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs @@ -857,11 +857,11 @@ private async ValueTask BsGetInt32Async(CancellationToken cancellationToken public static async ValueTask CreateAsync( Stream zStream, bool decompressConcatenated, + bool leaveOpen = false, CancellationToken cancellationToken = default ) { - var cbZip2InputStream = new CBZip2InputStream(); - cbZip2InputStream.decompressConcatenated = decompressConcatenated; + var cbZip2InputStream = new CBZip2InputStream(decompressConcatenated, leaveOpen); cbZip2InputStream.ll8 = null; cbZip2InputStream.tt = null; await cbZip2InputStream.BsSetStreamAsync(zStream, cancellationToken); diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs index e303d7d05..2bd0d94ae 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs @@ -147,8 +147,8 @@ during decompression. storedCombinedCRC; private int computedBlockCRC, computedCombinedCRC; - private bool decompressConcatenated; - private bool leaveOpen; + private readonly bool decompressConcatenated; + private readonly bool leaveOpen; private int i2, count, @@ -162,7 +162,11 @@ during decompression. private char z; private bool isDisposed; - private CBZip2InputStream() { } + private CBZip2InputStream(bool decompressConcatenated, bool leaveOpen) + { + this.decompressConcatenated = decompressConcatenated; + this.leaveOpen = leaveOpen; + } public static CBZip2InputStream Create( Stream zStream, @@ -170,9 +174,7 @@ public static CBZip2InputStream Create( bool leaveOpen ) { - var cbZip2InputStream = new CBZip2InputStream(); - cbZip2InputStream.decompressConcatenated = decompressConcatenated; - cbZip2InputStream.leaveOpen = leaveOpen; + var cbZip2InputStream = new CBZip2InputStream(decompressConcatenated, leaveOpen); cbZip2InputStream.ll8 = null; cbZip2InputStream.tt = null; cbZip2InputStream.BsSetStream(zStream); @@ -189,9 +191,6 @@ 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 c3a0348e4..f846c3eb1 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs @@ -337,10 +337,6 @@ public CBZip2OutputStream(Stream inStream, int inBlockSize, bool leaveOpen = fal BsSetStream(inStream); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(CBZip2OutputStream)); -#endif - workFactor = 50; if (inBlockSize > 9) { @@ -457,9 +453,6 @@ protected override void Dispose(bool disposing) Finish(); disposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(CBZip2OutputStream)); -#endif Dispose(); if (!leaveOpen) { diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs index 6b8c1c2c5..d9249d1f7 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs @@ -250,9 +250,6 @@ protected override void Dispose(bool disposing) { if (!_disposed) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(DeflateStream)); -#endif if (disposing && !_leaveOpen) { _baseStream?.Dispose(); diff --git a/src/SharpCompress/Compressors/Deflate/GZipStream.cs b/src/SharpCompress/Compressors/Deflate/GZipStream.cs index f3857b2c7..95b7c03df 100644 --- a/src/SharpCompress/Compressors/Deflate/GZipStream.cs +++ b/src/SharpCompress/Compressors/Deflate/GZipStream.cs @@ -64,9 +64,6 @@ Encoding encoding ) { BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(GZipStream)); -#endif _encoding = encoding; } @@ -215,9 +212,6 @@ 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 f4070ea71..513029db9 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -104,10 +104,6 @@ Encoding encoding _encoding = encoding; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ZlibBaseStream)); -#endif - // workitem 7159 if (flavor == ZlibStreamFlavor.GZIP) { @@ -523,9 +519,6 @@ protected override void Dispose(bool disposing) return; } isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(ZlibBaseStream)); -#endif base.Dispose(disposing); if (disposing) { @@ -557,9 +550,6 @@ public override async ValueTask DisposeAsync() return; } isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(ZlibBaseStream)); -#endif await base.DisposeAsync().ConfigureAwait(false); if (_stream is null) { diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs index ede19f6f1..417c32839 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs @@ -52,9 +52,6 @@ Encoding encoding ) { _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ZlibStream)); -#endif } #region Zlib properties @@ -224,9 +221,6 @@ 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 2300cfbc7..80b23dd82 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -49,9 +49,6 @@ public Deflate64Stream(Stream stream, CompressionMode mode) _stream = stream; _buffer = new byte[DEFAULT_BUFFER_SIZE]; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(Deflate64Stream)); -#endif } public override bool CanRead => _stream.CanRead; @@ -195,9 +192,6 @@ 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 92fda51c4..3d1aa2fd6 100644 --- a/src/SharpCompress/Compressors/Explode/ExplodeStream.cs +++ b/src/SharpCompress/Compressors/Explode/ExplodeStream.cs @@ -45,9 +45,6 @@ HeaderFlags generalPurposeBitFlag ) { inStream = inStr; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ExplodeStream)); -#endif this.compressedSize = (int)compressedSize; unCompressedSize = (long)uncompressedSize; this.generalPurposeBitFlag = generalPurposeBitFlag; @@ -69,9 +66,6 @@ HeaderFlags generalPurposeBitFlag protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(ExplodeStream)); -#endif base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/Filters/DeltaFilter.cs b/src/SharpCompress/Compressors/Filters/DeltaFilter.cs index a6954116e..5f7593ce9 100644 --- a/src/SharpCompress/Compressors/Filters/DeltaFilter.cs +++ b/src/SharpCompress/Compressors/Filters/DeltaFilter.cs @@ -1,36 +1,35 @@ using System.IO; -namespace SharpCompress.Compressors.Filters +namespace SharpCompress.Compressors.Filters; + +internal class DeltaFilter : Filter { - internal class DeltaFilter : Filter + private const int DISTANCE_MIN = 1; + private const int DISTANCE_MAX = 256; + private const int DISTANCE_MASK = DISTANCE_MAX - 1; + + private int _distance; + private byte[] _history; + private int _position; + + public DeltaFilter(bool isEncoder, Stream baseStream, byte[] info) + : base(isEncoder, baseStream, 1) { - private const int DISTANCE_MIN = 1; - private const int DISTANCE_MAX = 256; - private const int DISTANCE_MASK = DISTANCE_MAX - 1; + _distance = info[0]; + _history = new byte[DISTANCE_MAX]; + _position = 0; + } - private int _distance; - private byte[] _history; - private int _position; + protected override int Transform(byte[] buffer, int offset, int count) + { + var end = offset + count; - public DeltaFilter(bool isEncoder, Stream baseStream, byte[] info) - : base(isEncoder, baseStream, 1) + for (var i = offset; i < end; i++) { - _distance = info[0]; - _history = new byte[DISTANCE_MAX]; - _position = 0; + buffer[i] += _history[(_distance + _position--) & DISTANCE_MASK]; + _history[_position & DISTANCE_MASK] = buffer[i]; } - protected override int Transform(byte[] buffer, int offset, int count) - { - var end = offset + count; - - for (var i = offset; i < end; i++) - { - buffer[i] += _history[(_distance + _position--) & DISTANCE_MASK]; - _history[_position & DISTANCE_MASK] = buffer[i]; - } - - return count; - } + return count; } } diff --git a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs index bad6afbd1..48fd28b13 100644 --- a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs @@ -33,10 +33,6 @@ 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."); @@ -70,9 +66,6 @@ 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 caaf15e15..9b89993c3 100644 --- a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs @@ -111,10 +111,6 @@ public Bcj2DecoderStream(Stream[] streams, byte[] info, long limit) _mStatusDecoder[i] = new StatusDecoder(); } -#if DEBUG_STREAMS - this.DebugConstruct(typeof(Bcj2DecoderStream)); -#endif - _mIter = Run().GetEnumerator(); } @@ -125,9 +121,6 @@ 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 30be9b620..297447c20 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -50,7 +50,9 @@ public LZipStream(Stream stream, CompressionMode mode, bool leaveOpen = false) var dSize = 104 * 1024; WriteHeaderSize(stream); - _countingWritableSubStream = new CountingStream(new NonDisposingStream(stream)); + _countingWritableSubStream = new CountingStream( + SharpCompressStream.CreateNonDisposing(stream) + ); _stream = new Crc32Stream( LzmaStream.Create( new LzmaEncoderProperties(true, dSize), @@ -59,9 +61,6 @@ public LZipStream(Stream stream, CompressionMode mode, bool leaveOpen = false) _countingWritableSubStream ) ); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(LZipStream)); -#endif } } @@ -103,9 +102,6 @@ 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/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs index 123d8e036..f63143793 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -200,9 +200,6 @@ 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 e935fe6f0..523e3e1f3 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs @@ -15,9 +15,6 @@ internal partial class CrcBuilderStream : Stream public CrcBuilderStream(Stream target) { _mTarget = target; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(CrcBuilderStream)); -#endif _mCrc = Crc.INIT_CRC; } @@ -28,9 +25,6 @@ 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/LzwConstants.cs b/src/SharpCompress/Compressors/Lzw/LzwConstants.cs index 0325adbbd..d36210e8a 100644 --- a/src/SharpCompress/Compressors/Lzw/LzwConstants.cs +++ b/src/SharpCompress/Compressors/Lzw/LzwConstants.cs @@ -1,65 +1,64 @@ -namespace SharpCompress.Compressors.Lzw +namespace SharpCompress.Compressors.Lzw; + +/// +/// This class contains constants used for LZW +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1707:Identifiers should not contain underscores", + Justification = "kept for backwards compatibility" +)] +public sealed class LzwConstants { /// - /// This class contains constants used for LZW + /// Magic number found at start of LZW header: 0x1f 0x9d /// - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Naming", - "CA1707:Identifiers should not contain underscores", - Justification = "kept for backwards compatibility" - )] - public sealed class LzwConstants - { - /// - /// Magic number found at start of LZW header: 0x1f 0x9d - /// - public const int MAGIC = 0x1f9d; + public const int MAGIC = 0x1f9d; - /// - /// Maximum number of bits per code - /// - public const int MAX_BITS = 16; + /// + /// Maximum number of bits per code + /// + public const int MAX_BITS = 16; - /* 3rd header byte: - * bit 0..4 Number of compression bits - * bit 5 Extended header - * bit 6 Free - * bit 7 Block mode - */ + /* 3rd header byte: + * bit 0..4 Number of compression bits + * bit 5 Extended header + * bit 6 Free + * bit 7 Block mode + */ - /// - /// Mask for 'number of compression bits' - /// - public const int BIT_MASK = 0x1f; + /// + /// Mask for 'number of compression bits' + /// + public const int BIT_MASK = 0x1f; - /// - /// Indicates the presence of a fourth header byte - /// - public const int EXTENDED_MASK = 0x20; + /// + /// Indicates the presence of a fourth header byte + /// + public const int EXTENDED_MASK = 0x20; - //public const int FREE_MASK = 0x40; + //public const int FREE_MASK = 0x40; - /// - /// Reserved bits - /// - public const int RESERVED_MASK = 0x60; + /// + /// Reserved bits + /// + public const int RESERVED_MASK = 0x60; - /// - /// Block compression: if table is full and compression rate is dropping, - /// clear the dictionary. - /// - public const int BLOCK_MODE_MASK = 0x80; + /// + /// Block compression: if table is full and compression rate is dropping, + /// clear the dictionary. + /// + public const int BLOCK_MODE_MASK = 0x80; - /// - /// LZW file header size (in bytes) - /// - public const int HDR_SIZE = 3; + /// + /// LZW file header size (in bytes) + /// + public const int HDR_SIZE = 3; - /// - /// Initial number of bits per code - /// - public const int INIT_BITS = 9; + /// + /// Initial number of bits per code + /// + public const int INIT_BITS = 9; - private LzwConstants() { } - } + private LzwConstants() { } } diff --git a/src/SharpCompress/Compressors/Lzw/LzwStream.Async.cs b/src/SharpCompress/Compressors/Lzw/LzwStream.Async.cs index ae695080c..063b9df9d 100644 --- a/src/SharpCompress/Compressors/Lzw/LzwStream.Async.cs +++ b/src/SharpCompress/Compressors/Lzw/LzwStream.Async.cs @@ -5,369 +5,366 @@ using SharpCompress.Common; using SharpCompress.IO; -namespace SharpCompress.Compressors.Lzw +namespace SharpCompress.Compressors.Lzw; + +public partial class LzwStream { - public partial class LzwStream + /// + /// Asynchronously checks if the stream is an LZW stream + /// + /// The stream to read from + /// Cancellation token + /// True if the stream is an LZW stream, false otherwise + public static async ValueTask IsLzwStreamAsync( + Stream stream, + CancellationToken cancellationToken = default + ) { - /// - /// Asynchronously checks if the stream is an LZW stream - /// - /// The stream to read from - /// Cancellation token - /// True if the stream is an LZW stream, false otherwise - public static async ValueTask IsLzwStreamAsync( - Stream stream, - CancellationToken cancellationToken = default - ) + try { - try - { - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - int result = await stream.ReadAsync(hdr, 0, hdr.Length, cancellationToken); + int result = await stream.ReadAsync(hdr, 0, hdr.Length, cancellationToken); - // Check the magic marker - if (result < 0) - { - throw new IncompleteArchiveException("Failed to read LZW header"); - } - - if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) - { - throw new IncompleteArchiveException( - String.Format( - "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", - hdr[0], - hdr[1] - ) - ); - } + // Check the magic marker + if (result < 0) + { + throw new IncompleteArchiveException("Failed to read LZW header"); } - catch (Exception) + + if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) { - return false; + throw new IncompleteArchiveException( + String.Format( + "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", + hdr[0], + hdr[1] + ) + ); } - return true; } + catch (Exception) + { + return false; + } + return true; + } - /// - /// Reads decompressed data asynchronously into the provided buffer byte array - /// - /// The array to read and decompress data into - /// The offset indicating where the data should be placed - /// The number of bytes to decompress - /// Cancellation token - /// The number of bytes read. Zero signals the end of stream - public override async Task ReadAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken - ) + /// + /// Reads decompressed data asynchronously into the provided buffer byte array + /// + /// The array to read and decompress data into + /// The offset indicating where the data should be placed + /// The number of bytes to decompress + /// Cancellation token + /// The number of bytes read. Zero signals the end of stream + public override async Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + if (!headerParsed) { - if (!headerParsed) - { - await ParseHeaderAsync(cancellationToken).ConfigureAwait(false); - } + await ParseHeaderAsync(cancellationToken).ConfigureAwait(false); + } - if (eof) - { - return 0; - } + if (eof) + { + return 0; + } - int start = offset; - - int[] lTabPrefix = tabPrefix; - byte[] lTabSuffix = tabSuffix; - byte[] lStack = stack; - int lNBits = nBits; - int lMaxCode = maxCode; - int lMaxMaxCode = maxMaxCode; - int lBitMask = bitMask; - int lOldCode = oldCode; - byte lFinChar = finChar; - int lStackP = stackP; - int lFreeEnt = freeEnt; - byte[] lData = data; - int lBitPos = bitPos; - - int sSize = lStack.Length - lStackP; - if (sSize > 0) - { - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; - } + int start = offset; + + int[] lTabPrefix = tabPrefix; + byte[] lTabSuffix = tabSuffix; + byte[] lStack = stack; + int lNBits = nBits; + int lMaxCode = maxCode; + int lMaxMaxCode = maxMaxCode; + int lBitMask = bitMask; + int lOldCode = oldCode; + byte lFinChar = finChar; + int lStackP = stackP; + int lFreeEnt = freeEnt; + byte[] lData = data; + int lBitPos = bitPos; + + int sSize = lStack.Length - lStackP; + if (sSize > 0) + { + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + } + + if (count == 0) + { + stackP = lStackP; + return offset - start; + } - if (count == 0) + MainLoop: + do + { + if (end < EXTRA) { - stackP = lStackP; - return offset - start; + await FillAsync(cancellationToken).ConfigureAwait(false); } - MainLoop: - do + int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); + + while (lBitPos < bitIn) { - if (end < EXTRA) + if (count == 0) { - await FillAsync(cancellationToken).ConfigureAwait(false); + nBits = lNBits; + maxCode = lMaxCode; + maxMaxCode = lMaxMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; } - int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); - - while (lBitPos < bitIn) + if (lFreeEnt > lMaxCode) { - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - maxMaxCode = lMaxMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - if (lFreeEnt > lMaxCode) - { - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - - lNBits++; - lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : (1 << lNBits) - 1; + lNBits++; + lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : (1 << lNBits) - 1; - lBitMask = (1 << lNBits) - 1; - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } + lBitMask = (1 << lNBits) - 1; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } - int pos = lBitPos >> 3; - int code = + int pos = lBitPos >> 3; + int code = + ( ( - ( - (lData[pos] & 0xFF) - | ((lData[pos + 1] & 0xFF) << 8) - | ((lData[pos + 2] & 0xFF) << 16) - ) >> (lBitPos & 0x7) - ) & lBitMask; + (lData[pos] & 0xFF) + | ((lData[pos + 1] & 0xFF) << 8) + | ((lData[pos + 2] & 0xFF) << 16) + ) >> (lBitPos & 0x7) + ) & lBitMask; - lBitPos += lNBits; + lBitPos += lNBits; - if (lOldCode == -1) + if (lOldCode == -1) + { + if (code >= 256) { - if (code >= 256) - { - throw new IncompleteArchiveException( - "corrupt input: " + code + " > 255" - ); - } - - lFinChar = (byte)(lOldCode = code); - buffer[offset++] = lFinChar; - count--; - continue; + throw new IncompleteArchiveException("corrupt input: " + code + " > 255"); } - if (code == TBL_CLEAR && blockMode) - { - Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); - lFreeEnt = TBL_FIRST - 1; + lFinChar = (byte)(lOldCode = code); + buffer[offset++] = lFinChar; + count--; + continue; + } - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - lNBits = LzwConstants.INIT_BITS; - lMaxCode = (1 << lNBits) - 1; - lBitMask = lMaxCode; + if (code == TBL_CLEAR && blockMode) + { + Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); + lFreeEnt = TBL_FIRST - 1; - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; + lNBits = LzwConstants.INIT_BITS; + lMaxCode = (1 << lNBits) - 1; + lBitMask = lMaxCode; - int inCode = code; - lStackP = lStack.Length; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } - if (code >= lFreeEnt) - { - if (code > lFreeEnt) - { - throw new IncompleteArchiveException( - "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt - ); - } - - lStack[--lStackP] = lFinChar; - code = lOldCode; - } + int inCode = code; + lStackP = lStack.Length; - while (code >= 256) + if (code >= lFreeEnt) + { + if (code > lFreeEnt) { - lStack[--lStackP] = lTabSuffix[code]; - code = lTabPrefix[code]; + throw new IncompleteArchiveException( + "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt + ); } - lFinChar = lTabSuffix[code]; - buffer[offset++] = lFinChar; - count--; + lStack[--lStackP] = lFinChar; + code = lOldCode; + } - sSize = lStack.Length - lStackP; - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; + while (code >= 256) + { + lStack[--lStackP] = lTabSuffix[code]; + code = lTabPrefix[code]; + } - if (lFreeEnt < lMaxMaxCode) - { - lTabPrefix[lFreeEnt] = lOldCode; - lTabSuffix[lFreeEnt] = lFinChar; - lFreeEnt++; - } + lFinChar = lTabSuffix[code]; + buffer[offset++] = lFinChar; + count--; - lOldCode = inCode; + sSize = lStack.Length - lStackP; + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } + if (lFreeEnt < lMaxMaxCode) + { + lTabPrefix[lFreeEnt] = lOldCode; + lTabSuffix[lFreeEnt] = lFinChar; + lFreeEnt++; } - lBitPos = ResetBuf(lBitPos); - } while (got > 0); + lOldCode = inCode; - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } + } - eof = true; - return offset - start; - } + lBitPos = ResetBuf(lBitPos); + } while (got > 0); + + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + eof = true; + return offset - start; + } #if !LEGACY_DOTNET - /// - /// Reads decompressed data asynchronously into the provided buffer - /// - /// The memory to read and decompress data into - /// Cancellation token - /// The number of bytes read. Zero signals the end of stream - public override async ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default - ) + /// + /// Reads decompressed data asynchronously into the provided buffer + /// + /// The memory to read and decompress data into + /// Cancellation token + /// The number of bytes read. Zero signals the end of stream + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) + { + if (buffer.IsEmpty) { - if (buffer.IsEmpty) - { - return 0; - } - - byte[] array = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); - try - { - int read = await ReadAsync(array, 0, buffer.Length, cancellationToken) - .ConfigureAwait(false); - array.AsSpan(0, read).CopyTo(buffer.Span); - return read; - } - finally - { - System.Buffers.ArrayPool.Shared.Return(array); - } + return 0; } -#endif - private async ValueTask FillAsync(CancellationToken cancellationToken) + byte[] array = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); + try { - cancellationToken.ThrowIfCancellationRequested(); - got = await baseInputStream - .ReadAsync(data, end, data.Length - 1 - end, cancellationToken) + int read = await ReadAsync(array, 0, buffer.Length, cancellationToken) .ConfigureAwait(false); - if (got > 0) - { - end += got; - } + array.AsSpan(0, read).CopyTo(buffer.Span); + return read; + } + finally + { + System.Buffers.ArrayPool.Shared.Return(array); } + } +#endif - private async ValueTask ParseHeaderAsync(CancellationToken cancellationToken) + private async ValueTask FillAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + got = await baseInputStream + .ReadAsync(data, end, data.Length - 1 - end, cancellationToken) + .ConfigureAwait(false); + if (got > 0) { - headerParsed = true; + end += got; + } + } - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; + private async ValueTask ParseHeaderAsync(CancellationToken cancellationToken) + { + headerParsed = true; - int result = await baseInputStream - .ReadAsync(hdr, 0, hdr.Length, cancellationToken) - .ConfigureAwait(false); + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - if (result < 0) - { - throw new IncompleteArchiveException("Failed to read LZW header"); - } + int result = await baseInputStream + .ReadAsync(hdr, 0, hdr.Length, cancellationToken) + .ConfigureAwait(false); - if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) - { - throw new IncompleteArchiveException( - String.Format( - "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", - hdr[0], - hdr[1] - ) - ); - } + if (result < 0) + { + throw new IncompleteArchiveException("Failed to read LZW header"); + } - blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; - maxBits = hdr[2] & LzwConstants.BIT_MASK; + if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) + { + throw new IncompleteArchiveException( + String.Format( + "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", + hdr[0], + hdr[1] + ) + ); + } - if (maxBits > LzwConstants.MAX_BITS) - { - throw new ArchiveException( - "Stream compressed with " - + maxBits - + " bits, but decompression can only handle " - + LzwConstants.MAX_BITS - + " bits." - ); - } + blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; + maxBits = hdr[2] & LzwConstants.BIT_MASK; - if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) - { - throw new ArchiveException("Unsupported bits set in the header."); - } + if (maxBits > LzwConstants.MAX_BITS) + { + throw new ArchiveException( + "Stream compressed with " + + maxBits + + " bits, but decompression can only handle " + + LzwConstants.MAX_BITS + + " bits." + ); + } + + if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) + { + throw new ArchiveException("Unsupported bits set in the header."); + } - maxMaxCode = 1 << maxBits; - nBits = LzwConstants.INIT_BITS; - maxCode = (1 << nBits) - 1; - bitMask = maxCode; - oldCode = -1; - finChar = 0; - freeEnt = blockMode ? TBL_FIRST : 256; + maxMaxCode = 1 << maxBits; + nBits = LzwConstants.INIT_BITS; + maxCode = (1 << nBits) - 1; + bitMask = maxCode; + oldCode = -1; + finChar = 0; + freeEnt = blockMode ? TBL_FIRST : 256; - tabPrefix = new int[1 << maxBits]; - tabSuffix = new byte[1 << maxBits]; - stack = new byte[1 << maxBits]; - stackP = stack.Length; + tabPrefix = new int[1 << maxBits]; + tabSuffix = new byte[1 << maxBits]; + stack = new byte[1 << maxBits]; + stackP = stack.Length; - for (int idx = 255; idx >= 0; idx--) - { - tabSuffix[idx] = (byte)idx; - } + for (int idx = 255; idx >= 0; idx--) + { + tabSuffix[idx] = (byte)idx; } } } diff --git a/src/SharpCompress/Compressors/Lzw/LzwStream.cs b/src/SharpCompress/Compressors/Lzw/LzwStream.cs index 4a563c62d..274abca0a 100644 --- a/src/SharpCompress/Compressors/Lzw/LzwStream.cs +++ b/src/SharpCompress/Compressors/Lzw/LzwStream.cs @@ -4,617 +4,608 @@ using System.Threading.Tasks; using SharpCompress.Common; -namespace SharpCompress.Compressors.Lzw +namespace SharpCompress.Compressors.Lzw; + +/// +/// This filter stream is used to decompress a LZW format stream. +/// Specifically, a stream that uses the LZC compression method. +/// This file format is usually associated with the .Z file extension. +/// +/// See http://en.wikipedia.org/wiki/Compress +/// See http://wiki.wxwidgets.org/Development:_Z_File_Format +/// +/// The file header consists of 3 (or optionally 4) bytes. The first two bytes +/// contain the magic marker "0x1f 0x9d", followed by a byte of flags. +/// +/// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c +/// code in the gzip package. +/// +/// This sample shows how to unzip a compressed file +/// +/// using System; +/// using System.IO; +/// +/// using ICSharpCode.SharpZipLib.Core; +/// using ICSharpCode.SharpZipLib.LZW; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0]))) +/// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { +/// byte[] buffer = new byte[4096]; +/// StreamUtils.Copy(inStream, outStream, buffer); +/// // OR +/// inStream.Read(buffer, 0, buffer.Length); +/// // now do something with the buffer +/// } +/// } +/// } +/// +/// +public partial class LzwStream : Stream { - /// - /// This filter stream is used to decompress a LZW format stream. - /// Specifically, a stream that uses the LZC compression method. - /// This file format is usually associated with the .Z file extension. - /// - /// See http://en.wikipedia.org/wiki/Compress - /// See http://wiki.wxwidgets.org/Development:_Z_File_Format - /// - /// The file header consists of 3 (or optionally 4) bytes. The first two bytes - /// contain the magic marker "0x1f 0x9d", followed by a byte of flags. - /// - /// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c - /// code in the gzip package. - /// - /// This sample shows how to unzip a compressed file - /// - /// using System; - /// using System.IO; - /// - /// using ICSharpCode.SharpZipLib.Core; - /// using ICSharpCode.SharpZipLib.LZW; - /// - /// class MainClass - /// { - /// public static void Main(string[] args) - /// { - /// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0]))) - /// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { - /// byte[] buffer = new byte[4096]; - /// StreamUtils.Copy(inStream, outStream, buffer); - /// // OR - /// inStream.Read(buffer, 0, buffer.Length); - /// // now do something with the buffer - /// } - /// } - /// } - /// - /// - public partial class LzwStream : Stream + public static bool IsLzwStream(Stream stream) { - public static bool IsLzwStream(Stream stream) + try { - try - { - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - - int result = stream.Read(hdr, 0, hdr.Length); + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - // Check the magic marker - if (result < 0) - { - throw new IncompleteArchiveException("Failed to read LZW header"); - } + int result = stream.Read(hdr, 0, hdr.Length); - if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) - { - throw new IncompleteArchiveException( - String.Format( - "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", - hdr[0], - hdr[1] - ) - ); - } + // Check the magic marker + if (result < 0) + { + throw new IncompleteArchiveException("Failed to read LZW header"); } - catch (Exception) + + if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) { - return false; + throw new IncompleteArchiveException( + String.Format( + "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", + hdr[0], + hdr[1] + ) + ); } - return true; } + catch (Exception) + { + return false; + } + return true; + } + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = false; - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = false; - - /// - /// Creates a LzwInputStream - /// - /// - /// The stream to read compressed data from (baseInputStream LZW format) - /// - public LzwStream(Stream baseInputStream) + /// + /// Creates a LzwInputStream + /// + /// + /// The stream to read compressed data from (baseInputStream LZW format) + /// + public LzwStream(Stream baseInputStream) + { + this.baseInputStream = baseInputStream; + } + + /// + /// See + /// + /// + public override int ReadByte() + { + int b = Read(one, 0, 1); + if (b == 1) { - this.baseInputStream = baseInputStream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(LzwStream)); -#endif + return (one[0] & 0xff); } - /// - /// See - /// - /// - public override int ReadByte() + return -1; + } + + /// + /// Reads decompressed data into the provided buffer byte array + /// + /// + /// The array to read and decompress data into + /// + /// + /// The offset indicating where the data should be placed + /// + /// + /// The number of bytes to decompress + /// + /// The number of bytes read. Zero signals the end of stream + public override int Read(byte[] buffer, int offset, int count) + { + if (!headerParsed) { - int b = Read(one, 0, 1); - if (b == 1) - { - return (one[0] & 0xff); - } + ParseHeader(); + } - return -1; + if (eof) + { + return 0; } - /// - /// Reads decompressed data into the provided buffer byte array - /// - /// - /// The array to read and decompress data into - /// - /// - /// The offset indicating where the data should be placed - /// - /// - /// The number of bytes to decompress - /// - /// The number of bytes read. Zero signals the end of stream - public override int Read(byte[] buffer, int offset, int count) + int start = offset; + + /* Using local copies of various variables speeds things up by as + * much as 30% in Java! Performance not tested in C#. + */ + int[] lTabPrefix = tabPrefix; + byte[] lTabSuffix = tabSuffix; + byte[] lStack = stack; + int lNBits = nBits; + int lMaxCode = maxCode; + int lMaxMaxCode = maxMaxCode; + int lBitMask = bitMask; + int lOldCode = oldCode; + byte lFinChar = finChar; + int lStackP = stackP; + int lFreeEnt = freeEnt; + byte[] lData = data; + int lBitPos = bitPos; + + // empty stack if stuff still left + int sSize = lStack.Length - lStackP; + if (sSize > 0) { - if (!headerParsed) - { - ParseHeader(); - } + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + } - if (eof) - { - return 0; - } + if (count == 0) + { + stackP = lStackP; + return offset - start; + } - int start = offset; - - /* Using local copies of various variables speeds things up by as - * much as 30% in Java! Performance not tested in C#. - */ - int[] lTabPrefix = tabPrefix; - byte[] lTabSuffix = tabSuffix; - byte[] lStack = stack; - int lNBits = nBits; - int lMaxCode = maxCode; - int lMaxMaxCode = maxMaxCode; - int lBitMask = bitMask; - int lOldCode = oldCode; - byte lFinChar = finChar; - int lStackP = stackP; - int lFreeEnt = freeEnt; - byte[] lData = data; - int lBitPos = bitPos; - - // empty stack if stuff still left - int sSize = lStack.Length - lStackP; - if (sSize > 0) + // loop, filling local buffer until enough data has been decompressed + MainLoop: + do + { + if (end < EXTRA) { - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; + Fill(); } - if (count == 0) - { - stackP = lStackP; - return offset - start; - } + int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); - // loop, filling local buffer until enough data has been decompressed - MainLoop: - do + while (lBitPos < bitIn) { - if (end < EXTRA) + #region A + + // handle 1-byte reads correctly + if (count == 0) { - Fill(); + nBits = lNBits; + maxCode = lMaxCode; + maxMaxCode = lMaxMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; } - int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); - - while (lBitPos < bitIn) + // check for code-width expansion + if (lFreeEnt > lMaxCode) { - #region A + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - // handle 1-byte reads correctly - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - maxMaxCode = lMaxMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } - - // check for code-width expansion - if (lFreeEnt > lMaxCode) - { - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; + lNBits++; + lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : (1 << lNBits) - 1; - lNBits++; - lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : (1 << lNBits) - 1; - - lBitMask = (1 << lNBits) - 1; - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } + lBitMask = (1 << lNBits) - 1; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } - #endregion A + #endregion A - #region B + #region B - // read next code - int pos = lBitPos >> 3; - int code = + // read next code + int pos = lBitPos >> 3; + int code = + ( ( - ( - (lData[pos] & 0xFF) - | ((lData[pos + 1] & 0xFF) << 8) - | ((lData[pos + 2] & 0xFF) << 16) - ) >> (lBitPos & 0x7) - ) & lBitMask; + (lData[pos] & 0xFF) + | ((lData[pos + 1] & 0xFF) << 8) + | ((lData[pos + 2] & 0xFF) << 16) + ) >> (lBitPos & 0x7) + ) & lBitMask; - lBitPos += lNBits; + lBitPos += lNBits; - // handle first iteration - if (lOldCode == -1) + // handle first iteration + if (lOldCode == -1) + { + if (code >= 256) { - if (code >= 256) - { - throw new IncompleteArchiveException( - "corrupt input: " + code + " > 255" - ); - } - - lFinChar = (byte)(lOldCode = code); - buffer[offset++] = lFinChar; - count--; - continue; + throw new IncompleteArchiveException("corrupt input: " + code + " > 255"); } - // handle CLEAR code - if (code == TBL_CLEAR && blockMode) - { - Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); - lFreeEnt = TBL_FIRST - 1; - - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - lNBits = LzwConstants.INIT_BITS; - lMaxCode = (1 << lNBits) - 1; - lBitMask = lMaxCode; - - // Code tables reset - - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } - - #endregion B - - #region C + lFinChar = (byte)(lOldCode = code); + buffer[offset++] = lFinChar; + count--; + continue; + } - // setup - int inCode = code; - lStackP = lStack.Length; + // handle CLEAR code + if (code == TBL_CLEAR && blockMode) + { + Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); + lFreeEnt = TBL_FIRST - 1; - // Handle KwK case - if (code >= lFreeEnt) - { - if (code > lFreeEnt) - { - throw new IncompleteArchiveException( - "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt - ); - } - - lStack[--lStackP] = lFinChar; - code = lOldCode; - } + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; + lNBits = LzwConstants.INIT_BITS; + lMaxCode = (1 << lNBits) - 1; + lBitMask = lMaxCode; - // Generate output characters in reverse order - while (code >= 256) - { - lStack[--lStackP] = lTabSuffix[code]; - code = lTabPrefix[code]; - } + // Code tables reset - lFinChar = lTabSuffix[code]; - buffer[offset++] = lFinChar; - count--; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } - // And put them out in forward order - sSize = lStack.Length - lStackP; - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; + #endregion B - #endregion C + #region C - #region D + // setup + int inCode = code; + lStackP = lStack.Length; - // generate new entry in table - if (lFreeEnt < lMaxMaxCode) + // Handle KwK case + if (code >= lFreeEnt) + { + if (code > lFreeEnt) { - lTabPrefix[lFreeEnt] = lOldCode; - lTabSuffix[lFreeEnt] = lFinChar; - lFreeEnt++; + throw new IncompleteArchiveException( + "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt + ); } - // Remember previous code - lOldCode = inCode; + lStack[--lStackP] = lFinChar; + code = lOldCode; + } - // if output buffer full, then return - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } + // Generate output characters in reverse order + while (code >= 256) + { + lStack[--lStackP] = lTabSuffix[code]; + code = lTabPrefix[code]; + } - #endregion D - } // while + lFinChar = lTabSuffix[code]; + buffer[offset++] = lFinChar; + count--; - lBitPos = ResetBuf(lBitPos); - } while (got > 0); // do..while + // And put them out in forward order + sSize = lStack.Length - lStackP; + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; + #endregion C - eof = true; - return offset - start; - } + #region D - /// - /// Moves the unread data in the buffer to the beginning and resets - /// the pointers. - /// - /// - /// - private int ResetBuf(int bitPosition) - { - int pos = bitPosition >> 3; - Array.Copy(data, pos, data, 0, end - pos); - end -= pos; - return 0; - } + // generate new entry in table + if (lFreeEnt < lMaxMaxCode) + { + lTabPrefix[lFreeEnt] = lOldCode; + lTabSuffix[lFreeEnt] = lFinChar; + lFreeEnt++; + } - private void Fill() - { - got = baseInputStream.Read(data, end, data.Length - 1 - end); - if (got > 0) - { - end += got; - } - } + // Remember previous code + lOldCode = inCode; - private void ParseHeader() - { - headerParsed = true; + // if output buffer full, then return + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; + #endregion D + } // while - int result = baseInputStream.Read(hdr, 0, hdr.Length); + lBitPos = ResetBuf(lBitPos); + } while (got > 0); // do..while - // Check the magic marker - if (result < 0) - { - throw new IncompleteArchiveException("Failed to read LZW header"); - } + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; - if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) - { - throw new IncompleteArchiveException( - String.Format( - "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", - hdr[0], - hdr[1] - ) - ); - } + eof = true; + return offset - start; + } - // Check the 3rd header byte - blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; - maxBits = hdr[2] & LzwConstants.BIT_MASK; + /// + /// Moves the unread data in the buffer to the beginning and resets + /// the pointers. + /// + /// + /// + private int ResetBuf(int bitPosition) + { + int pos = bitPosition >> 3; + Array.Copy(data, pos, data, 0, end - pos); + end -= pos; + return 0; + } - if (maxBits > LzwConstants.MAX_BITS) - { - throw new ArchiveException( - "Stream compressed with " - + maxBits - + " bits, but decompression can only handle " - + LzwConstants.MAX_BITS - + " bits." - ); - } + private void Fill() + { + got = baseInputStream.Read(data, end, data.Length - 1 - end); + if (got > 0) + { + end += got; + } + } - if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) - { - throw new ArchiveException("Unsupported bits set in the header."); - } + private void ParseHeader() + { + headerParsed = true; - // Initialize variables - maxMaxCode = 1 << maxBits; - nBits = LzwConstants.INIT_BITS; - maxCode = (1 << nBits) - 1; - bitMask = maxCode; - oldCode = -1; - finChar = 0; - freeEnt = blockMode ? TBL_FIRST : 256; - - tabPrefix = new int[1 << maxBits]; - tabSuffix = new byte[1 << maxBits]; - stack = new byte[1 << maxBits]; - stackP = stack.Length; - - for (int idx = 255; idx >= 0; idx--) - { - tabSuffix[idx] = (byte)idx; - } - } + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - #region Stream Overrides + int result = baseInputStream.Read(hdr, 0, hdr.Length); - /// - /// Gets a value indicating whether the current stream supports reading - /// - public override bool CanRead + // Check the magic marker + if (result < 0) { - get { return baseInputStream.CanRead; } + throw new IncompleteArchiveException("Failed to read LZW header"); } - /// - /// Gets a value of false indicating seeking is not supported for this stream. - /// - public override bool CanSeek + if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) { - get { return false; } + throw new IncompleteArchiveException( + String.Format( + "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", + hdr[0], + hdr[1] + ) + ); } - /// - /// Gets a value of false indicating that this stream is not writeable. - /// - public override bool CanWrite - { - get { return false; } - } + // Check the 3rd header byte + blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; + maxBits = hdr[2] & LzwConstants.BIT_MASK; - /// - /// A value representing the length of the stream in bytes. - /// - public override long Length + if (maxBits > LzwConstants.MAX_BITS) { - get { return got; } + throw new ArchiveException( + "Stream compressed with " + + maxBits + + " bits, but decompression can only handle " + + LzwConstants.MAX_BITS + + " bits." + ); } - /// - /// The current position within the stream. - /// Throws a NotSupportedException when attempting to set the position - /// - /// Attempting to set the position - public override long Position + if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) { - get { return baseInputStream.Position; } - set { throw new NotSupportedException("InflaterInputStream Position not supported"); } + throw new ArchiveException("Unsupported bits set in the header."); } - /// - /// Flushes the baseInputStream - /// - public override void Flush() + // Initialize variables + maxMaxCode = 1 << maxBits; + nBits = LzwConstants.INIT_BITS; + maxCode = (1 << nBits) - 1; + bitMask = maxCode; + oldCode = -1; + finChar = 0; + freeEnt = blockMode ? TBL_FIRST : 256; + + tabPrefix = new int[1 << maxBits]; + tabSuffix = new byte[1 << maxBits]; + stack = new byte[1 << maxBits]; + stackP = stack.Length; + + for (int idx = 255; idx >= 0; idx--) { - baseInputStream.Flush(); + tabSuffix[idx] = (byte)idx; } + } - /// - /// Sets the position within the current stream - /// Always throws a NotSupportedException - /// - /// The relative offset to seek to. - /// The defining where to seek from. - /// The new position in the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("Seek not supported"); - } + #region Stream Overrides - /// - /// Set the length of the current stream - /// Always throws a NotSupportedException - /// - /// The new length value for the stream. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("InflaterInputStream SetLength not supported"); - } + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get { return baseInputStream.CanRead; } + } - /// - /// Writes a sequence of bytes to stream and advances the current position - /// This method always throws a NotSupportedException - /// - /// The buffer containing data to write. - /// The offset of the first byte to write. - /// The number of bytes to write. - /// Any access - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("InflaterInputStream Write not supported"); - } + /// + /// Gets a value of false indicating seeking is not supported for this stream. + /// + public override bool CanSeek + { + get { return false; } + } - /// - /// Writes one byte to the current stream and advances the current position - /// Always throws a NotSupportedException - /// - /// The byte to write. - /// Any access - public override void WriteByte(byte value) - { - throw new NotSupportedException("InflaterInputStream WriteByte not supported"); - } + /// + /// Gets a value of false indicating that this stream is not writeable. + /// + public override bool CanWrite + { + get { return false; } + } + + /// + /// A value representing the length of the stream in bytes. + /// + public override long Length + { + get { return got; } + } + + /// + /// The current position within the stream. + /// Throws a NotSupportedException when attempting to set the position + /// + /// Attempting to set the position + public override long Position + { + get { return baseInputStream.Position; } + set { throw new NotSupportedException("InflaterInputStream Position not supported"); } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// Sets the position within the current stream + /// Always throws a NotSupportedException + /// + /// The relative offset to seek to. + /// The defining where to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); + } + + /// + /// Set the length of the current stream + /// Always throws a NotSupportedException + /// + /// The new length value for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("InflaterInputStream SetLength not supported"); + } - /// - /// Closes the input stream. When - /// is true the underlying stream is also closed. - /// - protected override void Dispose(bool disposing) + /// + /// Writes a sequence of bytes to stream and advances the current position + /// This method always throws a NotSupportedException + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("InflaterInputStream Write not supported"); + } + + /// + /// Writes one byte to the current stream and advances the current position + /// Always throws a NotSupportedException + /// + /// The byte to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("InflaterInputStream WriteByte not supported"); + } + + /// + /// Closes the input stream. When + /// is true the underlying stream is also closed. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed) { - if (!isClosed) + isClosed = true; + if (IsStreamOwner) { - isClosed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(LzwStream)); -#endif - if (IsStreamOwner) - { - baseInputStream.Dispose(); - } + baseInputStream.Dispose(); } } + } - #endregion Stream Overrides + #endregion Stream Overrides - #region Instance Fields + #region Instance Fields - private Stream baseInputStream; + private Stream baseInputStream; - /// - /// Flag indicating wether this instance has been closed or not. - /// - private bool isClosed; + /// + /// Flag indicating wether this instance has been closed or not. + /// + private bool isClosed; - private readonly byte[] one = new byte[1]; - private bool headerParsed; + private readonly byte[] one = new byte[1]; + private bool headerParsed; - // string table stuff - private const int TBL_CLEAR = 0x100; + // string table stuff + private const int TBL_CLEAR = 0x100; - private const int TBL_FIRST = TBL_CLEAR + 1; + private const int TBL_FIRST = TBL_CLEAR + 1; - private int[] tabPrefix = new int[0]; // - private byte[] tabSuffix = new byte[0]; // - private readonly int[] zeros = new int[256]; - private byte[] stack = new byte[0]; // + private int[] tabPrefix = new int[0]; // + private byte[] tabSuffix = new byte[0]; // + private readonly int[] zeros = new int[256]; + private byte[] stack = new byte[0]; // - // various state - private bool blockMode; + // various state + private bool blockMode; - private int nBits; - private int maxBits; - private int maxMaxCode; - private int maxCode; - private int bitMask; - private int oldCode; - private byte finChar; - private int stackP; - private int freeEnt; + private int nBits; + private int maxBits; + private int maxMaxCode; + private int maxCode; + private int bitMask; + private int oldCode; + private byte finChar; + private int stackP; + private int freeEnt; - // input buffer - private readonly byte[] data = new byte[1024 * 8]; + // input buffer + private readonly byte[] data = new byte[1024 * 8]; - private int bitPos; - private int end; - private int got; - private bool eof; - private const int EXTRA = 64; + private int bitPos; + private int end; + private int got; + private bool eof; + private const int EXTRA = 64; - #endregion Instance Fields - } + #endregion Instance Fields } diff --git a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs index e1f03de75..022f7df0a 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs @@ -27,10 +27,6 @@ private PpmdStream(PpmdProperties properties, Stream stream, bool compress) _stream = stream; _compress = compress; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(PpmdStream)); -#endif - InitializeSync(stream, compress); } @@ -45,10 +41,6 @@ bool skipInitialization _stream = stream; _compress = compress; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(PpmdStream)); -#endif - // Skip initialization - used by CreateAsync } @@ -182,9 +174,6 @@ 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/RLE.cs b/src/SharpCompress/Compressors/RLE90/RLE.cs index 8bc8ee1ba..68b52ea40 100644 --- a/src/SharpCompress/Compressors/RLE90/RLE.cs +++ b/src/SharpCompress/Compressors/RLE90/RLE.cs @@ -1,52 +1,51 @@ using System.Collections.Generic; using System.Linq; -namespace SharpCompress.Compressors.RLE90 +namespace SharpCompress.Compressors.RLE90; + +public static class RLE { - public static class RLE + private const byte DLE = 0x90; + + /// + /// Unpacks an RLE compressed buffer. + /// Format: DLE , where count == 0 -> DLE + /// + /// The compressed buffer to unpack. + /// A list of unpacked bytes. + public static List UnpackRLE(byte[] compressedBuffer) { - private const byte DLE = 0x90; + var result = new List(compressedBuffer.Length * 2); // Optimized initial capacity + var countMode = false; + byte last = 0; - /// - /// Unpacks an RLE compressed buffer. - /// Format: DLE , where count == 0 -> DLE - /// - /// The compressed buffer to unpack. - /// A list of unpacked bytes. - public static List UnpackRLE(byte[] compressedBuffer) + foreach (var c in compressedBuffer) { - var result = new List(compressedBuffer.Length * 2); // Optimized initial capacity - var countMode = false; - byte last = 0; - - foreach (var c in compressedBuffer) + if (!countMode) + { + if (c == DLE) + { + countMode = true; + } + else + { + result.Add(c); + last = c; + } + } + else { - if (!countMode) + countMode = false; + if (c == 0) { - if (c == DLE) - { - countMode = true; - } - else - { - result.Add(c); - last = c; - } + result.Add(DLE); } else { - countMode = false; - if (c == 0) - { - result.Add(DLE); - } - else - { - result.AddRange(Enumerable.Repeat(last, c - 1)); - } + result.AddRange(Enumerable.Repeat(last, c - 1)); } } - return result; } + return result; } } diff --git a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.Async.cs b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.Async.cs index 84b4a5545..e47ac2e1d 100644 --- a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.Async.cs +++ b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.Async.cs @@ -3,116 +3,115 @@ using System.Threading; using System.Threading.Tasks; -namespace SharpCompress.Compressors.RLE90 +namespace SharpCompress.Compressors.RLE90; + +public partial class RunLength90Stream { - public partial class RunLength90Stream + public override async Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) { - public override async Task ReadAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken - ) + if (buffer == null) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + throw new ArgumentNullException(nameof(buffer)); + } - if (offset < 0 || count < 0 || offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException(); - } + if (offset < 0 || count < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } - int bytesWritten = 0; + int bytesWritten = 0; - while (bytesWritten < count && !_endOfCompressedData) + while (bytesWritten < count && !_endOfCompressedData) + { + // Handle pending repeat bytes first + if (_repeatCount > 0) { - // Handle pending repeat bytes first - if (_repeatCount > 0) + int toWrite = Math.Min(_repeatCount, count - bytesWritten); + for (int i = 0; i < toWrite; i++) { - int toWrite = Math.Min(_repeatCount, count - bytesWritten); - for (int i = 0; i < toWrite; i++) - { - buffer[offset + bytesWritten++] = _lastByte; - } - _repeatCount -= toWrite; - continue; + buffer[offset + bytesWritten++] = _lastByte; } + _repeatCount -= toWrite; + continue; + } - // Try to read the next byte from compressed data - if (_bytesReadFromSource >= _compressedSize) - { - _endOfCompressedData = true; - break; - } + // Try to read the next byte from compressed data + if (_bytesReadFromSource >= _compressedSize) + { + _endOfCompressedData = true; + break; + } - byte[] singleByte = new byte[1]; - int bytesRead = await _stream - .ReadAsync(singleByte, 0, 1, cancellationToken) - .ConfigureAwait(false); - if (bytesRead == 0) - { - _endOfCompressedData = true; - break; - } + byte[] singleByte = new byte[1]; + int bytesRead = await _stream + .ReadAsync(singleByte, 0, 1, cancellationToken) + .ConfigureAwait(false); + if (bytesRead == 0) + { + _endOfCompressedData = true; + break; + } - _bytesReadFromSource++; - byte c = singleByte[0]; + _bytesReadFromSource++; + byte c = singleByte[0]; - if (_inDleMode) - { - _inDleMode = false; + if (_inDleMode) + { + _inDleMode = false; - if (c == 0) - { - buffer[offset + bytesWritten++] = DLE; - _lastByte = DLE; - } - else - { - _repeatCount = c - 1; - // We'll handle these repeats in next loop iteration. - } - } - else if (c == DLE) + if (c == 0) { - _inDleMode = true; + buffer[offset + bytesWritten++] = DLE; + _lastByte = DLE; } else { - buffer[offset + bytesWritten++] = c; - _lastByte = c; + _repeatCount = c - 1; + // We'll handle these repeats in next loop iteration. } } - - return bytesWritten; + else if (c == DLE) + { + _inDleMode = true; + } + else + { + buffer[offset + bytesWritten++] = c; + _lastByte = c; + } } + return bytesWritten; + } + #if !LEGACY_DOTNET - public override async ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default - ) + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) + { + if (buffer.IsEmpty) { - if (buffer.IsEmpty) - { - return 0; - } + return 0; + } - byte[] array = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); - try - { - int read = await ReadAsync(array, 0, buffer.Length, cancellationToken) - .ConfigureAwait(false); - array.AsSpan(0, read).CopyTo(buffer.Span); - return read; - } - finally - { - System.Buffers.ArrayPool.Shared.Return(array); - } + byte[] array = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); + try + { + int read = await ReadAsync(array, 0, buffer.Length, cancellationToken) + .ConfigureAwait(false); + array.AsSpan(0, read).CopyTo(buffer.Span); + return read; + } + finally + { + System.Buffers.ArrayPool.Shared.Return(array); } -#endif } +#endif } diff --git a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs index d1470944d..178a22205 100644 --- a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs +++ b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs @@ -1,135 +1,127 @@ using System; using System.IO; -namespace SharpCompress.Compressors.RLE90 +namespace SharpCompress.Compressors.RLE90; + +/// +/// Real-time streaming RLE90 decompression stream. +/// Decompresses bytes on demand without buffering the entire file in memory. +/// +public partial class RunLength90Stream : Stream { - /// - /// Real-time streaming RLE90 decompression stream. - /// Decompresses bytes on demand without buffering the entire file in memory. - /// - public partial class RunLength90Stream : Stream + private readonly Stream _stream; + private readonly int _compressedSize; + private int _bytesReadFromSource; + + private const byte DLE = 0x90; + private bool _inDleMode; + private byte _lastByte; + private int _repeatCount; + + private bool _endOfCompressedData; + + public RunLength90Stream(Stream stream, int compressedSize) { - private readonly Stream _stream; - private readonly int _compressedSize; - private int _bytesReadFromSource; + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + _compressedSize = compressedSize; + } - private const byte DLE = 0x90; - private bool _inDleMode; - private byte _lastByte; - private int _repeatCount; + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + } - private bool _endOfCompressedData; + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; - public RunLength90Stream(Stream stream, int compressedSize) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _compressedSize = compressedSize; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(RunLength90Stream)); -#endif - } + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } - protected override void Dispose(bool disposing) - { -#if DEBUG_STREAMS - this.DebugDispose(typeof(RunLength90Stream)); -#endif - base.Dispose(disposing); - } + public override void Flush() => throw new NotSupportedException(); - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override long Length => throw new NotSupportedException(); - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } + public override void SetLength(long value) => throw new NotSupportedException(); - public override void Flush() => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); - public override long Seek(long offset, SeekOrigin origin) => - throw new NotSupportedException(); + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - public override void SetLength(long value) => throw new NotSupportedException(); + if (offset < 0 || count < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); + int bytesWritten = 0; - public override int Read(byte[] buffer, int offset, int count) + while (bytesWritten < count && !_endOfCompressedData) { - if (buffer == null) + // Handle pending repeat bytes first + if (_repeatCount > 0) { - throw new ArgumentNullException(nameof(buffer)); + int toWrite = Math.Min(_repeatCount, count - bytesWritten); + for (int i = 0; i < toWrite; i++) + { + buffer[offset + bytesWritten++] = _lastByte; + } + _repeatCount -= toWrite; + continue; } - if (offset < 0 || count < 0 || offset + count > buffer.Length) + // Try to read the next byte from compressed data + if (_bytesReadFromSource >= _compressedSize) { - throw new ArgumentOutOfRangeException(); + _endOfCompressedData = true; + break; } - int bytesWritten = 0; - - while (bytesWritten < count && !_endOfCompressedData) + int next = _stream.ReadByte(); + if (next == -1) { - // Handle pending repeat bytes first - if (_repeatCount > 0) - { - int toWrite = Math.Min(_repeatCount, count - bytesWritten); - for (int i = 0; i < toWrite; i++) - { - buffer[offset + bytesWritten++] = _lastByte; - } - _repeatCount -= toWrite; - continue; - } - - // Try to read the next byte from compressed data - if (_bytesReadFromSource >= _compressedSize) - { - _endOfCompressedData = true; - break; - } + _endOfCompressedData = true; + break; + } - int next = _stream.ReadByte(); - if (next == -1) - { - _endOfCompressedData = true; - break; - } + _bytesReadFromSource++; + byte c = (byte)next; - _bytesReadFromSource++; - byte c = (byte)next; + if (_inDleMode) + { + _inDleMode = false; - if (_inDleMode) + if (c == 0) { - _inDleMode = false; - - if (c == 0) - { - buffer[offset + bytesWritten++] = DLE; - _lastByte = DLE; - } - else - { - _repeatCount = c - 1; - // We’ll handle these repeats in next loop iteration. - } - } - else if (c == DLE) - { - _inDleMode = true; + buffer[offset + bytesWritten++] = DLE; + _lastByte = DLE; } else { - buffer[offset + bytesWritten++] = c; - _lastByte = c; + _repeatCount = c - 1; + // We’ll handle these repeats in next loop iteration. } } - - return bytesWritten; + else if (c == DLE) + { + _inDleMode = true; + } + else + { + buffer[offset + bytesWritten++] = c; + _lastByte = c; + } } + + return bytesWritten; } } diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index 161922be3..c8235e3f7 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -19,9 +19,6 @@ internal MultiVolumeReadOnlyStream(IEnumerable parts) filePartEnumerator = parts.GetEnumerator(); filePartEnumerator.MoveNext(); InitializeNextFilePart(); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(MultiVolumeReadOnlyStream)); -#endif } protected override void Dispose(bool disposing) @@ -29,10 +26,6 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); if (disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(MultiVolumeReadOnlyStream)); -#endif - filePartEnumerator.Dispose(); currentStream = null; diff --git a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs index 42dd25c94..fa47181ad 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs @@ -94,9 +94,6 @@ MultiVolumeReadOnlyStreamBase readStream { this.readStream = readStream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(RarBLAKE2spStream)); -#endif disableCRCCheck = fileHeader.IsEncrypted; _hash = fileHeader.FileCrc.NotNull(); _blake2sp = new BLAKE2SP(); @@ -118,9 +115,6 @@ MultiVolumeReadOnlyStream readStream protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(RarBLAKE2spStream)); -#endif base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 8adf324ae..98fbb3b2d 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -21,9 +21,6 @@ MultiVolumeReadOnlyStreamBase readStream : base(unpack, fileHeader, readStream) { this.readStream = readStream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(RarCrcStream)); -#endif disableCRC = fileHeader.IsEncrypted; ResetCrc(); } @@ -43,9 +40,6 @@ MultiVolumeReadOnlyStream readStream protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(RarCrcStream)); -#endif base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index 31e7b11c2..d4d9b8e24 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -31,10 +31,6 @@ 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 } public void Initialize() @@ -51,9 +47,6 @@ protected override void Dispose(bool disposing) { if (disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(RarStream)); -#endif ArrayPool.Shared.Return(this.tmpBuffer); this.tmpBuffer = null; readStream.Dispose(); diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs index 17368531b..8fed421c3 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs @@ -1,10 +1,4 @@ -#if !Rar2017_64bit -using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif +using size_t = System.UInt32; #nullable disable diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index a9a6f280f..97d513c3b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -1,13 +1,7 @@ #nullable disable using System; -#if !Rar2017_64bit using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif namespace SharpCompress.Compressors.Rar.UnpackV2017; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index ea20a0fa1..e8b29a406 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -1,13 +1,7 @@ using System; using System.IO; using SharpCompress.Common.Rar.Headers; -#if !Rar2017_64bit using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif namespace SharpCompress.Compressors.Rar.UnpackV2017; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs index f99ae0e39..a650f21c2 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs @@ -1,12 +1,5 @@ using uint32 = System.UInt32; -/*#if !Rar2017_64bit -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif*/ - namespace SharpCompress.Compressors.Rar.UnpackV2017; internal partial class Unpack diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index b95d6df05..725d70ceb 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -2,13 +2,6 @@ using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; -/*#if !Rar2017_64bit -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif*/ - namespace SharpCompress.Compressors.Rar.UnpackV2017; internal partial class Unpack diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs index 9631f1e1d..af389904a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs @@ -3,13 +3,7 @@ using System; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; -#if !Rar2017_64bit using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif namespace SharpCompress.Compressors.Rar.UnpackV2017; diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs index cd8440044..1b4e49e2b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs @@ -1,12 +1,5 @@ using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; -/*#if !Rar2017_64bit -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif*/ - namespace SharpCompress.Compressors.Rar.UnpackV2017; internal partial class Unpack diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index 9f57af38f..cdd584a0c 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -3,13 +3,7 @@ using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; using int64 = System.Int64; -#if !Rar2017_64bit using size_t = System.UInt32; -#else -using nint = System.Int64; -using nuint = System.UInt64; -using size_t = System.UInt64; -#endif // TODO: REMOVE THIS... WIP #pragma warning disable 169 @@ -257,7 +251,7 @@ internal partial class Unpack void InitMT(); bool UnpackLargeBlock(UnpackThreadData &D); bool ProcessDecoded(UnpackThreadData &D); - + ThreadPool *UnpThreadPool; UnpackThreadData *UnpThreadData; uint MaxUserThreads; @@ -409,7 +403,7 @@ internal partial class Unpack // More than 8 threads are unlikely to provide a noticeable gain // for unpacking, but would use the additional memory. void SetThreads(uint Threads) {MaxUserThreads=Min(Threads,8);} - + void UnpackDecode(UnpackThreadData &D); #endif*/ diff --git a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs index ad95d2078..2f976d0ab 100644 --- a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs +++ b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs @@ -31,10 +31,6 @@ private 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; @@ -59,9 +55,6 @@ public static ReduceStream Create(Stream inStr, long compsize, long unCompSize, protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(ReduceStream)); -#endif base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/Shrink/BitStream.cs b/src/SharpCompress/Compressors/Shrink/BitStream.cs index 7668399f1..fc0d5e536 100644 --- a/src/SharpCompress/Compressors/Shrink/BitStream.cs +++ b/src/SharpCompress/Compressors/Shrink/BitStream.cs @@ -1,80 +1,79 @@ -namespace SharpCompress.Compressors.Shrink +namespace SharpCompress.Compressors.Shrink; + +internal class BitStream { - internal class BitStream + private const int EOF = 1234; + private byte[] _src; + private int _srcLen; + private int _byteIdx; + private int _bitIdx; + private int _bitsLeft; + private ulong _bitBuffer; + private static uint[] _maskBits = new uint[17] { - private const int EOF = 1234; - private byte[] _src; - private int _srcLen; - private int _byteIdx; - private int _bitIdx; - private int _bitsLeft; - private ulong _bitBuffer; - private static uint[] _maskBits = new uint[17] - { - 0U, - 1U, - 3U, - 7U, - 15U, - 31U, - 63U, - (uint)sbyte.MaxValue, - (uint)byte.MaxValue, - 511U, - 1023U, - 2047U, - 4095U, - 8191U, - 16383U, - (uint)short.MaxValue, - (uint)ushort.MaxValue, - }; + 0U, + 1U, + 3U, + 7U, + 15U, + 31U, + 63U, + (uint)sbyte.MaxValue, + (uint)byte.MaxValue, + 511U, + 1023U, + 2047U, + 4095U, + 8191U, + 16383U, + (uint)short.MaxValue, + (uint)ushort.MaxValue, + }; - public BitStream(byte[] src, int srcLen) - { - _src = src; - _srcLen = srcLen; - _byteIdx = 0; - _bitIdx = 0; - } + public BitStream(byte[] src, int srcLen) + { + _src = src; + _srcLen = srcLen; + _byteIdx = 0; + _bitIdx = 0; + } - public int BytesRead => (_byteIdx << 3) + _bitIdx; + public int BytesRead => (_byteIdx << 3) + _bitIdx; - private int NextByte() + private int NextByte() + { + if (_byteIdx >= _srcLen) { - if (_byteIdx >= _srcLen) - { - return EOF; - } - - return _src[_byteIdx++]; + return EOF; } - public int NextBits(int nbits) + return _src[_byteIdx++]; + } + + public int NextBits(int nbits) + { + var result = 0; + if (nbits > _bitsLeft) { - var result = 0; - if (nbits > _bitsLeft) + int num; + while (_bitsLeft <= 24 && (num = NextByte()) != 1234) { - int num; - while (_bitsLeft <= 24 && (num = NextByte()) != 1234) - { - _bitBuffer |= (ulong)num << _bitsLeft; - _bitsLeft += 8; - } + _bitBuffer |= (ulong)num << _bitsLeft; + _bitsLeft += 8; } - result = (int)((long)_bitBuffer & (long)_maskBits[nbits]); - _bitBuffer >>= nbits; - _bitsLeft -= nbits; - return result; } + result = (int)((long)_bitBuffer & (long)_maskBits[nbits]); + _bitBuffer >>= nbits; + _bitsLeft -= nbits; + return result; + } - public bool Advance(int count) + public bool Advance(int count) + { + if (_byteIdx > _srcLen) { - if (_byteIdx > _srcLen) - { - return false; - } - return true; + return false; } + return true; } } diff --git a/src/SharpCompress/Compressors/Shrink/HwUnshrink.cs b/src/SharpCompress/Compressors/Shrink/HwUnshrink.cs index 8f42753c3..81e411c11 100644 --- a/src/SharpCompress/Compressors/Shrink/HwUnshrink.cs +++ b/src/SharpCompress/Compressors/Shrink/HwUnshrink.cs @@ -1,275 +1,297 @@ using System; -namespace SharpCompress.Compressors.Shrink +namespace SharpCompress.Compressors.Shrink; + +public class HwUnshrink { - public class HwUnshrink - { - private const int MIN_CODE_SIZE = 9; - private const int MAX_CODE_SIZE = 13; + private const int MIN_CODE_SIZE = 9; + private const int MAX_CODE_SIZE = 13; + + private const ushort MAX_CODE = (ushort)((1U << MAX_CODE_SIZE) - 1); + private const ushort INVALID_CODE = ushort.MaxValue; + private const ushort CONTROL_CODE = 256; + private const ushort INC_CODE_SIZE = 1; + private const ushort PARTIAL_CLEAR = 2; - private const ushort MAX_CODE = (ushort)((1U << MAX_CODE_SIZE) - 1); - private const ushort INVALID_CODE = ushort.MaxValue; - private const ushort CONTROL_CODE = 256; - private const ushort INC_CODE_SIZE = 1; - private const ushort PARTIAL_CLEAR = 2; + private const int HASH_BITS = MAX_CODE_SIZE + 1; // For a load factor of 0.5. + private const int HASHTAB_SIZE = 1 << HASH_BITS; + private const ushort UNKNOWN_LEN = ushort.MaxValue; - private const int HASH_BITS = MAX_CODE_SIZE + 1; // For a load factor of 0.5. - private const int HASHTAB_SIZE = 1 << HASH_BITS; - private const ushort UNKNOWN_LEN = ushort.MaxValue; + private struct CodeTabEntry + { + public int prefixCode; // INVALID_CODE means the entry is invalid. + public byte extByte; + public ushort len; + public int lastDstPos; + } - private struct CodeTabEntry + private static void CodeTabInit(CodeTabEntry[] codeTab) + { + for (var i = 0; i <= byte.MaxValue; i++) { - public int prefixCode; // INVALID_CODE means the entry is invalid. - public byte extByte; - public ushort len; - public int lastDstPos; + codeTab[i].prefixCode = (ushort)i; + codeTab[i].extByte = (byte)i; + codeTab[i].len = 1; } - private static void CodeTabInit(CodeTabEntry[] codeTab) + for (var i = byte.MaxValue + 1; i <= MAX_CODE; i++) { - for (var i = 0; i <= byte.MaxValue; i++) - { - codeTab[i].prefixCode = (ushort)i; - codeTab[i].extByte = (byte)i; - codeTab[i].len = 1; - } - - for (var i = byte.MaxValue + 1; i <= MAX_CODE; i++) - { - codeTab[i].prefixCode = INVALID_CODE; - } + codeTab[i].prefixCode = INVALID_CODE; } + } - private static void UnshrinkPartialClear(CodeTabEntry[] codeTab, ref CodeQueue queue) - { - var isPrefix = new bool[MAX_CODE + 1]; - int codeQueueSize; + private static void UnshrinkPartialClear(CodeTabEntry[] codeTab, ref CodeQueue queue) + { + var isPrefix = new bool[MAX_CODE + 1]; + int codeQueueSize; - // Scan for codes that have been used as a prefix. - for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++) + // Scan for codes that have been used as a prefix. + for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++) + { + if (codeTab[i].prefixCode != INVALID_CODE) { - if (codeTab[i].prefixCode != INVALID_CODE) - { - isPrefix[codeTab[i].prefixCode] = true; - } + isPrefix[codeTab[i].prefixCode] = true; } - - // Clear "non-prefix" codes in the table; populate the code queue. - codeQueueSize = 0; - for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++) - { - if (!isPrefix[i]) - { - codeTab[i].prefixCode = INVALID_CODE; - queue.codes[codeQueueSize++] = (ushort)i; - } - } - - queue.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker. - queue.nextIdx = 0; } - private static bool ReadCode( - BitStream stream, - ref int codeSize, - CodeTabEntry[] codeTab, - ref CodeQueue queue, - out int nextCode - ) + // Clear "non-prefix" codes in the table; populate the code queue. + codeQueueSize = 0; + for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++) { - int code, - controlCode; - - code = (int)stream.NextBits(codeSize); - if (!stream.Advance(codeSize)) + if (!isPrefix[i]) { - nextCode = INVALID_CODE; - return false; + codeTab[i].prefixCode = INVALID_CODE; + queue.codes[codeQueueSize++] = (ushort)i; } + } - // Handle regular codes (the common case). - if (code != CONTROL_CODE) - { - nextCode = code; - return true; - } + queue.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker. + queue.nextIdx = 0; + } - // Handle control codes. - controlCode = (ushort)stream.NextBits(codeSize); - if (!stream.Advance(codeSize)) - { - nextCode = INVALID_CODE; - return true; - } + private static bool ReadCode( + BitStream stream, + ref int codeSize, + CodeTabEntry[] codeTab, + ref CodeQueue queue, + out int nextCode + ) + { + int code, + controlCode; - if (controlCode == INC_CODE_SIZE && codeSize < MAX_CODE_SIZE) - { - codeSize++; - return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); - } + code = (int)stream.NextBits(codeSize); + if (!stream.Advance(codeSize)) + { + nextCode = INVALID_CODE; + return false; + } - if (controlCode == PARTIAL_CLEAR) - { - UnshrinkPartialClear(codeTab, ref queue); - return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); - } + // Handle regular codes (the common case). + if (code != CONTROL_CODE) + { + nextCode = code; + return true; + } + // Handle control codes. + controlCode = (ushort)stream.NextBits(codeSize); + if (!stream.Advance(codeSize)) + { nextCode = INVALID_CODE; return true; } - private static void CopyFromPrevPos(byte[] dst, int prevPos, int dstPos, int len) + if (controlCode == INC_CODE_SIZE && codeSize < MAX_CODE_SIZE) { - if (dstPos + len > dst.Length) - { - // Not enough room in dst for the sloppy copy below. - Array.Copy(dst, prevPos, dst, dstPos, len); - return; - } + codeSize++; + return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); + } - if (prevPos + len > dstPos) - { - // Benign one-byte overlap possible in the KwKwK case. - //assert(prevPos + len == dstPos + 1); - //assert(dst[prevPos] == dst[prevPos + len - 1]); - } + if (controlCode == PARTIAL_CLEAR) + { + UnshrinkPartialClear(codeTab, ref queue); + return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); + } - Buffer.BlockCopy(dst, prevPos, dst, dstPos, len); + nextCode = INVALID_CODE; + return true; + } + + private static void CopyFromPrevPos(byte[] dst, int prevPos, int dstPos, int len) + { + if (dstPos + len > dst.Length) + { + // Not enough room in dst for the sloppy copy below. + Array.Copy(dst, prevPos, dst, dstPos, len); + return; } - private static UnshrnkStatus OutputCode( - int code, - byte[] dst, - int dstPos, - int dstCap, - int prevCode, - CodeTabEntry[] codeTab, - ref CodeQueue queue, - out byte firstByte, - out int len - ) + if (prevPos + len > dstPos) + { + // Benign one-byte overlap possible in the KwKwK case. + //assert(prevPos + len == dstPos + 1); + //assert(dst[prevPos] == dst[prevPos + len - 1]); + } + + Buffer.BlockCopy(dst, prevPos, dst, dstPos, len); + } + + private static UnshrnkStatus OutputCode( + int code, + byte[] dst, + int dstPos, + int dstCap, + int prevCode, + CodeTabEntry[] codeTab, + ref CodeQueue queue, + out byte firstByte, + out int len + ) + { + int prefixCode; + + //assert(code <= MAX_CODE && code != CONTROL_CODE); + //assert(dstPos < dstCap); + firstByte = 0; + if (code <= byte.MaxValue) { - int prefixCode; + // Output literal byte. + firstByte = (byte)code; + len = 1; + dst[dstPos] = (byte)code; + return UnshrnkStatus.Ok; + } - //assert(code <= MAX_CODE && code != CONTROL_CODE); - //assert(dstPos < dstCap); + if (codeTab[code].prefixCode == INVALID_CODE || codeTab[code].prefixCode == code) + { + // Reject invalid codes. Self-referential codes may exist in the table but cannot be used. firstByte = 0; - if (code <= byte.MaxValue) - { - // Output literal byte. - firstByte = (byte)code; - len = 1; - dst[dstPos] = (byte)code; - return UnshrnkStatus.Ok; - } + len = 0; + return UnshrnkStatus.Error; + } - if (codeTab[code].prefixCode == INVALID_CODE || codeTab[code].prefixCode == code) + if (codeTab[code].len != UNKNOWN_LEN) + { + // Output string with known length (the common case). + if (dstCap - dstPos < codeTab[code].len) { - // Reject invalid codes. Self-referential codes may exist in the table but cannot be used. firstByte = 0; len = 0; - return UnshrnkStatus.Error; + return UnshrnkStatus.Full; } - if (codeTab[code].len != UNKNOWN_LEN) - { - // Output string with known length (the common case). - if (dstCap - dstPos < codeTab[code].len) - { - firstByte = 0; - len = 0; - return UnshrnkStatus.Full; - } + CopyFromPrevPos(dst, codeTab[code].lastDstPos, dstPos, codeTab[code].len); + firstByte = dst[dstPos]; + len = codeTab[code].len; + return UnshrnkStatus.Ok; + } - CopyFromPrevPos(dst, codeTab[code].lastDstPos, dstPos, codeTab[code].len); - firstByte = dst[dstPos]; - len = codeTab[code].len; - return UnshrnkStatus.Ok; - } + // Output a string of unknown length. + //assert(codeTab[code].len == UNKNOWN_LEN); + prefixCode = codeTab[code].prefixCode; + // assert(prefixCode > CONTROL_CODE); - // Output a string of unknown length. - //assert(codeTab[code].len == UNKNOWN_LEN); - prefixCode = codeTab[code].prefixCode; - // assert(prefixCode > CONTROL_CODE); + if (prefixCode == queue.codes[queue.nextIdx]) + { + // The prefix code hasn't been added yet, but we were just about to: the KwKwK case. + //assert(codeTab[prevCode].prefixCode != INVALID_CODE); + codeTab[prefixCode].prefixCode = prevCode; + codeTab[prefixCode].extByte = firstByte; + codeTab[prefixCode].len = (ushort)(codeTab[prevCode].len + 1); + codeTab[prefixCode].lastDstPos = codeTab[prevCode].lastDstPos; + dst[dstPos] = firstByte; + } + else if (codeTab[prefixCode].prefixCode == INVALID_CODE) + { + // The prefix code is still invalid. + firstByte = 0; + len = 0; + return UnshrnkStatus.Error; + } - if (prefixCode == queue.codes[queue.nextIdx]) - { - // The prefix code hasn't been added yet, but we were just about to: the KwKwK case. - //assert(codeTab[prevCode].prefixCode != INVALID_CODE); - codeTab[prefixCode].prefixCode = prevCode; - codeTab[prefixCode].extByte = firstByte; - codeTab[prefixCode].len = (ushort)(codeTab[prevCode].len + 1); - codeTab[prefixCode].lastDstPos = codeTab[prevCode].lastDstPos; - dst[dstPos] = firstByte; - } - else if (codeTab[prefixCode].prefixCode == INVALID_CODE) - { - // The prefix code is still invalid. - firstByte = 0; - len = 0; - return UnshrnkStatus.Error; - } + // Output the prefix string, then the extension byte. + len = codeTab[prefixCode].len + 1; + if (dstCap - dstPos < len) + { + firstByte = 0; + len = 0; + return UnshrnkStatus.Full; + } - // Output the prefix string, then the extension byte. - len = codeTab[prefixCode].len + 1; - if (dstCap - dstPos < len) - { - firstByte = 0; - len = 0; - return UnshrnkStatus.Full; - } + CopyFromPrevPos(dst, codeTab[prefixCode].lastDstPos, dstPos, codeTab[prefixCode].len); + dst[dstPos + len - 1] = codeTab[code].extByte; + firstByte = dst[dstPos]; - CopyFromPrevPos(dst, codeTab[prefixCode].lastDstPos, dstPos, codeTab[prefixCode].len); - dst[dstPos + len - 1] = codeTab[code].extByte; - firstByte = dst[dstPos]; + // Update the code table now that the string has a length and pos. + //assert(prevCode != code); + codeTab[code].len = (ushort)len; + codeTab[code].lastDstPos = dstPos; - // Update the code table now that the string has a length and pos. - //assert(prevCode != code); - codeTab[code].len = (ushort)len; - codeTab[code].lastDstPos = dstPos; + return UnshrnkStatus.Ok; + } + public static UnshrnkStatus Unshrink( + byte[] src, + int srcLen, + out int srcUsed, + byte[] dst, + int dstCap, + out int dstUsed + ) + { + var codeTab = new CodeTabEntry[HASHTAB_SIZE]; + var queue = new CodeQueue(); + var stream = new BitStream(src, srcLen); + int codeSize, + dstPos, + len; + int currCode, + prevCode, + newCode; + byte firstByte; + + CodeTabInit(codeTab); + CodeQueueInit(ref queue); + codeSize = MIN_CODE_SIZE; + dstPos = 0; + + // Handle the first code separately since there is no previous code. + if (!ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode)) + { + srcUsed = stream.BytesRead; + dstUsed = 0; return UnshrnkStatus.Ok; } - public static UnshrnkStatus Unshrink( - byte[] src, - int srcLen, - out int srcUsed, - byte[] dst, - int dstCap, - out int dstUsed - ) + //assert(currCode != CONTROL_CODE); + if (currCode > byte.MaxValue) { - var codeTab = new CodeTabEntry[HASHTAB_SIZE]; - var queue = new CodeQueue(); - var stream = new BitStream(src, srcLen); - int codeSize, - dstPos, - len; - int currCode, - prevCode, - newCode; - byte firstByte; + srcUsed = stream.BytesRead; + dstUsed = 0; + return UnshrnkStatus.Error; // The first code must be a literal. + } - CodeTabInit(codeTab); - CodeQueueInit(ref queue); - codeSize = MIN_CODE_SIZE; - dstPos = 0; + if (dstPos == dstCap) + { + srcUsed = stream.BytesRead; + dstUsed = dstPos; + return UnshrnkStatus.Full; + } - // Handle the first code separately since there is no previous code. - if (!ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode)) - { - srcUsed = stream.BytesRead; - dstUsed = 0; - return UnshrnkStatus.Ok; - } + firstByte = (byte)currCode; + dst[dstPos] = (byte)currCode; + codeTab[currCode].lastDstPos = dstPos; + dstPos++; - //assert(currCode != CONTROL_CODE); - if (currCode > byte.MaxValue) + prevCode = currCode; + while (ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode)) + { + if (currCode == INVALID_CODE) { srcUsed = stream.BytesRead; dstUsed = 0; - return UnshrnkStatus.Error; // The first code must be a literal. + return UnshrnkStatus.Error; } if (dstPos == dstCap) @@ -279,153 +301,130 @@ out int dstUsed return UnshrnkStatus.Full; } - firstByte = (byte)currCode; - dst[dstPos] = (byte)currCode; - codeTab[currCode].lastDstPos = dstPos; - dstPos++; - - prevCode = currCode; - while (ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode)) + // Handle KwKwK: next code used before being added. + if (currCode == queue.codes[queue.nextIdx]) { - if (currCode == INVALID_CODE) + if (codeTab[prevCode].prefixCode == INVALID_CODE) { + // The previous code is no longer valid. srcUsed = stream.BytesRead; dstUsed = 0; return UnshrnkStatus.Error; } - if (dstPos == dstCap) - { - srcUsed = stream.BytesRead; - dstUsed = dstPos; - return UnshrnkStatus.Full; - } + // Extend the previous code with its first byte. + //assert(currCode != prevCode); + codeTab[currCode].prefixCode = prevCode; + codeTab[currCode].extByte = firstByte; + codeTab[currCode].len = (ushort)(codeTab[prevCode].len + 1); + codeTab[currCode].lastDstPos = codeTab[prevCode].lastDstPos; + //assert(dstPos < dstCap); + dst[dstPos] = firstByte; + } - // Handle KwKwK: next code used before being added. - if (currCode == queue.codes[queue.nextIdx]) - { - if (codeTab[prevCode].prefixCode == INVALID_CODE) - { - // The previous code is no longer valid. - srcUsed = stream.BytesRead; - dstUsed = 0; - return UnshrnkStatus.Error; - } - - // Extend the previous code with its first byte. - //assert(currCode != prevCode); - codeTab[currCode].prefixCode = prevCode; - codeTab[currCode].extByte = firstByte; - codeTab[currCode].len = (ushort)(codeTab[prevCode].len + 1); - codeTab[currCode].lastDstPos = codeTab[prevCode].lastDstPos; - //assert(dstPos < dstCap); - dst[dstPos] = firstByte; - } + // Output the string represented by the current code. + var status = OutputCode( + currCode, + dst, + dstPos, + dstCap, + prevCode, + codeTab, + ref queue, + out firstByte, + out len + ); + if (status != UnshrnkStatus.Ok) + { + srcUsed = stream.BytesRead; + dstUsed = 0; + return status; + } - // Output the string represented by the current code. - var status = OutputCode( - currCode, - dst, - dstPos, - dstCap, - prevCode, - codeTab, - ref queue, - out firstByte, - out len - ); - if (status != UnshrnkStatus.Ok) - { - srcUsed = stream.BytesRead; - dstUsed = 0; - return status; - } + // Verify that the output matches walking the prefixes. + var c = currCode; + for (var i = 0; i < len; i++) + { + // assert(codeTab[c].len == len - i); + //assert(codeTab[c].extByte == dst[dstPos + len - i - 1]); + c = codeTab[c].prefixCode; + } - // Verify that the output matches walking the prefixes. - var c = currCode; - for (var i = 0; i < len; i++) - { - // assert(codeTab[c].len == len - i); - //assert(codeTab[c].extByte == dst[dstPos + len - i - 1]); - c = codeTab[c].prefixCode; - } + // Add a new code to the string table if there's room. + // The string is the previous code's string extended with the first byte of the current code's string. + newCode = CodeQueueRemoveNext(ref queue); + if (newCode != INVALID_CODE) + { + //assert(codeTab[prevCode].lastDstPos < dstPos); + codeTab[newCode].prefixCode = prevCode; + codeTab[newCode].extByte = firstByte; + codeTab[newCode].len = (ushort)(codeTab[prevCode].len + 1); + codeTab[newCode].lastDstPos = codeTab[prevCode].lastDstPos; - // Add a new code to the string table if there's room. - // The string is the previous code's string extended with the first byte of the current code's string. - newCode = CodeQueueRemoveNext(ref queue); - if (newCode != INVALID_CODE) + if (codeTab[prevCode].prefixCode == INVALID_CODE) { - //assert(codeTab[prevCode].lastDstPos < dstPos); - codeTab[newCode].prefixCode = prevCode; - codeTab[newCode].extByte = firstByte; - codeTab[newCode].len = (ushort)(codeTab[prevCode].len + 1); - codeTab[newCode].lastDstPos = codeTab[prevCode].lastDstPos; - - if (codeTab[prevCode].prefixCode == INVALID_CODE) - { - // prevCode was invalidated in a partial clearing. Until that code is re-used, the - // string represented by newCode is indeterminate. - codeTab[newCode].len = UNKNOWN_LEN; - } - // If prevCode was invalidated in a partial clearing, it's possible that newCode == prevCode, - // in which case it will never be used or cleared. + // prevCode was invalidated in a partial clearing. Until that code is re-used, the + // string represented by newCode is indeterminate. + codeTab[newCode].len = UNKNOWN_LEN; } - - codeTab[currCode].lastDstPos = dstPos; - dstPos += len; - - prevCode = currCode; + // If prevCode was invalidated in a partial clearing, it's possible that newCode == prevCode, + // in which case it will never be used or cleared. } - srcUsed = stream.BytesRead; - dstUsed = dstPos; + codeTab[currCode].lastDstPos = dstPos; + dstPos += len; - return UnshrnkStatus.Ok; + prevCode = currCode; } - public enum UnshrnkStatus - { - Ok, - Full, - Error, - } + srcUsed = stream.BytesRead; + dstUsed = dstPos; - private struct CodeQueue - { - public int nextIdx; - public ushort[] codes; - } + return UnshrnkStatus.Ok; + } - private static void CodeQueueInit(ref CodeQueue q) - { - int codeQueueSize; - ushort code; + public enum UnshrnkStatus + { + Ok, + Full, + Error, + } - codeQueueSize = 0; - q.codes = new ushort[MAX_CODE - CONTROL_CODE + 2]; + private struct CodeQueue + { + public int nextIdx; + public ushort[] codes; + } - for (code = CONTROL_CODE + 1; code <= MAX_CODE; code++) - { - q.codes[codeQueueSize++] = code; - } + private static void CodeQueueInit(ref CodeQueue q) + { + int codeQueueSize; + ushort code; - //assert(codeQueueSize < q.codes.Length); - q.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker. - q.nextIdx = 0; + codeQueueSize = 0; + q.codes = new ushort[MAX_CODE - CONTROL_CODE + 2]; + + for (code = CONTROL_CODE + 1; code <= MAX_CODE; code++) + { + q.codes[codeQueueSize++] = code; } - private static ushort CodeQueueNext(ref CodeQueue q) => - //assert(q.nextIdx < q.codes.Length); - q.codes[q.nextIdx]; + //assert(codeQueueSize < q.codes.Length); + q.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker. + q.nextIdx = 0; + } + + private static ushort CodeQueueNext(ref CodeQueue q) => + //assert(q.nextIdx < q.codes.Length); + q.codes[q.nextIdx]; - private static ushort CodeQueueRemoveNext(ref CodeQueue q) + private static ushort CodeQueueRemoveNext(ref CodeQueue q) + { + var code = CodeQueueNext(ref q); + if (code != INVALID_CODE) { - var code = CodeQueueNext(ref q); - if (code != INVALID_CODE) - { - q.nextIdx++; - } - return code; + q.nextIdx++; } + return code; } } diff --git a/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs b/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs index 14bc6c3d7..dfd61834a 100644 --- a/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs +++ b/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs @@ -26,10 +26,6 @@ long uncompressedSize inStream = stream; _compressionMode = compressionMode; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ShrinkStream)); -#endif - _compressedSize = (ulong)compressedSize; _uncompressedSize = uncompressedSize; _byteOut = new byte[_uncompressedSize]; @@ -38,9 +34,6 @@ long uncompressedSize protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(ShrinkStream)); -#endif base.Dispose(disposing); } diff --git a/src/SharpCompress/Compressors/Squeezed/BitReader.Async.cs b/src/SharpCompress/Compressors/Squeezed/BitReader.Async.cs index a4c032e75..6522a923d 100644 --- a/src/SharpCompress/Compressors/Squeezed/BitReader.Async.cs +++ b/src/SharpCompress/Compressors/Squeezed/BitReader.Async.cs @@ -3,31 +3,30 @@ using System.Threading; using System.Threading.Tasks; -namespace SharpCompress.Compressors.Squeezed +namespace SharpCompress.Compressors.Squeezed; + +public partial class BitReader { - public partial class BitReader + public async ValueTask ReadBitAsync(CancellationToken cancellationToken = default) { - public async ValueTask ReadBitAsync(CancellationToken cancellationToken = default) + if (_bitCount == 0) { - if (_bitCount == 0) + byte[] buffer = new byte[1]; + int bytesRead = await _stream + .ReadAsync(buffer, 0, 1, cancellationToken) + .ConfigureAwait(false); + if (bytesRead == 0) { - byte[] buffer = new byte[1]; - int bytesRead = await _stream - .ReadAsync(buffer, 0, 1, cancellationToken) - .ConfigureAwait(false); - if (bytesRead == 0) - { - throw new EndOfStreamException(); - } - - _bitBuffer = buffer[0]; - _bitCount = 8; + throw new EndOfStreamException(); } - bool bit = (_bitBuffer & 1) != 0; - _bitBuffer >>= 1; - _bitCount--; - return bit; + _bitBuffer = buffer[0]; + _bitCount = 8; } + + bool bit = (_bitBuffer & 1) != 0; + _bitBuffer >>= 1; + _bitCount--; + return bit; } } diff --git a/src/SharpCompress/Compressors/Squeezed/BitReader.cs b/src/SharpCompress/Compressors/Squeezed/BitReader.cs index 6f5286687..25c0601dc 100644 --- a/src/SharpCompress/Compressors/Squeezed/BitReader.cs +++ b/src/SharpCompress/Compressors/Squeezed/BitReader.cs @@ -1,57 +1,53 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Squeezed +namespace SharpCompress.Compressors.Squeezed; + +public partial class BitReader { - public partial class BitReader - { - private readonly Stream _stream; - private int _bitBuffer; - private int _bitCount; + private readonly Stream _stream; + private int _bitBuffer; + private int _bitCount; - public BitReader(Stream stream) - { - _stream = stream; - _bitBuffer = 0; - _bitCount = 0; - } + public BitReader(Stream stream) + { + _stream = stream; + _bitBuffer = 0; + _bitCount = 0; + } - public bool ReadBit() + public bool ReadBit() + { + if (_bitCount == 0) { - if (_bitCount == 0) + int nextByte = _stream.ReadByte(); + if (nextByte == -1) { - int nextByte = _stream.ReadByte(); - if (nextByte == -1) - { - throw new EndOfStreamException(); - } - - _bitBuffer = nextByte; - _bitCount = 8; + throw new EndOfStreamException(); } - bool bit = (_bitBuffer & 1) != 0; - _bitBuffer >>= 1; - _bitCount--; - return bit; + _bitBuffer = nextByte; + _bitCount = 8; } - public int ReadBits(int count) + bool bit = (_bitBuffer & 1) != 0; + _bitBuffer >>= 1; + _bitCount--; + return bit; + } + + public int ReadBits(int count) + { + if (count < 1 || count > 32) { - if (count < 1 || count > 32) - { - throw new ArgumentOutOfRangeException( - nameof(count), - "Count must be between 1 and 32." - ); - } + throw new ArgumentOutOfRangeException(nameof(count), "Count must be between 1 and 32."); + } - int value = 0; - for (int i = 0; i < count; i++) - { - value = (value << 1) | (ReadBit() ? 1 : 0); - } - return value; + int value = 0; + for (int i = 0; i < count; i++) + { + value = (value << 1) | (ReadBit() ? 1 : 0); } + return value; } } diff --git a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs index e040094a4..1b416af5e 100644 --- a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs +++ b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs @@ -4,108 +4,103 @@ using System.Threading.Tasks; using SharpCompress.Compressors.RLE90; -namespace SharpCompress.Compressors.Squeezed +namespace SharpCompress.Compressors.Squeezed; + +public partial class SqueezeStream { - public partial class SqueezeStream + public static async ValueTask CreateAsync( + Stream stream, + int compressedSize, + CancellationToken cancellationToken = default + ) { - public static async ValueTask CreateAsync( - Stream stream, - int compressedSize, - CancellationToken cancellationToken = default - ) - { - var squeezeStream = new SqueezeStream(stream, compressedSize); - squeezeStream._decodedStream = await squeezeStream - .BuildDecodedStreamAsync(cancellationToken) - .ConfigureAwait(false); + var squeezeStream = new SqueezeStream(stream, compressedSize); + squeezeStream._decodedStream = await squeezeStream + .BuildDecodedStreamAsync(cancellationToken) + .ConfigureAwait(false); + + return squeezeStream; + } + + public override async Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + return await _decodedStream + .ReadAsync(buffer, offset, count, cancellationToken) + .ConfigureAwait(false); + } -#if DEBUG_STREAMS - squeezeStream.DebugConstruct(typeof(SqueezeStream)); +#if !LEGACY_DOTNET + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) + { + return await _decodedStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } #endif - return squeezeStream; - } + private async Task BuildDecodedStreamAsync(CancellationToken cancellationToken) + { + byte[] numNodesBytes = new byte[2]; + int bytesRead = await _stream + .ReadAsync(numNodesBytes, 0, 2, cancellationToken) + .ConfigureAwait(false); - public override async Task ReadAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken - ) + if (bytesRead != 2) { - return await _decodedStream - .ReadAsync(buffer, offset, count, cancellationToken) - .ConfigureAwait(false); + return new MemoryStream(Array.Empty()); } -#if !LEGACY_DOTNET - public override async ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default - ) + int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8); + + if (numnodes >= NUMVALS || numnodes == 0) { - return await _decodedStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + return new MemoryStream(Array.Empty()); } -#endif - private async Task BuildDecodedStreamAsync(CancellationToken cancellationToken) + var dnode = new int[numnodes, 2]; + for (int j = 0; j < numnodes; j++) { - byte[] numNodesBytes = new byte[2]; - int bytesRead = await _stream - .ReadAsync(numNodesBytes, 0, 2, cancellationToken) + byte[] nodeBytes = new byte[4]; + bytesRead = await _stream + .ReadAsync(nodeBytes, 0, 4, cancellationToken) .ConfigureAwait(false); - if (bytesRead != 2) - { - return new MemoryStream(Array.Empty()); - } - - int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8); - - if (numnodes >= NUMVALS || numnodes == 0) + if (bytesRead != 4) { - return new MemoryStream(Array.Empty()); + throw new EndOfStreamException(); } - var dnode = new int[numnodes, 2]; - for (int j = 0; j < numnodes; j++) - { - byte[] nodeBytes = new byte[4]; - bytesRead = await _stream - .ReadAsync(nodeBytes, 0, 4, cancellationToken) - .ConfigureAwait(false); - - if (bytesRead != 4) - { - throw new EndOfStreamException(); - } - - dnode[j, 0] = (short)(nodeBytes[0] | (nodeBytes[1] << 8)); - dnode[j, 1] = (short)(nodeBytes[2] | (nodeBytes[3] << 8)); - } + dnode[j, 0] = (short)(nodeBytes[0] | (nodeBytes[1] << 8)); + dnode[j, 1] = (short)(nodeBytes[2] | (nodeBytes[3] << 8)); + } - var bitReader = new BitReader(_stream); - var huffmanDecoded = new MemoryStream(); - int i = 0; + var bitReader = new BitReader(_stream); + var huffmanDecoded = new MemoryStream(); + int i = 0; - while (true) + while (true) + { + bool bit = await bitReader.ReadBitAsync(cancellationToken).ConfigureAwait(false); + i = dnode[i, bit ? 1 : 0]; + if (i < 0) { - bool bit = await bitReader.ReadBitAsync(cancellationToken).ConfigureAwait(false); - i = dnode[i, bit ? 1 : 0]; - if (i < 0) + i = -(i + 1); + if (i == SPEOF) { - i = -(i + 1); - if (i == SPEOF) - { - break; - } - huffmanDecoded.WriteByte((byte)i); - i = 0; + break; } + huffmanDecoded.WriteByte((byte)i); + i = 0; } - - huffmanDecoded.Position = 0; - return new RunLength90Stream(huffmanDecoded, (int)huffmanDecoded.Length); } + + huffmanDecoded.Position = 0; + return new RunLength90Stream(huffmanDecoded, (int)huffmanDecoded.Length); } } diff --git a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs index 072c80f15..d1f179b3b 100644 --- a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs +++ b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs @@ -4,74 +4,67 @@ using System.Text; using SharpCompress.Compressors.RLE90; -namespace SharpCompress.Compressors.Squeezed -{ - [CLSCompliant(true)] - public partial class SqueezeStream : Stream - { - private readonly Stream _stream; - private readonly int _compressedSize; - private const int NUMVALS = 257; - private const int SPEOF = 256; +namespace SharpCompress.Compressors.Squeezed; - private Stream _decodedStream = null!; +[CLSCompliant(true)] +public partial class SqueezeStream : Stream +{ + private readonly Stream _stream; + private readonly int _compressedSize; + private const int NUMVALS = 257; + private const int SPEOF = 256; - private SqueezeStream(Stream stream, int compressedSize) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _compressedSize = compressedSize; - } + private Stream _decodedStream = null!; - public static SqueezeStream Create(Stream stream, int compressedSize) - { - var squeezeStream = new SqueezeStream(stream, compressedSize); - squeezeStream._decodedStream = squeezeStream.BuildDecodedStream(); + private SqueezeStream(Stream stream, int compressedSize) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + _compressedSize = compressedSize; + } -#if DEBUG_STREAMS - squeezeStream.DebugConstruct(typeof(SqueezeStream)); -#endif + public static SqueezeStream Create(Stream stream, int compressedSize) + { + var squeezeStream = new SqueezeStream(stream, compressedSize); + squeezeStream._decodedStream = squeezeStream.BuildDecodedStream(); - return squeezeStream; - } + return squeezeStream; + } - protected override void Dispose(bool disposing) - { -#if DEBUG_STREAMS - this.DebugDispose(typeof(SqueezeStream)); -#endif - _decodedStream?.Dispose(); - base.Dispose(disposing); - } + protected override void Dispose(bool disposing) + { + _decodedStream?.Dispose(); + base.Dispose(disposing); + } - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; - public override long Length => throw new NotSupportedException(); - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } - public override void Flush() => throw new NotSupportedException(); + public override void Flush() => throw new NotSupportedException(); - public override long Seek(long offset, SeekOrigin origin) => - 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 SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); - public override int Read(byte[] buffer, int offset, int count) - { - return _decodedStream.Read(buffer, offset, count); - } + public override int Read(byte[] buffer, int offset, int count) + { + return _decodedStream.Read(buffer, offset, count); + } - private Stream BuildDecodedStream() + private Stream BuildDecodedStream() + { + using (var binaryReader = new BinaryReader(_stream, Encoding.Default, leaveOpen: true)) { - var binaryReader = new BinaryReader(_stream, Encoding.Default, leaveOpen: true); int numnodes = binaryReader.ReadUInt16(); if (numnodes >= NUMVALS || numnodes == 0) diff --git a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs index 5541fb2db..0e3f80398 100644 --- a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs @@ -12,16 +12,10 @@ 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 dda9aa789..489b449a8 100644 --- a/src/SharpCompress/Compressors/Xz/XZStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZStream.cs @@ -15,16 +15,10 @@ 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); } diff --git a/src/SharpCompress/Compressors/ZStandard/BitOperations.cs b/src/SharpCompress/Compressors/ZStandard/BitOperations.cs index fc8e3108d..00da0c99a 100644 --- a/src/SharpCompress/Compressors/ZStandard/BitOperations.cs +++ b/src/SharpCompress/Compressors/ZStandard/BitOperations.cs @@ -9,302 +9,301 @@ // Some routines inspired by the Stanford Bit Twiddling Hacks by Sean Eron Anderson: // http://graphics.stanford.edu/~seander/bithacks.html -namespace System.Numerics +namespace System.Numerics; + +/// +/// Utility methods for intrinsic bit-twiddling operations. +/// The methods use hardware intrinsics when available on the underlying platform, +/// otherwise they use optimized software fallbacks. +/// +public static unsafe class BitOperations { + // hack: should be public because of inline + public static readonly byte* TrailingZeroCountDeBruijn = GetArrayPointer( + new byte[] + { + 00, + 01, + 28, + 02, + 29, + 14, + 24, + 03, + 30, + 22, + 20, + 15, + 25, + 17, + 04, + 08, + 31, + 27, + 13, + 23, + 21, + 19, + 16, + 07, + 26, + 12, + 18, + 06, + 11, + 05, + 10, + 09, + } + ); + + // hack: should be public because of inline + public static readonly byte* Log2DeBruijn = GetArrayPointer( + new byte[] + { + 00, + 09, + 01, + 10, + 13, + 21, + 02, + 29, + 11, + 14, + 16, + 18, + 22, + 25, + 03, + 30, + 08, + 12, + 20, + 28, + 15, + 17, + 24, + 07, + 19, + 27, + 23, + 06, + 26, + 05, + 04, + 31, + } + ); + /// - /// Utility methods for intrinsic bit-twiddling operations. - /// The methods use hardware intrinsics when available on the underlying platform, - /// otherwise they use optimized software fallbacks. + /// Returns the integer (floor) log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since log(0) is undefined. /// - public static unsafe class BitOperations + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Log2(uint value) { - // hack: should be public because of inline - public static readonly byte* TrailingZeroCountDeBruijn = GetArrayPointer( - new byte[] - { - 00, - 01, - 28, - 02, - 29, - 14, - 24, - 03, - 30, - 22, - 20, - 15, - 25, - 17, - 04, - 08, - 31, - 27, - 13, - 23, - 21, - 19, - 16, - 07, - 26, - 12, - 18, - 06, - 11, - 05, - 10, - 09, - } - ); - - // hack: should be public because of inline - public static readonly byte* Log2DeBruijn = GetArrayPointer( - new byte[] - { - 00, - 09, - 01, - 10, - 13, - 21, - 02, - 29, - 11, - 14, - 16, - 18, - 22, - 25, - 03, - 30, - 08, - 12, - 20, - 28, - 15, - 17, - 24, - 07, - 19, - 27, - 23, - 06, - 26, - 05, - 04, - 31, - } - ); - - /// - /// Returns the integer (floor) log of the specified value, base 2. - /// Note that by convention, input value 0 returns 0 since log(0) is undefined. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Log2(uint value) + // The 0->0 contract is fulfilled by setting the LSB to 1. + // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result. + value |= 1; + + // value lzcnt actual expected + // ..0001 31 31-31 0 + // ..0010 30 31-30 1 + // 0010.. 2 31-2 29 + // 0100.. 1 31-1 30 + // 1000.. 0 31-0 31 + + // Fallback contract is 0->0 + // No AggressiveInlining due to large method size + // Has conventional contract 0->0 (Log(0) is undefined) + + // Fill trailing zeros with ones, eg 00010010 becomes 00011111 + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check + return Log2DeBruijn[ + // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u + (int)((value * 0x07C4ACDDu) >> 27) + ]; + } + + /// + /// Returns the integer (floor) log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since log(0) is undefined. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Log2(ulong value) + { + value |= 1; + + uint hi = (uint)(value >> 32); + + if (hi == 0) { - // The 0->0 contract is fulfilled by setting the LSB to 1. - // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result. - value |= 1; - - // value lzcnt actual expected - // ..0001 31 31-31 0 - // ..0010 30 31-30 1 - // 0010.. 2 31-2 29 - // 0100.. 1 31-1 30 - // 1000.. 0 31-0 31 - - // Fallback contract is 0->0 - // No AggressiveInlining due to large method size - // Has conventional contract 0->0 (Log(0) is undefined) - - // Fill trailing zeros with ones, eg 00010010 becomes 00011111 - value |= value >> 01; - value |= value >> 02; - value |= value >> 04; - value |= value >> 08; - value |= value >> 16; - - // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check - return Log2DeBruijn[ - // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u - (int)((value * 0x07C4ACDDu) >> 27) - ]; + return Log2((uint)value); } - /// - /// Returns the integer (floor) log of the specified value, base 2. - /// Note that by convention, input value 0 returns 0 since log(0) is undefined. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Log2(ulong value) + return 32 + Log2(hi); + } + + /// + /// Count the number of trailing zero bits in an integer value. + /// Similar in behavior to the x86 instruction TZCNT. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TrailingZeroCount(int value) => TrailingZeroCount((uint)value); + + /// + /// Count the number of trailing zero bits in an integer value. + /// Similar in behavior to the x86 instruction TZCNT. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TrailingZeroCount(uint value) + { + // Unguarded fallback contract is 0->0, BSF contract is 0->undefined + if (value == 0) { - value |= 1; + return 32; + } - uint hi = (uint)(value >> 32); + // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check + return TrailingZeroCountDeBruijn[ + // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_0111_1100_1011_0101_0011_0001u + (int)(((value & (uint)-(int)value) * 0x077CB531u) >> 27) + ]; // Multi-cast mitigates redundant conv.u8 + } - if (hi == 0) - { - return Log2((uint)value); - } + /// + /// Count the number of trailing zero bits in a mask. + /// Similar in behavior to the x86 instruction TZCNT. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TrailingZeroCount(long value) => TrailingZeroCount((ulong)value); - return 32 + Log2(hi); - } + /// + /// Count the number of trailing zero bits in a mask. + /// Similar in behavior to the x86 instruction TZCNT. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TrailingZeroCount(ulong value) + { + uint lo = (uint)value; - /// - /// Count the number of trailing zero bits in an integer value. - /// Similar in behavior to the x86 instruction TZCNT. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TrailingZeroCount(int value) => TrailingZeroCount((uint)value); - - /// - /// Count the number of trailing zero bits in an integer value. - /// Similar in behavior to the x86 instruction TZCNT. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TrailingZeroCount(uint value) + if (lo == 0) { - // Unguarded fallback contract is 0->0, BSF contract is 0->undefined - if (value == 0) - { - return 32; - } - - // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check - return TrailingZeroCountDeBruijn[ - // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_0111_1100_1011_0101_0011_0001u - (int)(((value & (uint)-(int)value) * 0x077CB531u) >> 27) - ]; // Multi-cast mitigates redundant conv.u8 + return 32 + TrailingZeroCount((uint)(value >> 32)); } - /// - /// Count the number of trailing zero bits in a mask. - /// Similar in behavior to the x86 instruction TZCNT. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TrailingZeroCount(long value) => TrailingZeroCount((ulong)value); - - /// - /// Count the number of trailing zero bits in a mask. - /// Similar in behavior to the x86 instruction TZCNT. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TrailingZeroCount(ulong value) - { - uint lo = (uint)value; + return TrailingZeroCount(lo); + } - if (lo == 0) - { - return 32 + TrailingZeroCount((uint)(value >> 32)); - } + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) => + (value << offset) | (value >> (32 - offset)); - return TrailingZeroCount(lo); - } + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..63] is treated as congruent mod 64. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong RotateLeft(ulong value, int offset) => + (value << offset) | (value >> (64 - offset)); + + /// + /// Rotates the specified value right by the specified number of bits. + /// Similar in behavior to the x86 instruction ROR. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateRight(uint value, int offset) => + (value >> offset) | (value << (32 - offset)); + + /// + /// Rotates the specified value right by the specified number of bits. + /// Similar in behavior to the x86 instruction ROR. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..63] is treated as congruent mod 64. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong RotateRight(ulong value, int offset) => + (value >> offset) | (value << (64 - offset)); - /// - /// Rotates the specified value left by the specified number of bits. - /// Similar in behavior to the x86 instruction ROL. - /// - /// The value to rotate. - /// The number of bits to rotate by. - /// Any value outside the range [0..31] is treated as congruent mod 32. - /// The rotated value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint RotateLeft(uint value, int offset) => - (value << offset) | (value >> (32 - offset)); - - /// - /// Rotates the specified value left by the specified number of bits. - /// Similar in behavior to the x86 instruction ROL. - /// - /// The value to rotate. - /// The number of bits to rotate by. - /// Any value outside the range [0..63] is treated as congruent mod 64. - /// The rotated value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong RotateLeft(ulong value, int offset) => - (value << offset) | (value >> (64 - offset)); - - /// - /// Rotates the specified value right by the specified number of bits. - /// Similar in behavior to the x86 instruction ROR. - /// - /// The value to rotate. - /// The number of bits to rotate by. - /// Any value outside the range [0..31] is treated as congruent mod 32. - /// The rotated value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint RotateRight(uint value, int offset) => - (value >> offset) | (value << (32 - offset)); - - /// - /// Rotates the specified value right by the specified number of bits. - /// Similar in behavior to the x86 instruction ROR. - /// - /// The value to rotate. - /// The number of bits to rotate by. - /// Any value outside the range [0..63] is treated as congruent mod 64. - /// The rotated value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong RotateRight(ulong value, int offset) => - (value >> offset) | (value << (64 - offset)); - - /// - /// Count the number of leading zero bits in a mask. - /// Similar in behavior to the x86 instruction LZCNT. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LeadingZeroCount(uint value) + /// + /// Count the number of leading zero bits in a mask. + /// Similar in behavior to the x86 instruction LZCNT. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZeroCount(uint value) + { + // Unguarded fallback contract is 0->31, BSR contract is 0->undefined + if (value == 0) { - // Unguarded fallback contract is 0->31, BSR contract is 0->undefined - if (value == 0) - { - return 32; - } - - // No AggressiveInlining due to large method size - // Has conventional contract 0->0 (Log(0) is undefined) - - // Fill trailing zeros with ones, eg 00010010 becomes 00011111 - value |= value >> 01; - value |= value >> 02; - value |= value >> 04; - value |= value >> 08; - value |= value >> 16; - - // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check - return 31 - ^ Log2DeBruijn[ - // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here - (int)((value * 0x07C4ACDDu) >> 27) - ]; + return 32; } - /// - /// Count the number of leading zero bits in a mask. - /// Similar in behavior to the x86 instruction LZCNT. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LeadingZeroCount(ulong value) - { - uint hi = (uint)(value >> 32); + // No AggressiveInlining due to large method size + // Has conventional contract 0->0 (Log(0) is undefined) + + // Fill trailing zeros with ones, eg 00010010 becomes 00011111 + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check + return 31 + ^ Log2DeBruijn[ + // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here + (int)((value * 0x07C4ACDDu) >> 27) + ]; + } - if (hi == 0) - { - return 32 + LeadingZeroCount((uint)value); - } + /// + /// Count the number of leading zero bits in a mask. + /// Similar in behavior to the x86 instruction LZCNT. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZeroCount(ulong value) + { + uint hi = (uint)(value >> 32); - return LeadingZeroCount(hi); + if (hi == 0) + { + return 32 + LeadingZeroCount((uint)value); } + + return LeadingZeroCount(hi); } } diff --git a/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs b/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs index 3a4ffbb04..7b65f1ce1 100644 --- a/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs +++ b/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs @@ -33,9 +33,6 @@ public ZStandardStream(Stream baseInputStream) : base(baseInputStream) { this.stream = baseInputStream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ZStandardStream)); -#endif } /// diff --git a/src/SharpCompress/Crypto/Crc32Stream.cs b/src/SharpCompress/Crypto/Crc32Stream.cs index 73a04ed38..1cbe88845 100644 --- a/src/SharpCompress/Crypto/Crc32Stream.cs +++ b/src/SharpCompress/Crypto/Crc32Stream.cs @@ -25,16 +25,10 @@ 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); } diff --git a/src/SharpCompress/Factories/AceFactory.cs b/src/SharpCompress/Factories/AceFactory.cs index aa0ed79e2..f2fdfaa6e 100644 --- a/src/SharpCompress/Factories/AceFactory.cs +++ b/src/SharpCompress/Factories/AceFactory.cs @@ -10,39 +10,38 @@ using SharpCompress.Readers; using SharpCompress.Readers.Ace; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +public class AceFactory : Factory, IReaderFactory { - public class AceFactory : Factory, IReaderFactory + public override string Name => "Ace"; + + public override ArchiveType? KnownArchiveType => ArchiveType.Ace; + + public override IEnumerable GetSupportedExtensions() + { + yield return "ace"; + } + + public override bool IsArchive(Stream stream, string? password = null) => + AceHeader.IsArchive(stream); + + public override ValueTask IsArchiveAsync( + Stream stream, + string? password = null, + CancellationToken cancellationToken = default + ) => AceHeader.IsArchiveAsync(stream, cancellationToken); + + public IReader OpenReader(Stream stream, ReaderOptions? options) => + AceReader.OpenReader(stream, options); + + public ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options, + CancellationToken cancellationToken = default + ) { - public override string Name => "Ace"; - - public override ArchiveType? KnownArchiveType => ArchiveType.Ace; - - public override IEnumerable GetSupportedExtensions() - { - yield return "ace"; - } - - public override bool IsArchive(Stream stream, string? password = null) => - AceHeader.IsArchive(stream); - - public override ValueTask IsArchiveAsync( - Stream stream, - string? password = null, - CancellationToken cancellationToken = default - ) => AceHeader.IsArchiveAsync(stream, cancellationToken); - - public IReader OpenReader(Stream stream, ReaderOptions? options) => - AceReader.OpenReader(stream, options); - - public ValueTask OpenAsyncReader( - Stream stream, - ReaderOptions? options, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new((IAsyncReader)AceReader.OpenReader(stream, options)); - } + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)AceReader.OpenReader(stream, options)); } } diff --git a/src/SharpCompress/Factories/ArcFactory.cs b/src/SharpCompress/Factories/ArcFactory.cs index effaa02f1..75558ce70 100644 --- a/src/SharpCompress/Factories/ArcFactory.cs +++ b/src/SharpCompress/Factories/ArcFactory.cs @@ -12,77 +12,76 @@ using SharpCompress.Readers.Arc; using static System.Net.Mime.MediaTypeNames; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +public class ArcFactory : Factory, IReaderFactory { - public class ArcFactory : Factory, IReaderFactory - { - public override string Name => "Arc"; + public override string Name => "Arc"; - public override ArchiveType? KnownArchiveType => ArchiveType.Arc; + public override ArchiveType? KnownArchiveType => ArchiveType.Arc; - public override IEnumerable GetSupportedExtensions() - { - yield return "arc"; - } + 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) + { + //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 + //a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a + //Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for + //"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file, + //see the ZOO entry below. + var buffer = ArrayPool.Shared.Rent(2); + try { - //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 - //a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a - //Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for - //"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file, - //see the ZOO entry below. - var buffer = ArrayPool.Shared.Rent(2); - try - { - if (stream.ReadFully(buffer.AsSpan(0, 2))) - { - return buffer[0] == 0x1A && buffer[1] < 10; //rather thin, but this is all we have - } - return false; - } - finally + if (stream.ReadFully(buffer.AsSpan(0, 2))) { - ArrayPool.Shared.Return(buffer); + return buffer[0] == 0x1A && buffer[1] < 10; //rather thin, but this is all we have } + return false; + } + finally + { + ArrayPool.Shared.Return(buffer); } + } + + public IReader OpenReader(Stream stream, ReaderOptions? options) => + ArcReader.OpenReader(stream, options); - public IReader OpenReader(Stream stream, ReaderOptions? options) => - ArcReader.OpenReader(stream, options); + public ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)ArcReader.OpenReader(stream, options)); + } - public ValueTask OpenAsyncReader( - Stream stream, - ReaderOptions? options, - CancellationToken cancellationToken = default - ) + public override async ValueTask IsArchiveAsync( + Stream stream, + string? password = null, + CancellationToken cancellationToken = default + ) + { + //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 + //a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a + //Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for + //"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file, + //see the ZOO entry below. + var buffer = ArrayPool.Shared.Rent(2); + try { - cancellationToken.ThrowIfCancellationRequested(); - return new((IAsyncReader)ArcReader.OpenReader(stream, options)); + await stream.ReadExactAsync(buffer, 0, 2, cancellationToken); + return buffer[0] == 0x1A && buffer[1] < 10; //rather thin, but this is all we have } - - public override async ValueTask IsArchiveAsync( - Stream stream, - string? password = null, - CancellationToken cancellationToken = default - ) + finally { - //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 - //a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a - //Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for - //"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file, - //see the ZOO entry below. - var buffer = ArrayPool.Shared.Rent(2); - try - { - await stream.ReadExactAsync(buffer, 0, 2, cancellationToken); - return buffer[0] == 0x1A && buffer[1] < 10; //rather thin, but this is all we have - } - finally - { - ArrayPool.Shared.Return(buffer); - } + ArrayPool.Shared.Return(buffer); } } } diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs index 1377407bd..d599d81d4 100644 --- a/src/SharpCompress/Factories/ArjFactory.cs +++ b/src/SharpCompress/Factories/ArjFactory.cs @@ -10,39 +10,38 @@ using SharpCompress.Readers; using SharpCompress.Readers.Arj; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +public class ArjFactory : Factory, IReaderFactory { - public class ArjFactory : Factory, IReaderFactory + public override string Name => "Arj"; + + public override ArchiveType? KnownArchiveType => ArchiveType.Arj; + + public override IEnumerable GetSupportedExtensions() + { + yield return "arj"; + } + + public override bool IsArchive(Stream stream, string? password = null) => + ArjHeader.IsArchive(stream); + + public override ValueTask IsArchiveAsync( + Stream stream, + string? password = null, + CancellationToken cancellationToken = default + ) => ArjHeader.IsArchiveAsync(stream, cancellationToken); + + public IReader OpenReader(Stream stream, ReaderOptions? options) => + ArjReader.OpenReader(stream, options); + + public ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options, + CancellationToken cancellationToken = default + ) { - public override string Name => "Arj"; - - public override ArchiveType? KnownArchiveType => ArchiveType.Arj; - - public override IEnumerable GetSupportedExtensions() - { - yield return "arj"; - } - - public override bool IsArchive(Stream stream, string? password = null) => - ArjHeader.IsArchive(stream); - - public override ValueTask IsArchiveAsync( - Stream stream, - string? password = null, - CancellationToken cancellationToken = default - ) => ArjHeader.IsArchiveAsync(stream, cancellationToken); - - public IReader OpenReader(Stream stream, ReaderOptions? options) => - ArjReader.OpenReader(stream, options); - - public ValueTask OpenAsyncReader( - Stream stream, - ReaderOptions? options, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new((IAsyncReader)ArjReader.OpenReader(stream, options)); - } + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)ArjReader.OpenReader(stream, options)); } } diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs index aea200f59..7bca8ecb7 100644 --- a/src/SharpCompress/Factories/Factory.cs +++ b/src/SharpCompress/Factories/Factory.cs @@ -65,7 +65,7 @@ public abstract ValueTask IsArchiveAsync( 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. @@ -75,7 +75,7 @@ public abstract ValueTask IsArchiveAsync( /// /// internal virtual bool TryOpenReader( - RewindableStream stream, + SharpCompressStream stream, ReaderOptions options, out IReader? reader ) diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index 40907e1dc..ae4ac8b87 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -107,28 +107,28 @@ public IAsyncArchive OpenAsyncArchive( /// internal override bool TryOpenReader( - RewindableStream rewindableStream, + SharpCompressStream sharpCompressStream, ReaderOptions options, out IReader? reader ) { reader = null; - if (GZipArchive.IsGZipFile(rewindableStream)) + if (GZipArchive.IsGZipFile(sharpCompressStream)) { - rewindableStream.Rewind(); - var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); + sharpCompressStream.Rewind(); + var testStream = new GZipStream(sharpCompressStream, CompressionMode.Decompress); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.StopRecording(); - reader = new TarReader(rewindableStream, options, CompressionType.GZip); + sharpCompressStream.StopRecording(); + reader = new TarReader(sharpCompressStream, options, CompressionType.GZip); return true; } - rewindableStream.StopRecording(); - reader = OpenReader(rewindableStream, options); + sharpCompressStream.StopRecording(); + reader = OpenReader(sharpCompressStream, options); return true; } - rewindableStream.Rewind(); + sharpCompressStream.Rewind(); return false; } diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index b643d29ad..f8f4b7797 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -94,7 +94,7 @@ public IAsyncArchive OpenAsyncArchive( #region reader internal override bool TryOpenReader( - RewindableStream rewindableStream, + SharpCompressStream sharpCompressStream, ReaderOptions options, out IReader? reader ) diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 532868803..8857cca77 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -49,18 +49,18 @@ public override IEnumerable GetSupportedExtensions() /// public override bool IsArchive(Stream stream, string? password = null) { - var rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); + var sharpCompressStream = new SharpCompressStream(stream); + sharpCompressStream.StartRecording(); foreach (var wrapper in TarWrapper.Wrappers) { - rewindableStream.Rewind(); - if (wrapper.IsMatch(rewindableStream)) + sharpCompressStream.Rewind(); + if (wrapper.IsMatch(sharpCompressStream)) { - rewindableStream.Rewind(); - var decompressedStream = wrapper.CreateStream(rewindableStream); + sharpCompressStream.Rewind(); + var decompressedStream = wrapper.CreateStream(sharpCompressStream); if (TarArchive.IsTarFile(decompressedStream)) { - rewindableStream.Rewind(); + sharpCompressStream.Rewind(); return true; } } @@ -76,21 +76,21 @@ public override async ValueTask IsArchiveAsync( CancellationToken cancellationToken = default ) { - var rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); + var sharpCompressStream = new SharpCompressStream(stream); + sharpCompressStream.StartRecording(); foreach (var wrapper in TarWrapper.Wrappers) { - rewindableStream.Rewind(); - if (await wrapper.IsMatchAsync(rewindableStream, cancellationToken)) + sharpCompressStream.Rewind(); + if (await wrapper.IsMatchAsync(sharpCompressStream, cancellationToken)) { - rewindableStream.Rewind(); + sharpCompressStream.Rewind(); var decompressedStream = await wrapper.CreateStreamAsync( - rewindableStream, + sharpCompressStream, cancellationToken ); if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken)) { - rewindableStream.Rewind(); + sharpCompressStream.Rewind(); return true; } } @@ -160,19 +160,19 @@ public IAsyncArchive OpenAsyncArchive( public IReader OpenReader(Stream stream, ReaderOptions? options) { options ??= new ReaderOptions(); - var rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); + var sharpCompressStream = new SharpCompressStream(stream); + sharpCompressStream.StartRecording(); foreach (var wrapper in TarWrapper.Wrappers) { - rewindableStream.Rewind(); - if (wrapper.IsMatch(rewindableStream)) + sharpCompressStream.Rewind(); + if (wrapper.IsMatch(sharpCompressStream)) { - rewindableStream.Rewind(); - var decompressedStream = wrapper.CreateStream(rewindableStream); + sharpCompressStream.Rewind(); + var decompressedStream = wrapper.CreateStream(sharpCompressStream); if (TarArchive.IsTarFile(decompressedStream)) { - rewindableStream.StopRecording(); - return new TarReader(rewindableStream, options, wrapper.CompressionType); + sharpCompressStream.StopRecording(); + return new TarReader(sharpCompressStream, options, wrapper.CompressionType); } } } @@ -188,20 +188,23 @@ public async ValueTask OpenAsyncReader( { cancellationToken.ThrowIfCancellationRequested(); options ??= new ReaderOptions(); - var rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); + var sharpCompressStream = new SharpCompressStream(stream); + sharpCompressStream.StartRecording(); foreach (var wrapper in TarWrapper.Wrappers) { - rewindableStream.Rewind(); - if (await wrapper.IsMatchAsync(rewindableStream, cancellationToken)) + sharpCompressStream.Rewind(); + if (await wrapper.IsMatchAsync(sharpCompressStream, cancellationToken)) { - rewindableStream.Rewind(); - var decompressedStream = wrapper.CreateStream(rewindableStream); + sharpCompressStream.Rewind(); + var decompressedStream = await wrapper.CreateStreamAsync( + sharpCompressStream, + cancellationToken + ); if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken)) { - rewindableStream.Rewind(); - rewindableStream.StopRecording(); - return new TarReader(rewindableStream, options, wrapper.CompressionType); + sharpCompressStream.Rewind(); + sharpCompressStream.StopRecording(); + return new TarReader(sharpCompressStream, options, wrapper.CompressionType); } } } diff --git a/src/SharpCompress/IO/AsyncBinaryReader.cs b/src/SharpCompress/IO/AsyncBinaryReader.cs new file mode 100644 index 000000000..9fef056a1 --- /dev/null +++ b/src/SharpCompress/IO/AsyncBinaryReader.cs @@ -0,0 +1,106 @@ +using System; +using System.Buffers.Binary; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpCompress.IO; + +public sealed class AsyncBinaryReader : IDisposable +#if NET8_0_OR_GREATER + , IAsyncDisposable +#endif +{ + private readonly Stream _stream; + private readonly Stream _originalStream; + private readonly bool _leaveOpen; + private readonly byte[] _buffer = new byte[8]; + private bool _disposed; + + public AsyncBinaryReader(Stream stream, bool leaveOpen = false) + { + if (!stream.CanRead) + { + throw new ArgumentException("Stream must be readable."); + } + + _originalStream = stream ?? throw new ArgumentNullException(nameof(stream)); + _leaveOpen = leaveOpen; + _stream = stream; + } + + public Stream BaseStream => _stream; + + public async ValueTask ReadByteAsync(CancellationToken ct = default) + { + await _stream.ReadExactAsync(_buffer, 0, 1, ct).ConfigureAwait(false); + return _buffer[0]; + } + + public async ValueTask ReadUInt16Async(CancellationToken ct = default) + { + await _stream.ReadExactAsync(_buffer, 0, 2, ct).ConfigureAwait(false); + return BinaryPrimitives.ReadUInt16LittleEndian(_buffer); + } + + public async ValueTask ReadUInt32Async(CancellationToken ct = default) + { + await _stream.ReadExactAsync(_buffer, 0, 4, ct).ConfigureAwait(false); + return BinaryPrimitives.ReadUInt32LittleEndian(_buffer); + } + + public async ValueTask ReadUInt64Async(CancellationToken ct = default) + { + await _stream.ReadExactAsync(_buffer, 0, 8, ct).ConfigureAwait(false); + return BinaryPrimitives.ReadUInt64LittleEndian(_buffer); + } + + public async ValueTask ReadBytesAsync( + byte[] bytes, + int offset, + int count, + CancellationToken ct = default + ) + { + await _stream.ReadExactAsync(bytes, offset, count, ct).ConfigureAwait(false); + } + + public async ValueTask SkipAsync(int count, CancellationToken ct = default) + { + await _stream.SkipAsync(count, ct).ConfigureAwait(false); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + + // Dispose the original stream if we own it + if (!_leaveOpen) + { + _originalStream.Dispose(); + } + } + +#if NET8_0_OR_GREATER + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + _disposed = true; + + // Dispose the original stream if we own it + if (!_leaveOpen) + { + await _originalStream.DisposeAsync().ConfigureAwait(false); + } + } +#endif +} diff --git a/src/SharpCompress/IO/IStreamStack.cs b/src/SharpCompress/IO/IStreamStack.cs index df9156c44..d156ab757 100644 --- a/src/SharpCompress/IO/IStreamStack.cs +++ b/src/SharpCompress/IO/IStreamStack.cs @@ -6,77 +6,76 @@ using System.Linq; using System.Text; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +public interface IStreamStack { - public interface IStreamStack - { - /// - /// Returns the immediate underlying stream in the stack. - /// - Stream BaseStream(); - } + /// + /// Returns the immediate underlying stream in the stack. + /// + Stream BaseStream(); +} - public static class StreamStackExtensions +public static class StreamStackExtensions +{ + public static T? GetStream(this IStreamStack stack) + where T : Stream { - public static T? GetStream(this IStreamStack stack) - where T : Stream + var baseStream = stack.BaseStream(); + if (baseStream is T tStream) { - var baseStream = stack.BaseStream(); - if (baseStream is T tStream) - { - return tStream; - } - else if (baseStream is IStreamStack innerStack) - { - return innerStack.GetStream(); - } - else - { - return null; - } + return tStream; } - - /// - /// Gets the root underlying stream at the bottom of the stack. - /// This is useful for seeking when the intermediate streams don't support it. - /// - public static Stream GetRootStream(this IStreamStack stack) + else if (baseStream is IStreamStack innerStack) { - var current = stack.BaseStream(); - while (current is IStreamStack streamStack) - { - current = streamStack.BaseStream(); - } - return current; + return innerStack.GetStream(); + } + else + { + return null; } + } - internal static void Rewind(this IStreamStack stream, int count) + /// + /// Gets the root underlying stream at the bottom of the stack. + /// This is useful for seeking when the intermediate streams don't support it. + /// + public static Stream GetRootStream(this IStreamStack stack) + { + var current = stack.BaseStream(); + while (current is IStreamStack streamStack) { - IStreamStack? current = stream; + current = streamStack.BaseStream(); + } + return current; + } + + internal static void Rewind(this IStreamStack stream, int count) + { + IStreamStack? current = stream; - while (current != null) + while (current != null) + { + if (current is SharpCompressStream sharpCompressStream) { - if (current is RewindableStream rewindableStream) + // Try to rewind within the buffer. If the position is outside the buffered + // region, silently ignore (matching release behavior where streams without + // buffering simply didn't rewind). + var targetPosition = sharpCompressStream.Position - count; + if (targetPosition >= 0) { - // Try to rewind within the buffer. If the position is outside the buffered - // region, silently ignore (matching release behavior where streams without - // buffering simply didn't rewind). - var targetPosition = rewindableStream.Position - count; - if (targetPosition >= 0) + try + { + sharpCompressStream.Position = targetPosition; + } + catch (NotSupportedException) { - try - { - rewindableStream.Position = targetPosition; - } - catch (NotSupportedException) - { - // Cannot seek outside buffered region - silently ignore - } + // Cannot seek outside buffered region - silently ignore } - return; } - current = current.BaseStream() as IStreamStack; + return; } + current = current.BaseStream() as IStreamStack; } } } diff --git a/src/SharpCompress/IO/NonDisposingStream.cs b/src/SharpCompress/IO/NonDisposingStream.cs deleted file mode 100644 index 51fabf68f..000000000 --- a/src/SharpCompress/IO/NonDisposingStream.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SharpCompress.IO; - -/// -/// A stream wrapper that prevents disposal of the underlying stream. -/// This is useful when working with compression streams directly and you want -/// to keep the base stream open after the compression stream is disposed. -/// -internal class NonDisposingStream : Stream, IStreamStack -{ - private readonly Stream _stream; - private bool _isDisposed; - - /// - /// Gets or sets a value indicating whether to throw an exception when the stream is disposed. - /// This is useful for testing to ensure streams are not disposed prematurely. - /// - public bool ThrowOnDispose { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The stream to wrap. This stream will NOT be disposed when this wrapper is disposed. - /// Thrown when is null. - public NonDisposingStream(Stream stream) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - } - - public override bool CanRead => !_isDisposed && _stream.CanRead; - - public override bool CanSeek => - !_isDisposed && _stream.CanSeek && _stream is not RewindableStream; - - public override bool CanWrite => !_isDisposed && _stream.CanWrite; - - public override long Length - { - get - { - ThrowIfDisposed(); - return _stream.Length; - } - } - - public override long Position - { - get - { - ThrowIfDisposed(); - return _stream.Position; - } - set - { - ThrowIfDisposed(); - _stream.Position = value; - } - } - - public override void Flush() - { - ThrowIfDisposed(); - _stream.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - ThrowIfDisposed(); - return _stream.Read(buffer, offset, count); - } - -#if !LEGACY_DOTNET - public override int Read(Span buffer) - { - ThrowIfDisposed(); - return _stream.Read(buffer); - } -#endif - - public override long Seek(long offset, SeekOrigin origin) - { - ThrowIfDisposed(); - return _stream.Seek(offset, origin); - } - - public override void SetLength(long value) - { - ThrowIfDisposed(); - _stream.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - ThrowIfDisposed(); - _stream.Write(buffer, offset, count); - } - -#if !LEGACY_DOTNET - public override void Write(ReadOnlySpan buffer) - { - ThrowIfDisposed(); - _stream.Write(buffer); - } -#endif - - public override Task ReadAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken - ) - { - ThrowIfDisposed(); - return _stream.ReadAsync(buffer, offset, count, cancellationToken); - } - -#if !LEGACY_DOTNET - public override ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default - ) - { - ThrowIfDisposed(); - return _stream.ReadAsync(buffer, cancellationToken); - } -#endif - - public override Task WriteAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken - ) - { - ThrowIfDisposed(); - return _stream.WriteAsync(buffer, offset, count, cancellationToken); - } - -#if !LEGACY_DOTNET - public override ValueTask WriteAsync( - ReadOnlyMemory buffer, - CancellationToken cancellationToken = default - ) - { - ThrowIfDisposed(); - return _stream.WriteAsync(buffer, cancellationToken); - } -#endif - - public override Task FlushAsync(CancellationToken cancellationToken) - { - ThrowIfDisposed(); - return _stream.FlushAsync(cancellationToken); - } - - public override Task CopyToAsync( - Stream destination, - int bufferSize, - CancellationToken cancellationToken - ) - { - ThrowIfDisposed(); - return _stream.CopyToAsync(destination, bufferSize, cancellationToken); - } - - /// - /// Disposes this wrapper without disposing the underlying stream. - /// - protected override void Dispose(bool disposing) - { - if (!_isDisposed) - { - if (ThrowOnDispose) - { - throw new InvalidOperationException( - $"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is true" - ); - } - _isDisposed = true; - // Intentionally do NOT dispose _stream - } - base.Dispose(disposing); - } - -#if !LEGACY_DOTNET - /// - /// Asynchronously disposes this wrapper without disposing the underlying stream. - /// - public override async ValueTask DisposeAsync() - { - if (!_isDisposed) - { - if (ThrowOnDispose) - { - throw new InvalidOperationException( - $"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is true" - ); - } - _isDisposed = true; - // Intentionally do NOT dispose _stream - } - await base.DisposeAsync(); - } -#endif - - private void ThrowIfDisposed() - { - if (_isDisposed) - { - throw new ObjectDisposedException(nameof(NonDisposingStream)); - } - } - - public int DefaultBufferSize { get; set; } - - public Stream BaseStream() => _stream; - - public int BufferSize { get; set; } - public int BufferPosition { get; set; } - - public void SetPosition(long position) => throw new NotImplementedException(); -} diff --git a/src/SharpCompress/IO/ReadOnlySubStream.cs b/src/SharpCompress/IO/ReadOnlySubStream.cs index 728b05124..763000deb 100644 --- a/src/SharpCompress/IO/ReadOnlySubStream.cs +++ b/src/SharpCompress/IO/ReadOnlySubStream.cs @@ -6,10 +6,6 @@ namespace SharpCompress.IO; internal partial class ReadOnlySubStream : Stream, IStreamStack { -#if DEBUG_STREAMS - long IStreamStack.InstanceId { get; set; } -#endif - Stream IStreamStack.BaseStream() => _stream; private readonly Stream _stream; @@ -30,9 +26,6 @@ public ReadOnlySubStream(Stream stream, long? origin, long bytesToRead, bool lea } BytesLeftToRead = bytesToRead; _position = 0; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ReadOnlySubStream)); -#endif } private long BytesLeftToRead { get; set; } @@ -106,9 +99,6 @@ public override void Write(byte[] buffer, int offset, int count) => protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(ReadOnlySubStream)); -#endif if (disposing && !_leaveOpen) { _stream.Dispose(); diff --git a/src/SharpCompress/IO/RewindableStream.cs b/src/SharpCompress/IO/RewindableStream.cs deleted file mode 100644 index aeaef809c..000000000 --- a/src/SharpCompress/IO/RewindableStream.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; -using System.IO; - -namespace SharpCompress.IO; - -internal partial class RewindableStream : Stream, IStreamStack -{ - public virtual Stream BaseStream() => stream; - - private readonly Stream stream; - private MemoryStream bufferStream = new MemoryStream(); - private bool isRewound; - private bool isDisposed; - private long streamPosition; - - // Rolling buffer for limited backward seeking without unbounded memory growth. - private RingBuffer? _ringBuffer; - private long _logicalPosition; // The current logical read position (can be behind streamPosition) - - /// - /// Default size for rolling buffer (same as .NET Stream.CopyTo default) - /// - public const int DefaultRollingBufferSize = 81920; - - public RewindableStream(Stream stream) - { - this.stream = stream; - _logicalPosition = 0; - } - - /// - /// Creates a RewindableStream with a rolling buffer that enables limited backward seeking. - /// - /// The underlying stream to wrap. - /// Size of the rolling buffer in bytes. - public RewindableStream(Stream stream, int rollingBufferSize) - : this(stream) - { - if (rollingBufferSize > 0) - { - _ringBuffer = new RingBuffer(rollingBufferSize); - } - } - - internal virtual bool IsRecording { get; private set; } - - protected override void Dispose(bool disposing) - { - if (isDisposed) - { - return; - } - isDisposed = true; - base.Dispose(disposing); - if (disposing) - { - stream.Dispose(); - _ringBuffer?.Dispose(); - _ringBuffer = null; - } - } - - public void Rewind() => Rewind(false); - - public virtual void Rewind(bool stopRecording) - { - isRewound = true; - IsRecording = !stopRecording; - bufferStream.Position = 0; - } - - public virtual void StopRecording() - { - if (!IsRecording) - { - throw new InvalidOperationException( - "StopRecording can only be called when recording is active." - ); - } - isRewound = true; - IsRecording = false; - bufferStream.Position = 0; - } - - public static RewindableStream EnsureSeekable(Stream stream) - { - if (stream is RewindableStream rewindableStream) - { - return rewindableStream; - } - - // Check if stream is wrapping a RewindableStream (e.g., NonDisposingStream) - if (stream is IStreamStack streamStack) - { - var underlying = streamStack.GetStream(); - if (underlying is not null) - { - return underlying; - } - } - - if (stream.CanSeek) - { - return new SeekableRewindableStream(stream); - } - - // For non-seekable streams, create a RewindableStream with rolling buffer - // to allow limited backward seeking (required by decompressors that over-read) - return new RewindableStream(stream, DefaultRollingBufferSize); - } - - public virtual void StartRecording() - { - if (IsRecording) - { - throw new InvalidOperationException( - "StartRecording can only be called when not already recording." - ); - } - //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 => true; - - public override bool CanWrite => false; - - public override void Flush() => throw new NotSupportedException(); - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get - { - // If recording is active or rewound from recording, use recording buffer position - if (IsRecording || (isRewound && bufferStream.Position < bufferStream.Length)) - { - return streamPosition - bufferStream.Length + bufferStream.Position; - } - // If ring buffer is active (and not recording), use logical position - if (_ringBuffer is not null) - { - return _logicalPosition; - } - return streamPosition; - } - set => SeekToPosition(value); - } - - private void SeekToPosition(long targetPosition) - { - // If recording is active, use recording buffer for seeking - if (IsRecording || isRewound) - { - long bufferStart = streamPosition - bufferStream.Length; - long bufferEnd = streamPosition; - - if (targetPosition >= bufferStart && targetPosition <= bufferEnd) - { - isRewound = true; - bufferStream.Position = targetPosition - bufferStart; - return; - } - throw new NotSupportedException("Cannot seek outside recorded region."); - } - - // If ring buffer is enabled, check if we can seek within it - if (_ringBuffer is not null) - { - long ringBufferStart = streamPosition - _ringBuffer.Length; - if (targetPosition >= ringBufferStart && targetPosition <= streamPosition) - { - _logicalPosition = targetPosition; - return; - } - // Can't seek outside ring buffer range - throw new NotSupportedException( - $"Cannot seek to position {targetPosition}. Valid range with ring buffer: [{ringBufferStart}, {streamPosition}]" - ); - } - - // No buffering available - throw new NotSupportedException("Cannot seek on non-buffered stream."); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (count == 0) - { - return 0; - } - - // If recording is active or we're reading from the recording buffer, use legacy behavior - // Recording takes precedence over rolling buffer for format detection - if (IsRecording || (isRewound && bufferStream.Position != bufferStream.Length)) - { - return ReadWithRecording(buffer, offset, count); - } - - // If ring buffer is enabled (and not recording), use ring buffer logic - if (_ringBuffer is not null) - { - return ReadWithRingBuffer(buffer, offset, count); - } - - // No buffering - read directly from stream - int read = stream.Read(buffer, offset, count); - streamPosition += read; - _logicalPosition = streamPosition; - return read; - } - - /// - /// Reads data using the recording buffer (legacy behavior for format detection). - /// - private int ReadWithRecording(byte[] buffer, int offset, int count) - { - int read; - if (isRewound && bufferStream.Position != bufferStream.Length) - { - var readCount = Math.Min(count, (int)(bufferStream.Length - bufferStream.Position)); - read = bufferStream.Read(buffer, offset, readCount); - if (read < count) - { - var tempRead = stream.Read(buffer, offset + read, count - read); - if (IsRecording) - { - bufferStream.Write(buffer, offset + read, tempRead); - } - else if (_ringBuffer is not null && tempRead > 0) - { - // When transitioning out of recording mode, add to ring buffer - // so that future rewinds will work - _ringBuffer.Write(buffer, offset + read, tempRead); - } - streamPosition += tempRead; - _logicalPosition = streamPosition; - read += tempRead; - } - if (bufferStream.Position == bufferStream.Length) - { - isRewound = false; - } - return read; - } - - read = stream.Read(buffer, offset, count); - if (IsRecording) - { - bufferStream.Write(buffer, offset, read); - } - streamPosition += read; - _logicalPosition = streamPosition; - return read; - } - - /// - /// Reads data using the ring buffer. If logical position is behind stream position, - /// serves data from the ring buffer first. - /// - private int ReadWithRingBuffer(byte[] buffer, int offset, int count) - { - int totalRead = 0; - - // If logical position is behind stream position, read from ring buffer first - while (count > 0 && _logicalPosition < streamPosition) - { - long bytesFromEnd = streamPosition - _logicalPosition; - int available = _ringBuffer!.ReadFromEnd(bytesFromEnd, buffer, offset, count); - totalRead += available; - offset += available; - count -= available; - _logicalPosition += available; - } - - // If more data needed, read from underlying stream - if (count > 0) - { - int read = stream.Read(buffer, offset, count); - if (read > 0) - { - _ringBuffer!.Write(buffer, offset, read); - streamPosition += read; - _logicalPosition += read; - totalRead += read; - } - } - - return totalRead; - } - - public override long Seek(long offset, SeekOrigin origin) - { - long targetPosition = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => Position + offset, - SeekOrigin.End => throw new NotSupportedException("Seeking from end is not supported."), - _ => throw new ArgumentOutOfRangeException(nameof(origin)), - }; - - SeekToPosition(targetPosition); - return targetPosition; - } - - 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/SeekableRewindableStream.Async.cs b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs similarity index 74% rename from src/SharpCompress/IO/SeekableRewindableStream.Async.cs rename to src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs index 400a6553a..34a9ec0eb 100644 --- a/src/SharpCompress/IO/SeekableRewindableStream.Async.cs +++ b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs @@ -5,7 +5,7 @@ namespace SharpCompress.IO; -internal sealed partial class SeekableRewindableStream +internal sealed partial class SeekableSharpCompressStream { public override Task ReadAsync( byte[] buffer, @@ -27,7 +27,21 @@ public override ValueTask WriteAsync( public override ValueTask DisposeAsync() { - _underlyingStream.Dispose(); + if (_isDisposed) + { + return base.DisposeAsync(); + } + if (ThrowOnDispose) + { + throw new InvalidOperationException( + $"Attempt to dispose of a {nameof(SeekableSharpCompressStream)} when {nameof(ThrowOnDispose)} is true" + ); + } + _isDisposed = true; + if (!LeaveStreamOpen) + { + _underlyingStream.Dispose(); + } return base.DisposeAsync(); } #endif diff --git a/src/SharpCompress/IO/SeekableRewindableStream.cs b/src/SharpCompress/IO/SeekableSharpCompressStream.cs similarity index 79% rename from src/SharpCompress/IO/SeekableRewindableStream.cs rename to src/SharpCompress/IO/SeekableSharpCompressStream.cs index 12cbc7c16..0e8d83d27 100644 --- a/src/SharpCompress/IO/SeekableRewindableStream.cs +++ b/src/SharpCompress/IO/SeekableSharpCompressStream.cs @@ -3,14 +3,26 @@ namespace SharpCompress.IO; -internal sealed partial class SeekableRewindableStream : RewindableStream +internal sealed partial class SeekableSharpCompressStream : SharpCompressStream { public override Stream BaseStream() => _underlyingStream; private readonly Stream _underlyingStream; private long? _recordedPosition; + private bool _isDisposed; - public SeekableRewindableStream(Stream stream) + /// + /// Gets or sets whether to leave the underlying stream open when disposed. + /// + public new bool LeaveStreamOpen { get; set; } + + /// + /// Gets or sets whether to throw an exception when Dispose is called. + /// Useful for testing to ensure streams are not disposed prematurely. + /// + public new bool ThrowOnDispose { get; set; } + + public SeekableSharpCompressStream(Stream stream) : base(new NullStream()) { if (stream is null) @@ -87,7 +99,18 @@ public override void StopRecording() protected override void Dispose(bool disposing) { - if (disposing) + if (_isDisposed) + { + return; + } + if (ThrowOnDispose) + { + throw new InvalidOperationException( + $"Attempt to dispose of a {nameof(SeekableSharpCompressStream)} when {nameof(ThrowOnDispose)} is true" + ); + } + _isDisposed = true; + if (disposing && !LeaveStreamOpen) { _underlyingStream.Dispose(); } diff --git a/src/SharpCompress/IO/RewindableStream.Async.cs b/src/SharpCompress/IO/SharpCompressStream.Async.cs similarity index 50% rename from src/SharpCompress/IO/RewindableStream.Async.cs rename to src/SharpCompress/IO/SharpCompressStream.Async.cs index 48daa3ef8..3544a649b 100644 --- a/src/SharpCompress/IO/RewindableStream.Async.cs +++ b/src/SharpCompress/IO/SharpCompressStream.Async.cs @@ -5,9 +5,9 @@ namespace SharpCompress.IO; -internal partial class RewindableStream +internal partial class SharpCompressStream { - public override async Task ReadAsync( + public override Task ReadAsync( byte[] buffer, int offset, int count, @@ -16,86 +16,36 @@ CancellationToken cancellationToken { if (count == 0) { - return 0; + return Task.FromResult(0); } - // If recording is active or we're reading from the recording buffer, use legacy behavior - if (IsRecording || (isRewound && bufferStream.Position != bufferStream.Length)) + // In passthrough mode, delegate directly to underlying stream + if (_isPassthrough) { - return await ReadWithRecordingAsync(buffer, offset, count, cancellationToken) - .ConfigureAwait(false); - } - - // If ring buffer is enabled (and not recording), use ring buffer logic - if (_ringBuffer is not null) - { - return await ReadWithRingBufferAsync(buffer, offset, count, cancellationToken) - .ConfigureAwait(false); + return stream.ReadAsync(buffer, offset, count, cancellationToken); } - // No buffering - read directly from stream - int read = await stream - .ReadAsync(buffer, offset, count, cancellationToken) - .ConfigureAwait(false); - streamPosition += read; - _logicalPosition = streamPosition; - return read; + return ReadAsyncCore(buffer, offset, count, cancellationToken); } - /// - /// Async version of ReadWithRecording (legacy behavior for format detection). - /// - private async Task ReadWithRecordingAsync( + private async Task ReadAsyncCore( byte[] buffer, int offset, int count, CancellationToken cancellationToken ) { - int read; - if (isRewound && bufferStream.Position != bufferStream.Length) + // If ring buffer is enabled, use ring buffer logic + if (_ringBuffer is not null) { - var readCount = Math.Min(count, (int)(bufferStream.Length - bufferStream.Position)); - read = await bufferStream - .ReadAsync(buffer, offset, readCount, cancellationToken) + return await ReadWithRingBufferAsync(buffer, offset, count, cancellationToken) .ConfigureAwait(false); - if (read < count) - { - var tempRead = await stream - .ReadAsync(buffer, offset + read, count - read, cancellationToken) - .ConfigureAwait(false); - if (IsRecording) - { - await bufferStream - .WriteAsync(buffer, offset + read, tempRead, cancellationToken) - .ConfigureAwait(false); - } - else if (_ringBuffer is not null && tempRead > 0) - { - // When transitioning out of recording mode, add to ring buffer - // so that future rewinds will work - _ringBuffer.Write(buffer, offset + read, tempRead); - } - streamPosition += tempRead; - _logicalPosition = streamPosition; - read += tempRead; - } - if (bufferStream.Position == bufferStream.Length) - { - isRewound = false; - } - return read; } - read = await stream + // No buffering - read directly from stream + int read = await stream .ReadAsync(buffer, offset, count, cancellationToken) .ConfigureAwait(false); - if (IsRecording) - { - await bufferStream - .WriteAsync(buffer, offset, read, cancellationToken) - .ConfigureAwait(false); - } streamPosition += read; _logicalPosition = streamPosition; return read; @@ -117,15 +67,25 @@ CancellationToken cancellationToken while (count > 0 && _logicalPosition < streamPosition) { long bytesFromEnd = streamPosition - _logicalPosition; - int available = _ringBuffer!.ReadFromEnd(bytesFromEnd, buffer, offset, count); + + // Verify data is available in ring buffer + if (!_ringBuffer!.CanReadFromEnd(bytesFromEnd)) + { + throw new InvalidOperationException( + $"Ring buffer underflow: trying to read {bytesFromEnd} bytes back, " + + $"but buffer only holds {_ringBuffer.Length} bytes." + ); + } + + int available = _ringBuffer.ReadFromEnd(bytesFromEnd, buffer, offset, count); totalRead += available; offset += available; count -= available; _logicalPosition += available; } - // If more data needed, read from underlying stream - if (count > 0) + // If more data needed and we're caught up, read from underlying stream + if (count > 0 && _logicalPosition == streamPosition) { int read = await stream .ReadAsync(buffer, offset, count, cancellationToken) @@ -143,87 +103,38 @@ CancellationToken cancellationToken } #if !LEGACY_DOTNET - public override async ValueTask ReadAsync( + public override ValueTask ReadAsync( Memory buffer, CancellationToken cancellationToken = default ) { if (buffer.Length == 0) { - return 0; + return ValueTask.FromResult(0); } - // If recording is active or we're reading from the recording buffer, use legacy behavior - if (IsRecording || (isRewound && bufferStream.Position != bufferStream.Length)) - { - return await ReadWithRecordingAsync(buffer, cancellationToken).ConfigureAwait(false); - } - - // If ring buffer is enabled (and not recording), use ring buffer logic - if (_ringBuffer is not null) + // In passthrough mode, delegate directly to underlying stream + if (_isPassthrough) { - return await ReadWithRingBufferAsync(buffer, cancellationToken).ConfigureAwait(false); + return stream.ReadAsync(buffer, cancellationToken); } - // No buffering - read directly from stream - int read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - streamPosition += read; - _logicalPosition = streamPosition; - return read; + return ReadAsyncCore(buffer, cancellationToken); } - /// - /// Async version of ReadWithRecording for Memory<byte> (legacy behavior for format detection). - /// - private async ValueTask ReadWithRecordingAsync( + private async ValueTask ReadAsyncCore( Memory buffer, CancellationToken cancellationToken ) { - int read; - if (isRewound && bufferStream.Position != bufferStream.Length) + // If ring buffer is enabled, use ring buffer logic + if (_ringBuffer is not null) { - var readCount = (int) - Math.Min(buffer.Length, bufferStream.Length - bufferStream.Position); - read = await bufferStream - .ReadAsync(buffer.Slice(0, readCount), cancellationToken) - .ConfigureAwait(false); - if (read < buffer.Length) - { - var tempRead = await stream - .ReadAsync(buffer.Slice(read), cancellationToken) - .ConfigureAwait(false); - if (IsRecording) - { - await bufferStream - .WriteAsync(buffer.Slice(read, tempRead), cancellationToken) - .ConfigureAwait(false); - } - else if (_ringBuffer is not null && tempRead > 0) - { - // When transitioning out of recording mode, add to ring buffer - // so that future rewinds will work - var tempBuffer = buffer.Slice(read, tempRead).ToArray(); - _ringBuffer.Write(tempBuffer, 0, tempRead); - } - streamPosition += tempRead; - _logicalPosition = streamPosition; - read += tempRead; - } - if (bufferStream.Position == bufferStream.Length) - { - isRewound = false; - } - return read; + return await ReadWithRingBufferAsync(buffer, cancellationToken).ConfigureAwait(false); } - read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - if (IsRecording) - { - await bufferStream - .WriteAsync(buffer.Slice(0, read), cancellationToken) - .ConfigureAwait(false); - } + // No buffering - read directly from stream + int read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); streamPosition += read; _logicalPosition = streamPosition; return read; @@ -246,13 +157,18 @@ CancellationToken cancellationToken while (count > 0 && _logicalPosition < streamPosition) { long bytesFromEnd = streamPosition - _logicalPosition; + + // Verify data is available in ring buffer + if (!_ringBuffer!.CanReadFromEnd(bytesFromEnd)) + { + throw new InvalidOperationException( + $"Ring buffer underflow: trying to read {bytesFromEnd} bytes back, " + + $"but buffer only holds {_ringBuffer.Length} bytes." + ); + } + var tempBuffer = new byte[Math.Min(count, (int)bytesFromEnd)]; - int available = _ringBuffer!.ReadFromEnd( - bytesFromEnd, - tempBuffer, - 0, - tempBuffer.Length - ); + int available = _ringBuffer.ReadFromEnd(bytesFromEnd, tempBuffer, 0, tempBuffer.Length); tempBuffer.AsSpan(0, available).CopyTo(buffer.Span.Slice(offset)); totalRead += available; @@ -261,8 +177,8 @@ CancellationToken cancellationToken _logicalPosition += available; } - // If more data needed, read from underlying stream - if (count > 0) + // If more data needed and we're caught up, read from underlying stream + if (count > 0 && _logicalPosition == streamPosition) { int read = await stream .ReadAsync(buffer.Slice(offset, count), cancellationToken) @@ -287,17 +203,37 @@ public override Task WriteAsync( int offset, int count, CancellationToken cancellationToken - ) => throw new NotSupportedException(); + ) + { + if (_isPassthrough) + { + return stream.WriteAsync(buffer, offset, count, cancellationToken); + } + throw new NotSupportedException(); + } #if !LEGACY_DOTNET public override ValueTask WriteAsync( ReadOnlyMemory buffer, CancellationToken cancellationToken = default - ) => throw new NotSupportedException(); + ) + { + if (_isPassthrough) + { + return stream.WriteAsync(buffer, cancellationToken); + } + throw new NotSupportedException(); + } #endif - public override Task FlushAsync(CancellationToken cancellationToken) => + public override Task FlushAsync(CancellationToken cancellationToken) + { + if (_isPassthrough) + { + return stream.FlushAsync(cancellationToken); + } throw new NotSupportedException(); + } public override async Task CopyToAsync( Stream destination, @@ -318,8 +254,17 @@ public override async ValueTask DisposeAsync() { if (!isDisposed) { + if (ThrowOnDispose) + { + throw new InvalidOperationException( + $"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is true" + ); + } isDisposed = true; - await stream.DisposeAsync(); + if (!LeaveStreamOpen) + { + await stream.DisposeAsync(); + } _ringBuffer?.Dispose(); _ringBuffer = null; } diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs new file mode 100644 index 000000000..eae6217f5 --- /dev/null +++ b/src/SharpCompress/IO/SharpCompressStream.cs @@ -0,0 +1,471 @@ +using System; +using System.IO; +using SharpCompress.Common; + +namespace SharpCompress.IO; + +internal partial class SharpCompressStream : Stream, IStreamStack +{ + public virtual Stream BaseStream() => stream; + + private readonly Stream stream; + private bool isDisposed; + private long streamPosition; + + // Ring buffer for recording mode and over-read protection. + // Single unified buffering mechanism for both use cases. + private RingBuffer? _ringBuffer; + private long _logicalPosition; // The current logical read position (can be behind streamPosition) + + // Recording state: anchor position when StartRecording was called + private long? _recordingStartPosition; + private bool _isRecording; + + // Passthrough mode - no buffering, delegates CanSeek to underlying stream + private readonly bool _isPassthrough; + + /// + /// Gets whether this stream is in passthrough mode (no buffering, delegates to underlying stream). + /// + internal bool IsPassthrough => _isPassthrough; + + /// + /// Default size for rolling buffer (same as .NET Stream.CopyTo default) + /// + public const int DefaultRollingBufferSize = 81920; + + /// + /// Gets or sets whether to leave the underlying stream open when disposed. + /// + public bool LeaveStreamOpen { get; set; } + + /// + /// Gets or sets whether to throw an exception when Dispose is called. + /// Useful for testing to ensure streams are not disposed prematurely. + /// + public bool ThrowOnDispose { get; set; } + + public SharpCompressStream(Stream stream) + { + this.stream = stream; + _logicalPosition = 0; + } + + /// + /// Creates a SharpCompressStream with a rolling buffer that enables limited backward seeking. + /// + /// The underlying stream to wrap. + /// Size of the rolling buffer in bytes. + public SharpCompressStream(Stream stream, int rollingBufferSize) + : this(stream) + { + if (rollingBufferSize > 0) + { + _ringBuffer = new RingBuffer(rollingBufferSize); + } + } + + /// + /// Private constructor for passthrough mode. + /// + private SharpCompressStream(Stream stream, bool leaveStreamOpen, bool passthrough) + { + this.stream = stream; + LeaveStreamOpen = leaveStreamOpen; + _isPassthrough = passthrough; + _logicalPosition = 0; + } + + /// + /// Creates a SharpCompressStream that acts as a passthrough wrapper. + /// No buffering is performed; CanSeek delegates to the underlying stream. + /// The underlying stream will not be disposed when this stream is disposed. + /// + public static SharpCompressStream CreateNonDisposing(Stream stream) => + new(stream, leaveStreamOpen: true, passthrough: true); + + /// + /// Gets whether the stream is actively recording reads to the ring buffer. + /// + internal virtual bool IsRecording => _isRecording; + + protected override void Dispose(bool disposing) + { + if (isDisposed) + { + return; + } + if (ThrowOnDispose) + { + throw new InvalidOperationException( + $"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is true" + ); + } + isDisposed = true; + base.Dispose(disposing); + if (disposing) + { + if (!LeaveStreamOpen) + { + stream.Dispose(); + } + _ringBuffer?.Dispose(); + _ringBuffer = null; + } + } + + public void Rewind() => Rewind(false); + + public virtual void Rewind(bool stopRecording) + { + if (_isPassthrough) + { + throw new InvalidOperationException( + "Rewind cannot be called on a passthrough stream. Use EnsureSeekable() first." + ); + } + + if (_recordingStartPosition is null) + { + throw new InvalidOperationException( + "Rewind can only be called after StartRecording() has been called." + ); + } + + // Verify recording anchor is within ring buffer range + long anchorAge = streamPosition - _recordingStartPosition.Value; + if (anchorAge > _ringBuffer!.Length) + { + throw new InvalidOperationException( + $"Cannot rewind: recording anchor is {anchorAge} bytes behind current position, " + + $"but ring buffer only holds {_ringBuffer.Length} bytes. " + + $"Recording buffer overflow - increase DefaultRollingBufferSize or reduce format detection reads." + ); + } + + // Rewind logical position to recording anchor + _logicalPosition = _recordingStartPosition.Value; + + if (stopRecording) + { + _isRecording = false; + // Note: We keep _recordingStartPosition so Rewind() can be called again + // (frozen recording mode). The anchor is only cleared when a new recording + // starts or the stream is disposed. + } + } + + public virtual void StopRecording() + { + if (_isPassthrough) + { + throw new InvalidOperationException( + "StopRecording cannot be called on a passthrough stream. Use EnsureSeekable() first." + ); + } + if (!IsRecording) + { + throw new InvalidOperationException( + "StopRecording can only be called when recording is active." + ); + } + + // Mark that we're no longer actively recording + _isRecording = false; + + // Rewind to recording anchor position + _logicalPosition = _recordingStartPosition!.Value; + + // Note: We keep _recordingStartPosition so future Rewind() calls still work + // (frozen recording mode) until Rewind(stopRecording: true) is called + } + + public static SharpCompressStream EnsureSeekable( + Stream stream, + int? rewindableBufferSize = null + ) + { + int bufferSize = rewindableBufferSize ?? Constants.RewindableBufferSize; + + // If it's a passthrough SharpCompressStream, unwrap it and create proper seekable wrapper + if (stream is SharpCompressStream sharpCompressStream) + { + if (sharpCompressStream._isPassthrough) + { + // Unwrap the passthrough and create appropriate wrapper + var underlying = sharpCompressStream.stream; + if (underlying.CanSeek) + { + // Create SeekableSharpCompressStream that preserves LeaveStreamOpen + return new SeekableSharpCompressStream(underlying) + { + LeaveStreamOpen = true, // Preserve non-disposing behavior + }; + } + // Non-seekable underlying stream - wrap with rolling buffer + return new SharpCompressStream(underlying, bufferSize) { LeaveStreamOpen = true }; + } + // Not passthrough - return as-is + return sharpCompressStream; + } + + // Check if stream is wrapping a SharpCompressStream (e.g., via IStreamStack) + if (stream is IStreamStack streamStack) + { + var underlying = streamStack.GetStream(); + if (underlying is not null) + { + return underlying; + } + } + + if (stream.CanSeek) + { + return new SeekableSharpCompressStream(stream); + } + + // For non-seekable streams, create a SharpCompressStream with rolling buffer + // to allow limited backward seeking (required by decompressors that over-read) + return new SharpCompressStream(stream, bufferSize); + } + + public virtual void StartRecording() + { + if (_isPassthrough) + { + throw new InvalidOperationException( + "StartRecording cannot be called on a passthrough stream. Use EnsureSeekable() first." + ); + } + if (IsRecording) + { + throw new InvalidOperationException( + "StartRecording can only be called when not already recording." + ); + } + + // Ensure ring buffer exists + if (_ringBuffer is null) + { + _ringBuffer = new RingBuffer(DefaultRollingBufferSize); + } + + // Mark current position as recording anchor + _recordingStartPosition = streamPosition; + _logicalPosition = streamPosition; + _isRecording = true; + } + + public override bool CanRead => true; + + public override bool CanSeek => _isPassthrough ? stream.CanSeek : true; + + public override bool CanWrite => _isPassthrough && stream.CanWrite; + + public override void Flush() + { + if (_isPassthrough) + { + stream.Flush(); + return; + } + throw new NotSupportedException(); + } + + public override long Length => + _isPassthrough ? stream.Length : throw new NotSupportedException(); + + public override long Position + { + get + { + // In passthrough mode, delegate to underlying stream + if (_isPassthrough) + { + return stream.Position; + } + // Use logical position (same for both recording and ring buffer modes) + return _logicalPosition; + } + set + { + // In passthrough mode, delegate to underlying stream + if (_isPassthrough) + { + stream.Position = value; + return; + } + SeekToPosition(value); + } + } + + private void SeekToPosition(long targetPosition) + { + // If we have a recording anchor, allow seeking within the recorded range + if (_recordingStartPosition is not null) + { + if (targetPosition >= _recordingStartPosition.Value && targetPosition <= streamPosition) + { + _logicalPosition = targetPosition; + return; + } + throw new NotSupportedException( + $"Cannot seek to position {targetPosition}. Valid recorded range: " + + $"[{_recordingStartPosition.Value}, {streamPosition}]" + ); + } + + // If ring buffer is enabled (and not recording), check if we can seek within it + if (_ringBuffer is not null) + { + long ringBufferStart = streamPosition - _ringBuffer.Length; + if (targetPosition >= ringBufferStart && targetPosition <= streamPosition) + { + _logicalPosition = targetPosition; + return; + } + throw new NotSupportedException( + $"Cannot seek to position {targetPosition}. Valid ring buffer range: " + + $"[{ringBufferStart}, {streamPosition}]" + ); + } + + // No buffering available + throw new NotSupportedException("Cannot seek on non-buffered stream."); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count == 0) + { + return 0; + } + + // In passthrough mode, delegate directly to underlying stream + if (_isPassthrough) + { + return stream.Read(buffer, offset, count); + } + + // If ring buffer exists, use unified buffered read logic + if (_ringBuffer is not null) + { + return ReadWithRingBuffer(buffer, offset, count); + } + + // No buffering - read directly from stream + int read = stream.Read(buffer, offset, count); + streamPosition += read; + _logicalPosition = streamPosition; + return read; + } + + /// + /// Reads data using the ring buffer. If logical position is behind stream position, + /// serves data from the ring buffer first. Handles both recording mode and + /// over-read protection uniformly. + /// + private int ReadWithRingBuffer(byte[] buffer, int offset, int count) + { + int totalRead = 0; + + // If logical position is behind stream position, read from ring buffer first + while (count > 0 && _logicalPosition < streamPosition) + { + long bytesFromEnd = streamPosition - _logicalPosition; + + // Verify data is available in ring buffer + if (!_ringBuffer!.CanReadFromEnd(bytesFromEnd)) + { + throw new InvalidOperationException( + $"Ring buffer underflow: trying to read {bytesFromEnd} bytes back, " + + $"but buffer only holds {_ringBuffer.Length} bytes." + ); + } + + int available = _ringBuffer.ReadFromEnd(bytesFromEnd, buffer, offset, count); + totalRead += available; + offset += available; + count -= available; + _logicalPosition += available; + } + + // If more data needed and we're caught up, read from underlying stream + if (count > 0 && _logicalPosition == streamPosition) + { + // Use async read if stream doesn't support sync reads (e.g., AsyncOnlyStream) + int read = stream.Read(buffer, offset, count); + if (read > 0) + { + _ringBuffer!.Write(buffer, offset, read); + streamPosition += read; + _logicalPosition += read; + totalRead += read; + } + } + + return totalRead; + } + + public override long Seek(long offset, SeekOrigin origin) + { + // In passthrough mode, delegate to underlying stream + if (_isPassthrough) + { + return stream.Seek(offset, origin); + } + + long targetPosition = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => Position + offset, + SeekOrigin.End => throw new NotSupportedException("Seeking from end is not supported."), + _ => throw new ArgumentOutOfRangeException(nameof(origin)), + }; + + SeekToPosition(targetPosition); + return targetPosition; + } + + public override void SetLength(long value) + { + if (_isPassthrough) + { + stream.SetLength(value); + return; + } + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (_isPassthrough) + { + stream.Write(buffer, offset, count); + return; + } + throw new NotSupportedException(); + } + +#if !LEGACY_DOTNET + public override int Read(Span buffer) + { + if (_isPassthrough) + { + return stream.Read(buffer); + } + // Fall back to base implementation for buffered modes + return base.Read(buffer); + } + + public override void Write(ReadOnlySpan buffer) + { + if (_isPassthrough) + { + stream.Write(buffer); + return; + } + throw new NotSupportedException(); + } +#endif +} diff --git a/src/SharpCompress/IO/SourceStream.cs b/src/SharpCompress/IO/SourceStream.cs index 673285ddf..35ec3b9ae 100644 --- a/src/SharpCompress/IO/SourceStream.cs +++ b/src/SharpCompress/IO/SourceStream.cs @@ -58,10 +58,6 @@ ReaderOptions options } _stream = 0; _prevSize = 0; - -#if DEBUG_STREAMS - this.DebugConstruct(typeof(SourceStream)); -#endif } public void LoadAllParts() @@ -262,9 +258,6 @@ 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/Polyfills/StreamExtensions.cs b/src/SharpCompress/Polyfills/StreamExtensions.cs index aec6ce867..2c654ccbf 100644 --- a/src/SharpCompress/Polyfills/StreamExtensions.cs +++ b/src/SharpCompress/Polyfills/StreamExtensions.cs @@ -13,7 +13,7 @@ public static class StreamExtensions { public void Skip(long advanceAmount) { - if (stream.CanSeek && stream is not RewindableStream) + if (stream.CanSeek && stream is not SharpCompressStream) { stream.Position += advanceAmount; return; diff --git a/src/SharpCompress/Readers/AbstractReader.Async.cs b/src/SharpCompress/Readers/AbstractReader.Async.cs index 979f701db..958045e03 100644 --- a/src/SharpCompress/Readers/AbstractReader.Async.cs +++ b/src/SharpCompress/Readers/AbstractReader.Async.cs @@ -178,11 +178,7 @@ protected virtual async ValueTask GetEntryStreamAsync( .Parts.First() .GetCompressedStreamAsync(cancellationToken) .ConfigureAwait(false); - var useSyncOverAsync = false; -#if LEGACY_DOTNET - useSyncOverAsync = true; -#endif - return CreateEntryStream(stream, useSyncOverAsync); + return CreateEntryStream(stream); } internal virtual ValueTask NextEntryForCurrentStreamAsync() => diff --git a/src/SharpCompress/Readers/AbstractReader.cs b/src/SharpCompress/Readers/AbstractReader.cs index 46b595b0e..c2a12a2ae 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -246,7 +246,7 @@ public EntryStream OpenEntryStream() { throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); } - var stream = GetEntryStream(false); + var stream = GetEntryStream(); _wroteCurrentEntry = true; return stream; } @@ -254,11 +254,11 @@ public EntryStream OpenEntryStream() /// /// Retains a reference to the entry stream, so we can check whether it completed later. /// - protected EntryStream CreateEntryStream(Stream? decompressed, bool useSyncOverAsyncDispose) => - new(this, decompressed.NotNull(), useSyncOverAsyncDispose); + protected EntryStream CreateEntryStream(Stream? decompressed) => + new(this, decompressed.NotNull()); - protected virtual EntryStream GetEntryStream(bool useSyncOverAsyncDispose) => - CreateEntryStream(Entry.Parts.First().GetCompressedStream(), useSyncOverAsyncDispose); + protected virtual EntryStream GetEntryStream() => + CreateEntryStream(Entry.Parts.First().GetCompressedStream()); #endregion diff --git a/src/SharpCompress/Readers/Ace/MultiVolumeAceReader.cs b/src/SharpCompress/Readers/Ace/MultiVolumeAceReader.cs index a729ab3fd..0b000f128 100644 --- a/src/SharpCompress/Readers/Ace/MultiVolumeAceReader.cs +++ b/src/SharpCompress/Readers/Ace/MultiVolumeAceReader.cs @@ -10,108 +10,107 @@ using SharpCompress.Common; using SharpCompress.Common.Ace; -namespace SharpCompress.Readers.Ace +namespace SharpCompress.Readers.Ace; + +internal class MultiVolumeAceReader : AceReader { - internal class MultiVolumeAceReader : AceReader - { - private readonly IEnumerator streams; - private Stream tempStream; + private readonly IEnumerator streams; + private Stream tempStream; - internal MultiVolumeAceReader(IEnumerable streams, ReaderOptions options) - : base(options) => this.streams = streams.GetEnumerator(); + internal MultiVolumeAceReader(IEnumerable streams, ReaderOptions options) + : base(options) => this.streams = streams.GetEnumerator(); - protected override void ValidateArchive(AceVolume archive) { } + protected override void ValidateArchive(AceVolume archive) { } - protected override Stream RequestInitialStream() + protected override Stream RequestInitialStream() + { + if (streams.MoveNext()) { - if (streams.MoveNext()) - { - return streams.Current; - } - throw new MultiVolumeExtractionException( - "No stream provided when requested by MultiVolumeAceReader" - ); + return streams.Current; } + throw new MultiVolumeExtractionException( + "No stream provided when requested by MultiVolumeAceReader" + ); + } - internal override bool NextEntryForCurrentStream() + internal override bool NextEntryForCurrentStream() + { + if (!base.NextEntryForCurrentStream()) { - if (!base.NextEntryForCurrentStream()) - { - // if we're got another stream to try to process then do so - return streams.MoveNext() && LoadStreamForReading(streams.Current); - } - return true; + // if we're got another stream to try to process then do so + return streams.MoveNext() && LoadStreamForReading(streams.Current); } + return true; + } - protected override IEnumerable CreateFilePartEnumerableForCurrentEntry() - { - var enumerator = new MultiVolumeStreamEnumerator(this, streams, tempStream); - tempStream = null; - return enumerator; - } + protected override IEnumerable CreateFilePartEnumerableForCurrentEntry() + { + var enumerator = new MultiVolumeStreamEnumerator(this, streams, tempStream); + tempStream = null; + return enumerator; + } - private class MultiVolumeStreamEnumerator : IEnumerable, IEnumerator + private class MultiVolumeStreamEnumerator : IEnumerable, IEnumerator + { + private readonly MultiVolumeAceReader reader; + private readonly IEnumerator nextReadableStreams; + private Stream tempStream; + private bool isFirst = true; + + internal MultiVolumeStreamEnumerator( + MultiVolumeAceReader r, + IEnumerator nextReadableStreams, + Stream tempStream + ) { - private readonly MultiVolumeAceReader reader; - private readonly IEnumerator nextReadableStreams; - private Stream tempStream; - private bool isFirst = true; - - internal MultiVolumeStreamEnumerator( - MultiVolumeAceReader r, - IEnumerator nextReadableStreams, - Stream tempStream - ) - { - reader = r; - this.nextReadableStreams = nextReadableStreams; - this.tempStream = tempStream; - } + reader = r; + this.nextReadableStreams = nextReadableStreams; + this.tempStream = tempStream; + } - public IEnumerator GetEnumerator() => this; + public IEnumerator GetEnumerator() => this; - IEnumerator IEnumerable.GetEnumerator() => this; + IEnumerator IEnumerable.GetEnumerator() => this; - public FilePart Current { get; private set; } + public FilePart Current { get; private set; } - public void Dispose() { } + public void Dispose() { } - object IEnumerator.Current => Current; + object IEnumerator.Current => Current; - public bool MoveNext() + public bool MoveNext() + { + if (isFirst) { - if (isFirst) - { - Current = reader.Entry.Parts.First(); - isFirst = false; //first stream already to go - return true; - } - - if (!reader.Entry.IsSplitAfter) - { - return false; - } - if (tempStream != null) - { - reader.LoadStreamForReading(tempStream); - tempStream = null; - } - else if (!nextReadableStreams.MoveNext()) - { - throw new MultiVolumeExtractionException( - "No stream provided when requested by MultiVolumeAceReader" - ); - } - else - { - reader.LoadStreamForReading(nextReadableStreams.Current); - } - Current = reader.Entry.Parts.First(); + isFirst = false; //first stream already to go return true; } - public void Reset() { } + if (!reader.Entry.IsSplitAfter) + { + return false; + } + if (tempStream != null) + { + reader.LoadStreamForReading(tempStream); + tempStream = null; + } + else if (!nextReadableStreams.MoveNext()) + { + throw new MultiVolumeExtractionException( + "No stream provided when requested by MultiVolumeAceReader" + ); + } + else + { + reader.LoadStreamForReading(nextReadableStreams.Current); + } + + Current = reader.Entry.Parts.First(); + return true; } + + public void Reset() { } } } diff --git a/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs b/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs index 61182d66a..ce42c6e1d 100644 --- a/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs +++ b/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs @@ -3,29 +3,28 @@ using SharpCompress.Common; using SharpCompress.Common.Ace; -namespace SharpCompress.Readers.Ace +namespace SharpCompress.Readers.Ace; + +internal class SingleVolumeAceReader : AceReader { - internal class SingleVolumeAceReader : AceReader - { - private readonly Stream _stream; + private readonly Stream _stream; - internal SingleVolumeAceReader(Stream stream, ReaderOptions options) - : base(options) - { - stream.NotNull(nameof(stream)); - _stream = stream; - } + internal SingleVolumeAceReader(Stream stream, ReaderOptions options) + : base(options) + { + stream.NotNull(nameof(stream)); + _stream = stream; + } - protected override Stream RequestInitialStream() => _stream; + protected override Stream RequestInitialStream() => _stream; - protected override void ValidateArchive(AceVolume archive) + protected override void ValidateArchive(AceVolume archive) + { + if (archive.IsMultiVolume) { - if (archive.IsMultiVolume) - { - throw new MultiVolumeExtractionException( - "Streamed archive is a Multi-volume archive. Use a different AceReader method to extract." - ); - } + throw new MultiVolumeExtractionException( + "Streamed archive is a Multi-volume archive. Use a different AceReader method to extract." + ); } } } diff --git a/src/SharpCompress/Readers/Arc/ArcReader.Async.cs b/src/SharpCompress/Readers/Arc/ArcReader.Async.cs index f0569d381..ebb0e269c 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.Async.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.Async.cs @@ -3,21 +3,19 @@ using System.Threading; using SharpCompress.Common.Arc; -namespace SharpCompress.Readers.Arc +namespace SharpCompress.Readers.Arc; + +public partial class ArcReader { - public partial class ArcReader + protected override async IAsyncEnumerable GetEntriesAsync(Stream stream) { - protected override async IAsyncEnumerable GetEntriesAsync(Stream stream) + ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding); + ArcEntryHeader? header; + while ( + (header = await headerReader.ReadHeaderAsync(stream, CancellationToken.None)) != null + ) { - ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding); - ArcEntryHeader? header; - while ( - (header = await headerReader.ReadHeaderAsync(stream, CancellationToken.None)) - != null - ) - { - yield return new ArcEntry(new ArcFilePart(header, stream)); - } + yield return new ArcEntry(new ArcFilePart(header, stream)); } } } diff --git a/src/SharpCompress/Readers/Arc/ArcReader.cs b/src/SharpCompress/Readers/Arc/ArcReader.cs index c99e80966..ee9d5fa31 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.cs @@ -7,35 +7,34 @@ using SharpCompress.Common; using SharpCompress.Common.Arc; -namespace SharpCompress.Readers.Arc +namespace SharpCompress.Readers.Arc; + +public partial class ArcReader : AbstractReader { - public partial class ArcReader : AbstractReader - { - private ArcReader(Stream stream, ReaderOptions options) - : base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0); + private ArcReader(Stream stream, ReaderOptions options) + : base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0); - public override ArcVolume Volume { get; } + public override ArcVolume Volume { get; } - /// - /// Opens an ArcReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static IReader OpenReader(Stream stream, ReaderOptions? options = null) - { - stream.NotNull(nameof(stream)); - return new ArcReader(stream, options ?? new ReaderOptions()); - } + /// + /// Opens an ArcReader for Non-seeking usage with a single volume + /// + /// + /// + /// + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) + { + stream.NotNull(nameof(stream)); + return new ArcReader(stream, options ?? new ReaderOptions()); + } - protected override IEnumerable GetEntries(Stream stream) + protected override IEnumerable GetEntries(Stream stream) + { + ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding); + ArcEntryHeader? header; + while ((header = headerReader.ReadHeader(stream)) != null) { - ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding); - ArcEntryHeader? header; - while ((header = headerReader.ReadHeader(stream)) != null) - { - yield return new ArcEntry(new ArcFilePart(header, stream)); - } + yield return new ArcEntry(new ArcFilePart(header, stream)); } } } diff --git a/src/SharpCompress/Readers/Arj/ArjReader.cs b/src/SharpCompress/Readers/Arj/ArjReader.cs index 3bc39d10a..9a589925c 100644 --- a/src/SharpCompress/Readers/Arj/ArjReader.cs +++ b/src/SharpCompress/Readers/Arj/ArjReader.cs @@ -8,142 +8,137 @@ using SharpCompress.Common.Arj.Headers; using SharpCompress.Readers.Rar; -namespace SharpCompress.Readers.Arj +namespace SharpCompress.Readers.Arj; + +public abstract partial class ArjReader : AbstractReader { - public abstract partial class ArjReader : AbstractReader + internal ArjReader(ReaderOptions options) + : base(options, ArchiveType.Arj) { } + + /// + /// Derived class must create or manage the Volume itself. + /// AbstractReader.Volume is get-only, so it cannot be set here. + /// + public override ArjVolume? Volume => _volume; + + private ArjVolume? _volume; + + /// + /// Opens an ArjReader for Non-seeking usage with a single volume + /// + /// + /// + /// + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { - internal ArjReader(ReaderOptions options) - : base(options, ArchiveType.Arj) { } - - /// - /// Derived class must create or manage the Volume itself. - /// AbstractReader.Volume is get-only, so it cannot be set here. - /// - public override ArjVolume? Volume => _volume; - - private ArjVolume? _volume; - - /// - /// Opens an ArjReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static IReader OpenReader(Stream stream, ReaderOptions? options = null) + stream.NotNull(nameof(stream)); + return new SingleVolumeArjReader(stream, options ?? new ReaderOptions()); + } + + /// + /// Opens an ArjReader for Non-seeking usage with multiple volumes + /// + /// + /// + /// + public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) + { + streams.NotNull(nameof(streams)); + return new MultiVolumeArjReader(streams, options ?? new ReaderOptions()); + } + + protected abstract void ValidateArchive(ArjVolume archive); + + protected override IEnumerable GetEntries(Stream stream) + { + var encoding = new ArchiveEncoding(); + var mainHeaderReader = new ArjMainHeader(encoding); + var localHeaderReader = new ArjLocalHeader(encoding); + + var mainHeader = mainHeaderReader.Read(stream); + if (mainHeader?.IsVolume == true) { - stream.NotNull(nameof(stream)); - return new SingleVolumeArjReader(stream, options ?? new ReaderOptions()); + throw new MultiVolumeExtractionException("Multi volumes are currently not supported"); } - - /// - /// Opens an ArjReader for Non-seeking usage with multiple volumes - /// - /// - /// - /// - public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) + if (mainHeader?.IsGabled == true) { - streams.NotNull(nameof(streams)); - return new MultiVolumeArjReader(streams, options ?? new ReaderOptions()); + throw new CryptographicException( + "Password protected archives are currently not supported" + ); } - protected abstract void ValidateArchive(ArjVolume archive); - - protected override IEnumerable GetEntries(Stream stream) + if (_volume == null) { - var encoding = new ArchiveEncoding(); - var mainHeaderReader = new ArjMainHeader(encoding); - var localHeaderReader = new ArjLocalHeader(encoding); + _volume = new ArjVolume(stream, Options, 0); + ValidateArchive(_volume); + } - var mainHeader = mainHeaderReader.Read(stream); - if (mainHeader?.IsVolume == true) - { - throw new MultiVolumeExtractionException( - "Multi volumes are currently not supported" - ); - } - if (mainHeader?.IsGabled == true) + while (true) + { + var localHeader = localHeaderReader.Read(stream); + if (localHeader == null) { - throw new CryptographicException( - "Password protected archives are currently not supported" - ); + break; } - if (_volume == null) + // Skip non-file headers (like CommentHeader) + if ( + localHeader.FileType != FileType.Binary + && localHeader.FileType != FileType.Text7Bit + ) { - _volume = new ArjVolume(stream, Options, 0); - ValidateArchive(_volume); + continue; } - while (true) - { - var localHeader = localHeaderReader.Read(stream); - if (localHeader == null) - { - break; - } - - // Skip non-file headers (like CommentHeader) - if ( - localHeader.FileType != FileType.Binary - && localHeader.FileType != FileType.Text7Bit - ) - { - continue; - } - - yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream)); - } + yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream)); } + } + + protected override async IAsyncEnumerable GetEntriesAsync(Stream stream) + { + var encoding = new ArchiveEncoding(); + var mainHeaderReader = new ArjMainHeader(encoding); + var localHeaderReader = new ArjLocalHeader(encoding); - protected override async IAsyncEnumerable GetEntriesAsync(Stream stream) + var mainHeader = await mainHeaderReader.ReadAsync(stream); + if (mainHeader?.IsVolume == true) + { + throw new MultiVolumeExtractionException("Multi volumes are currently not supported"); + } + if (mainHeader?.IsGabled == true) { - var encoding = new ArchiveEncoding(); - var mainHeaderReader = new ArjMainHeader(encoding); - var localHeaderReader = new ArjLocalHeader(encoding); + throw new CryptographicException( + "Password protected archives are currently not supported" + ); + } - var mainHeader = await mainHeaderReader.ReadAsync(stream); - if (mainHeader?.IsVolume == true) - { - throw new MultiVolumeExtractionException( - "Multi volumes are currently not supported" - ); - } - if (mainHeader?.IsGabled == true) - { - throw new CryptographicException( - "Password protected archives are currently not supported" - ); - } + if (_volume == null) + { + _volume = new ArjVolume(stream, Options, 0); + ValidateArchive(_volume); + } - if (_volume == null) + while (true) + { + var localHeader = await localHeaderReader.ReadAsync(stream); + if (localHeader == null) { - _volume = new ArjVolume(stream, Options, 0); - ValidateArchive(_volume); + break; } - while (true) + // Skip non-file headers (like CommentHeader) + if ( + localHeader.FileType != FileType.Binary + && localHeader.FileType != FileType.Text7Bit + ) { - var localHeader = await localHeaderReader.ReadAsync(stream); - if (localHeader == null) - { - break; - } - - // Skip non-file headers (like CommentHeader) - if ( - localHeader.FileType != FileType.Binary - && localHeader.FileType != FileType.Text7Bit - ) - { - continue; - } - - yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream)); + continue; } - } - protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() => - Entry.Parts; + yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream)); + } } + + protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() => + Entry.Parts; } diff --git a/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs b/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs index 548257123..71eb46e4b 100644 --- a/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs +++ b/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs @@ -3,29 +3,28 @@ using SharpCompress.Common; using SharpCompress.Common.Arj; -namespace SharpCompress.Readers.Arj +namespace SharpCompress.Readers.Arj; + +internal class SingleVolumeArjReader : ArjReader { - internal class SingleVolumeArjReader : ArjReader - { - private readonly Stream _stream; + private readonly Stream _stream; - internal SingleVolumeArjReader(Stream stream, ReaderOptions options) - : base(options) - { - stream.NotNull(nameof(stream)); - _stream = stream; - } + internal SingleVolumeArjReader(Stream stream, ReaderOptions options) + : base(options) + { + stream.NotNull(nameof(stream)); + _stream = stream; + } - protected override Stream RequestInitialStream() => _stream; + protected override Stream RequestInitialStream() => _stream; - protected override void ValidateArchive(ArjVolume archive) + protected override void ValidateArchive(ArjVolume archive) + { + if (archive.IsMultiVolume) { - if (archive.IsMultiVolume) - { - throw new MultiVolumeExtractionException( - "Streamed archive is a Multi-volume archive. Use a different ArjReader method to extract." - ); - } + throw new MultiVolumeExtractionException( + "Streamed archive is a Multi-volume archive. Use a different ArjReader method to extract." + ); } } } diff --git a/src/SharpCompress/Readers/Rar/RarReader.Async.cs b/src/SharpCompress/Readers/Rar/RarReader.Async.cs index 9dc50f112..0e1943d23 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.Async.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.Async.cs @@ -27,10 +27,6 @@ protected override async ValueTask GetEntryStreamAsync( CancellationToken cancellationToken = default ) { - var useSyncOverAsync = false; -#if LEGACY_DOTNET - useSyncOverAsync = true; -#endif if (Entry.IsRedir) { throw new InvalidOperationException("no stream for redirect entry"); @@ -44,8 +40,7 @@ protected override async ValueTask GetEntryStreamAsync( return CreateEntryStream( await RarCrcStream .CreateAsync(UnpackV1.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false), - useSyncOverAsync + .ConfigureAwait(false) ); } @@ -54,16 +49,14 @@ await RarCrcStream return CreateEntryStream( await RarBLAKE2spStream .CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false), - useSyncOverAsync + .ConfigureAwait(false) ); } return CreateEntryStream( await RarCrcStream .CreateAsync(UnpackV2017.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false), - useSyncOverAsync + .ConfigureAwait(false) ); } } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index cae92ee17..cd0aebc97 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -110,7 +110,7 @@ protected override async IAsyncEnumerable GetEntriesAsync(Stream protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() => Entry.Parts; - protected override EntryStream GetEntryStream(bool useSyncOverAsyncDispose) + protected override EntryStream GetEntryStream() { if (Entry.IsRedir) { @@ -122,24 +122,17 @@ protected override EntryStream GetEntryStream(bool useSyncOverAsyncDispose) ); if (Entry.IsRarV3) { - return CreateEntryStream( - RarCrcStream.Create(UnpackV1.Value, Entry.FileHeader, stream), - useSyncOverAsyncDispose - ); + return CreateEntryStream(RarCrcStream.Create(UnpackV1.Value, Entry.FileHeader, stream)); } if (Entry.FileHeader.FileCrc?.Length > 5) { return CreateEntryStream( - RarBLAKE2spStream.Create(UnpackV2017.Value, Entry.FileHeader, stream), - useSyncOverAsyncDispose + RarBLAKE2spStream.Create(UnpackV2017.Value, Entry.FileHeader, stream) ); } - return CreateEntryStream( - RarCrcStream.Create(UnpackV2017.Value, Entry.FileHeader, stream), - useSyncOverAsyncDispose - ); + return CreateEntryStream(RarCrcStream.Create(UnpackV2017.Value, Entry.FileHeader, stream)); } // GetEntryStreamAsync moved to RarReader.Async.cs diff --git a/src/SharpCompress/Readers/ReaderFactory.Async.cs b/src/SharpCompress/Readers/ReaderFactory.Async.cs index f279895e7..2293ea076 100644 --- a/src/SharpCompress/Readers/ReaderFactory.Async.cs +++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs @@ -54,8 +54,11 @@ public static async ValueTask OpenAsyncReader( stream.NotNull(nameof(stream)); options ??= new ReaderOptions() { LeaveStreamOpen = false }; - var bStream = new RewindableStream(stream); - bStream.StartRecording(); + var sharpCompressStream = SharpCompressStream.EnsureSeekable( + stream, + options.RewindableBufferSize + ); + sharpCompressStream.StartRecording(); var factories = Factory.Factories.OfType(); @@ -68,20 +71,24 @@ public static async ValueTask OpenAsyncReader( ); if (testedFactory is IReaderFactory readerFactory) { - bStream.Rewind(); + sharpCompressStream.Rewind(); if ( await testedFactory.IsArchiveAsync( - bStream, + sharpCompressStream, cancellationToken: cancellationToken ) ) { - bStream.Rewind(); - bStream.StopRecording(); - return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken); + sharpCompressStream.Rewind(); + sharpCompressStream.StopRecording(); + return await readerFactory.OpenAsyncReader( + sharpCompressStream, + options, + cancellationToken + ); } } - bStream.Rewind(); + sharpCompressStream.Rewind(); } foreach (var factory in factories) @@ -90,15 +97,22 @@ await testedFactory.IsArchiveAsync( { continue; // Already tested above } - bStream.Rewind(); + sharpCompressStream.Rewind(); if ( factory is IReaderFactory readerFactory - && await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken) + && await factory.IsArchiveAsync( + sharpCompressStream, + cancellationToken: cancellationToken + ) ) { - bStream.Rewind(); - bStream.StopRecording(); - return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken); + sharpCompressStream.Rewind(); + sharpCompressStream.StopRecording(); + return await readerFactory.OpenAsyncReader( + sharpCompressStream, + options, + cancellationToken + ); } } diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 8f40587eb..180c28092 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -34,8 +34,11 @@ public static IReader OpenReader(Stream stream, ReaderOptions? options = null) stream.NotNull(nameof(stream)); options ??= new ReaderOptions() { LeaveStreamOpen = false }; - var bStream = RewindableStream.EnsureSeekable(stream); - bStream.StartRecording(); + var sharpCompressStream = SharpCompressStream.EnsureSeekable( + stream, + options.RewindableBufferSize + ); + sharpCompressStream.StartRecording(); var factories = Factories.Factory.Factories.OfType(); @@ -48,11 +51,11 @@ public static IReader OpenReader(Stream stream, ReaderOptions? options = null) .Contains(options.ExtensionHint, StringComparer.CurrentCultureIgnoreCase) ); if ( - testedFactory?.TryOpenReader(bStream, options, out var reader) == true + testedFactory?.TryOpenReader(sharpCompressStream, options, out var reader) == true && reader != null ) { - bStream.Rewind(true); + sharpCompressStream.Rewind(true); return reader; } } @@ -63,8 +66,11 @@ public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { continue; // Already tested above } - bStream.Rewind(); - if (factory.TryOpenReader(bStream, options, out var reader) && reader != null) + sharpCompressStream.Rewind(); + if ( + factory.TryOpenReader(sharpCompressStream, options, out var reader) + && reader != null + ) { return reader; } diff --git a/src/SharpCompress/Readers/ReaderOptions.cs b/src/SharpCompress/Readers/ReaderOptions.cs index b016a3b6a..2ddb8ac40 100644 --- a/src/SharpCompress/Readers/ReaderOptions.cs +++ b/src/SharpCompress/Readers/ReaderOptions.cs @@ -36,4 +36,47 @@ public class ReaderOptions : OptionsBase /// When set, progress updates will be reported as entries are extracted. /// public IProgress? Progress { get; set; } + + /// + /// Size of the rewindable buffer for non-seekable streams. + /// Used during format detection to enable multiple rewinds. + /// + /// + /// + /// When opening archives from non-seekable streams (network streams, pipes, + /// compressed streams), SharpCompress uses a ring buffer to enable format + /// auto-detection. This buffer allows the library to try multiple decoders + /// by rewinding and re-reading the same data. + /// + /// + /// Default: Constants.RewindableBufferSize (81920 bytes / 81KB) + /// + /// + /// Typical usage: 500-1000 bytes for most archives + /// + /// + /// Increase if: + /// + /// Opening self-extracting RAR archives (may need 512KB+) + /// Format detection fails with "recording anchor" errors + /// Using custom formats with large headers + /// + /// + /// + /// Memory impact: Buffer is allocated for non-seekable streams only. + /// Seekable streams (FileStream, MemoryStream) use zero-copy seeking instead. + /// + /// + /// + /// + /// // For self-extracting archives, use larger buffer + /// var options = new ReaderOptions + /// { + /// RewindableBufferSize = 1_048_576, // 1MB + /// LookForHeader = true + /// }; + /// using var reader = ReaderFactory.OpenReader(networkStream, options); + /// + /// + public int? RewindableBufferSize { get; set; } } diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index 030ad33fb..c20687892 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -57,61 +57,64 @@ public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); options = options ?? new ReaderOptions(); - var rewindableStream = RewindableStream.EnsureSeekable(stream); - long pos = rewindableStream.Position; - if (GZipArchive.IsGZipFile(rewindableStream)) + var sharpCompressStream = SharpCompressStream.EnsureSeekable( + stream, + options.RewindableBufferSize + ); + long pos = sharpCompressStream.Position; + if (GZipArchive.IsGZipFile(sharpCompressStream)) { - rewindableStream.Position = pos; - var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); + sharpCompressStream.Position = pos; + var testStream = new GZipStream(sharpCompressStream, CompressionMode.Decompress); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Position = pos; - return new TarReader(rewindableStream, options, CompressionType.GZip); + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.GZip); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Position = pos; - if (BZip2Stream.IsBZip2(rewindableStream)) + sharpCompressStream.Position = pos; + if (BZip2Stream.IsBZip2(sharpCompressStream)) { - rewindableStream.Position = pos; + sharpCompressStream.Position = pos; var testStream = BZip2Stream.Create( - rewindableStream, + sharpCompressStream, CompressionMode.Decompress, false ); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Position = pos; - return new TarReader(rewindableStream, options, CompressionType.BZip2); + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.BZip2); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Position = pos; - if (ZStandardStream.IsZStandard(rewindableStream)) + sharpCompressStream.Position = pos; + if (ZStandardStream.IsZStandard(sharpCompressStream)) { - rewindableStream.Position = pos; - var testStream = new ZStandardStream(rewindableStream); + sharpCompressStream.Position = pos; + var testStream = new ZStandardStream(sharpCompressStream); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Position = pos; - return new TarReader(rewindableStream, options, CompressionType.ZStandard); + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.ZStandard); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Position = pos; - if (LZipStream.IsLZipFile(rewindableStream)) + sharpCompressStream.Position = pos; + if (LZipStream.IsLZipFile(sharpCompressStream)) { - rewindableStream.Position = pos; - var testStream = new LZipStream(rewindableStream, CompressionMode.Decompress); + sharpCompressStream.Position = pos; + var testStream = new LZipStream(sharpCompressStream, CompressionMode.Decompress); if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Position = pos; - return new TarReader(rewindableStream, options, CompressionType.LZip); + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.LZip); } throw new InvalidFormatException("Not a tar file."); } - rewindableStream.Position = pos; - return new TarReader(rewindableStream, options, CompressionType.None); + sharpCompressStream.Position = pos; + return new TarReader(sharpCompressStream, options, CompressionType.None); } #endregion OpenReader diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index f98a48c09..de3a1ddcc 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -13,6 +13,15 @@ namespace SharpCompress; internal static partial class Utility { + public static bool UseSyncOverAsyncDispose() + { + var useSyncOverAsync = false; +#if LEGACY_DOTNET + useSyncOverAsync = true; +#endif + return useSyncOverAsync; + } + private static readonly HashSet invalidChars = new(Path.GetInvalidFileNameChars()); public static ReadOnlyCollection ToReadOnly(this IList items) => new(items); @@ -144,7 +153,7 @@ public async ValueTask SkipAsync( CancellationToken cancellationToken = default ) { - if (source.CanSeek && source is not RewindableStream) + if (source.CanSeek && source is not SharpCompressStream) { source.Position += advanceAmount; return; diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.cs b/src/SharpCompress/Writers/GZip/GZipWriter.cs index 633d9cc9f..672692727 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 = new NonDisposingStream(destination); + destination = SharpCompressStream.CreateNonDisposing(destination); } InitializeStream( new GZipStream( diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index 974ed5a68..eb7070fb1 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -29,7 +29,7 @@ public TarWriter(Stream destination, TarWriterOptions options) } if (WriterOptions.LeaveStreamOpen) { - destination = new NonDisposingStream(destination); + destination = SharpCompressStream.CreateNonDisposing(destination); } switch (options.CompressionType) { diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index e69af91e3..fb21908e9 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -44,7 +44,7 @@ public ZipWriter(Stream destination, ZipWriterOptions zipWriterOptions) if (WriterOptions.LeaveStreamOpen) { - destination = new NonDisposingStream(destination); + destination = SharpCompressStream.CreateNonDisposing(destination); } InitializeStream(destination); } @@ -417,7 +417,7 @@ public override long Position private Stream GetWriteStream(Stream writeStream) { - counting = new CountingStream(new NonDisposingStream(writeStream)); + counting = new CountingStream(SharpCompressStream.CreateNonDisposing(writeStream)); Stream output = counting; switch (zipCompressionMethod) { diff --git a/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs b/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs index 02b54f0d7..4dde67802 100644 --- a/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs @@ -9,132 +9,131 @@ using SharpCompress.Test.Mocks; using Xunit; -namespace SharpCompress.Test.Ace +namespace SharpCompress.Test.Ace; + +public class AceReaderAsyncTests : ReaderTests { - public class AceReaderAsyncTests : ReaderTests + public AceReaderAsyncTests() { - public AceReaderAsyncTests() - { - UseExtensionInsteadOfNameToVerify = true; - UseCaseInsensitiveToVerify = true; - } + UseExtensionInsteadOfNameToVerify = true; + UseCaseInsensitiveToVerify = true; + } - [Fact] - public async ValueTask Ace_Uncompressed_Read_Async() => - await ReadAsync("Ace.store.ace", CompressionType.None); + [Fact] + public async ValueTask Ace_Uncompressed_Read_Async() => + await ReadAsync("Ace.store.ace", CompressionType.None); - [Fact] - public async ValueTask Ace_Encrypted_Read_Async() - { - var exception = await Assert.ThrowsAsync(() => - ReadAsync("Ace.encrypted.ace") - ); - } + [Fact] + public async ValueTask Ace_Encrypted_Read_Async() + { + var exception = await Assert.ThrowsAsync(() => + ReadAsync("Ace.encrypted.ace") + ); + } - [Theory] - [InlineData("Ace.method1.ace", CompressionType.AceLZ77)] - [InlineData("Ace.method1-solid.ace", CompressionType.AceLZ77)] - [InlineData("Ace.method2.ace", CompressionType.AceLZ77)] - [InlineData("Ace.method2-solid.ace", CompressionType.AceLZ77)] - public async ValueTask Ace_Unsupported_ShouldThrow_Async( - string fileName, - CompressionType compressionType - ) - { - var exception = await Assert.ThrowsAsync(() => - ReadAsync(fileName, compressionType) - ); - } + [Theory] + [InlineData("Ace.method1.ace", CompressionType.AceLZ77)] + [InlineData("Ace.method1-solid.ace", CompressionType.AceLZ77)] + [InlineData("Ace.method2.ace", CompressionType.AceLZ77)] + [InlineData("Ace.method2-solid.ace", CompressionType.AceLZ77)] + public async ValueTask Ace_Unsupported_ShouldThrow_Async( + string fileName, + CompressionType compressionType + ) + { + var exception = await Assert.ThrowsAsync(() => + ReadAsync(fileName, compressionType) + ); + } - [Theory] - [InlineData("Ace.store.largefile.ace", CompressionType.None)] - public async ValueTask Ace_LargeFileTest_Read_Async( - string fileName, - CompressionType compressionType - ) - { - await ReadForBufferBoundaryCheckAsync(fileName, compressionType); - } + [Theory] + [InlineData("Ace.store.largefile.ace", CompressionType.None)] + public async ValueTask Ace_LargeFileTest_Read_Async( + string fileName, + CompressionType compressionType + ) + { + await ReadForBufferBoundaryCheckAsync(fileName, compressionType); + } - [Fact] - public async ValueTask Ace_Multi_Reader_Async() - { - var exception = await Assert.ThrowsAsync(() => - DoMultiReaderAsync( - new[] { "Ace.store.split.ace", "Ace.store.split.c01" }, - streams => AceReader.OpenAsyncReader(streams, null) - ) - ); - } + [Fact] + public async ValueTask Ace_Multi_Reader_Async() + { + var exception = await Assert.ThrowsAsync(() => + DoMultiReaderAsync( + new[] { "Ace.store.split.ace", "Ace.store.split.c01" }, + streams => AceReader.OpenAsyncReader(streams, null) + ) + ); + } - private async Task ReadAsync(string testArchive, CompressionType expectedCompression) + private async Task ReadAsync(string testArchive, CompressionType expectedCompression) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + using Stream stream = File.OpenRead(testArchive); + await using var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions() + ); + while (await reader.MoveToNextEntryAsync()) { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using Stream stream = File.OpenRead(testArchive); - await using var reader = await ReaderFactory.OpenAsyncReader( - new AsyncOnlyStream(stream), - new ReaderOptions() - ); - while (await reader.MoveToNextEntryAsync()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(expectedCompression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); - } + Assert.Equal(expectedCompression, reader.Entry.CompressionType); + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); } - VerifyFiles(); } + VerifyFiles(); + } - private async Task ReadForBufferBoundaryCheckAsync( - string testArchive, - CompressionType expectedCompression - ) + private async Task ReadForBufferBoundaryCheckAsync( + string testArchive, + CompressionType expectedCompression + ) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + using Stream stream = File.OpenRead(testArchive); + await using var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions { LookForHeader = true } + ); + while (await reader.MoveToNextEntryAsync()) { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using Stream stream = File.OpenRead(testArchive); - await using var reader = await ReaderFactory.OpenAsyncReader( - new AsyncOnlyStream(stream), - new ReaderOptions { LookForHeader = true } - ); - while (await reader.MoveToNextEntryAsync()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(expectedCompression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); - } + Assert.Equal(expectedCompression, reader.Entry.CompressionType); + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); } - CompareFilesByPath( - Path.Combine(SCRATCH_FILES_PATH, "alice29.txt"), - Path.Combine(MISC_TEST_FILES_PATH, "alice29.txt") - ); } + CompareFilesByPath( + Path.Combine(SCRATCH_FILES_PATH, "alice29.txt"), + Path.Combine(MISC_TEST_FILES_PATH, "alice29.txt") + ); + } - private async Task DoMultiReaderAsync( - string[] archives, - Func, IAsyncReader> readerFactory - ) - { - await using var reader = readerFactory( - archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).Select(File.OpenRead) - ); + private async Task DoMultiReaderAsync( + string[] archives, + Func, IAsyncReader> readerFactory + ) + { + await using var reader = readerFactory( + archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).Select(File.OpenRead) + ); - while (await reader.MoveToNextEntryAsync()) + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); - } + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); } } } diff --git a/tests/SharpCompress.Test/Ace/AceReaderTests.cs b/tests/SharpCompress.Test/Ace/AceReaderTests.cs index 60c51c120..d6c609e96 100644 --- a/tests/SharpCompress.Test/Ace/AceReaderTests.cs +++ b/tests/SharpCompress.Test/Ace/AceReaderTests.cs @@ -9,53 +9,50 @@ using SharpCompress.Readers.Ace; using Xunit; -namespace SharpCompress.Test.Ace +namespace SharpCompress.Test.Ace; + +public class AceReaderTests : ReaderTests { - public class AceReaderTests : ReaderTests + public AceReaderTests() { - public AceReaderTests() - { - UseExtensionInsteadOfNameToVerify = true; - UseCaseInsensitiveToVerify = true; - } + UseExtensionInsteadOfNameToVerify = true; + UseCaseInsensitiveToVerify = true; + } - [Fact] - public void Ace_Uncompressed_Read() => Read("Ace.store.ace", CompressionType.None); + [Fact] + public void Ace_Uncompressed_Read() => Read("Ace.store.ace", CompressionType.None); - [Fact] - public void Ace_Encrypted_Read() - { - var exception = Assert.Throws(() => Read("Ace.encrypted.ace")); - } + [Fact] + public void Ace_Encrypted_Read() + { + var exception = Assert.Throws(() => Read("Ace.encrypted.ace")); + } - [Theory] - [InlineData("Ace.method1.ace", CompressionType.AceLZ77)] - [InlineData("Ace.method1-solid.ace", CompressionType.AceLZ77)] - [InlineData("Ace.method2.ace", CompressionType.AceLZ77)] - [InlineData("Ace.method2-solid.ace", CompressionType.AceLZ77)] - public void Ace_Unsupported_ShouldThrow(string fileName, CompressionType compressionType) - { - var exception = Assert.Throws(() => - Read(fileName, compressionType) - ); - } + [Theory] + [InlineData("Ace.method1.ace", CompressionType.AceLZ77)] + [InlineData("Ace.method1-solid.ace", CompressionType.AceLZ77)] + [InlineData("Ace.method2.ace", CompressionType.AceLZ77)] + [InlineData("Ace.method2-solid.ace", CompressionType.AceLZ77)] + public void Ace_Unsupported_ShouldThrow(string fileName, CompressionType compressionType) + { + var exception = Assert.Throws(() => Read(fileName, compressionType)); + } - [Theory] - [InlineData("Ace.store.largefile.ace", CompressionType.None)] - public void Ace_LargeFileTest_Read(string fileName, CompressionType compressionType) - { - ReadForBufferBoundaryCheck(fileName, compressionType); - } + [Theory] + [InlineData("Ace.store.largefile.ace", CompressionType.None)] + public void Ace_LargeFileTest_Read(string fileName, CompressionType compressionType) + { + ReadForBufferBoundaryCheck(fileName, compressionType); + } - [Fact] - public void Ace_Multi_Reader() - { - var exception = Assert.Throws(() => - DoMultiReader( - ["Ace.store.split.ace", "Ace.store.split.c01"], - streams => AceReader.OpenReader(streams) - ) - ); - } + [Fact] + public void Ace_Multi_Reader() + { + var exception = Assert.Throws(() => + DoMultiReader( + ["Ace.store.split.ace", "Ace.store.split.c01"], + streams => AceReader.OpenReader(streams) + ) + ); } } diff --git a/tests/SharpCompress.Test/Arc/ArcReaderTests.cs b/tests/SharpCompress.Test/Arc/ArcReaderTests.cs index 54f169c39..c5de5ebd1 100644 --- a/tests/SharpCompress.Test/Arc/ArcReaderTests.cs +++ b/tests/SharpCompress.Test/Arc/ArcReaderTests.cs @@ -2,40 +2,39 @@ using SharpCompress.Common; using Xunit; -namespace SharpCompress.Test.Arc +namespace SharpCompress.Test.Arc; + +public class ArcReaderTests : ReaderTests { - public class ArcReaderTests : ReaderTests + public ArcReaderTests() { - public ArcReaderTests() - { - UseExtensionInsteadOfNameToVerify = true; - UseCaseInsensitiveToVerify = true; - } + UseExtensionInsteadOfNameToVerify = true; + UseCaseInsensitiveToVerify = true; + } - [Fact] - public void Arc_Uncompressed_Read() => Read("Arc.uncompressed.arc", CompressionType.None); + [Fact] + public void Arc_Uncompressed_Read() => Read("Arc.uncompressed.arc", CompressionType.None); - [Fact] - public void Arc_Squeezed_Read() => Read("Arc.squeezed.arc"); + [Fact] + public void Arc_Squeezed_Read() => Read("Arc.squeezed.arc"); - [Fact] - public void Arc_Crunched_Read() => Read("Arc.crunched.arc"); + [Fact] + public void Arc_Crunched_Read() => Read("Arc.crunched.arc"); - [Theory] - [InlineData("Arc.crunched.largefile.arc", CompressionType.Crunched)] - public void Arc_LargeFile_ShouldThrow(string fileName, CompressionType compressionType) - { - var exception = Assert.Throws(() => - ReadForBufferBoundaryCheck(fileName, compressionType) - ); - } + [Theory] + [InlineData("Arc.crunched.largefile.arc", CompressionType.Crunched)] + public void Arc_LargeFile_ShouldThrow(string fileName, CompressionType compressionType) + { + var exception = Assert.Throws(() => + ReadForBufferBoundaryCheck(fileName, compressionType) + ); + } - [Theory] - [InlineData("Arc.uncompressed.largefile.arc", CompressionType.None)] - [InlineData("Arc.squeezed.largefile.arc", CompressionType.Squeezed)] - public void Arc_LargeFileTest_Read(string fileName, CompressionType compressionType) - { - ReadForBufferBoundaryCheck(fileName, compressionType); - } + [Theory] + [InlineData("Arc.uncompressed.largefile.arc", CompressionType.None)] + [InlineData("Arc.squeezed.largefile.arc", CompressionType.Squeezed)] + public void Arc_LargeFileTest_Read(string fileName, CompressionType compressionType) + { + ReadForBufferBoundaryCheck(fileName, compressionType); } } diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index ee2bd6a35..4d09ae84f 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -43,7 +43,7 @@ CompressionType compression { foreach (var path in testArchives) { - using (var stream = new NonDisposingStream(File.OpenRead(path))) + using (var stream = SharpCompressStream.CreateNonDisposing(File.OpenRead(path))) { try { @@ -144,7 +144,7 @@ string extension ExtensionTest(extension, archiveFactory); foreach (var path in testArchives) { - using (var stream = new NonDisposingStream(File.OpenRead(path))) + using (var stream = SharpCompressStream.CreateNonDisposing(File.OpenRead(path))) using (var archive = archiveFactory.OpenArchive(stream, readerOptions)) { try @@ -641,7 +641,7 @@ IEnumerable testArchives { foreach (var path in testArchives) { - using (var stream = new NonDisposingStream(File.OpenRead(path))) + using (var stream = SharpCompressStream.CreateNonDisposing(File.OpenRead(path))) await using ( var archive = archiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream), diff --git a/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs b/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs index 5e03b47ac..6a32cdebf 100644 --- a/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs @@ -11,168 +11,162 @@ using Xunit; using Xunit.Sdk; -namespace SharpCompress.Test.Arj +namespace SharpCompress.Test.Arj; + +public class ArjReaderAsyncTests : ReaderTests { - public class ArjReaderAsyncTests : ReaderTests + public ArjReaderAsyncTests() { - public ArjReaderAsyncTests() - { - UseExtensionInsteadOfNameToVerify = true; - UseCaseInsensitiveToVerify = true; - } + UseExtensionInsteadOfNameToVerify = true; + UseCaseInsensitiveToVerify = true; + } - [Fact] - public async ValueTask Arj_Uncompressed_Read_Async() => - await ReadAsync("Arj.store.arj", CompressionType.None); + [Fact] + public async ValueTask Arj_Uncompressed_Read_Async() => + await ReadAsync("Arj.store.arj", CompressionType.None); - [Fact] - public async ValueTask Arj_Method1_Read_Async() => await ReadAsync("Arj.method1.arj"); + [Fact] + public async ValueTask Arj_Method1_Read_Async() => await ReadAsync("Arj.method1.arj"); - [Fact] - public async ValueTask Arj_Method2_Read_Async() => await ReadAsync("Arj.method2.arj"); + [Fact] + public async ValueTask Arj_Method2_Read_Async() => await ReadAsync("Arj.method2.arj"); - [Fact] - public async ValueTask Arj_Method3_Read_Async() => await ReadAsync("Arj.method3.arj"); + [Fact] + public async ValueTask Arj_Method3_Read_Async() => await ReadAsync("Arj.method3.arj"); - [Fact] - public async ValueTask Arj_Method4_Read_Async() => await ReadAsync("Arj.method4.arj"); + [Fact] + public async ValueTask Arj_Method4_Read_Async() => await ReadAsync("Arj.method4.arj"); - [Fact] - public async ValueTask Arj_Encrypted_Read_Async() - { - var exception = await Assert.ThrowsAsync(() => - ReadAsync("Arj.encrypted.arj") - ); - } + [Fact] + public async ValueTask Arj_Encrypted_Read_Async() + { + var exception = await Assert.ThrowsAsync(() => + ReadAsync("Arj.encrypted.arj") + ); + } - [Fact] - public async ValueTask Arj_Multi_Reader_Async() - { - var exception = await Assert.ThrowsAsync(() => - DoMultiReaderAsync( - [ - "Arj.store.split.arj", - "Arj.store.split.a01", - "Arj.store.split.a02", - "Arj.store.split.a03", - "Arj.store.split.a04", - "Arj.store.split.a05", - ], - streams => ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(streams.First())) - ) - ); - } + [Fact] + public async ValueTask Arj_Multi_Reader_Async() + { + var exception = await Assert.ThrowsAsync(() => + DoMultiReaderAsync( + [ + "Arj.store.split.arj", + "Arj.store.split.a01", + "Arj.store.split.a02", + "Arj.store.split.a03", + "Arj.store.split.a04", + "Arj.store.split.a05", + ], + streams => ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(streams.First())) + ) + ); + } - [Theory] - [InlineData("Arj.method1.largefile.arj", CompressionType.ArjLZ77)] - [InlineData("Arj.method2.largefile.arj", CompressionType.ArjLZ77)] - [InlineData("Arj.method3.largefile.arj", CompressionType.ArjLZ77)] - public async ValueTask Arj_LargeFile_ShouldThrow_Async( - string fileName, - CompressionType compressionType - ) - { - var exception = await Assert.ThrowsAsync(() => - ReadForBufferBoundaryCheckAsync(fileName, compressionType) - ); - } + [Theory] + [InlineData("Arj.method1.largefile.arj", CompressionType.ArjLZ77)] + [InlineData("Arj.method2.largefile.arj", CompressionType.ArjLZ77)] + [InlineData("Arj.method3.largefile.arj", CompressionType.ArjLZ77)] + public async ValueTask Arj_LargeFile_ShouldThrow_Async( + string fileName, + CompressionType compressionType + ) + { + var exception = await Assert.ThrowsAsync(() => + ReadForBufferBoundaryCheckAsync(fileName, compressionType) + ); + } - [Theory] - [InlineData("Arj.store.largefile.arj", CompressionType.None)] - [InlineData("Arj.method4.largefile.arj", CompressionType.ArjLZ77)] - public async ValueTask Arj_LargeFileTest_Read_Async( - string fileName, - CompressionType compressionType - ) - { - await ReadForBufferBoundaryCheckAsync(fileName, compressionType); - } + [Theory] + [InlineData("Arj.store.largefile.arj", CompressionType.None)] + [InlineData("Arj.method4.largefile.arj", CompressionType.ArjLZ77)] + public async ValueTask Arj_LargeFileTest_Read_Async( + string fileName, + CompressionType compressionType + ) + { + await ReadForBufferBoundaryCheckAsync(fileName, compressionType); + } - private async Task ReadAsync( - string testArchive, - CompressionType? expectedCompression = null - ) + private async Task ReadAsync(string testArchive, CompressionType? expectedCompression = null) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + using Stream stream = File.OpenRead(testArchive); + await using var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions() + ); + while (await reader.MoveToNextEntryAsync()) { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using Stream stream = File.OpenRead(testArchive); - await using var reader = await ReaderFactory.OpenAsyncReader( - new AsyncOnlyStream(stream), - new ReaderOptions() - ); - while (await reader.MoveToNextEntryAsync()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) + if (expectedCompression.HasValue) { - if (expectedCompression.HasValue) - { - Assert.Equal(expectedCompression.Value, reader.Entry.CompressionType); - } - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + Assert.Equal(expectedCompression.Value, reader.Entry.CompressionType); } + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); } - VerifyFiles(); } + VerifyFiles(); + } - private async Task ReadForBufferBoundaryCheckAsync( - string testArchive, - CompressionType expectedCompression - ) + private async Task ReadForBufferBoundaryCheckAsync( + string testArchive, + CompressionType expectedCompression + ) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + using Stream stream = File.OpenRead(testArchive); + await using var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions() { LookForHeader = true } + ); + while (await reader.MoveToNextEntryAsync()) { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using Stream stream = File.OpenRead(testArchive); - await using var reader = await ReaderFactory.OpenAsyncReader( - new AsyncOnlyStream(stream), - new ReaderOptions() { LookForHeader = true } - ); + if (!reader.Entry.IsDirectory) + { + Assert.Equal(expectedCompression, reader.Entry.CompressionType); + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); + } + } + CompareFilesByPath( + Path.Combine(SCRATCH_FILES_PATH, "alice29.txt"), + Path.Combine(MISC_TEST_FILES_PATH, "alice29.txt") + ); + } + + private async Task DoMultiReaderAsync( + string[] archiveNames, + Func, ValueTask> openReader + ) + { + var testArchives = archiveNames.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).ToList(); + var streams = testArchives.Select(File.OpenRead).ToList(); + try + { + await using var reader = await openReader(streams); while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { - Assert.Equal(expectedCompression, reader.Entry.CompressionType); await reader.WriteEntryToDirectoryAsync( SCRATCH_FILES_PATH, new ExtractionOptions { ExtractFullPath = true, Overwrite = true } ); } } - CompareFilesByPath( - Path.Combine(SCRATCH_FILES_PATH, "alice29.txt"), - Path.Combine(MISC_TEST_FILES_PATH, "alice29.txt") - ); } - - private async Task DoMultiReaderAsync( - string[] archiveNames, - Func, ValueTask> openReader - ) + finally { - var testArchives = archiveNames - .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) - .ToList(); - var streams = testArchives.Select(File.OpenRead).ToList(); - try - { - await using var reader = await openReader(streams); - while (await reader.MoveToNextEntryAsync()) - { - if (!reader.Entry.IsDirectory) - { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); - } - } - } - finally + foreach (var stream in streams) { - foreach (var stream in streams) - { - stream.Dispose(); - } + stream.Dispose(); } } } diff --git a/tests/SharpCompress.Test/Arj/ArjReaderTests.cs b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs index 054c18f60..f4b5b4d5c 100644 --- a/tests/SharpCompress.Test/Arj/ArjReaderTests.cs +++ b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs @@ -10,72 +10,71 @@ using Xunit; using Xunit.Sdk; -namespace SharpCompress.Test.Arj +namespace SharpCompress.Test.Arj; + +public class ArjReaderTests : ReaderTests { - public class ArjReaderTests : ReaderTests + public ArjReaderTests() { - public ArjReaderTests() - { - UseExtensionInsteadOfNameToVerify = true; - UseCaseInsensitiveToVerify = true; - } + UseExtensionInsteadOfNameToVerify = true; + UseCaseInsensitiveToVerify = true; + } - [Fact] - public void Arj_Uncompressed_Read() => Read("Arj.store.arj", CompressionType.None); + [Fact] + public void Arj_Uncompressed_Read() => Read("Arj.store.arj", CompressionType.None); - [Fact] - public void Arj_Method1_Read() => Read("Arj.method1.arj"); + [Fact] + public void Arj_Method1_Read() => Read("Arj.method1.arj"); - [Fact] - public void Arj_Method2_Read() => Read("Arj.method2.arj"); + [Fact] + public void Arj_Method2_Read() => Read("Arj.method2.arj"); - [Fact] - public void Arj_Method3_Read() => Read("Arj.method3.arj"); + [Fact] + public void Arj_Method3_Read() => Read("Arj.method3.arj"); - [Fact] - public void Arj_Method4_Read() => Read("Arj.method4.arj"); + [Fact] + public void Arj_Method4_Read() => Read("Arj.method4.arj"); - [Fact] - public void Arj_Encrypted_Read() - { - var exception = Assert.Throws(() => Read("Arj.encrypted.arj")); - } + [Fact] + public void Arj_Encrypted_Read() + { + var exception = Assert.Throws(() => Read("Arj.encrypted.arj")); + } - [Fact] - public void Arj_Multi_Reader() - { - var exception = Assert.Throws(() => - DoMultiReader( - [ - "Arj.store.split.arj", - "Arj.store.split.a01", - "Arj.store.split.a02", - "Arj.store.split.a03", - "Arj.store.split.a04", - "Arj.store.split.a05", - ], - streams => ArjReader.OpenReader(streams) - ) - ); - } + [Fact] + public void Arj_Multi_Reader() + { + var exception = Assert.Throws(() => + DoMultiReader( + [ + "Arj.store.split.arj", + "Arj.store.split.a01", + "Arj.store.split.a02", + "Arj.store.split.a03", + "Arj.store.split.a04", + "Arj.store.split.a05", + ], + streams => ArjReader.OpenReader(streams) + ) + ); + } - [Theory] - [InlineData("Arj.method1.largefile.arj", CompressionType.ArjLZ77)] - [InlineData("Arj.method2.largefile.arj", CompressionType.ArjLZ77)] - [InlineData("Arj.method3.largefile.arj", CompressionType.ArjLZ77)] - public void Arj_LargeFile_ShouldThrow(string fileName, CompressionType compressionType) - { - var exception = Assert.Throws(() => - ReadForBufferBoundaryCheck(fileName, compressionType) - ); - } + [Theory] + [InlineData("Arj.method1.largefile.arj", CompressionType.ArjLZ77)] + [InlineData("Arj.method2.largefile.arj", CompressionType.ArjLZ77)] + [InlineData("Arj.method3.largefile.arj", CompressionType.ArjLZ77)] + public void Arj_LargeFile_ShouldThrow(string fileName, CompressionType compressionType) + { + var exception = Assert.Throws(() => + ReadForBufferBoundaryCheck(fileName, compressionType) + ); + } - [Theory] - [InlineData("Arj.store.largefile.arj", CompressionType.None)] - [InlineData("Arj.method4.largefile.arj", CompressionType.ArjLZ77)] - public void Arj_LargeFileTest_Read(string fileName, CompressionType compressionType) - { - ReadForBufferBoundaryCheck(fileName, compressionType); - } + [Theory] + [InlineData("Arj.store.largefile.arj", CompressionType.None)] + [InlineData("Arj.method4.largefile.arj", CompressionType.ArjLZ77)] + public void Arj_LargeFileTest_Read(string fileName, CompressionType compressionType) + { + ReadForBufferBoundaryCheck(fileName, compressionType); } } diff --git a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs index 4151855a0..665b6f8fb 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.OpenReader(new NonDisposingStream(stream)); + using var reader = GZipReader.OpenReader(SharpCompressStream.CreateNonDisposing(stream)); while (reader.MoveToNextEntry()) // Crash here { Assert.NotEqual(0, reader.Entry.Size); diff --git a/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs b/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs index 291bae8d4..07a679ef0 100644 --- a/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs +++ b/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs @@ -12,9 +12,6 @@ public class AsyncOnlyStream : Stream public AsyncOnlyStream(Stream stream) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(AsyncOnlyStream)); -#endif } public override bool CanRead => _stream.CanRead; @@ -69,9 +66,6 @@ public override void Write(byte[] buffer, int offset, int count) => protected override void Dispose(bool disposing) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(AsyncOnlyStream)); -#endif if (disposing) { _stream.Dispose(); diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index d62b5c9ac..a6e1c25b9 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -67,7 +67,7 @@ ReaderOptions options private void ReadImplCore(string testArchive, ReaderOptions options, Action useReader) { using var file = File.OpenRead(testArchive); - using var protectedStream = new NonDisposingStream( + using var protectedStream = SharpCompressStream.CreateNonDisposing( new ForwardOnlyStream(file, options.BufferSize) ); using var testStream = new TestStream(protectedStream); @@ -160,13 +160,13 @@ private async ValueTask ReadImplAsync( using var file = File.OpenRead(testArchive); #if !LEGACY_DOTNET - await using var protectedStream = new NonDisposingStream( + await using var protectedStream = SharpCompressStream.CreateNonDisposing( new ForwardOnlyStream(file, options.BufferSize) ); await using var testStream = new TestStream(protectedStream); #else - using var protectedStream = new NonDisposingStream( + using var protectedStream = SharpCompressStream.CreateNonDisposing( new ForwardOnlyStream(file, options.BufferSize) ); using var testStream = new TestStream(protectedStream); diff --git a/tests/SharpCompress.Test/Streams/SeekableRewindableStreamAsyncTest.cs b/tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamAsyncTest.cs similarity index 85% rename from tests/SharpCompress.Test/Streams/SeekableRewindableStreamAsyncTest.cs rename to tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamAsyncTest.cs index d3a2dc5c6..d39b82ade 100644 --- a/tests/SharpCompress.Test/Streams/SeekableRewindableStreamAsyncTest.cs +++ b/tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamAsyncTest.cs @@ -8,13 +8,13 @@ namespace SharpCompress.Test.Streams; -public class SeekableRewindableStreamAsyncTest +public class SeekableSharpCompressStreamAsyncTest { [Fact] public async Task ReadAsync_Buffers() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[5]; int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); Assert.Equal(5, bytesRead); @@ -25,7 +25,7 @@ public async Task ReadAsync_Buffers() public async Task ReadAsync_WithCancellation() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[5]; var cts = new CancellationTokenSource(); int bytesRead = await stream @@ -39,7 +39,7 @@ public async Task ReadAsync_WithCancellation() public async Task ReadAsync_PartialRead() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[10]; int bytesRead = await stream.ReadAsync(buffer, 0, 10).ConfigureAwait(false); Assert.Equal(5, bytesRead); @@ -50,7 +50,7 @@ public async Task ReadAsync_PartialRead() public async Task WriteAsync_Buffers() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; await stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); Assert.Equal(data, ms.ToArray()); @@ -60,7 +60,7 @@ public async Task WriteAsync_Buffers() public async Task WriteAsync_WithCancellation() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; var cts = new CancellationTokenSource(); await stream.WriteAsync(data, 0, data.Length, cts.Token).ConfigureAwait(false); @@ -71,7 +71,7 @@ public async Task WriteAsync_WithCancellation() public async Task FlushAsync_DelegatesToUnderlyingStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; await stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); await stream.FlushAsync().ConfigureAwait(false); @@ -82,7 +82,7 @@ public async Task FlushAsync_DelegatesToUnderlyingStream() public async Task CopyToAsync_CopiesAllData() { var sourceMs = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(sourceMs); + var stream = new SeekableSharpCompressStream(sourceMs); var destinationMs = new MemoryStream(); await stream.CopyToAsync(destinationMs, 4096).ConfigureAwait(false); Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, destinationMs.ToArray()); @@ -92,7 +92,7 @@ public async Task CopyToAsync_CopiesAllData() public async Task ReadAsyncAndSeek_MultipleOperations() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[3]; await stream.ReadAsync(buffer, 0, 3).ConfigureAwait(false); @@ -112,7 +112,7 @@ public async Task ReadAsyncAndSeek_MultipleOperations() public async Task WriteAsyncAndReadAsync_WrittenDataIsReadable() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var writeData = new byte[] { 1, 2, 3, 4, 5 }; await stream.WriteAsync(writeData, 0, writeData.Length).ConfigureAwait(false); @@ -125,13 +125,13 @@ public async Task WriteAsyncAndReadAsync_WrittenDataIsReadable() } #if !LEGACY_DOTNET -public partial class SeekableRewindableStreamMemoryAsyncTest +public partial class SeekableSharpCompressStreamMemoryAsyncTest { [Fact] public async ValueTask ReadAsync_Memory() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[5]; int bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false); Assert.Equal(5, bytesRead); @@ -142,7 +142,7 @@ public async ValueTask ReadAsync_Memory() public async ValueTask ReadAsync_Memory_WithCancellation() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[5]; var cts = new CancellationTokenSource(); int bytesRead = await stream.ReadAsync(buffer, cts.Token).ConfigureAwait(false); @@ -154,7 +154,7 @@ public async ValueTask ReadAsync_Memory_WithCancellation() public async ValueTask WriteAsync_Memory() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; await stream.WriteAsync(data).ConfigureAwait(false); Assert.Equal(data, ms.ToArray()); @@ -164,7 +164,7 @@ public async ValueTask WriteAsync_Memory() public async ValueTask WriteAsync_Memory_WithCancellation() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; var cts = new CancellationTokenSource(); await stream.WriteAsync(data, cts.Token).ConfigureAwait(false); @@ -175,7 +175,7 @@ public async ValueTask WriteAsync_Memory_WithCancellation() public async ValueTask ReadMemoryAndWriteMemory_MemoryOperations() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var writeData = new byte[] { 1, 2, 3, 4, 5 }; await stream.WriteAsync(writeData).ConfigureAwait(false); @@ -190,7 +190,7 @@ public async ValueTask ReadMemoryAndWriteMemory_MemoryOperations() public async ValueTask DisposeAsync_DisposesUnderlyingStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); await stream.DisposeAsync().ConfigureAwait(false); Assert.Throws(() => ms.Read(new byte[1], 0, 1)); } diff --git a/tests/SharpCompress.Test/Streams/SeekableRewindableStreamTest.cs b/tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamTest.cs similarity index 81% rename from tests/SharpCompress.Test/Streams/SeekableRewindableStreamTest.cs rename to tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamTest.cs index 7e4e74367..0071d4203 100644 --- a/tests/SharpCompress.Test/Streams/SeekableRewindableStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamTest.cs @@ -7,26 +7,26 @@ namespace SharpCompress.Test.Streams; -public class SeekableRewindableStreamTest +public class SeekableSharpCompressStreamTest { [Fact] public void Constructor_ThrowsOnNullStream() { - Assert.Throws(() => new SeekableRewindableStream(null!)); + Assert.Throws(() => new SeekableSharpCompressStream(null!)); } [Fact] public void Constructor_ThrowsOnNonSeekableStream() { var nonSeekable = new ForwardOnlyStream(new MemoryStream()); - Assert.Throws(() => new SeekableRewindableStream(nonSeekable)); + Assert.Throws(() => new SeekableSharpCompressStream(nonSeekable)); } [Fact] public void Constructor_AcceptsSeekableStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.NotNull(stream); } @@ -34,7 +34,7 @@ public void Constructor_AcceptsSeekableStream() public void CanRead_DelegatesToUnderlyingStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.Equal(ms.CanRead, stream.CanRead); } @@ -42,7 +42,7 @@ public void CanRead_DelegatesToUnderlyingStream() public void CanSeek_DelegatesToUnderlyingStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.Equal(ms.CanSeek, stream.CanSeek); } @@ -50,7 +50,7 @@ public void CanSeek_DelegatesToUnderlyingStream() public void CanWrite_DelegatesToUnderlyingStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.Equal(ms.CanWrite, stream.CanWrite); } @@ -58,7 +58,7 @@ public void CanWrite_DelegatesToUnderlyingStream() public void Length_DelegatesToUnderlyingStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.Equal(5, stream.Length); } @@ -67,7 +67,7 @@ public void Position_Getter_DelegatesToUnderlyingStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); ms.Position = 2; - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.Equal(2, stream.Position); } @@ -75,7 +75,7 @@ public void Position_Getter_DelegatesToUnderlyingStream() public void Position_Setter_DelegatesToUnderlyingStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.Position = 3; Assert.Equal(3, ms.Position); Assert.Equal(3, stream.Position); @@ -85,7 +85,7 @@ public void Position_Setter_DelegatesToUnderlyingStream() public void IsRecording_AlwaysFalse() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); Assert.False(stream.IsRecording); } @@ -93,7 +93,7 @@ public void IsRecording_AlwaysFalse() public void Read_Buffers() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[5]; int bytesRead = stream.Read(buffer, 0, buffer.Length); Assert.Equal(5, bytesRead); @@ -104,7 +104,7 @@ public void Read_Buffers() public void Seek_DelegatesToUnderlyingStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); long result = stream.Seek(3, SeekOrigin.Begin); Assert.Equal(3, result); Assert.Equal(3, ms.Position); @@ -114,7 +114,7 @@ public void Seek_DelegatesToUnderlyingStream() public void SetLength_DelegatesToUnderlyingStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.SetLength(20); Assert.Equal(20, stream.Length); Assert.Equal(20, ms.Length); @@ -124,7 +124,7 @@ public void SetLength_DelegatesToUnderlyingStream() public void Write_DelegatesToUnderlyingStream() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; stream.Write(data, 0, data.Length); Assert.Equal(data, ms.ToArray()); @@ -134,7 +134,7 @@ public void Write_DelegatesToUnderlyingStream() public void Rewind_IsNoOp() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.Rewind(); Assert.Equal(0, stream.Position); ms.Position = 2; @@ -146,7 +146,7 @@ public void Rewind_IsNoOp() public void StartRecording_IsNotNoOp() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.StartRecording(); Assert.True(stream.IsRecording); } @@ -155,7 +155,7 @@ public void StartRecording_IsNotNoOp() public void StopRecording_IsNoOp() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.StopRecording(); Assert.False(stream.IsRecording); } @@ -164,7 +164,7 @@ public void StopRecording_IsNoOp() public void Dispose_DisposesUnderlyingStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.Dispose(); Assert.Throws(() => ms.Read(new byte[1], 0, 1)); } @@ -173,7 +173,7 @@ public void Dispose_DisposesUnderlyingStream() public void ReadAndSeek_MultipleOperations() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[3]; stream.Read(buffer, 0, 3); @@ -193,7 +193,7 @@ public void ReadAndSeek_MultipleOperations() public void SeekWithDifferentOrigins() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.Seek(3, SeekOrigin.Begin); Assert.Equal(3, stream.Position); @@ -209,7 +209,7 @@ public void SeekWithDifferentOrigins() public void WriteAndRead_WrittenDataIsReadable() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var writeData = new byte[] { 1, 2, 3, 4, 5 }; stream.Write(writeData, 0, writeData.Length); @@ -224,7 +224,7 @@ public void WriteAndRead_WrittenDataIsReadable() public void RecordingOperationsDoAffectStream() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); stream.StartRecording(); var buffer = new byte[3]; @@ -249,7 +249,7 @@ public partial class SeekableRewindableSpanTest public void Read_Span() { var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var buffer = new byte[5]; int bytesRead = stream.Read(buffer); Assert.Equal(5, bytesRead); @@ -260,7 +260,7 @@ public void Read_Span() public void Write_Span() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var data = new byte[] { 1, 2, 3, 4, 5 }; stream.Write(data); Assert.Equal(data, ms.ToArray()); @@ -270,7 +270,7 @@ public void Write_Span() public void ReadAndWrite_SpanOperations() { var ms = new MemoryStream(); - var stream = new SeekableRewindableStream(ms); + var stream = new SeekableSharpCompressStream(ms); var writeData = new byte[] { 1, 2, 3, 4, 5 }; stream.Write(writeData); diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs similarity index 94% rename from tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs rename to tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs index b8b5aea3e..6b417f7c0 100644 --- a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs @@ -10,7 +10,7 @@ namespace SharpCompress.Test.Streams; -public class RewindableStreamAsyncTest +public class SharpCompressStreamAsyncTest { [Fact] public async ValueTask TestRewindAsync() @@ -26,7 +26,7 @@ public async ValueTask TestRewindAsync() bw.Write(7); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); @@ -57,7 +57,7 @@ public async ValueTask TestIncompleteRewindAsync() bw.Write(7); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); @@ -85,7 +85,7 @@ public async ValueTask TestRecordingAsync() bw.Write(4); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); @@ -111,13 +111,13 @@ public async ValueTask TestAsyncProducesSameResultAsSync() byte[] asyncResult; var ms1 = new MemoryStream(testData); - using (var stream = new RewindableStream(ms1)) + using (var stream = new SharpCompressStream(ms1)) { syncResult = ReadAllSync(stream); } var ms2 = new MemoryStream(testData); - using (var stream = new RewindableStream(ms2)) + using (var stream = new SharpCompressStream(ms2)) { asyncResult = await ReadAllAsync(stream).ConfigureAwait(false); } @@ -136,7 +136,7 @@ public async ValueTask TestAsyncWithRewind() } bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var buffer = new byte[8]; @@ -159,7 +159,7 @@ public async ValueTask TestAsyncCancellationSupport() { var ms = new MemoryStream(new byte[10000]); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var cts = new CancellationTokenSource(); var buffer = new byte[4096]; @@ -176,7 +176,7 @@ public async ValueTask TestAsyncEmptyBuffer() { var ms = new MemoryStream(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var buffer = new byte[0]; int bytesRead = await stream.ReadAsync(buffer, 0, 0).ConfigureAwait(false); @@ -194,7 +194,7 @@ public async ValueTask TestAsyncMultipleReads() } bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var totalData = new byte[50 * 4]; var buffer = new byte[8]; @@ -223,7 +223,7 @@ public async ValueTask TestAsyncReturnsZeroAtEndOfStream() bw.Write(2); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var buffer = new byte[4096]; @@ -249,7 +249,7 @@ public async ValueTask TestAsyncPosition() } bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); Assert.Equal(0, stream.Position); var buffer = new byte[4]; @@ -270,7 +270,7 @@ public async ValueTask TestAsyncMemoryCancellationSupport() { var ms = new MemoryStream(new byte[10000]); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var cts = new CancellationTokenSource(); var buffer = new byte[4096]; @@ -285,7 +285,7 @@ public async ValueTask TestAsyncMemoryEmptyBuffer() { var ms = new MemoryStream(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var buffer = Memory.Empty; int bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false); @@ -303,7 +303,7 @@ public async ValueTask TestAsyncMemoryMultipleReads() } bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); var totalData = new byte[50 * 4]; var buffer = new byte[8]; @@ -346,7 +346,7 @@ private static async ValueTask ReadInt32AsyncMemory(Stream stream) } #endif - private static byte[] ReadAllSync(RewindableStream stream) + private static byte[] ReadAllSync(SharpCompressStream stream) { var result = new List(); var buffer = new byte[4096]; @@ -363,7 +363,7 @@ private static byte[] ReadAllSync(RewindableStream stream) return result.ToArray(); } - private static async Task ReadAllAsync(RewindableStream stream) + private static async Task ReadAllAsync(SharpCompressStream stream) { var result = new List(); var buffer = new byte[4096]; @@ -383,7 +383,7 @@ private static async Task ReadAllAsync(RewindableStream stream) } #if !LEGACY_DOTNET - private static async ValueTask ReadAllAsyncMemory(RewindableStream stream) + private static async ValueTask ReadAllAsyncMemory(SharpCompressStream stream) { var result = new List(); var buffer = new byte[4096]; @@ -416,7 +416,7 @@ public async ValueTask TestStopRecordingAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); @@ -449,7 +449,7 @@ public async ValueTask TestStopRecordingNoFurtherBufferingAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var buffer = new byte[8]; @@ -483,7 +483,7 @@ public async ValueTask TestStopRecordingThenRewindAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); // Read first 4 values (gets buffered) @@ -528,7 +528,7 @@ public async ValueTask TestMultipleRewindsAfterStopRecordingAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); // Read first 4 values (gets buffered) @@ -579,7 +579,7 @@ public async ValueTask TestStopRecordingTwiceThrowsAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); @@ -616,7 +616,7 @@ public async ValueTask TestReadMoreThanBufferSizeAfterRewindAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Simulate factory detection: record first 512 bytes stream.StartRecording(); @@ -663,7 +663,7 @@ public async ValueTask TestReadExactlyBufferSizeAfterRewindAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); @@ -698,7 +698,7 @@ public async ValueTask TestReadLessThanBufferSizeAfterRewindAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); @@ -733,7 +733,7 @@ public async ValueTask TestMultipleReadsExceedingBufferAfterRewindAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); @@ -775,7 +775,7 @@ public async ValueTask TestReadPartiallyFromBufferThenUnderlyingStreamAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 100 bytes stream.StartRecording(); @@ -828,7 +828,7 @@ public async ValueTask TestReadMoreThanBufferSizeAfterRewindMemoryAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Simulate factory detection: record first 512 bytes stream.StartRecording(); @@ -875,7 +875,7 @@ public async ValueTask TestMultipleReadsExceedingBufferAfterRewindMemoryAsync() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs similarity index 93% rename from tests/SharpCompress.Test/Streams/RewindableStreamTest.cs rename to tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs index 44029468a..3e846f39e 100644 --- a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs @@ -7,7 +7,7 @@ namespace SharpCompress.Test.Streams; -public class RewindableStreamTest +public class SharpCompressStreamTest { [Fact] public void TestRewind() @@ -23,7 +23,7 @@ public void TestRewind() bw.Write(7); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var br = new BinaryReader(stream); Assert.Equal(1, br.ReadInt32()); @@ -54,7 +54,7 @@ public void TestIncompleteRewind() bw.Write(7); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var br = new BinaryReader(stream); Assert.Equal(1, br.ReadInt32()); @@ -82,7 +82,7 @@ public void TestRecording() bw.Write(4); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var br = new BinaryReader(stream); Assert.Equal(1, br.ReadInt32()); @@ -105,7 +105,7 @@ public void TestPosition() } bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); Assert.Equal(0, stream.Position); var buffer = new byte[4]; @@ -131,7 +131,7 @@ public void TestPositionSeek() } bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); stream.StartRecording(); var br = new BinaryReader(stream); @@ -152,7 +152,7 @@ public void TestDispose() bw.Write(2); bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); stream.Dispose(); Assert.Throws(() => stream.Read(new byte[4], 0, 4)); } @@ -172,7 +172,7 @@ public void TestStopRecordingBasic() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); stream.StartRecording(); var br = new BinaryReader(stream); @@ -206,7 +206,7 @@ public void TestStopRecordingNoFurtherBuffering() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); stream.StartRecording(); var buffer = new byte[8]; @@ -240,7 +240,7 @@ public void TestStopRecordingWithSpan() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var buffer = new byte[8]; @@ -275,7 +275,7 @@ public void TestNonSeekableStream_Rewind() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); stream.StartRecording(); var br = new BinaryReader(stream); @@ -306,7 +306,7 @@ public void TestNonSeekableStream_Recording() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); stream.StartRecording(); var br = new BinaryReader(stream); @@ -332,7 +332,7 @@ public void TestNonSeekableStream_Position() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); Assert.Equal(0, stream.Position); Assert.True(stream.CanSeek); @@ -361,7 +361,7 @@ public void TestNonSeekableStream_PositionSet_WithinBuffer() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); stream.StartRecording(); var buffer = new byte[4]; @@ -394,7 +394,7 @@ public void TestNonSeekableStream_PositionSet_OutsideBuffer_Throws() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); stream.StartRecording(); var buffer = new byte[4]; @@ -419,7 +419,7 @@ public void TestNonSeekableStream_StopRecordingBasic() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); stream.StartRecording(); var br = new BinaryReader(stream); @@ -457,7 +457,7 @@ public void TestStopRecordingThenRewind() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var br = new BinaryReader(new ForwardOnlyStream(stream)); @@ -511,7 +511,7 @@ public void TestNonSeekableStream_StopRecordingThenRewind() ms.Position = 0; var nonSeekableStream = new NonSeekableStreamWrapper(ms); - var stream = new RewindableStream(nonSeekableStream); + var stream = new SharpCompressStream(nonSeekableStream); stream.StartRecording(); var br = new BinaryReader(stream); @@ -557,7 +557,7 @@ public void TestMultipleRewindsAfterStopRecording() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var br = new BinaryReader(stream); @@ -609,7 +609,7 @@ public void TestStopRecordingTwiceThrows() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(ms); + var stream = new SharpCompressStream(ms); stream.StartRecording(); var br = new BinaryReader(stream); @@ -647,7 +647,7 @@ public void TestReadMoreThanBufferSizeAfterRewind() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Simulate factory detection: record first 512 bytes stream.StartRecording(); @@ -694,7 +694,7 @@ public void TestReadExactlyBufferSizeAfterRewind() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); @@ -729,7 +729,7 @@ public void TestReadLessThanBufferSizeAfterRewind() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); @@ -764,7 +764,7 @@ public void TestMultipleReadsExceedingBufferAfterRewind() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 512 bytes stream.StartRecording(); @@ -806,7 +806,7 @@ public void TestReadPartiallyFromBufferThenUnderlyingStream() bw.Flush(); ms.Position = 0; - var stream = new RewindableStream(new ForwardOnlyStream(ms)); + var stream = new SharpCompressStream(new ForwardOnlyStream(ms)); // Record first 100 bytes stream.StartRecording(); diff --git a/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs index ba9ad605b..e2f1506c6 100644 --- a/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs +++ b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs @@ -86,7 +86,7 @@ await GetBytesAsync(byteBufferStream).ConfigureAwait(false) private async ValueTask CompressAsync(Stream input, Stream output, int compressionLevel) { using var zlibStream = new ZlibStream( - new NonDisposingStream(output), + SharpCompressStream.CreateNonDisposing(output), CompressionMode.Compress, (CompressionLevel)compressionLevel ); @@ -97,7 +97,7 @@ private async ValueTask CompressAsync(Stream input, Stream output, int compressi private async ValueTask DecompressAsync(Stream input, Stream output) { using var zlibStream = new ZlibStream( - new NonDisposingStream(input), + SharpCompressStream.CreateNonDisposing(input), CompressionMode.Decompress ); await zlibStream.CopyToAsync(output).ConfigureAwait(false); diff --git a/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs b/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs index 0ba0a8c44..0fd53d8fb 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( - new NonDisposingStream(output), + SharpCompressStream.CreateNonDisposing(output), 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( - new NonDisposingStream(input), + SharpCompressStream.CreateNonDisposing(input), CompressionMode.Decompress ); zlibStream.CopyTo(output); diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index e2ad56f8a..0759d726c 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -253,7 +253,11 @@ public async ValueTask Tar_Read_One_At_A_Time_Async() { ++numberOfEntries; +#if LEGACY_DOTNET using var tarEntryStream = await entry.OpenEntryStreamAsync(); +#else + await using var tarEntryStream = await entry.OpenEntryStreamAsync(); +#endif using var testFileStream = new MemoryStream(); await tarEntryStream.CopyToAsync(testFileStream); Assert.Equal(testBytes.Length, testFileStream.Length); diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index 5b6ae7ff3..f02b65d7a 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -44,7 +44,7 @@ protected void Write( readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; using var reader = ReaderFactory.OpenReader( - new NonDisposingStream(stream), + SharpCompressStream.CreateNonDisposing(stream), readerOptions ); reader.WriteAllToDirectory( @@ -93,7 +93,7 @@ await writer.WriteAllAsync( readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; await using var reader = await ReaderFactory.OpenAsyncReader( - new AsyncOnlyStream(new NonDisposingStream(stream)), + new AsyncOnlyStream(SharpCompressStream.CreateNonDisposing(stream)), readerOptions, cancellationToken ); diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 65a307a24..6460a766a 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -297,7 +297,9 @@ public void TestSharpCompressWithEmptyStream() stream = new MemoryStream(memory.ToArray()); File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), memory.ToArray()); - using IReader zipReader = ZipReader.OpenReader(new NonDisposingStream(stream)); + using IReader zipReader = ZipReader.OpenReader( + SharpCompressStream.CreateNonDisposing(stream) + ); var i = 0; while (zipReader.MoveToNextEntry()) {