diff --git a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs index 48abea7d..729ad529 100644 --- a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs @@ -58,7 +58,7 @@ internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArch { //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 d61d03da..be352cdb 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -46,7 +46,13 @@ public static class IArchiveEntryExtensions 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 3f766cd8..e9b27d6a 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -153,12 +153,12 @@ public partial class SevenZipArchive : AbstractArchive 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 e27a603c..99254369 100644 --- a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs @@ -79,7 +79,7 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv } //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 6d58ac99..168f3a4f 100644 --- a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs @@ -80,7 +80,7 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry } //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 f075c6a0..bbd51d92 100644 --- a/src/SharpCompress/Common/Ace/AceCrc.cs +++ b/src/SharpCompress/Common/Ace/AceCrc.cs @@ -4,62 +4,61 @@ using System.Linq; 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) + return table; + } + + /// + /// 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) { - uint crc = 0xFFFFFFFFu; - - foreach (byte b in data) - { - crc = (crc >> 8) ^ Crc32Table[(crc ^ b) & 0xFF]; - } - - return crc; // No final XOR for ACE + crc = (crc >> 8) ^ Crc32Table[(crc ^ b) & 0xFF]; } - /// - /// ACE CRC-16 is the lower 16 bits of the ACE CRC-32. - /// - public static ushort AceCrc16(ReadOnlySpan data) - { - return (ushort)(AceCrc32(data) & 0xFFFF); - } + 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 a3dde1e7..5aa94fb4 100644 --- a/src/SharpCompress/Common/Ace/AceEntry.cs +++ b/src/SharpCompress/Common/Ace/AceEntry.cs @@ -6,63 +6,62 @@ using System.Text; 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; + + internal AceEntry(AceFilePart filePart) { - private readonly AceFilePart _filePart; - - internal AceEntry(AceFilePart filePart) - { - _filePart = filePart; - } - - public override long Crc - { - get - { - if (_filePart == null) - { - return 0; - } - return _filePart.Header.Crc32; - } - } - - public override string? Key => _filePart?.Header.Filename; - - public override string? LinkTarget => null; - - public override long CompressedSize => _filePart?.Header.PackedSize ?? 0; - - public override CompressionType CompressionType - { - get - { - if (_filePart.Header.CompressionType == Headers.CompressionType.Stored) - { - return CompressionType.None; - } - return CompressionType.AceLZ77; - } - } - - public override long Size => _filePart?.Header.OriginalSize ?? 0; - - public override DateTime? LastModifiedTime => _filePart.Header.DateTime; - - public override DateTime? CreatedTime => null; - - public override DateTime? LastAccessedTime => null; - - public override DateTime? ArchivedTime => null; - - public override bool IsEncrypted => _filePart.Header.IsFileEncrypted; - - public override bool IsDirectory => _filePart.Header.IsDirectory; - - public override bool IsSplitAfter => false; - - internal override IEnumerable Parts => _filePart.Empty(); + _filePart = filePart; } + + public override long Crc + { + get + { + if (_filePart == null) + { + return 0; + } + return _filePart.Header.Crc32; + } + } + + public override string? Key => _filePart?.Header.Filename; + + public override string? LinkTarget => null; + + public override long CompressedSize => _filePart?.Header.PackedSize ?? 0; + + public override CompressionType CompressionType + { + get + { + if (_filePart.Header.CompressionType == Headers.CompressionType.Stored) + { + return CompressionType.None; + } + return CompressionType.AceLZ77; + } + } + + public override long Size => _filePart?.Header.OriginalSize ?? 0; + + public override DateTime? LastModifiedTime => _filePart.Header.DateTime; + + public override DateTime? CreatedTime => null; + + public override DateTime? LastAccessedTime => null; + + public override DateTime? ArchivedTime => null; + + public override bool IsEncrypted => _filePart.Header.IsFileEncrypted; + + public override bool IsDirectory => _filePart.Header.IsDirectory; + + public override bool IsSplitAfter => false; + + internal override IEnumerable Parts => _filePart.Empty(); } diff --git a/src/SharpCompress/Common/Ace/AceFilePart.cs b/src/SharpCompress/Common/Ace/AceFilePart.cs index 6efefaf5..a56c17f6 100644 --- a/src/SharpCompress/Common/Ace/AceFilePart.cs +++ b/src/SharpCompress/Common/Ace/AceFilePart.cs @@ -7,46 +7,45 @@ using System.Threading.Tasks; 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; } + + internal AceFilePart(AceFileHeader localAceHeader, Stream seekableStream) + : base(localAceHeader.ArchiveEncoding) { - private readonly Stream _stream; - internal AceFileHeader Header { get; set; } - - internal AceFilePart(AceFileHeader localAceHeader, Stream seekableStream) - : base(localAceHeader.ArchiveEncoding) - { - _stream = seekableStream; - Header = localAceHeader; - } - - internal override string? FilePartName => Header.Filename; - - internal override Stream GetCompressedStream() - { - if (_stream != null) - { - 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; - } - return _stream.NotNull(); - } - - internal override Stream? GetRawStream() => _stream; + _stream = seekableStream; + Header = localAceHeader; } + + internal override string? FilePartName => Header.Filename; + + internal override Stream GetCompressedStream() + { + if (_stream != null) + { + 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; + } + 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 28456866..f3931d2c 100644 --- a/src/SharpCompress/Common/Ace/AceVolume.cs +++ b/src/SharpCompress/Common/Ace/AceVolume.cs @@ -7,29 +7,28 @@ using System.Threading.Tasks; 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 override bool IsFirstVolume { - public AceVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) { } + 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 16aa1e3b..9a968810 100644 --- a/src/SharpCompress/Common/Ace/Headers/AceFileHeader.cs +++ b/src/SharpCompress/Common/Ace/Headers/AceFileHeader.cs @@ -7,169 +7,168 @@ using System.Threading.Tasks; 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) { } - - /// - /// 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) - { - return null; - } - int offset = 0; - - // 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; - } - - 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; - - // Packed size (4 bytes) - PackedSize = 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 attributes (4 bytes) - Attributes = (int)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 quality/parameter (1 byte) - byte compressionQuality = headerData[offset++]; - CompressionQuality = GetCompressionQuality(compressionQuality); - - // Parameters (2 bytes) - Parameters = BitConverter.ToUInt16(headerData, offset); - offset += 2; - - // Reserved (2 bytes) - skip - 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; - } - - // Handle comment if present - if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) - { - // Comment length (2 bytes) - if (offset + 2 <= headerData.Length) - { - 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 - - 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, - }; } + + 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) + { + var headerData = ReadHeader(stream); + if (headerData.Length == 0) + { + return null; + } + int offset = 0; + + // 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; + } + + 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; + + // Packed size (4 bytes) + PackedSize = 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 attributes (4 bytes) + Attributes = (int)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 quality/parameter (1 byte) + byte compressionQuality = headerData[offset++]; + CompressionQuality = GetCompressionQuality(compressionQuality); + + // Parameters (2 bytes) + Parameters = BitConverter.ToUInt16(headerData, offset); + offset += 2; + + // Reserved (2 bytes) - skip + 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; + } + + // Handle comment if present + if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) + { + // Comment length (2 bytes) + if (offset + 2 <= headerData.Length) + { + 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 + + 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 eb4bf5b7..5e3657bc 100644 --- a/src/SharpCompress/Common/Ace/Headers/AceHeader.cs +++ b/src/SharpCompress/Common/Ace/Headers/AceHeader.cs @@ -5,153 +5,152 @@ using System.Threading.Tasks; 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, +} + +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) { - MAIN = 0, - FILE = 1, - RECOVERY32 = 2, - RECOVERY64A = 3, - RECOVERY64B = 4, + AceHeaderType = type; + ArchiveEncoding = archiveEncoding; } - public abstract partial class AceHeader + 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 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 IsMultiVolume => + (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.MULTIVOLUME) != 0; + + public abstract AceHeader? Read(Stream reader); + + // Async methods moved to AceHeader.Async.cs + + public byte[] ReadHeader(Stream stream) { - // 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) + // Read header CRC (2 bytes) and header size (2 bytes) + var headerBytes = new byte[4]; + if (!stream.ReadFully(headerBytes)) { - AceHeaderType = type; - ArchiveEncoding = archiveEncoding; + return Array.Empty(); } - 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 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 IsMultiVolume => - (HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.MULTIVOLUME) != 0; - - public abstract AceHeader? Read(Stream reader); - - // Async methods moved to AceHeader.Async.cs - - public byte[] ReadHeader(Stream stream) + HeaderCrc = BitConverter.ToUInt16(headerBytes, 0); // CRC for validation + HeaderSize = BitConverter.ToUInt16(headerBytes, 2); + if (HeaderSize == 0) { - // 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(); - } - - // Read the header data - var body = new byte[HeaderSize]; - if (!stream.ReadFully(body)) - { - return Array.Empty(); - } - - // Verify crc - var checksum = AceCrc.AceCrc16(body); - if (checksum != HeaderCrc) - { - throw new InvalidDataException("Header checksum is invalid"); - } - return body; + return Array.Empty(); } - public static bool IsArchive(Stream stream) + // Read the header data + var body = new byte[HeaderSize]; + if (!stream.ReadFully(body)) { - // 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 Array.Empty(); + } + + // Verify crc + var checksum = AceCrc.AceCrc16(body); + if (checksum != HeaderCrc) + { + throw new InvalidDataException("Header checksum is invalid"); + } + return body; + } + + 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; + } + + // 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++) + { + if (headerBytes[offset + i] != AceSignature[i]) { return false; } - - // Check for "**ACE**" at offset 7 - return CheckMagicBytes(bytes, 7); } + return true; + } - protected static bool CheckMagicBytes(byte[] headerBytes, int offset) + protected DateTime ConvertDosDateTime(uint dosDateTime) + { + try { - // Check for "**ACE**" at specified offset - for (int i = 0; i < AceSignature.Length; i++) - { - if (headerBytes[offset + i] != AceSignature[i]) - { - return false; - } - } - return true; - } + 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; - protected DateTime ConvertDosDateTime(uint dosDateTime) - { - 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 + 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 7b189cd6..fd74354f 100644 --- a/src/SharpCompress/Common/Ace/Headers/AceMainHeader.cs +++ b/src/SharpCompress/Common/Ace/Headers/AceMainHeader.cs @@ -8,94 +8,93 @@ using SharpCompress.Common.Ace.Headers; 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 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; - - // 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++]; - - // 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; - } - - // Skip additional fields based on flags - // Handle comment if present - if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) - { - if (offset + 2 <= headerData.Length) - { - ushort commentLength = BitConverter.ToUInt16(headerData, offset); - offset += 2 + commentLength; - } - } - - return this; + // Header type should be 0 for main header + if (headerData[offset++] != HeaderType) + { + return null; } - // ReadAsync moved to AceMainHeader.Async.cs + // 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; + + // 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++]; + + // 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; + } + + // Skip additional fields based on flags + // Handle comment if present + if ((HeaderFlags & SharpCompress.Common.Ace.Headers.HeaderFlags.COMMENT) != 0) + { + if (offset + 2 <= headerData.Length) + { + ushort commentLength = BitConverter.ToUInt16(headerData, offset); + offset += 2 + commentLength; + } + } + + 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 57017b55..eb53d639 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 799e7929..f86fa080 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 6a5f8926..b3d67898 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 173d56eb..d58d30c0 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 a67f10d1..0a94ae0c 100644 --- a/src/SharpCompress/Common/Arc/ArcEntry.cs +++ b/src/SharpCompress/Common/Arc/ArcEntry.cs @@ -7,54 +7,53 @@ using System.Threading.Tasks; 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; + + internal ArcEntry(ArcFilePart? filePart) { - private readonly ArcFilePart? _filePart; - - internal ArcEntry(ArcFilePart? filePart) - { - _filePart = filePart; - } - - public override long Crc - { - get - { - if (_filePart == null) - { - return 0; - } - return _filePart.Header.Crc16; - } - } - - public override string? Key => _filePart?.Header.Name; - - public override string? LinkTarget => null; - - public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; - - public override CompressionType CompressionType => - _filePart?.Header.CompressionMethod ?? CompressionType.Unknown; - - public override long Size => throw new NotImplementedException(); - - public override DateTime? LastModifiedTime => null; - - public override DateTime? CreatedTime => null; - - public override DateTime? LastAccessedTime => null; - - public override DateTime? ArchivedTime => null; - - public override bool IsEncrypted => false; - - public override bool IsDirectory => false; - - public override bool IsSplitAfter => false; - - internal override IEnumerable Parts => _filePart.Empty(); + _filePart = filePart; } + + public override long Crc + { + get + { + if (_filePart == null) + { + return 0; + } + return _filePart.Header.Crc16; + } + } + + public override string? Key => _filePart?.Header.Name; + + public override string? LinkTarget => null; + + public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; + + public override CompressionType CompressionType => + _filePart?.Header.CompressionMethod ?? CompressionType.Unknown; + + public override long Size => throw new NotImplementedException(); + + public override DateTime? LastModifiedTime => null; + + public override DateTime? CreatedTime => null; + + public override DateTime? LastAccessedTime => null; + + public override DateTime? ArchivedTime => null; + + public override bool IsEncrypted => false; + + public override bool IsDirectory => false; + + public override bool IsSplitAfter => false; + + internal override IEnumerable Parts => _filePart.Empty(); } diff --git a/src/SharpCompress/Common/Arc/ArcEntryHeader.cs b/src/SharpCompress/Common/Arc/ArcEntryHeader.cs index d2c0479f..3645020d 100644 --- a/src/SharpCompress/Common/Arc/ArcEntryHeader.cs +++ b/src/SharpCompress/Common/Arc/ArcEntryHeader.cs @@ -5,91 +5,90 @@ using System.Text; 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 ArcEntryHeader(IArchiveEncoding archiveEncoding) { - 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; } + this.ArchiveEncoding = archiveEncoding; + } - public ArcEntryHeader(IArchiveEncoding archiveEncoding) + public ArcEntryHeader? ReadHeader(Stream stream) + { + byte[] headerBytes = new byte[29]; + if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length) { - this.ArchiveEncoding = archiveEncoding; + return null; } + DataStartPosition = stream.Position; + return LoadFrom(headerBytes); + } - public ArcEntryHeader? ReadHeader(Stream stream) - { - byte[] headerBytes = new byte[29]; - if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length) - { - 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) + 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); + + 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) + { + return value switch { - CompressionMethod = GetCompressionType(headerBytes[1]); + 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, + }; + } - // 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; - } - - private CompressionType GetCompressionType(byte value) - { - 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, - }; - } - - 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 60f6b697..0ae8004d 100644 --- a/src/SharpCompress/Common/Arc/ArcFilePart.Async.cs +++ b/src/SharpCompress/Common/Arc/ArcFilePart.Async.cs @@ -7,62 +7,52 @@ using SharpCompress.Compressors.RLE90; 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 0c4474ff..4a5231a1 100644 --- a/src/SharpCompress/Common/Arc/ArcFilePart.cs +++ b/src/SharpCompress/Common/Arc/ArcFilePart.cs @@ -13,74 +13,61 @@ using SharpCompress.Compressors.RLE90; 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; + + internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream) + : base(localArcHeader.ArchiveEncoding) { - private readonly Stream? _stream; - - internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream) - : base(localArcHeader.ArchiveEncoding) - { - _stream = seekableStream; - Header = localArcHeader; - } - - internal ArcEntryHeader Header { get; set; } - - internal override string? FilePartName => Header.Name; - - internal override Stream GetCompressedStream() - { - if (_stream != null) - { - 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: - throw new NotSupportedException( - "CompressionMethod: " + Header.CompressionMethod - ); - } - return compressedStream; - } - return _stream.NotNull(); - } - - internal override Stream? GetRawStream() => _stream; + _stream = seekableStream; + Header = localArcHeader; } + + internal ArcEntryHeader Header { get; set; } + + internal override string? FilePartName => Header.Name; + + internal override Stream GetCompressedStream() + { + if (_stream != null) + { + 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: + throw new NotSupportedException( + "CompressionMethod: " + Header.CompressionMethod + ); + } + return compressedStream; + } + 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 8ebd11ea..99fb56ee 100644 --- a/src/SharpCompress/Common/Arc/ArcVolume.cs +++ b/src/SharpCompress/Common/Arc/ArcVolume.cs @@ -6,11 +6,10 @@ using System.Text; 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 8dd84e87..fe4a525b 100644 --- a/src/SharpCompress/Common/Arj/ArjEntry.cs +++ b/src/SharpCompress/Common/Arj/ArjEntry.cs @@ -6,53 +6,52 @@ using System.Threading.Tasks; 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; + + internal ArjEntry(ArjFilePart filePart) { - private readonly ArjFilePart _filePart; - - internal ArjEntry(ArjFilePart filePart) - { - _filePart = filePart; - } - - public override long Crc => _filePart.Header.OriginalCrc32; - - public override string? Key => _filePart?.Header.Name; - - public override string? LinkTarget => null; - - public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; - - public override CompressionType CompressionType - { - get - { - if (_filePart.Header.CompressionMethod == CompressionMethod.Stored) - { - return CompressionType.None; - } - return CompressionType.ArjLZ77; - } - } - - public override long Size => _filePart?.Header.OriginalSize ?? 0; - - public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime; - - public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime; - - public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime; - - public override DateTime? ArchivedTime => null; - - public override bool IsEncrypted => false; - - public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory; - - public override bool IsSplitAfter => false; - - internal override IEnumerable Parts => _filePart.Empty(); + _filePart = filePart; } + + public override long Crc => _filePart.Header.OriginalCrc32; + + public override string? Key => _filePart?.Header.Name; + + public override string? LinkTarget => null; + + public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0; + + public override CompressionType CompressionType + { + get + { + if (_filePart.Header.CompressionMethod == CompressionMethod.Stored) + { + return CompressionType.None; + } + return CompressionType.ArjLZ77; + } + } + + public override long Size => _filePart?.Header.OriginalSize ?? 0; + + public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime; + + public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime; + + public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime; + + public override DateTime? ArchivedTime => null; + + public override bool IsEncrypted => false; + + public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory; + + public override bool IsSplitAfter => false; + + internal override IEnumerable Parts => _filePart.Empty(); } diff --git a/src/SharpCompress/Common/Arj/ArjFilePart.cs b/src/SharpCompress/Common/Arj/ArjFilePart.cs index 83246d22..f8bb2311 100644 --- a/src/SharpCompress/Common/Arj/ArjFilePart.cs +++ b/src/SharpCompress/Common/Arj/ArjFilePart.cs @@ -8,65 +8,62 @@ using SharpCompress.Common.Arj.Headers; 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; } + + internal ArjFilePart(ArjLocalHeader localArjHeader, Stream seekableStream) + : base(localArjHeader.ArchiveEncoding) { - private readonly Stream _stream; - internal ArjLocalHeader Header { get; set; } - - internal ArjFilePart(ArjLocalHeader localArjHeader, Stream seekableStream) - : base(localArjHeader.ArchiveEncoding) - { - _stream = seekableStream; - Header = localArjHeader; - } - - internal override string? FilePartName => Header.Name; - - internal override Stream GetCompressedStream() - { - if (_stream != null) - { - 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: - throw new NotSupportedException( - "CompressionMethod: " + Header.CompressionMethod - ); - } - return compressedStream; - } - return _stream.NotNull(); - } - - internal override Stream GetRawStream() => _stream; + _stream = seekableStream; + Header = localArjHeader; } + + internal override string? FilePartName => Header.Name; + + internal override Stream GetCompressedStream() + { + if (_stream != null) + { + 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: + throw new NotSupportedException( + "CompressionMethod: " + Header.CompressionMethod + ); + } + return compressedStream; + } + 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 872da93a..d65d56c9 100644 --- a/src/SharpCompress/Common/Arj/ArjVolume.cs +++ b/src/SharpCompress/Common/Arj/ArjVolume.cs @@ -8,29 +8,28 @@ using SharpCompress.Common.Rar; 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 override bool IsFirstVolume { - public ArjVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) { } + 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 dd7ca402..a12eb01d 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs @@ -8,156 +8,153 @@ using System.Threading.Tasks; using SharpCompress.Common.Zip.Headers; using SharpCompress.Crypto; -namespace SharpCompress.Common.Arj.Headers +namespace SharpCompress.Common.Arj.Headers; + +public enum ArjHeaderType { - public enum ArjHeaderType + MainHeader, + LocalHeader, +} + +public abstract partial class ArjHeader +{ + 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 - { - private const int FIRST_HDR_SIZE = 34; - private const ushort ARJ_MAGIC = 0xEA60; + public ArjHeaderType ArjHeaderType { get; } + public byte Flags { get; set; } + public FileType FileType { get; set; } - public ArjHeader(ArjHeaderType type) + public abstract ArjHeader? Read(Stream reader); + + // Async methods moved to ArjHeader.Async.cs + + public byte[] ReadHeader(Stream stream) + { + // check for magic bytes + var magic = new byte[2]; + if (stream.Read(magic) != 2) { - ArjHeaderType = type; + return Array.Empty(); } - 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) + if (!CheckMagicBytes(magic)) { - // check for magic bytes - var magic = new byte[2]; - if (stream.Read(magic) != 2) + throw new InvalidDataException("Not an ARJ file (wrong magic bytes)"); + } + + // 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; + } + + // 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) { - 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(); - } - - var body = new byte[headerSize]; - var read = stream.Read(body, 0, headerSize); - if (read < headerSize) - { - return Array.Empty(); + throw new EndOfStreamException( + "Unexpected end of stream while reading extended header data." + ); } byte[] crc = new byte[4]; - read = stream.Read(crc, 0, 4); - var checksum = Crc32Stream.Compute(body); - // Compute the hash value + 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("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); - } - } - - // 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) - { - var bytes = new byte[2]; - if (stream.Read(bytes, 0, 2) != 2) - { - return false; + throw new InvalidDataException("Extended header checksum is invalid"); } - return CheckMagicBytes(bytes); - } - - protected static bool CheckMagicBytes(byte[] headerBytes) - { - var magicValue = (ushort)(headerBytes[0] | headerBytes[1] << 8); - return magicValue == ARJ_MAGIC; + 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; + } + + public static bool IsArchive(Stream stream) + { + var bytes = new byte[2]; + if (stream.Read(bytes, 0, 2) != 2) + { + return false; + } + + 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 a16596df..4ace325d 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs @@ -7,158 +7,156 @@ using System.Text; 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; } + ArchiveEncoding = + archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); + } - 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 override ArjHeader? Read(Stream stream) + { + var body = ReadHeader(stream); + if (body.Length > 0) { - ArchiveEncoding = - archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); + ReadExtendedHeaders(stream); + var header = LoadFrom(body); + header.DataStartPosition = stream.Position; + return header; + } + return null; + } + + // ReadAsync moved to ArjLocalHeader.Async.cs + + public ArjLocalHeader LoadFrom(byte[] headerBytes) + { + int offset = 0; + + int ReadInt16() + { + if (offset + 1 >= headerBytes.Length) + { + throw new EndOfStreamException(); + } + var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8; + offset += 2; + return v; + } + long ReadInt32() + { + 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; } - public override ArjHeader? Read(Stream stream) + 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 + + var rawTimestamp = ReadInt32(); + DateTimeModified = rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); + + CompressedSize = ReadInt32(); + OriginalSize = ReadInt32(); + OriginalCrc32 = ReadInt32(); + FileSpecPosition = ReadInt16(); + FileAccessMode = ReadInt16(); + + FirstChapter = headerBytes[offset++]; + LastChapter = headerBytes[offset++]; + + ExtendedFilePosition = 0; + OriginalSizeEvenForVolumes = 0; + + if (headerSize > StdHdrSize) { - var body = ReadHeader(stream); - if (body.Length > 0) + ExtendedFilePosition = ReadInt32(); + + if (headerSize >= R9HdrSize) { - ReadExtendedHeaders(stream); - var header = LoadFrom(body); - header.DataStartPosition = stream.Position; - return header; + rawTimestamp = ReadInt32(); + DateTimeAccessed = + rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); + rawTimestamp = ReadInt32(); + DateTimeCreated = + rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); + OriginalSizeEvenForVolumes = ReadInt32(); } - return null; } - // ReadAsync moved to ArjLocalHeader.Async.cs + Name = Encoding.ASCII.GetString( + headerBytes, + offset, + Array.IndexOf(headerBytes, (byte)0, offset) - offset + ); + offset += Name.Length + 1; - public ArjLocalHeader LoadFrom(byte[] headerBytes) + 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 { - int offset = 0; - - int ReadInt16() - { - if (offset + 1 >= headerBytes.Length) - { - throw new EndOfStreamException(); - } - var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8; - offset += 2; - return v; - } - long ReadInt32() - { - 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 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 - - var rawTimestamp = ReadInt32(); - DateTimeModified = - rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0); - - CompressedSize = ReadInt32(); - OriginalSize = ReadInt32(); - OriginalCrc32 = ReadInt32(); - FileSpecPosition = ReadInt16(); - FileAccessMode = ReadInt16(); - - FirstChapter = headerBytes[offset++]; - LastChapter = headerBytes[offset++]; - - ExtendedFilePosition = 0; - OriginalSizeEvenForVolumes = 0; - - if (headerSize > StdHdrSize) - { - 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(); - } - } - - 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 - { - 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 6fb13896..3f458613 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs @@ -6,137 +6,136 @@ using System.Threading.Tasks; 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) { - private const int FIRST_HDR_SIZE = 34; - private const ushort ARJ_MAGIC = 0xEA60; + ArchiveEncoding = + archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding)); + } - public ArchiveEncoding ArchiveEncoding { get; } + public override ArjHeader? Read(Stream stream) + { + var body = ReadHeader(stream); + ReadExtendedHeaders(stream); + return LoadFrom(body); + } - 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; } + // ReadAsync moved to ArjMainHeader.Async.cs - 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 LoadFrom(byte[] headerBytes) + { + var offset = 1; - public ArjMainHeader(ArchiveEncoding archiveEncoding) - : base(ArjHeaderType.MainHeader) + 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; - } - - long ReadInt32() - { - 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; - } - 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 + result.Append((char)x[i]); 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(); } - ArchiverVersionNumber = ReadByte(); - MinVersionToExtract = ReadByte(); + // 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; + } - var hostOsByte = ReadByte(); - HostOs = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown; - - Flags = ReadByte(); - SecurityVersion = ReadByte(); - FileType = FileTypeFromByte(ReadByte()); - - offset++; // skip reserved - - CreationDateTime = new DosDateTime((int)ReadInt32()); - CompressedSize = ReadInt32(); - ArchiveSize = ReadInt32(); - - SecurityEnvelope = ReadInt32(); - FileSpecPosition = ReadInt16(); - SecurityEnvelopeLength = ReadInt16(); - - EncryptionVersion = ReadByte(); - LastChapter = ReadByte(); - - Name = ReadNullTerminatedString(headerBytes, offset); - Comment = ReadNullTerminatedString(headerBytes, offset + 1 + Name.Length); - - return this; + return result.ToString(); } + + ArchiverVersionNumber = ReadByte(); + MinVersionToExtract = ReadByte(); + + var hostOsByte = ReadByte(); + HostOs = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown; + + Flags = ReadByte(); + SecurityVersion = ReadByte(); + FileType = FileTypeFromByte(ReadByte()); + + offset++; // skip reserved + + CreationDateTime = new DosDateTime((int)ReadInt32()); + CompressedSize = ReadInt32(); + ArchiveSize = ReadInt32(); + + SecurityEnvelope = ReadInt32(); + FileSpecPosition = ReadInt16(); + SecurityEnvelopeLength = ReadInt16(); + + EncryptionVersion = ReadByte(); + LastChapter = ReadByte(); + + Name = ReadNullTerminatedString(headerBytes, offset); + Comment = ReadNullTerminatedString(headerBytes, offset + 1 + Name.Length); + + return this; } } diff --git a/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs b/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs index acdf2ce9..e4423e2a 100644 --- a/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs +++ b/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs @@ -4,17 +4,16 @@ using System.Linq; 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 d51045ad..c882e212 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)); - public DosDateTime(long dosValue) + 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 { - // 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 39c529f0..18b4535b 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 a74e7b0f..ded53a94 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 efcaf9e4..00000000 --- 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 8b8f20b2..b9ffcbe3 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 21708a89..a58afe70 100644 --- a/src/SharpCompress/Common/EntryStream.Async.cs +++ b/src/SharpCompress/Common/EntryStream.Async.cs @@ -42,9 +42,6 @@ public partial class EntryStream 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 537edabb..4ebd100a 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 @@ public partial class EntryStream : Stream _isDisposed = true; if (!(_completed || _reader.Cancelled)) { - if (_useSyncOverAsyncDispose) + if (Utility.UseSyncOverAsyncDispose()) { SkipEntryAsync().GetAwaiter().GetResult(); } @@ -72,9 +67,6 @@ public partial class EntryStream : Stream 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 57837f84..5b58e0a2 100644 --- a/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs @@ -4,6 +4,7 @@ using System.IO; 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 f753eb03..a43ad55b 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; 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 f0b7a53c..966dc2bc 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -6,13 +6,7 @@ using System.Threading; 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 e3ca687f..f5d796a4 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.IO; 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 ad6a357e..59e0fc6b 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 7edbed2f..d6c17889 100644 --- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs +++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs @@ -16,9 +16,6 @@ internal class TarReadOnlySubStream : Stream _stream = stream; _useSyncOverAsyncDispose = useSyncOverAsyncDispose; BytesLeftToRead = bytesToRead; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(TarReadOnlySubStream)); -#endif } protected override void Dispose(bool disposing) @@ -29,9 +26,6 @@ internal class TarReadOnlySubStream : Stream } _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 @@ internal class TarReadOnlySubStream : Stream if (bytesInLastBlock != 0) { - if (_useSyncOverAsyncDispose) + if (Utility.UseSyncOverAsyncDispose()) { _stream.SkipAsync(512 - bytesInLastBlock).GetAwaiter().GetResult(); } @@ -64,9 +58,6 @@ internal class TarReadOnlySubStream : Stream } _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 849f90b7..d00ed4f0 100644 --- a/src/SharpCompress/Common/Volume.cs +++ b/src/SharpCompress/Common/Volume.cs @@ -17,13 +17,14 @@ public abstract partial class Volume : IVolume, IAsyncDisposable 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 811beeff..86e0f771 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 e723f1e5..6cc356d6 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.IO; 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 9c648baf..86b66a81 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 3a7426a1..9a8a991e 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 29aaabaa..d5e68fee 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 386db415..9bbfe4f8 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 804f05da..c4188c8b 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 9ce1caa3..5daf0560 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 618f9175..65e23347 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs @@ -25,10 +25,6 @@ internal partial class PkwareTraditionalCryptoStream : Stream _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 @@ internal partial class PkwareTraditionalCryptoStream : Stream 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 4eaf7c1f..23422ae4 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; 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 @@ internal sealed partial class SeekableZipHeaderFactory ) { 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 9e7651c6..dbd62841 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.Async.cs @@ -24,7 +24,7 @@ internal sealed partial class StreamingZipFilePart .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 9c5875c8..5368469f 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs @@ -26,16 +26,16 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart ); 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 sealed partial class StreamingZipFilePart : ZipFilePart 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 71004b77..b1ddb576 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs @@ -60,7 +60,7 @@ internal sealed partial class StreamingZipHeaderFactory 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 @@ internal sealed partial class StreamingZipHeaderFactory ) { _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 @@ internal sealed partial class StreamingZipHeaderFactory 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 @@ internal sealed partial class StreamingZipHeaderFactory 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 @@ internal sealed partial class StreamingZipHeaderFactory { 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 @@ internal sealed partial class StreamingZipHeaderFactory 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 e7091d42..cc91e184 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs @@ -20,173 +20,192 @@ internal partial class StreamingZipHeaderFactory : ZipHeaderFactory 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; - } + if (_lastEntryHeader.Part is null) + { + continue; + } - // removed requirement for FixStreamedFileLocation() + // removed requirement for FixStreamedFileLocation() - var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; + var pos = sharpCompressStream.CanSeek + ? (long?)sharpCompressStream.Position + : null; - var crc = reader.ReadUInt32(); - if (crc == POST_DATA_DESCRIPTOR) - { - crc = reader.ReadUInt32(); - } - _lastEntryHeader.Crc = crc; + 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(); + //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(); + 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 (isSentinel) + else if (_lastEntryHeader != null && _lastEntryHeader.IsZip64) { - //standards-compliant zip64 descriptor - compSize = reader.ReadUInt64(); - uncompSize = reader.ReadUInt64(); - } + if (_lastEntryHeader.Part is null) + { + continue; + } - _lastEntryHeader.CompressedSize = (long)compSize; - _lastEntryHeader.UncompressedSize = (long)uncompSize; + //reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( + // ref sharpCompressStream + //); - if (pos.HasValue) - { - _lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize; - } - } - else if (_lastEntryHeader != null && _lastEntryHeader.IsZip64) - { - if (_lastEntryHeader.Part is null) - { - continue; - } + var pos = sharpCompressStream.CanSeek + ? (long?)sharpCompressStream.Position + : null; - //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) - { - 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(); + + 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; } + + //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) + { + 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; } - 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) - { - 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; - } - } - 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 cffb8386..deb4dd10 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.Async.cs @@ -16,9 +16,6 @@ internal partial class WinzipAesCryptoStream 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 5434c58b..167f1461 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 @@ internal partial class WinzipAesCryptoStream : Stream 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 dbb9b322..bcd5650f 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs @@ -39,7 +39,7 @@ internal abstract partial class ZipFilePart .ConfigureAwait(false); if (LeaveStreamOpen) { - return new NonDisposingStream(decompressionStream); + return SharpCompressStream.CreateNonDisposing(decompressionStream); } return decompressionStream; } @@ -63,7 +63,7 @@ internal abstract partial class ZipFilePart ) || 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 @@ internal abstract partial class ZipFilePart { 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 43eac529..7dd860e7 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -45,7 +45,7 @@ internal abstract partial class ZipFilePart : FilePart ); if (LeaveStreamOpen) { - return new NonDisposingStream(decompressionStream); + return SharpCompressStream.CreateNonDisposing(decompressionStream); } return decompressionStream; } @@ -150,18 +150,26 @@ internal abstract partial class ZipFilePart : FilePart { 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 @@ internal abstract partial class ZipFilePart : FilePart ) || 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 @@ internal abstract partial class ZipFilePart : FilePart 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 74211134..cb67d9d2 100644 --- a/src/SharpCompress/Compressors/ADC/ADCStream.cs +++ b/src/SharpCompress/Compressors/ADC/ADCStream.cs @@ -76,9 +76,6 @@ public sealed partial class ADCStream : Stream } _stream = stream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ADCStream)); -#endif } public override bool CanRead => _stream.CanRead; @@ -104,9 +101,6 @@ public sealed partial class ADCStream : Stream 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 11110690..f9308228 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 partial class ArcLzwStream : Stream 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 feb8cb94..efe5bf47 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 + + public BitReader(Stream input) { - private readonly Stream _input; - private int _bitBuffer; // currently buffered bits - private int _bitCount; // number of bits in buffer + _input = input ?? throw new ArgumentNullException(nameof(input)); + _bitBuffer = 0; + _bitCount = 0; + } - public BitReader(Stream input) + /// + /// Reads a single bit from the stream. Returns 0 or 1. + /// + public int ReadBit() + { + if (_bitCount == 0) { - _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() - { - 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) + int bit = (_bitBuffer >> (_bitCount - 1)) & 1; + _bitCount--; + return bit; + } + + /// + /// Reads n bits (up to 32) from the stream. + /// + public int ReadBits(int count) + { + if (count < 0 || count > 32) { - if (count < 0 || count > 32) - { - throw new ArgumentOutOfRangeException( - nameof(count), - "Count must be between 0 and 32." - ); - } - - int result = 0; - for (int i = 0; i < count; i++) - { - result = (result << 1) | ReadBit(); - } - return result; + 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 e38d1296..2341be55 100644 --- a/src/SharpCompress/Compressors/Arj/HistoryIterator.cs +++ b/src/SharpCompress/Compressors/Arj/HistoryIterator.cs @@ -2,42 +2,41 @@ using System; 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; + + public HistoryIterator(IRingBuffer ring, int startIndex) { - private int _index; - private readonly IRingBuffer _ring; - - public HistoryIterator(IRingBuffer ring, int startIndex) - { - _ring = ring; - _index = startIndex; - } - - public bool MoveNext() - { - Current = _ring[_index]; - _index = unchecked(_index + 1); - - // Push value back into the ring buffer - _ring.Push(Current); - - return true; // iterator is infinite - } - - public void Reset() - { - throw new NotSupportedException(); - } - - public byte Current { get; private set; } - - object IEnumerator.Current => Current; - - public void Dispose() { } + _ring = ring; + _index = startIndex; } + + public bool MoveNext() + { + Current = _ring[_index]; + _index = unchecked(_index + 1); + + // Push value back into the ring buffer + _ring.Push(Current); + + return true; // iterator is infinite + } + + public void Reset() + { + throw new NotSupportedException(); + } + + public byte Current { get; private set; } + + object IEnumerator.Current => Current; + + public void Dispose() { } } diff --git a/src/SharpCompress/Compressors/Arj/HuffmanTree.cs b/src/SharpCompress/Compressors/Arj/HuffmanTree.cs index 6d4c7e86..63bf8968 100644 --- a/src/SharpCompress/Compressors/Arj/HuffmanTree.cs +++ b/src/SharpCompress/Compressors/Arj/HuffmanTree.cs @@ -3,216 +3,212 @@ using System.Collections.Generic; 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; - - private TreeEntry(NodeType type, int leafValue, int branchIndex) - { - Type = type; - LeafValue = leafValue; - BranchIndex = branchIndex; - } - - public static TreeEntry Leaf(int value) - { - return new TreeEntry(NodeType.Leaf, value, -1); - } - - 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); - } + return new TreeEntry(NodeType.Leaf, value, -1); } - [CLSCompliant(true)] - public sealed partial class HuffTree + public static TreeEntry Branch(int index) { - private readonly List _tree; - - public HuffTree(int capacity = 0) + if (index >= MAX_INDEX) { - _tree = new List(capacity); - } - - public void SetSingle(int value) - { - _tree.Clear(); - _tree.Add(TreeEntry.Leaf(value)); - } - - public void BuildTree(byte[] lengths, int count) - { - if (lengths == null) - { - throw new ArgumentNullException(nameof(lengths)); - } - - if (count < 0 || count > lengths.Length) - { - throw new ArgumentOutOfRangeException(nameof(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); - } - - public void BuildTree(byte[] valueLengths) - { - if (valueLengths == null) - { - throw new ArgumentNullException(nameof(valueLengths)); - } - - if (valueLengths.Length > TreeEntry.MAX_INDEX / 2) - { - throw new InvalidOperationException("Too many code lengths"); - } - - _tree.Clear(); - - 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 (int i = _tree.Count; i < maxLimit; i++) - { - // 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; - } - - // 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) - { - _tree.Add(TreeEntry.Leaf(value)); - } - else if (len > currentLen) - { - moreLeaves = true; // there are more leaves to process - } - } - - // sanity check (too many leaves) - if (_tree.Count > maxAllocated) - { - throw new InvalidOperationException("Too many leaves"); - } - - // stop when no longer finding longer codes - if (!moreLeaves) - { - break; - } - } - - // ensure tree is complete - if (_tree.Count != maxAllocated) - { - throw new InvalidOperationException( - $"Missing some leaves: tree count = {_tree.Count}, expected = {maxAllocated}" - ); - } - } - - public int ReadEntry(BitReader reader) - { - if (_tree.Count == 0) - { - throw new InvalidOperationException("Tree not initialized"); - } - - TreeEntry node = _tree[0]; - while (true) - { - if (node.Type == NodeType.Leaf) - { - return node.LeafValue; - } - - int bit = reader.ReadBit(); - int index = node.BranchIndex + bit; - - if (index >= _tree.Count) - { - throw new InvalidOperationException("Invalid branch index during read"); - } - - node = _tree[index]; - } - } - - public override string ToString() - { - var result = new StringBuilder(); - - void FormatStep(int index, string prefix) - { - 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"); - } - } - - if (_tree.Count > 0) - { - FormatStep(0, ""); - } - - return result.ToString(); + throw new ArgumentOutOfRangeException(nameof(index), "Branch index exceeds MAX_INDEX"); } + return new TreeEntry(NodeType.Branch, 0, index); + } +} + +[CLSCompliant(true)] +public sealed partial class HuffTree +{ + private readonly List _tree; + + public HuffTree(int capacity = 0) + { + _tree = new List(capacity); + } + + public void SetSingle(int value) + { + _tree.Clear(); + _tree.Add(TreeEntry.Leaf(value)); + } + + public void BuildTree(byte[] lengths, int count) + { + if (lengths == null) + { + throw new ArgumentNullException(nameof(lengths)); + } + + if (count < 0 || count > lengths.Length) + { + throw new ArgumentOutOfRangeException(nameof(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); + } + + public void BuildTree(byte[] valueLengths) + { + if (valueLengths == null) + { + throw new ArgumentNullException(nameof(valueLengths)); + } + + if (valueLengths.Length > TreeEntry.MAX_INDEX / 2) + { + throw new InvalidOperationException("Too many code lengths"); + } + + _tree.Clear(); + + 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 (int i = _tree.Count; i < maxLimit; i++) + { + // 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; + } + + // 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) + { + _tree.Add(TreeEntry.Leaf(value)); + } + else if (len > currentLen) + { + moreLeaves = true; // there are more leaves to process + } + } + + // sanity check (too many leaves) + if (_tree.Count > maxAllocated) + { + throw new InvalidOperationException("Too many leaves"); + } + + // stop when no longer finding longer codes + if (!moreLeaves) + { + break; + } + } + + // ensure tree is complete + if (_tree.Count != maxAllocated) + { + throw new InvalidOperationException( + $"Missing some leaves: tree count = {_tree.Count}, expected = {maxAllocated}" + ); + } + } + + public int ReadEntry(BitReader reader) + { + if (_tree.Count == 0) + { + throw new InvalidOperationException("Tree not initialized"); + } + + TreeEntry node = _tree[0]; + while (true) + { + if (node.Type == NodeType.Leaf) + { + return node.LeafValue; + } + + int bit = reader.ReadBit(); + int index = node.BranchIndex + bit; + + if (index >= _tree.Count) + { + throw new InvalidOperationException("Invalid branch index during read"); + } + + node = _tree[index]; + } + } + + public override string ToString() + { + var result = new StringBuilder(); + + void FormatStep(int index, string prefix) + { + 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"); + } + } + + 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 8a82b978..b546e14f 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 40585b1d..dab0f3a6 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 4ada9767..52b51a73 100644 --- a/src/SharpCompress/Compressors/Arj/LHDecoderStream.cs +++ b/src/SharpCompress/Compressors/Arj/LHDecoderStream.cs @@ -2,183 +2,181 @@ using System; 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; + + // 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 const int THRESHOLD = 3; + + public LHDecoderStream(Stream compressedStream, int originalSize) { - private readonly BitReader _bitReader; - private readonly Stream _stream; - - // 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 const int THRESHOLD = 3; - - 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 override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; + public Stream BaseStream => _stream; - public override long Length => _originalSize; + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; - public override long Position + public override long Length => _originalSize; + + 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) + byte nextChar = (byte)_bitReader.ReadBits(8); + _buffer.Add(nextChar); + } + else + { + int repCount = len + THRESHOLD - 1; + int backPtr = DecodeVal(9, 13); + + if (backPtr >= _buffer.Count) { - _finishedDecoding = true; - return false; + throw new InvalidDataException("Invalid back_ptr in LH stream"); } - int len = DecodeVal(0, 7); - if (len == 0) + int srcIndex = _buffer.Count - 1 - backPtr; + for (int j = 0; j < repCount && _buffer.Count < _originalSize; j++) { - byte nextChar = (byte)_bitReader.ReadBits(8); - _buffer.Add(nextChar); + byte b = _buffer[srcIndex]; + _buffer.Add(b); + srcIndex++; + // srcIndex may grow; it's allowed (source region can overlap destination) } - 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) - } - } - - if (_buffer.Count >= _originalSize) - { - _finishedDecoding = true; - } - - return true; } - private int DecodeVal(int from, int to) + if (_buffer.Count >= _originalSize) { - int add = 0; - int bit = from; - - while (bit < to && _bitReader.ReadBits(1) == 1) - { - add |= 1 << bit; - bit++; - } - - int res = bit > 0 ? _bitReader.ReadBits(bit) : 0; - return res + add; + _finishedDecoding = true; } - /// - /// 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) + return true; + } + + private int DecodeVal(int from, int to) + { + int add = 0; + int bit = from; + + while (bit < to && _bitReader.ReadBits(1) == 1) { - if (_disposed) - { - throw new ObjectDisposedException(nameof(LHDecoderStream)); - } + add |= 1 << bit; + bit++; + } - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + int res = bit > 0 ? _bitReader.ReadBits(bit) : 0; + return res + add; + } - if (offset < 0 || count < 0 || offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException("offset/count"); - } + /// + /// 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) + { + throw new ObjectDisposedException(nameof(LHDecoderStream)); + } - if (_readPosition >= _originalSize) - { - return 0; // EOF - } + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - int totalRead = 0; + if (offset < 0 || count < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException("offset/count"); + } - while (totalRead < count && _readPosition < _originalSize) + if (_readPosition >= _originalSize) + { + return 0; // EOF + } + + int totalRead = 0; + + while (totalRead < count && _readPosition < _originalSize) + { + if (_readPosition >= _buffer.Count) { - if (_readPosition >= _buffer.Count) + bool had = DecodeNext(); + if (!had) { - bool had = DecodeNext(); - if (!had) - { - break; - } - } - - int available = _buffer.Count - (int)_readPosition; - if (available <= 0) - { - if (!_finishedDecoding) - { - continue; - } break; } - - int toCopy = Math.Min(available, count - totalRead); - _buffer.CopyTo((int)_readPosition, buffer, offset + totalRead, toCopy); - - _readPosition += toCopy; - totalRead += toCopy; } - return totalRead; + int available = _buffer.Count - (int)_readPosition; + if (available <= 0) + { + if (!_finishedDecoding) + { + continue; + } + break; + } + + 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(); - - public override long Seek(long offset, SeekOrigin origin) => - throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); + return totalRead; } + + public override void Flush() => throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); } diff --git a/src/SharpCompress/Compressors/Arj/Lh5DecoderCfg.cs b/src/SharpCompress/Compressors/Arj/Lh5DecoderCfg.cs index ebff12b8..81f025a9 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 88c7f2ba..a7393454 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 853e6103..76071b45 100644 --- a/src/SharpCompress/Compressors/Arj/LhaStream.cs +++ b/src/SharpCompress/Compressors/Arj/LhaStream.cs @@ -3,340 +3,336 @@ using System.Data; 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 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 readonly int _originalSize; + private int _producedBytes = 0; + + public LhaStream(Stream compressedStream, int originalSize) { - private readonly BitReader _bitReader; - private readonly Stream _stream; + _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; + } - 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(); + 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(); + } - private const int NUM_COMMANDS = 510; - private const int NUM_TEMP_CODELEN = 20; + public override void Flush() { } - private readonly int _originalSize; - private int _producedBytes = 0; + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public LhaStream(Stream compressedStream, int originalSize) + public override void SetLength(long value) => 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) + { + if (buffer == null) { - _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; + throw new ArgumentNullException(nameof(buffer)); + } + if (offset < 0 || count < 0 || (offset + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(); } - 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 + if (_producedBytes >= _originalSize) { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); + return 0; // EOF + } + if (count == 0) + { + return 0; } - public override void Flush() { } + int bytesRead = FillBuffer(buffer); + return bytesRead; + } - public override long Seek(long offset, SeekOrigin origin) => - throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); - - public override int Read(byte[] buffer, int offset, int count) + private byte ReadCodeLength() + { + byte len = (byte)_bitReader.ReadBits(3); + if (len == 7) { - if (buffer == null) + while (_bitReader.ReadBit() != 0) { - 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; - } - - int bytesRead = FillBuffer(buffer); - return bytesRead; - } - - private byte ReadCodeLength() - { - byte len = (byte)_bitReader.ReadBits(3); - if (len == 7) - { - 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) + private int ReadCodeSkip(int skipRange) + { + int bits; + int increment; + + switch (skipRange) { - int bits; - int increment; - - 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); - - // single code only - if (numCodes == 0) - { - int code = _bitReader.ReadBits(5); - _offsetTree.SetSingle((byte)code); - return; - } - - if (numCodes > NUM_TEMP_CODELEN) - { - 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(); - } - - // 2-bit skip value follows - int skip = _bitReader.ReadBits(2); - - if (3 + skip > numCodes) - { - throw new Exception("temporary codelen table has invalid size"); - } - - for (int i = 3 + skip; i < numCodes; i++) - { - codeLengths[i] = (byte)ReadCodeLength(); - } - - _offsetTree.BuildTree(codeLengths, numCodes); - } - - private void ReadCommandTree() - { - byte[] codeLengths = new byte[NUM_COMMANDS]; - - // number of codes to read (9 bits) - int numCodes = _bitReader.ReadBits(9); - - // single code only - if (numCodes == 0) - { - int code = _bitReader.ReadBits(9); - _commandTree.SetSingle((ushort)code); - return; - } - - 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++) - { - 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); - } - } + case 0: + return 1; + case 1: + bits = 4; + increment = 3; // 3..=18 + break; + default: + bits = 9; + increment = 20; // 20..=531 break; - - outerLoop: - ; - } - - _commandTree.BuildTree(codeLengths, numCodes); } - private void ReadOffsetTree() + 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); + + // single code only + if (numCodes == 0) { - 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"); - } - - byte[] codeLengths = new byte[NUM_TEMP_CODELEN]; - for (int i = 0; i < numCodes; i++) - { - codeLengths[i] = (byte)ReadCodeLength(); - } - - _offsetTree.BuildTree(codeLengths, numCodes); + int code = _bitReader.ReadBits(5); + _offsetTree.SetSingle((byte)code); + return; } - private void BeginNewBlock() + if (numCodes > NUM_TEMP_CODELEN) { - ReadTempTree(); - ReadCommandTree(); - ReadOffsetTree(); + throw new Exception("temporary codelen table has invalid size"); } - private int ReadCommand() => _commandTree.ReadEntry(_bitReader); - - private int ReadOffset() + // read actual lengths + int count = Math.Min(3, numCodes); + for (int i = 0; i < count; i++) { - int bits = _offsetTree.ReadEntry(_bitReader); - if (bits <= 1) - { - return bits; - } - - int res = _bitReader.ReadBits(bits - 1); - return res | (1 << (bits - 1)); + codeLengths[i] = (byte)ReadCodeLength(); } - private int CopyFromHistory(byte[] target, int targetIndex, int offset, int count) + // 2-bit skip value follows + int skip = _bitReader.ReadBits(2); + + if (3 + skip > numCodes) { - var historyIter = _ringBuffer.IterFromOffset(offset); - int copied = 0; - - while ( - copied < count && historyIter.MoveNext() && (targetIndex + copied) < target.Length - ) - { - target[targetIndex + copied] = historyIter.Current; - copied++; - } - - if (copied < count) - { - _copyProgress = (offset, count - copied); - } - - return copied; + throw new Exception("temporary codelen table has invalid size"); } - public int FillBuffer(byte[] buffer) + for (int i = 3 + skip; i < numCodes; i++) { - int bufLen = buffer.Length; - int bufIndex = 0; + codeLengths[i] = (byte)ReadCodeLength(); + } - // stop when we reached original size - if (_producedBytes >= _originalSize) + _offsetTree.BuildTree(codeLengths, numCodes); + } + + private void ReadCommandTree() + { + byte[] codeLengths = new byte[NUM_COMMANDS]; + + // number of codes to read (9 bits) + int numCodes = _bitReader.ReadBits(9); + + // single code only + if (numCodes == 0) + { + int code = _bitReader.ReadBits(9); + _commandTree.SetSingle((ushort)code); + return; + } + + 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++) { - return 0; - } + int code = _offsetTree.ReadEntry(_bitReader); - // calculate limit, so that we don't go over the original size - int remaining = (int)Math.Min(bufLen, _originalSize - _producedBytes); - - while (bufIndex < remaining) - { - if (_copyProgress.HasValue) + if (code >= 0 && code <= 2) // skip range { - 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) - { - break; - } - BeginNewBlock(); - } - - _remainingCommands--; - - int command = ReadCommand(); - - if (command >= 0 && command <= 0xFF) - { - byte value = (byte)command; - buffer[bufIndex++] = value; - _ringBuffer.Push(value); + int skipCount = ReadCodeSkip(code); + index += n + skipCount; + goto outerLoop; } else { - int count = command - 0x100 + 3; - int offset = ReadOffset(); - int copyCount = (int)Math.Min(count, remaining - bufIndex); - bufIndex += CopyFromHistory(buffer, bufIndex, offset, copyCount); + codeLengths[index + n] = (byte)(code - 2); } } + break; - _producedBytes += bufIndex; - return bufIndex; + outerLoop: + ; } + + _commandTree.BuildTree(codeLengths, numCodes); + } + + 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"); + } + + byte[] codeLengths = new byte[NUM_TEMP_CODELEN]; + for (int i = 0; i < numCodes; i++) + { + codeLengths[i] = (byte)ReadCodeLength(); + } + + _offsetTree.BuildTree(codeLengths, numCodes); + } + + private void BeginNewBlock() + { + ReadTempTree(); + ReadCommandTree(); + ReadOffsetTree(); + } + + private int ReadCommand() => _commandTree.ReadEntry(_bitReader); + + private int ReadOffset() + { + int bits = _offsetTree.ReadEntry(_bitReader); + if (bits <= 1) + { + 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; + + while (copied < count && historyIter.MoveNext() && (targetIndex + copied) < target.Length) + { + target[targetIndex + copied] = historyIter.Current; + copied++; + } + + if (copied < count) + { + _copyProgress = (offset, count - copied); + } + + return copied; + } + + public int FillBuffer(byte[] buffer) + { + int bufLen = buffer.Length; + int bufIndex = 0; + + // stop when we reached original size + if (_producedBytes >= _originalSize) + { + return 0; + } + + // calculate limit, so that we don't go over the original size + int remaining = (int)Math.Min(bufLen, _originalSize - _producedBytes); + + while (bufIndex < remaining) + { + 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) + { + break; + } + BeginNewBlock(); + } + + _remainingCommands--; + + 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); + } + } + + _producedBytes += bufIndex; + return bufIndex; } } diff --git a/src/SharpCompress/Compressors/Arj/RingBuffer.cs b/src/SharpCompress/Compressors/Arj/RingBuffer.cs index 9722f993..39285b97 100644 --- a/src/SharpCompress/Compressors/Arj/RingBuffer.cs +++ b/src/SharpCompress/Compressors/Arj/RingBuffer.cs @@ -2,66 +2,65 @@ using System; 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; + + public int BufferSize { get; } + + public int Cursor => _cursor; + + private readonly int _mask; + + public RingBuffer(int size) { - private readonly byte[] _buffer; - private int _cursor; - - public int BufferSize { get; } - - public int Cursor => _cursor; - - private readonly int _mask; - - 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; - } - - 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); + _buffer[i] = (byte)' '; } } + + public void SetCursor(int pos) + { + _cursor = pos & _mask; + } + + public void Push(byte value) + { + int index = _cursor; + _buffer[index & _mask] = value; + _cursor = (index + 1) & _mask; + } + + 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 276a57e6..c9b0b031 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs @@ -23,11 +23,7 @@ public sealed partial class BZip2Stream 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 sealed partial class BZip2Stream 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 c145cb81..0aa690f6 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 sealed partial class BZip2Stream : Stream 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 @@ public sealed partial class BZip2Stream : Stream return; } isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(BZip2Stream)); -#endif if (disposing) { stream.Dispose(); @@ -111,7 +108,7 @@ public sealed partial class BZip2Stream : Stream /// 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 7252a802..6a5992e8 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.Async.cs @@ -857,11 +857,11 @@ internal partial class CBZip2InputStream 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 e303d7d0..2bd0d94a 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs @@ -147,8 +147,8 @@ internal partial class CBZip2InputStream : Stream 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 @@ internal partial class CBZip2InputStream : Stream 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 @@ internal partial class CBZip2InputStream : Stream 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 @@ internal partial class CBZip2InputStream : Stream 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 c3a0348e..f846c3eb 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs @@ -337,10 +337,6 @@ internal sealed class CBZip2OutputStream : Stream BsSetStream(inStream); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(CBZip2OutputStream)); -#endif - workFactor = 50; if (inBlockSize > 9) { @@ -457,9 +453,6 @@ internal sealed class CBZip2OutputStream : Stream 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 6b8c1c2c..d9249d1f 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs @@ -250,9 +250,6 @@ public partial class DeflateStream : Stream, IStreamStack { 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 f3857b2c..95b7c03d 100644 --- a/src/SharpCompress/Compressors/Deflate/GZipStream.cs +++ b/src/SharpCompress/Compressors/Deflate/GZipStream.cs @@ -64,9 +64,6 @@ public partial class GZipStream : Stream ) { BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(GZipStream)); -#endif _encoding = encoding; } @@ -215,9 +212,6 @@ public partial class GZipStream : Stream 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 f4070ea7..513029db 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -104,10 +104,6 @@ internal class ZlibBaseStream : Stream, IStreamStack _encoding = encoding; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ZlibBaseStream)); -#endif - // workitem 7159 if (flavor == ZlibStreamFlavor.GZIP) { @@ -523,9 +519,6 @@ internal class ZlibBaseStream : Stream, IStreamStack return; } isDisposed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(ZlibBaseStream)); -#endif base.Dispose(disposing); if (disposing) { @@ -557,9 +550,6 @@ internal class ZlibBaseStream : Stream, IStreamStack 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 ede19f6f..417c3283 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs @@ -52,9 +52,6 @@ public partial class ZlibStream : Stream ) { _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ZlibStream)); -#endif } #region Zlib properties @@ -224,9 +221,6 @@ public partial class ZlibStream : Stream _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 2300cfbc..80b23dd8 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -49,9 +49,6 @@ public sealed partial class Deflate64Stream : Stream _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 @@ public sealed partial class Deflate64Stream : Stream // 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 92fda51c..3d1aa2fd 100644 --- a/src/SharpCompress/Compressors/Explode/ExplodeStream.cs +++ b/src/SharpCompress/Compressors/Explode/ExplodeStream.cs @@ -45,9 +45,6 @@ public partial class ExplodeStream : Stream ) { inStream = inStr; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ExplodeStream)); -#endif this.compressedSize = (int)compressedSize; unCompressedSize = (long)uncompressedSize; this.generalPurposeBitFlag = generalPurposeBitFlag; @@ -69,9 +66,6 @@ public partial class ExplodeStream : Stream 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 a6954116..5f7593ce 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 bad6afbd..48fd28b1 100644 --- a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs @@ -33,10 +33,6 @@ internal sealed partial class AesDecoderStream : DecoderStream2 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 @@ internal sealed partial class AesDecoderStream : DecoderStream2 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 caaf15e1..9b89993c 100644 --- a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs @@ -111,10 +111,6 @@ internal class Bcj2DecoderStream : DecoderStream2 _mStatusDecoder[i] = new StatusDecoder(); } -#if DEBUG_STREAMS - this.DebugConstruct(typeof(Bcj2DecoderStream)); -#endif - _mIter = Run().GetEnumerator(); } @@ -125,9 +121,6 @@ internal class Bcj2DecoderStream : DecoderStream2 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 30be9b62..297447c2 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -50,7 +50,9 @@ public sealed partial class LZipStream : Stream 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 sealed partial class LZipStream : Stream _countingWritableSubStream ) ); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(LZipStream)); -#endif } } @@ -103,9 +102,6 @@ public sealed partial class LZipStream : Stream 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 123d8e03..f6314379 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -200,9 +200,6 @@ public partial class LzmaStream : Stream, IStreamStack 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 e935fe6f..523e3e1f 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 @@ internal partial class CrcBuilderStream : Stream 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 0325adbb..d36210e8 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 ae695080..063b9df9 100644 --- a/src/SharpCompress/Compressors/Lzw/LzwStream.Async.cs +++ b/src/SharpCompress/Compressors/Lzw/LzwStream.Async.cs @@ -5,317 +5,28 @@ using System.Threading.Tasks; 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]; - - 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] - ) - ); - } - } - 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 - ) - { - if (!headerParsed) - { - await ParseHeaderAsync(cancellationToken).ConfigureAwait(false); - } - - 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; - } - - if (count == 0) - { - stackP = lStackP; - return offset - start; - } - - MainLoop: - do - { - if (end < EXTRA) - { - await FillAsync(cancellationToken).ConfigureAwait(false); - } - - int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); - - while (lBitPos < bitIn) - { - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - maxMaxCode = lMaxMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } - - if (lFreeEnt > lMaxCode) - { - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - - lNBits++; - lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : (1 << lNBits) - 1; - - lBitMask = (1 << lNBits) - 1; - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } - - int pos = lBitPos >> 3; - int code = - ( - ( - (lData[pos] & 0xFF) - | ((lData[pos + 1] & 0xFF) << 8) - | ((lData[pos + 2] & 0xFF) << 16) - ) >> (lBitPos & 0x7) - ) & lBitMask; - - lBitPos += lNBits; - - if (lOldCode == -1) - { - if (code >= 256) - { - throw new IncompleteArchiveException( - "corrupt input: " + code + " > 255" - ); - } - - lFinChar = (byte)(lOldCode = code); - buffer[offset++] = lFinChar; - count--; - continue; - } - - 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; - - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } - - int inCode = code; - lStackP = lStack.Length; - - if (code >= lFreeEnt) - { - if (code > lFreeEnt) - { - throw new IncompleteArchiveException( - "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt - ); - } - - lStack[--lStackP] = lFinChar; - code = lOldCode; - } - - while (code >= 256) - { - lStack[--lStackP] = lTabSuffix[code]; - code = lTabPrefix[code]; - } - - lFinChar = lTabSuffix[code]; - buffer[offset++] = lFinChar; - count--; - - sSize = lStack.Length - lStackP; - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; - - if (lFreeEnt < lMaxMaxCode) - { - lTabPrefix[lFreeEnt] = lOldCode; - lTabSuffix[lFreeEnt] = lFinChar; - lFreeEnt++; - } - - lOldCode = inCode; - - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - 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 - ) - { - 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); - } - } -#endif - - private async ValueTask FillAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - got = await baseInputStream - .ReadAsync(data, end, data.Length - 1 - end, cancellationToken) - .ConfigureAwait(false); - if (got > 0) - { - end += got; - } - } - - private async ValueTask ParseHeaderAsync(CancellationToken cancellationToken) - { - headerParsed = true; - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - int result = await baseInputStream - .ReadAsync(hdr, 0, hdr.Length, cancellationToken) - .ConfigureAwait(false); + 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"); @@ -331,43 +42,329 @@ namespace SharpCompress.Compressors.Lzw ) ); } + } + catch (Exception) + { + return false; + } + return true; + } - blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; - maxBits = hdr[2] & LzwConstants.BIT_MASK; + /// + /// 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) + { + await ParseHeaderAsync(cancellationToken).ConfigureAwait(false); + } - if (maxBits > LzwConstants.MAX_BITS) + 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; + } + + if (count == 0) + { + stackP = lStackP; + return offset - start; + } + + MainLoop: + do + { + if (end < EXTRA) { - throw new ArchiveException( - "Stream compressed with " - + maxBits - + " bits, but decompression can only handle " - + LzwConstants.MAX_BITS - + " bits." - ); + await FillAsync(cancellationToken).ConfigureAwait(false); } - if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) + int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); + + while (lBitPos < bitIn) { - throw new ArchiveException("Unsupported bits set in the header."); + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + maxMaxCode = lMaxMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } + + if (lFreeEnt > lMaxCode) + { + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; + + lNBits++; + lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : (1 << lNBits) - 1; + + lBitMask = (1 << lNBits) - 1; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + int pos = lBitPos >> 3; + int code = + ( + ( + (lData[pos] & 0xFF) + | ((lData[pos + 1] & 0xFF) << 8) + | ((lData[pos + 2] & 0xFF) << 16) + ) >> (lBitPos & 0x7) + ) & lBitMask; + + lBitPos += lNBits; + + if (lOldCode == -1) + { + if (code >= 256) + { + throw new IncompleteArchiveException("corrupt input: " + code + " > 255"); + } + + lFinChar = (byte)(lOldCode = code); + buffer[offset++] = lFinChar; + count--; + continue; + } + + 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; + + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + int inCode = code; + lStackP = lStack.Length; + + if (code >= lFreeEnt) + { + if (code > lFreeEnt) + { + throw new IncompleteArchiveException( + "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt + ); + } + + lStack[--lStackP] = lFinChar; + code = lOldCode; + } + + while (code >= 256) + { + lStack[--lStackP] = lTabSuffix[code]; + code = lTabPrefix[code]; + } + + lFinChar = lTabSuffix[code]; + buffer[offset++] = lFinChar; + count--; + + sSize = lStack.Length - lStackP; + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + + if (lFreeEnt < lMaxMaxCode) + { + lTabPrefix[lFreeEnt] = lOldCode; + lTabSuffix[lFreeEnt] = lFinChar; + lFreeEnt++; + } + + lOldCode = inCode; + + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } } - maxMaxCode = 1 << maxBits; - nBits = LzwConstants.INIT_BITS; - maxCode = (1 << nBits) - 1; - bitMask = maxCode; - oldCode = -1; - finChar = 0; - freeEnt = blockMode ? TBL_FIRST : 256; + lBitPos = ResetBuf(lBitPos); + } while (got > 0); - tabPrefix = new int[1 << maxBits]; - tabSuffix = new byte[1 << maxBits]; - stack = new byte[1 << maxBits]; - stackP = stack.Length; + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; - for (int idx = 255; idx >= 0; idx--) - { - tabSuffix[idx] = (byte)idx; - } + 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 + ) + { + 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); + } + } +#endif + + private async ValueTask FillAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + got = await baseInputStream + .ReadAsync(data, end, data.Length - 1 - end, cancellationToken) + .ConfigureAwait(false); + if (got > 0) + { + end += got; + } + } + + private async ValueTask ParseHeaderAsync(CancellationToken cancellationToken) + { + headerParsed = true; + + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; + + int result = await baseInputStream + .ReadAsync(hdr, 0, hdr.Length, cancellationToken) + .ConfigureAwait(false); + + 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] + ) + ); + } + + blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; + maxBits = hdr[2] & LzwConstants.BIT_MASK; + + 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; + + 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; } } } diff --git a/src/SharpCompress/Compressors/Lzw/LzwStream.cs b/src/SharpCompress/Compressors/Lzw/LzwStream.cs index 4a563c62..274abca0 100644 --- a/src/SharpCompress/Compressors/Lzw/LzwStream.cs +++ b/src/SharpCompress/Compressors/Lzw/LzwStream.cs @@ -4,393 +4,55 @@ using System.Threading; 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); - - // 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] - ) - ); - } - } - 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; - - /// - /// Creates a LzwInputStream - /// - /// - /// The stream to read compressed data from (baseInputStream LZW format) - /// - public LzwStream(Stream baseInputStream) - { - this.baseInputStream = baseInputStream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(LzwStream)); -#endif - } - - /// - /// See - /// - /// - public override int ReadByte() - { - int b = Read(one, 0, 1); - if (b == 1) - { - return (one[0] & 0xff); - } - - 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) - { - ParseHeader(); - } - - if (eof) - { - return 0; - } - - 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) - { - 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; - } - - // loop, filling local buffer until enough data has been decompressed - MainLoop: - do - { - if (end < EXTRA) - { - Fill(); - } - - int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); - - while (lBitPos < bitIn) - { - #region A - - // 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; - - lBitMask = (1 << lNBits) - 1; - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } - - #endregion A - - #region B - - // 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; - - lBitPos += lNBits; - - // handle first iteration - if (lOldCode == -1) - { - if (code >= 256) - { - throw new IncompleteArchiveException( - "corrupt input: " + code + " > 255" - ); - } - - lFinChar = (byte)(lOldCode = code); - buffer[offset++] = lFinChar; - count--; - continue; - } - - // 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 - - // setup - int inCode = code; - lStackP = lStack.Length; - - // Handle KwK case - if (code >= lFreeEnt) - { - if (code > lFreeEnt) - { - throw new IncompleteArchiveException( - "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt - ); - } - - lStack[--lStackP] = lFinChar; - code = lOldCode; - } - - // Generate output characters in reverse order - while (code >= 256) - { - lStack[--lStackP] = lTabSuffix[code]; - code = lTabPrefix[code]; - } - - lFinChar = lTabSuffix[code]; - buffer[offset++] = lFinChar; - count--; - - // 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 C - - #region D - - // generate new entry in table - if (lFreeEnt < lMaxMaxCode) - { - lTabPrefix[lFreeEnt] = lOldCode; - lTabSuffix[lFreeEnt] = lFinChar; - lFreeEnt++; - } - - // Remember previous code - lOldCode = inCode; - - // 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; - } - - #endregion D - } // while - - lBitPos = ResetBuf(lBitPos); - } while (got > 0); // do..while - - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - eof = true; - return offset - start; - } - - /// - /// 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; - } - - private void Fill() - { - got = baseInputStream.Read(data, end, data.Length - 1 - end); - if (got > 0) - { - end += got; - } - } - - private void ParseHeader() - { - headerParsed = true; - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - int result = baseInputStream.Read(hdr, 0, hdr.Length); + int result = stream.Read(hdr, 0, hdr.Length); // Check the magic marker if (result < 0) @@ -408,213 +70,542 @@ namespace SharpCompress.Compressors.Lzw ) ); } - - // Check the 3rd header byte - blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; - maxBits = hdr[2] & LzwConstants.BIT_MASK; - - 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."); - } - - // 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; - } } - - #region Stream Overrides - - /// - /// Gets a value indicating whether the current stream supports reading - /// - public override bool CanRead + catch (Exception) { - get { return baseInputStream.CanRead; } + return false; } - - /// - /// Gets a value of false indicating seeking is not supported for this stream. - /// - public override bool CanSeek - { - get { return false; } - } - - /// - /// 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"); - } - - /// - /// 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) - { - isClosed = true; -#if DEBUG_STREAMS - this.DebugDispose(typeof(LzwStream)); -#endif - if (IsStreamOwner) - { - baseInputStream.Dispose(); - } - } - } - - #endregion Stream Overrides - - #region Instance Fields - - private Stream baseInputStream; - - /// - /// Flag indicating wether this instance has been closed or not. - /// - private bool isClosed; - - private readonly byte[] one = new byte[1]; - private bool headerParsed; - - // string table stuff - private const int TBL_CLEAR = 0x100; - - 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]; // - - // 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; - - // 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; - - #endregion Instance Fields + 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; + + /// + /// 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) + { + return (one[0] & 0xff); + } + + 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) + { + ParseHeader(); + } + + if (eof) + { + return 0; + } + + 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) + { + 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; + } + + // loop, filling local buffer until enough data has been decompressed + MainLoop: + do + { + if (end < EXTRA) + { + Fill(); + } + + int bitIn = (got > 0) ? (end - end % lNBits) << 3 : (end << 3) - (lNBits - 1); + + while (lBitPos < bitIn) + { + #region A + + // 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; + + lBitMask = (1 << lNBits) - 1; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + #endregion A + + #region B + + // 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; + + lBitPos += lNBits; + + // handle first iteration + if (lOldCode == -1) + { + if (code >= 256) + { + throw new IncompleteArchiveException("corrupt input: " + code + " > 255"); + } + + lFinChar = (byte)(lOldCode = code); + buffer[offset++] = lFinChar; + count--; + continue; + } + + // 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 + + // setup + int inCode = code; + lStackP = lStack.Length; + + // Handle KwK case + if (code >= lFreeEnt) + { + if (code > lFreeEnt) + { + throw new IncompleteArchiveException( + "corrupt input: code=" + code + ", freeEnt=" + lFreeEnt + ); + } + + lStack[--lStackP] = lFinChar; + code = lOldCode; + } + + // Generate output characters in reverse order + while (code >= 256) + { + lStack[--lStackP] = lTabSuffix[code]; + code = lTabPrefix[code]; + } + + lFinChar = lTabSuffix[code]; + buffer[offset++] = lFinChar; + count--; + + // 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 C + + #region D + + // generate new entry in table + if (lFreeEnt < lMaxMaxCode) + { + lTabPrefix[lFreeEnt] = lOldCode; + lTabSuffix[lFreeEnt] = lFinChar; + lFreeEnt++; + } + + // Remember previous code + lOldCode = inCode; + + // 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; + } + + #endregion D + } // while + + lBitPos = ResetBuf(lBitPos); + } while (got > 0); // do..while + + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + eof = true; + return offset - start; + } + + /// + /// 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; + } + + private void Fill() + { + got = baseInputStream.Read(data, end, data.Length - 1 - end); + if (got > 0) + { + end += got; + } + } + + private void ParseHeader() + { + headerParsed = true; + + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; + + int result = baseInputStream.Read(hdr, 0, hdr.Length); + + // 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 3rd header byte + blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; + maxBits = hdr[2] & LzwConstants.BIT_MASK; + + 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."); + } + + // 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; + } + } + + #region Stream Overrides + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get { return baseInputStream.CanRead; } + } + + /// + /// Gets a value of false indicating seeking is not supported for this stream. + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// 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"); + } + + /// + /// 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) + { + isClosed = true; + if (IsStreamOwner) + { + baseInputStream.Dispose(); + } + } + } + + #endregion Stream Overrides + + #region Instance Fields + + private Stream baseInputStream; + + /// + /// Flag indicating wether this instance has been closed or not. + /// + private bool isClosed; + + private readonly byte[] one = new byte[1]; + private bool headerParsed; + + // string table stuff + private const int TBL_CLEAR = 0x100; + + 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]; // + + // 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; + + // 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; + + #endregion Instance Fields } diff --git a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs index e1f03de7..022f7df0 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs @@ -27,10 +27,6 @@ public class PpmdStream : Stream _stream = stream; _compress = compress; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(PpmdStream)); -#endif - InitializeSync(stream, compress); } @@ -45,10 +41,6 @@ public class PpmdStream : Stream _stream = stream; _compress = compress; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(PpmdStream)); -#endif - // Skip initialization - used by CreateAsync } @@ -182,9 +174,6 @@ public class PpmdStream : Stream 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 8bc8ee1b..68b52ea4 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 (!countMode) + if (c == DLE) { - if (c == DLE) - { - countMode = true; - } - else - { - result.Add(c); - last = c; - } + countMode = true; } else { - countMode = false; - if (c == 0) - { - result.Add(DLE); - } - else - { - result.AddRange(Enumerable.Repeat(last, c - 1)); - } + result.Add(c); + last = c; + } + } + else + { + countMode = false; + if (c == 0) + { + result.Add(DLE); + } + else + { + 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 84b4a554..e47ac2e1 100644 --- a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.Async.cs +++ b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.Async.cs @@ -3,116 +3,115 @@ using System.IO; 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)); + } + + if (offset < 0 || count < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } + + int bytesWritten = 0; + + while (bytesWritten < count && !_endOfCompressedData) + { + // 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) + byte[] singleByte = new byte[1]; + int bytesRead = await _stream + .ReadAsync(singleByte, 0, 1, cancellationToken) + .ConfigureAwait(false); + if (bytesRead == 0) { - // 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; - } + _endOfCompressedData = true; + break; + } - // Try to read the next byte from compressed data - if (_bytesReadFromSource >= _compressedSize) - { - _endOfCompressedData = true; - break; - } + _bytesReadFromSource++; + byte c = singleByte[0]; - byte[] singleByte = new byte[1]; - int bytesRead = await _stream - .ReadAsync(singleByte, 0, 1, cancellationToken) - .ConfigureAwait(false); - if (bytesRead == 0) - { - _endOfCompressedData = true; - break; - } + if (_inDleMode) + { + _inDleMode = false; - _bytesReadFromSource++; - byte c = singleByte[0]; - - 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; + } + #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; - } - - 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; + } + + 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 d1470944..178a2220 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) + 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 long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _compressedSize = compressedSize; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(RunLength90Stream)); -#endif + throw new ArgumentNullException(nameof(buffer)); } - protected override void Dispose(bool disposing) + if (offset < 0 || count < 0 || offset + count > buffer.Length) { -#if DEBUG_STREAMS - this.DebugDispose(typeof(RunLength90Stream)); -#endif - base.Dispose(disposing); + throw new ArgumentOutOfRangeException(); } - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; + int bytesWritten = 0; - public override long Length => throw new NotSupportedException(); - public override long Position + while (bytesWritten < count && !_endOfCompressedData) { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override void Flush() => throw new NotSupportedException(); - - public override long Seek(long offset, SeekOrigin origin) => - throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); - - public override int Read(byte[] buffer, int offset, int count) - { - 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; - } + _endOfCompressedData = true; + break; + } - // Try to read the next byte from compressed data - if (_bytesReadFromSource >= _compressedSize) - { - _endOfCompressedData = true; - break; - } + _bytesReadFromSource++; + byte c = (byte)next; - int next = _stream.ReadByte(); - if (next == -1) - { - _endOfCompressedData = true; - break; - } + if (_inDleMode) + { + _inDleMode = false; - _bytesReadFromSource++; - byte c = (byte)next; - - 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 161922be..c8235e3f 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -19,9 +19,6 @@ internal sealed partial class MultiVolumeReadOnlyStream : MultiVolumeReadOnlyStr filePartEnumerator = parts.GetEnumerator(); filePartEnumerator.MoveNext(); InitializeNextFilePart(); -#if DEBUG_STREAMS - this.DebugConstruct(typeof(MultiVolumeReadOnlyStream)); -#endif } protected override void Dispose(bool disposing) @@ -29,10 +26,6 @@ internal sealed partial class MultiVolumeReadOnlyStream : MultiVolumeReadOnlyStr 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 42dd25c9..fa47181a 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs @@ -94,9 +94,6 @@ internal partial class RarBLAKE2spStream : RarStream { 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 @@ internal partial class RarBLAKE2spStream : RarStream 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 8adf324a..98fbb3b2 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -21,9 +21,6 @@ internal partial class RarCrcStream : RarStream : base(unpack, fileHeader, readStream) { this.readStream = readStream; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(RarCrcStream)); -#endif disableCRC = fileHeader.IsEncrypted; ResetCrc(); } @@ -43,9 +40,6 @@ internal partial class RarCrcStream : RarStream 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 31e7b11c..d4d9b8e2 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -31,10 +31,6 @@ internal partial class RarStream : Stream this.unpack = unpack; this.fileHeader = fileHeader; this.readStream = readStream; - -#if DEBUG_STREAMS - this.DebugConstruct(typeof(RarStream)); -#endif } public void Initialize() @@ -51,9 +47,6 @@ internal partial class RarStream : Stream { 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 17368531..8fed421c 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 a9a6f280..97d513c3 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 ea20a0fa..e8b29a40 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 f99ae0e3..a650f21c 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 b95d6df0..725d70ce 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 System; 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 9631f1e1..af389904 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 cd844004..1b4e49e2 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 9f57af38..cdd584a0 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -3,13 +3,7 @@ using System.Collections.Generic; 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 ad95d207..2f976d0a 100644 --- a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs +++ b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs @@ -31,10 +31,6 @@ public partial class ReduceStream : Stream 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 partial class ReduceStream : Stream 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 7668399f..fc0d5e53 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) + public BitStream(byte[] src, int srcLen) + { + _src = src; + _srcLen = srcLen; + _byteIdx = 0; + _bitIdx = 0; + } + + public int BytesRead => (_byteIdx << 3) + _bitIdx; + + private int NextByte() + { + if (_byteIdx >= _srcLen) { - _src = src; - _srcLen = srcLen; - _byteIdx = 0; - _bitIdx = 0; + return EOF; } - public int BytesRead => (_byteIdx << 3) + _bitIdx; + return _src[_byteIdx++]; + } - private int NextByte() + public int NextBits(int nbits) + { + var result = 0; + if (nbits > _bitsLeft) { - if (_byteIdx >= _srcLen) + int num; + while (_bitsLeft <= 24 && (num = NextByte()) != 1234) { - return EOF; + _bitBuffer |= (ulong)num << _bitsLeft; + _bitsLeft += 8; } - - return _src[_byteIdx++]; } + result = (int)((long)_bitBuffer & (long)_maskBits[nbits]); + _bitBuffer >>= nbits; + _bitsLeft -= nbits; + return result; + } - public int NextBits(int nbits) + public bool Advance(int count) + { + if (_byteIdx > _srcLen) { - var result = 0; - if (nbits > _bitsLeft) - { - int num; - while (_bitsLeft <= 24 && (num = NextByte()) != 1234) - { - _bitBuffer |= (ulong)num << _bitsLeft; - _bitsLeft += 8; - } - } - result = (int)((long)_bitBuffer & (long)_maskBits[nbits]); - _bitBuffer >>= nbits; - _bitsLeft -= nbits; - return result; - } - - public bool Advance(int count) - { - 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 8f42753c..81e411c1 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 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 struct CodeTabEntry { - private const int MIN_CODE_SIZE = 9; - private const int MAX_CODE_SIZE = 13; + public int prefixCode; // INVALID_CODE means the entry is invalid. + public byte extByte; + public ushort len; + public int lastDstPos; + } - 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 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; - } + codeTab[i].prefixCode = INVALID_CODE; + } + } - for (var i = byte.MaxValue + 1; i <= MAX_CODE; i++) + 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++) + { + if (codeTab[i].prefixCode != INVALID_CODE) + { + 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; } } - private static void UnshrinkPartialClear(CodeTabEntry[] codeTab, ref CodeQueue queue) + 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 + ) + { + int code, + controlCode; + + code = (int)stream.NextBits(codeSize); + if (!stream.Advance(codeSize)) { - 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++) - { - if (codeTab[i].prefixCode != INVALID_CODE) - { - 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; + nextCode = INVALID_CODE; + return false; } - private static bool ReadCode( - BitStream stream, - ref int codeSize, - CodeTabEntry[] codeTab, - ref CodeQueue queue, - out int nextCode - ) + // Handle regular codes (the common case). + if (code != CONTROL_CODE) { - int code, - controlCode; - - code = (int)stream.NextBits(codeSize); - if (!stream.Advance(codeSize)) - { - nextCode = INVALID_CODE; - return false; - } - - // 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; - } - - if (controlCode == INC_CODE_SIZE && codeSize < MAX_CODE_SIZE) - { - codeSize++; - return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); - } - - if (controlCode == PARTIAL_CLEAR) - { - UnshrinkPartialClear(codeTab, ref queue); - return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); - } + 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; - } - - 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); + codeSize++; + return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); } - 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 (controlCode == PARTIAL_CLEAR) { - int prefixCode; + UnshrinkPartialClear(codeTab, ref queue); + return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode); + } - //assert(code <= MAX_CODE && code != CONTROL_CODE); - //assert(dstPos < dstCap); + 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; + } + + 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) + { + // Output literal byte. + firstByte = (byte)code; + len = 1; + dst[dstPos] = (byte)code; + return UnshrnkStatus.Ok; + } + + 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) - { - // Reject invalid codes. Self-referential codes may exist in the table but cannot be used. - firstByte = 0; - len = 0; - return UnshrnkStatus.Error; - } - - 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; - } - - // 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; - } - - // Output the prefix string, then the extension byte. - len = codeTab[prefixCode].len + 1; - if (dstCap - dstPos < len) + 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[prefixCode].lastDstPos, dstPos, codeTab[prefixCode].len); - dst[dstPos + len - 1] = codeTab[code].extByte; + CopyFromPrevPos(dst, codeTab[code].lastDstPos, dstPos, codeTab[code].len); 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; - + len = codeTab[code].len; return UnshrnkStatus.Ok; } - public static UnshrnkStatus Unshrink( - byte[] src, - int srcLen, - out int srcUsed, - byte[] dst, - int dstCap, - out int dstUsed - ) + // 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]) { - 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; + // 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; + } - CodeTabInit(codeTab); - CodeQueueInit(ref queue); - codeSize = MIN_CODE_SIZE; - dstPos = 0; + // Output the prefix string, then the extension byte. + len = codeTab[prefixCode].len + 1; + if (dstCap - dstPos < len) + { + firstByte = 0; + len = 0; + return UnshrnkStatus.Full; + } - // Handle the first code separately since there is no previous code. - if (!ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode)) + 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; + + 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; + } + + //assert(currCode != CONTROL_CODE); + if (currCode > byte.MaxValue) + { + srcUsed = stream.BytesRead; + dstUsed = 0; + return UnshrnkStatus.Error; // The first code must be a literal. + } + + if (dstPos == dstCap) + { + srcUsed = stream.BytesRead; + dstUsed = dstPos; + 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)) + { + if (currCode == INVALID_CODE) { srcUsed = stream.BytesRead; dstUsed = 0; - return UnshrnkStatus.Ok; - } - - //assert(currCode != CONTROL_CODE); - if (currCode > byte.MaxValue) - { - 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 @@ namespace SharpCompress.Compressors.Shrink 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; - } - - // 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; - } - - // 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; - - 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. - } - - codeTab[currCode].lastDstPos = dstPos; - dstPos += len; - - prevCode = currCode; + // 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; } - srcUsed = stream.BytesRead; - dstUsed = dstPos; - - return UnshrnkStatus.Ok; - } - - public enum UnshrnkStatus - { - Ok, - Full, - Error, - } - - private struct CodeQueue - { - public int nextIdx; - public ushort[] codes; - } - - private static void CodeQueueInit(ref CodeQueue q) - { - int codeQueueSize; - ushort code; - - codeQueueSize = 0; - q.codes = new ushort[MAX_CODE - CONTROL_CODE + 2]; - - for (code = CONTROL_CODE + 1; code <= MAX_CODE; code++) + // 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) { - q.codes[codeQueueSize++] = code; + srcUsed = stream.BytesRead; + dstUsed = 0; + return status; } - //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) - { - var code = CodeQueueNext(ref q); - if (code != INVALID_CODE) + // Verify that the output matches walking the prefixes. + var c = currCode; + for (var i = 0; i < len; i++) { - q.nextIdx++; + // assert(codeTab[c].len == len - i); + //assert(codeTab[c].extByte == dst[dstPos + len - i - 1]); + c = codeTab[c].prefixCode; } - return code; + + // 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; + + 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. + } + + codeTab[currCode].lastDstPos = dstPos; + dstPos += len; + + prevCode = currCode; } + + srcUsed = stream.BytesRead; + dstUsed = dstPos; + + return UnshrnkStatus.Ok; + } + + public enum UnshrnkStatus + { + Ok, + Full, + Error, + } + + private struct CodeQueue + { + public int nextIdx; + public ushort[] codes; + } + + private static void CodeQueueInit(ref CodeQueue q) + { + int codeQueueSize; + ushort code; + + codeQueueSize = 0; + q.codes = new ushort[MAX_CODE - CONTROL_CODE + 2]; + + for (code = CONTROL_CODE + 1; code <= MAX_CODE; code++) + { + q.codes[codeQueueSize++] = code; + } + + //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) + { + var code = CodeQueueNext(ref q); + if (code != INVALID_CODE) + { + q.nextIdx++; + } + return code; } } diff --git a/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs b/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs index 14bc6c3d..dfd61834 100644 --- a/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs +++ b/src/SharpCompress/Compressors/Shrink/ShrinkStream.cs @@ -26,10 +26,6 @@ internal partial class ShrinkStream : Stream 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 @@ internal partial class ShrinkStream : Stream 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 a4c032e7..6522a923 100644 --- a/src/SharpCompress/Compressors/Squeezed/BitReader.Async.cs +++ b/src/SharpCompress/Compressors/Squeezed/BitReader.Async.cs @@ -3,31 +3,30 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace SharpCompress.Compressors.Squeezed -{ - public partial class BitReader - { - public async ValueTask ReadBitAsync(CancellationToken cancellationToken = default) - { - if (_bitCount == 0) - { - byte[] buffer = new byte[1]; - int bytesRead = await _stream - .ReadAsync(buffer, 0, 1, cancellationToken) - .ConfigureAwait(false); - if (bytesRead == 0) - { - throw new EndOfStreamException(); - } +namespace SharpCompress.Compressors.Squeezed; - _bitBuffer = buffer[0]; - _bitCount = 8; +public partial class BitReader +{ + public async ValueTask ReadBitAsync(CancellationToken cancellationToken = default) + { + if (_bitCount == 0) + { + byte[] buffer = new byte[1]; + int bytesRead = await _stream + .ReadAsync(buffer, 0, 1, cancellationToken) + .ConfigureAwait(false); + if (bytesRead == 0) + { + 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 6f528668..25c0601d 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; + + public BitReader(Stream stream) { - private readonly Stream _stream; - private int _bitBuffer; - private int _bitCount; + _stream = stream; + _bitBuffer = 0; + _bitCount = 0; + } - public BitReader(Stream stream) + public bool ReadBit() + { + if (_bitCount == 0) { - _stream = stream; - _bitBuffer = 0; - _bitCount = 0; - } - - public bool ReadBit() - { - 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." - ); - } - - int value = 0; - for (int i = 0; i < count; i++) - { - value = (value << 1) | (ReadBit() ? 1 : 0); - } - return value; + 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; } } diff --git a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs index e040094a..1b416af5 100644 --- a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs +++ b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs @@ -4,108 +4,103 @@ using System.Threading; 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); -#if DEBUG_STREAMS - squeezeStream.DebugConstruct(typeof(SqueezeStream)); -#endif + return squeezeStream; + } - 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); - } + public override async Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + return await _decodedStream + .ReadAsync(buffer, offset, count, cancellationToken) + .ConfigureAwait(false); + } #if !LEGACY_DOTNET - public override async ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default - ) - { - return await _decodedStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - } + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) + { + return await _decodedStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } #endif - private async Task BuildDecodedStreamAsync(CancellationToken cancellationToken) + private async Task BuildDecodedStreamAsync(CancellationToken cancellationToken) + { + byte[] numNodesBytes = new byte[2]; + int bytesRead = await _stream + .ReadAsync(numNodesBytes, 0, 2, cancellationToken) + .ConfigureAwait(false); + + if (bytesRead != 2) { - byte[] numNodesBytes = new byte[2]; - int bytesRead = await _stream - .ReadAsync(numNodesBytes, 0, 2, cancellationToken) + return new MemoryStream(Array.Empty()); + } + + int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8); + + if (numnodes >= NUMVALS || numnodes == 0) + { + return new MemoryStream(Array.Empty()); + } + + 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 != 2) + if (bytesRead != 4) { - return new MemoryStream(Array.Empty()); + throw new EndOfStreamException(); } - int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8); - - if (numnodes >= NUMVALS || numnodes == 0) - { - return new MemoryStream(Array.Empty()); - } - - 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)); - } - - var bitReader = new BitReader(_stream); - var huffmanDecoded = new MemoryStream(); - int i = 0; - - while (true) - { - bool bit = await bitReader.ReadBitAsync(cancellationToken).ConfigureAwait(false); - i = dnode[i, bit ? 1 : 0]; - if (i < 0) - { - i = -(i + 1); - if (i == SPEOF) - { - break; - } - huffmanDecoded.WriteByte((byte)i); - i = 0; - } - } - - huffmanDecoded.Position = 0; - return new RunLength90Stream(huffmanDecoded, (int)huffmanDecoded.Length); + 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; + + while (true) + { + bool bit = await bitReader.ReadBitAsync(cancellationToken).ConfigureAwait(false); + i = dnode[i, bit ? 1 : 0]; + if (i < 0) + { + i = -(i + 1); + if (i == SPEOF) + { + break; + } + huffmanDecoded.WriteByte((byte)i); + i = 0; + } + } + + 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 072c80f1..d1f179b3 100644 --- a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs +++ b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs @@ -4,74 +4,67 @@ using System.IO; using System.Text; using SharpCompress.Compressors.RLE90; -namespace SharpCompress.Compressors.Squeezed +namespace SharpCompress.Compressors.Squeezed; + +[CLSCompliant(true)] +public partial class SqueezeStream : Stream { - [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 Stream _decodedStream = null!; + + private SqueezeStream(Stream stream, int compressedSize) { - private readonly Stream _stream; - private readonly int _compressedSize; - private const int NUMVALS = 257; - private const int SPEOF = 256; + _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) + return squeezeStream; + } + + 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 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 long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + return _decodedStream.Read(buffer, offset, count); + } + + private Stream BuildDecodedStream() + { + using (var binaryReader = new BinaryReader(_stream, Encoding.Default, leaveOpen: true)) { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _compressedSize = compressedSize; - } - - public static SqueezeStream Create(Stream stream, int compressedSize) - { - var squeezeStream = new SqueezeStream(stream, compressedSize); - squeezeStream._decodedStream = squeezeStream.BuildDecodedStream(); - -#if DEBUG_STREAMS - squeezeStream.DebugConstruct(typeof(SqueezeStream)); -#endif - - return squeezeStream; - } - - protected override void Dispose(bool disposing) - { -#if DEBUG_STREAMS - this.DebugDispose(typeof(SqueezeStream)); -#endif - _decodedStream?.Dispose(); - base.Dispose(disposing); - } - - 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() => throw new NotSupportedException(); - - public override long Seek(long offset, SeekOrigin origin) => - throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) => - throw new NotSupportedException(); - - public override int Read(byte[] buffer, int offset, int count) - { - return _decodedStream.Read(buffer, offset, count); - } - - private Stream BuildDecodedStream() - { - 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 5541fb2d..0e3f8039 100644 --- a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs @@ -12,16 +12,10 @@ public abstract class XZReadOnlyStream : ReadOnlyStream { 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 dda9aa78..489b449a 100644 --- a/src/SharpCompress/Compressors/Xz/XZStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZStream.cs @@ -15,16 +15,10 @@ public sealed partial class XZStream : XZReadOnlyStream : 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 fc8e3108..00da0c99 100644 --- a/src/SharpCompress/Compressors/ZStandard/BitOperations.cs +++ b/src/SharpCompress/Compressors/ZStandard/BitOperations.cs @@ -9,302 +9,301 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper; // 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 { - /// - /// 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, - } - ); - - /// - /// 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) + // hack: should be public because of inline + public static readonly byte* TrailingZeroCountDeBruijn = GetArrayPointer( + new byte[] { - // 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; + 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, + } + ); - // 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 + // 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, + } + ); - // Fallback contract is 0->0 - // No AggressiveInlining due to large method size - // Has conventional contract 0->0 (Log(0) is undefined) + /// + /// 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; - // Fill trailing zeros with ones, eg 00010010 becomes 00011111 - value |= value >> 01; - value |= value >> 02; - value |= value >> 04; - value |= value >> 08; - value |= value >> 16; + // 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 - // 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 + // 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) + { + return Log2((uint)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) + { + 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 + } + + /// + /// 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; + + if (lo == 0) + { + return 32 + TrailingZeroCount((uint)(value >> 32)); + } + + 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..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) + { + // 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) ]; - } + } - /// - /// 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) + /// + /// 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); + + if (hi == 0) { - value |= 1; - - uint hi = (uint)(value >> 32); - - if (hi == 0) - { - return Log2((uint)value); - } - - return 32 + Log2(hi); + return 32 + LeadingZeroCount((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) - { - // 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 - } - - /// - /// 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; - - if (lo == 0) - { - return 32 + TrailingZeroCount((uint)(value >> 32)); - } - - 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..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) - { - // 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) - ]; - } - - /// - /// 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); - - if (hi == 0) - { - return 32 + LeadingZeroCount((uint)value); - } - - return LeadingZeroCount(hi); - } + return LeadingZeroCount(hi); } } diff --git a/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs b/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs index 3a4ffbb0..7b65f1ce 100644 --- a/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs +++ b/src/SharpCompress/Compressors/ZStandard/ZStandardStream.cs @@ -33,9 +33,6 @@ internal partial class ZStandardStream : DecompressionStream : 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 73a04ed3..1cbe8884 100644 --- a/src/SharpCompress/Crypto/Crc32Stream.cs +++ b/src/SharpCompress/Crypto/Crc32Stream.cs @@ -25,16 +25,10 @@ public sealed class Crc32Stream : Stream 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 aa0ed79e..f2fdfaa6 100644 --- a/src/SharpCompress/Factories/AceFactory.cs +++ b/src/SharpCompress/Factories/AceFactory.cs @@ -10,39 +10,38 @@ using SharpCompress.Common.Ace.Headers; 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() { - public override string Name => "Ace"; + yield return "ace"; + } - public override ArchiveType? KnownArchiveType => ArchiveType.Ace; + public override bool IsArchive(Stream stream, string? password = null) => + AceHeader.IsArchive(stream); - public override IEnumerable GetSupportedExtensions() - { - yield return "ace"; - } + public override ValueTask IsArchiveAsync( + Stream stream, + string? password = null, + CancellationToken cancellationToken = default + ) => AceHeader.IsArchiveAsync(stream, cancellationToken); - public override bool IsArchive(Stream stream, string? password = null) => - AceHeader.IsArchive(stream); + public IReader OpenReader(Stream stream, ReaderOptions? options) => + AceReader.OpenReader(stream, options); - 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)); - } + public ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)AceReader.OpenReader(stream, options)); } } diff --git a/src/SharpCompress/Factories/ArcFactory.cs b/src/SharpCompress/Factories/ArcFactory.cs index effaa02f..75558ce7 100644 --- a/src/SharpCompress/Factories/ArcFactory.cs +++ b/src/SharpCompress/Factories/ArcFactory.cs @@ -12,77 +12,76 @@ using SharpCompress.Readers; 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 ArchiveType? KnownArchiveType => ArchiveType.Arc; + + public override IEnumerable GetSupportedExtensions() { - public override string Name => "Arc"; + yield return "arc"; + } - public override ArchiveType? KnownArchiveType => ArchiveType.Arc; - - public override IEnumerable GetSupportedExtensions() + 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 { - yield return "arc"; - } - - 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 + if (stream.ReadFully(buffer.AsSpan(0, 2))) { - 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 - { - ArrayPool.Shared.Return(buffer); - } - } - - 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 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 - { - 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); - } + return false; + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + 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 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 + { + 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); } } } diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs index 1377407b..d599d81d 100644 --- a/src/SharpCompress/Factories/ArjFactory.cs +++ b/src/SharpCompress/Factories/ArjFactory.cs @@ -10,39 +10,38 @@ using SharpCompress.Common.Arj.Headers; 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() { - public override string Name => "Arj"; + yield return "arj"; + } - public override ArchiveType? KnownArchiveType => ArchiveType.Arj; + public override bool IsArchive(Stream stream, string? password = null) => + ArjHeader.IsArchive(stream); - public override IEnumerable GetSupportedExtensions() - { - yield return "arj"; - } + public override ValueTask IsArchiveAsync( + Stream stream, + string? password = null, + CancellationToken cancellationToken = default + ) => ArjHeader.IsArchiveAsync(stream, cancellationToken); - public override bool IsArchive(Stream stream, string? password = null) => - ArjHeader.IsArchive(stream); + public IReader OpenReader(Stream stream, ReaderOptions? options) => + ArjReader.OpenReader(stream, options); - 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)); - } + public ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return new((IAsyncReader)ArjReader.OpenReader(stream, options)); } } diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs index aea200f5..7bca8ecb 100644 --- a/src/SharpCompress/Factories/Factory.cs +++ b/src/SharpCompress/Factories/Factory.cs @@ -65,7 +65,7 @@ public abstract class Factory : IFactory 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 class Factory : IFactory /// /// 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 40907e1d..ae4ac8b8 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -107,28 +107,28 @@ public class GZipFactory /// 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 b643d29a..f8f4b779 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -94,7 +94,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory #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 53286880..8857cca7 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -49,18 +49,18 @@ public class TarFactory /// 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 class TarFactory 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 class TarFactory 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 class TarFactory { 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 00000000..9fef056a --- /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 df9156c4..d156ab75 100644 --- a/src/SharpCompress/IO/IStreamStack.cs +++ b/src/SharpCompress/IO/IStreamStack.cs @@ -6,77 +6,76 @@ using System.IO; 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(); +} + +public static class StreamStackExtensions +{ + public static T? GetStream(this IStreamStack stack) + where T : Stream { - /// - /// Returns the immediate underlying stream in the stack. - /// - Stream BaseStream(); + var baseStream = stack.BaseStream(); + if (baseStream is T tStream) + { + return tStream; + } + else if (baseStream is IStreamStack innerStack) + { + return innerStack.GetStream(); + } + else + { + return null; + } } - public static class StreamStackExtensions + /// + /// 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) { - public static T? GetStream(this IStreamStack stack) - where T : Stream + var current = stack.BaseStream(); + while (current is IStreamStack streamStack) { - var baseStream = stack.BaseStream(); - if (baseStream is T tStream) - { - return tStream; - } - else if (baseStream is IStreamStack innerStack) - { - return innerStack.GetStream(); - } - else - { - return null; - } + current = streamStack.BaseStream(); } + return current; + } - /// - /// 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) + internal static void Rewind(this IStreamStack stream, int count) + { + IStreamStack? current = stream; + + while (current != null) { - var current = stack.BaseStream(); - while (current is IStreamStack streamStack) + if (current is SharpCompressStream sharpCompressStream) { - current = streamStack.BaseStream(); - } - return current; - } - - internal static void Rewind(this IStreamStack stream, int count) - { - IStreamStack? current = stream; - - while (current != null) - { - 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 { - try - { - rewindableStream.Position = targetPosition; - } - catch (NotSupportedException) - { - // Cannot seek outside buffered region - silently ignore - } + sharpCompressStream.Position = targetPosition; + } + catch (NotSupportedException) + { + // 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 51fabf68..00000000 --- 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 728b0512..763000de 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 @@ internal partial class ReadOnlySubStream : Stream, IStreamStack } BytesLeftToRead = bytesToRead; _position = 0; -#if DEBUG_STREAMS - this.DebugConstruct(typeof(ReadOnlySubStream)); -#endif } private long BytesLeftToRead { get; set; } @@ -106,9 +99,6 @@ internal partial class ReadOnlySubStream : Stream, IStreamStack 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 aeaef809..00000000 --- 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 400a6553..34a9ec0e 100644 --- a/src/SharpCompress/IO/SeekableRewindableStream.Async.cs +++ b/src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace SharpCompress.IO; -internal sealed partial class SeekableRewindableStream +internal sealed partial class SeekableSharpCompressStream { public override Task ReadAsync( byte[] buffer, @@ -27,7 +27,21 @@ internal sealed partial class SeekableRewindableStream 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 12cbc7c1..0e8d83d2 100644 --- a/src/SharpCompress/IO/SeekableRewindableStream.cs +++ b/src/SharpCompress/IO/SeekableSharpCompressStream.cs @@ -3,14 +3,26 @@ using System.IO; 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 @@ internal sealed partial class SeekableRewindableStream : RewindableStream 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 48daa3ef..3544a649 100644 --- a/src/SharpCompress/IO/RewindableStream.Async.cs +++ b/src/SharpCompress/IO/SharpCompressStream.Async.cs @@ -5,9 +5,9 @@ using System.Threading.Tasks; 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,17 +16,26 @@ internal partial class RewindableStream { 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); + return stream.ReadAsync(buffer, offset, count, cancellationToken); } - // If ring buffer is enabled (and not recording), use ring buffer logic + return ReadAsyncCore(buffer, offset, count, cancellationToken); + } + + private async Task ReadAsyncCore( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + // If ring buffer is enabled, use ring buffer logic if (_ringBuffer is not null) { return await ReadWithRingBufferAsync(buffer, offset, count, cancellationToken) @@ -42,65 +51,6 @@ internal partial class RewindableStream return read; } - /// - /// Async version of ReadWithRecording (legacy behavior for format detection). - /// - private async Task ReadWithRecordingAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken - ) - { - int read; - if (isRewound && bufferStream.Position != bufferStream.Length) - { - var readCount = Math.Min(count, (int)(bufferStream.Length - bufferStream.Position)); - read = await bufferStream - .ReadAsync(buffer, offset, readCount, 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 - .ReadAsync(buffer, offset, count, cancellationToken) - .ConfigureAwait(false); - if (IsRecording) - { - await bufferStream - .WriteAsync(buffer, offset, read, cancellationToken) - .ConfigureAwait(false); - } - streamPosition += read; - _logicalPosition = streamPosition; - return read; - } - /// /// Async version of ReadWithRingBuffer. /// @@ -117,15 +67,25 @@ internal partial class RewindableStream 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,23 +103,31 @@ internal partial class RewindableStream } #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)) + // In passthrough mode, delegate directly to underlying stream + if (_isPassthrough) { - return await ReadWithRecordingAsync(buffer, cancellationToken).ConfigureAwait(false); + return stream.ReadAsync(buffer, cancellationToken); } - // If ring buffer is enabled (and not recording), use ring buffer logic + return ReadAsyncCore(buffer, cancellationToken); + } + + private async ValueTask ReadAsyncCore( + Memory buffer, + CancellationToken cancellationToken + ) + { + // If ring buffer is enabled, use ring buffer logic if (_ringBuffer is not null) { return await ReadWithRingBufferAsync(buffer, cancellationToken).ConfigureAwait(false); @@ -172,63 +140,6 @@ internal partial class RewindableStream return read; } - /// - /// Async version of ReadWithRecording for Memory<byte> (legacy behavior for format detection). - /// - private async ValueTask ReadWithRecordingAsync( - Memory buffer, - CancellationToken cancellationToken - ) - { - int read; - if (isRewound && bufferStream.Position != bufferStream.Length) - { - 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; - } - - read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - if (IsRecording) - { - await bufferStream - .WriteAsync(buffer.Slice(0, read), cancellationToken) - .ConfigureAwait(false); - } - streamPosition += read; - _logicalPosition = streamPosition; - return read; - } - /// /// Async version of ReadWithRingBuffer for Memory<byte>. /// @@ -246,13 +157,18 @@ internal partial class RewindableStream 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 @@ internal partial class RewindableStream _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 @@ internal partial class RewindableStream 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 @@ internal partial class RewindableStream { 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 00000000..eae6217f --- /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 673285dd..35ec3b9a 100644 --- a/src/SharpCompress/IO/SourceStream.cs +++ b/src/SharpCompress/IO/SourceStream.cs @@ -58,10 +58,6 @@ public partial class SourceStream : Stream, IStreamStack } _stream = 0; _prevSize = 0; - -#if DEBUG_STREAMS - this.DebugConstruct(typeof(SourceStream)); -#endif } public void LoadAllParts() @@ -262,9 +258,6 @@ public partial class SourceStream : Stream, IStreamStack 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 aec6ce86..2c654ccb 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 979f701d..958045e0 100644 --- a/src/SharpCompress/Readers/AbstractReader.Async.cs +++ b/src/SharpCompress/Readers/AbstractReader.Async.cs @@ -178,11 +178,7 @@ public abstract partial class AbstractReader .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 46b595b0..c2a12a2a 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -246,7 +246,7 @@ public abstract partial class AbstractReader : IReader, IAsyncR { 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 abstract partial class AbstractReader : IReader, IAsyncR /// /// 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 a729ab3f..0b000f12 100644 --- a/src/SharpCompress/Readers/Ace/MultiVolumeAceReader.cs +++ b/src/SharpCompress/Readers/Ace/MultiVolumeAceReader.cs @@ -10,108 +10,107 @@ using System.Threading.Tasks; 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; + + internal MultiVolumeAceReader(IEnumerable streams, ReaderOptions options) + : base(options) => this.streams = streams.GetEnumerator(); + + protected override void ValidateArchive(AceVolume archive) { } + + protected override Stream RequestInitialStream() { - private readonly IEnumerator streams; + if (streams.MoveNext()) + { + return streams.Current; + } + throw new MultiVolumeExtractionException( + "No stream provided when requested by MultiVolumeAceReader" + ); + } + + internal override bool 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; + } + + protected override IEnumerable CreateFilePartEnumerableForCurrentEntry() + { + var enumerator = new MultiVolumeStreamEnumerator(this, streams, tempStream); + tempStream = null; + return enumerator; + } + + private class MultiVolumeStreamEnumerator : IEnumerable, IEnumerator + { + private readonly MultiVolumeAceReader reader; + private readonly IEnumerator nextReadableStreams; private Stream tempStream; + private bool isFirst = true; - internal MultiVolumeAceReader(IEnumerable streams, ReaderOptions options) - : base(options) => this.streams = streams.GetEnumerator(); - - protected override void ValidateArchive(AceVolume archive) { } - - protected override Stream RequestInitialStream() + internal MultiVolumeStreamEnumerator( + MultiVolumeAceReader r, + IEnumerator nextReadableStreams, + Stream tempStream + ) { - if (streams.MoveNext()) - { - return streams.Current; - } - throw new MultiVolumeExtractionException( - "No stream provided when requested by MultiVolumeAceReader" - ); + reader = r; + this.nextReadableStreams = nextReadableStreams; + this.tempStream = tempStream; } - internal override bool NextEntryForCurrentStream() + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => this; + + public FilePart Current { get; private set; } + + public void Dispose() { } + + object IEnumerator.Current => Current; + + public bool MoveNext() { - if (!base.NextEntryForCurrentStream()) + if (isFirst) { - // 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; - } - - 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 - ) - { - reader = r; - this.nextReadableStreams = nextReadableStreams; - this.tempStream = tempStream; - } - - public IEnumerator GetEnumerator() => this; - - IEnumerator IEnumerable.GetEnumerator() => this; - - public FilePart Current { get; private set; } - - public void Dispose() { } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - 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 61182d66..ce42c6e1 100644 --- a/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs +++ b/src/SharpCompress/Readers/Ace/SingleVolumeAceReader.cs @@ -3,29 +3,28 @@ using System.IO; 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; + + internal SingleVolumeAceReader(Stream stream, ReaderOptions options) + : base(options) { - private readonly Stream _stream; + stream.NotNull(nameof(stream)); + _stream = stream; + } - internal SingleVolumeAceReader(Stream stream, ReaderOptions options) - : base(options) + protected override Stream RequestInitialStream() => _stream; + + protected override void ValidateArchive(AceVolume archive) + { + if (archive.IsMultiVolume) { - stream.NotNull(nameof(stream)); - _stream = stream; - } - - protected override Stream RequestInitialStream() => _stream; - - protected override void ValidateArchive(AceVolume archive) - { - 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 f0569d38..ebb0e269 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.Async.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.Async.cs @@ -3,21 +3,19 @@ using System.IO; 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 c99e8096..ee9d5fa3 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.cs @@ -7,35 +7,34 @@ using System.Threading.Tasks; 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); + + 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) { - private ArcReader(Stream stream, ReaderOptions options) - : base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0); + stream.NotNull(nameof(stream)); + return new ArcReader(stream, options ?? new ReaderOptions()); + } - 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) + protected override IEnumerable GetEntries(Stream stream) + { + ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding); + ArcEntryHeader? header; + while ((header = headerReader.ReadHeader(stream)) != null) { - stream.NotNull(nameof(stream)); - return new ArcReader(stream, options ?? new ReaderOptions()); - } - - protected override IEnumerable GetEntries(Stream stream) - { - 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 3bc39d10..9a589925 100644 --- a/src/SharpCompress/Readers/Arj/ArjReader.cs +++ b/src/SharpCompress/Readers/Arj/ArjReader.cs @@ -8,142 +8,137 @@ using SharpCompress.Common.Arj; 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) - { - 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); - } - - 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)); - } - } - - protected override async IAsyncEnumerable GetEntriesAsync(Stream stream) - { - var encoding = new ArchiveEncoding(); - var mainHeaderReader = new ArjMainHeader(encoding); - var localHeaderReader = new ArjLocalHeader(encoding); - - 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); - } - - while (true) - { - 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)); - } - } - - protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() => - Entry.Parts; + 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) + { + 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); + } + + 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)); + } + } + + protected override async IAsyncEnumerable GetEntriesAsync(Stream stream) + { + var encoding = new ArchiveEncoding(); + var mainHeaderReader = new ArjMainHeader(encoding); + var localHeaderReader = new ArjLocalHeader(encoding); + + 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); + } + + while (true) + { + 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)); + } + } + + protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() => + Entry.Parts; } diff --git a/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs b/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs index 54825712..71eb46e4 100644 --- a/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs +++ b/src/SharpCompress/Readers/Arj/SingleVolumeArjReader.cs @@ -3,29 +3,28 @@ using System.IO; 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; + + internal SingleVolumeArjReader(Stream stream, ReaderOptions options) + : base(options) { - private readonly Stream _stream; + stream.NotNull(nameof(stream)); + _stream = stream; + } - internal SingleVolumeArjReader(Stream stream, ReaderOptions options) - : base(options) + protected override Stream RequestInitialStream() => _stream; + + protected override void ValidateArchive(ArjVolume archive) + { + if (archive.IsMultiVolume) { - stream.NotNull(nameof(stream)); - _stream = stream; - } - - protected override Stream RequestInitialStream() => _stream; - - protected override void ValidateArchive(ArjVolume archive) - { - 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 9dc50f11..0e1943d2 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.Async.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.Async.cs @@ -27,10 +27,6 @@ public abstract partial class RarReader 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 @@ public abstract partial class RarReader return CreateEntryStream( await RarCrcStream .CreateAsync(UnpackV1.Value, Entry.FileHeader, stream, cancellationToken) - .ConfigureAwait(false), - useSyncOverAsync + .ConfigureAwait(false) ); } @@ -54,16 +49,14 @@ public abstract partial class RarReader 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 cae92ee1..cd0aebc9 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -110,7 +110,7 @@ public abstract partial class RarReader : AbstractReader CreateFilePartEnumerableForCurrentEntry() => Entry.Parts; - protected override EntryStream GetEntryStream(bool useSyncOverAsyncDispose) + protected override EntryStream GetEntryStream() { if (Entry.IsRedir) { @@ -122,24 +122,17 @@ public abstract partial class RarReader : AbstractReader 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 f279895e..2293ea07 100644 --- a/src/SharpCompress/Readers/ReaderFactory.Async.cs +++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs @@ -54,8 +54,11 @@ public static partial class ReaderFactory 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 partial class ReaderFactory ); 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 @@ public static partial class ReaderFactory { 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 8f40587e..180c2809 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -34,8 +34,11 @@ public static partial class ReaderFactory 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 partial class ReaderFactory .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 partial class ReaderFactory { 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 b016a3b6..2ddb8ac4 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 030ad33f..c2068789 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -57,61 +57,64 @@ public partial class TarReader : AbstractReader { 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 f98a48c0..de3a1ddc 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 @@ internal static partial class Utility 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 633d9cc9..67269272 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriter.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriter.cs @@ -16,7 +16,7 @@ public sealed partial class GZipWriter : AbstractWriter { 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 974ed5a6..eb7070fb 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -29,7 +29,7 @@ public partial class TarWriter : AbstractWriter } 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 e69af91e..fb21908e 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -44,7 +44,7 @@ public partial class ZipWriter : AbstractWriter if (WriterOptions.LeaveStreamOpen) { - destination = new NonDisposingStream(destination); + destination = SharpCompressStream.CreateNonDisposing(destination); } InitializeStream(destination); } @@ -417,7 +417,7 @@ public partial class ZipWriter : AbstractWriter 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 02b54f0d..4dde6780 100644 --- a/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs @@ -9,132 +9,131 @@ using SharpCompress.Readers.Ace; 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 - ) + 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()) { - await using var reader = readerFactory( - archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).Select(File.OpenRead) - ); - - 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 60c51c12..d6c609e9 100644 --- a/tests/SharpCompress.Test/Ace/AceReaderTests.cs +++ b/tests/SharpCompress.Test/Ace/AceReaderTests.cs @@ -9,53 +9,50 @@ using SharpCompress.Readers; 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 54f169c3..c5de5ebd 100644 --- a/tests/SharpCompress.Test/Arc/ArcReaderTests.cs +++ b/tests/SharpCompress.Test/Arc/ArcReaderTests.cs @@ -2,40 +2,39 @@ using System; 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 ee2bd6a3..4d09ae84 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -43,7 +43,7 @@ public class ArchiveTests : ReaderTests { 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 @@ public class ArchiveTests : ReaderTests 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 @@ public class ArchiveTests : ReaderTests { 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 5e03b47a..6a32cdeb 100644 --- a/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs @@ -11,168 +11,162 @@ using SharpCompress.Test.Mocks; 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; + } + + [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_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_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_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.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) + { + 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()) { - UseExtensionInsteadOfNameToVerify = true; - UseCaseInsensitiveToVerify = true; + if (!reader.Entry.IsDirectory) + { + if (expectedCompression.HasValue) + { + Assert.Equal(expectedCompression.Value, reader.Entry.CompressionType); + } + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); + } } + VerifyFiles(); + } - [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_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_Method4_Read_Async() => await ReadAsync("Arj.method4.arj"); - - [Fact] - public async ValueTask Arj_Encrypted_Read_Async() + 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()) { - var exception = await Assert.ThrowsAsync(() => - ReadAsync("Arj.encrypted.arj") - ); + 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") + ); + } - [Fact] - public async ValueTask Arj_Multi_Reader_Async() + 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 { - 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.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 - ) - { - 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() - ); + await using var reader = await openReader(streams); while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { - if (expectedCompression.HasValue) - { - Assert.Equal(expectedCompression.Value, reader.Entry.CompressionType); - } await reader.WriteEntryToDirectoryAsync( SCRATCH_FILES_PATH, new ExtractionOptions { ExtractFullPath = true, Overwrite = true } ); } } - VerifyFiles(); } - - private async Task ReadForBufferBoundaryCheckAsync( - string testArchive, - CompressionType expectedCompression - ) + finally { - 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()) + foreach (var stream in streams) { - 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) - { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); - } - } - } - finally - { - 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 054c18f6..f4b5b4d5 100644 --- a/tests/SharpCompress.Test/Arj/ArjReaderTests.cs +++ b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs @@ -10,72 +10,71 @@ using SharpCompress.Readers.Arj; 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 4151855a..665b6f8f 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs @@ -18,7 +18,7 @@ public class GZipReaderTests : ReaderTests { //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 291bae8d..07a679ef 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 class AsyncOnlyStream : Stream 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 d62b5c9a..a6e1c25b 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -67,7 +67,7 @@ public abstract class ReaderTests : TestBase 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 @@ public abstract class ReaderTests : TestBase 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 d3a2dc5c..d39b82ad 100644 --- a/tests/SharpCompress.Test/Streams/SeekableRewindableStreamAsyncTest.cs +++ b/tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamAsyncTest.cs @@ -8,13 +8,13 @@ using Xunit; 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest 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 class SeekableRewindableStreamAsyncTest } #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 partial class SeekableRewindableStreamMemoryAsyncTest 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 partial class SeekableRewindableStreamMemoryAsyncTest 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 partial class SeekableRewindableStreamMemoryAsyncTest 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 partial class SeekableRewindableStreamMemoryAsyncTest 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 partial class SeekableRewindableStreamMemoryAsyncTest 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 7e4e7436..0071d420 100644 --- a/tests/SharpCompress.Test/Streams/SeekableRewindableStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/SeekableSharpCompressStreamTest.cs @@ -7,26 +7,26 @@ using Xunit; 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest { 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 class SeekableRewindableStreamTest 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 partial class SeekableRewindableSpanTest 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 partial class SeekableRewindableSpanTest 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 b8b5aea3..6b417f7c 100644 --- a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs @@ -10,7 +10,7 @@ using Xunit; namespace SharpCompress.Test.Streams; -public class RewindableStreamAsyncTest +public class SharpCompressStreamAsyncTest { [Fact] public async ValueTask TestRewindAsync() @@ -26,7 +26,7 @@ public class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest } 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 class RewindableStreamAsyncTest { 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 class RewindableStreamAsyncTest { 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 class RewindableStreamAsyncTest } 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest } 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 class RewindableStreamAsyncTest { 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 class RewindableStreamAsyncTest { 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 class RewindableStreamAsyncTest } 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 @@ public class RewindableStreamAsyncTest } #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 @@ public class RewindableStreamAsyncTest 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 @@ public class RewindableStreamAsyncTest } #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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 class RewindableStreamAsyncTest 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 44029468..3e846f39 100644 --- a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs @@ -7,7 +7,7 @@ using Xunit; namespace SharpCompress.Test.Streams; -public class RewindableStreamTest +public class SharpCompressStreamTest { [Fact] public void TestRewind() @@ -23,7 +23,7 @@ public class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest } 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 class RewindableStreamTest } 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 class RewindableStreamTest 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 ba9ad605..e2f1506c 100644 --- a/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs +++ b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs @@ -86,7 +86,7 @@ public class ZLibBaseStreamAsyncTests 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 @@ public class ZLibBaseStreamAsyncTests 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 0ba0a8c4..0fd53d8f 100644 --- a/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs +++ b/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs @@ -80,7 +80,7 @@ public class ZLibBaseStreamTests 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 @@ public class ZLibBaseStreamTests 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 e2ad56f8..0759d726 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -253,7 +253,11 @@ public class TarArchiveAsyncTests : ArchiveTests { ++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 5b6ae7ff..f02b65d7 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -44,7 +44,7 @@ public class WriterTests : TestBase readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; using var reader = ReaderFactory.OpenReader( - new NonDisposingStream(stream), + SharpCompressStream.CreateNonDisposing(stream), readerOptions ); reader.WriteAllToDirectory( @@ -93,7 +93,7 @@ public class WriterTests : TestBase 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 65a307a2..6460a766 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -297,7 +297,9 @@ public class ZipReaderTests : ReaderTests 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()) {