diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9a51cc35..ced78445 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
+
diff --git a/src/SharpCompress/Common/ArchiveType.cs b/src/SharpCompress/Common/ArchiveType.cs
index 503804c8..9d943895 100644
--- a/src/SharpCompress/Common/ArchiveType.cs
+++ b/src/SharpCompress/Common/ArchiveType.cs
@@ -8,4 +8,5 @@ public enum ArchiveType
SevenZip,
GZip,
Arc,
+ Arj,
}
diff --git a/src/SharpCompress/Common/Arj/ArjEntry.cs b/src/SharpCompress/Common/Arj/ArjEntry.cs
new file mode 100644
index 00000000..8dd84e87
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/ArjEntry.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Common.Arc;
+using SharpCompress.Common.Arj.Headers;
+
+namespace SharpCompress.Common.Arj
+{
+ public class ArjEntry : Entry
+ {
+ 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();
+ }
+}
diff --git a/src/SharpCompress/Common/Arj/ArjFilePart.cs b/src/SharpCompress/Common/Arj/ArjFilePart.cs
new file mode 100644
index 00000000..10fe24c6
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/ArjFilePart.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Common.Arj.Headers;
+using SharpCompress.Compressors.Arj;
+using SharpCompress.IO;
+
+namespace SharpCompress.Common.Arj
+{
+ public class ArjFilePart : FilePart
+ {
+ 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.CompressedFastest:
+ byte[] compressedData = new byte[Header.CompressedSize];
+ _stream.Position = Header.DataStartPosition;
+ _stream.Read(compressedData, 0, compressedData.Length);
+
+ byte[] decompressedData = LHDecoder.DecodeFastest(
+ compressedData,
+ (int)Header.OriginalSize // ARJ can only handle files up to 2GB, so casting to int should not be an issue.
+ );
+
+ compressedStream = new MemoryStream(decompressedData);
+ 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
new file mode 100644
index 00000000..bf3ba109
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/ArjVolume.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Readers;
+
+namespace SharpCompress.Common.Arj
+{
+ public class ArjVolume : Volume
+ {
+ public ArjVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
+ : base(stream, readerOptions, index) { }
+ }
+}
diff --git a/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs
new file mode 100644
index 00000000..138f75e7
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/ArjHeader.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Common.Zip.Headers;
+using SharpCompress.Crypto;
+
+namespace SharpCompress.Common.Arj.Headers
+{
+ public enum ArjHeaderType
+ {
+ MainHeader,
+ LocalHeader,
+ }
+
+ public abstract class ArjHeader
+ {
+ private const int FIRST_HDR_SIZE = 34;
+ private const ushort ARJ_MAGIC = 0xEA60;
+
+ public ArjHeader(ArjHeaderType type)
+ {
+ ArjHeaderType = type;
+ }
+
+ public ArjHeaderType ArjHeaderType { get; }
+ public byte Flags { get; set; }
+ public FileType FileType { get; set; }
+
+ public abstract ArjHeader? Read(Stream reader);
+
+ public byte[] ReadHeader(Stream stream)
+ {
+ // check for magic bytes
+ Span magic = stackalloc byte[2];
+ if (stream.Read(magic) != 2)
+ {
+ return Array.Empty();
+ }
+
+ var magicValue = (ushort)(magic[0] | magic[1] << 8);
+ if (magicValue != ARJ_MAGIC)
+ {
+ 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;
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs
new file mode 100644
index 00000000..f790b495
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpCompress.Common.Arj.Headers
+{
+ public 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)
+ {
+ ArchiveEncoding =
+ archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding));
+ }
+
+ public override ArjHeader? Read(Stream stream)
+ {
+ var body = ReadHeader(stream);
+ if (body.Length > 0)
+ {
+ ReadExtendedHeaders(stream);
+ var header = LoadFrom(body);
+ header.DataStartPosition = stream.Position;
+ return header;
+ }
+ return null;
+ }
+
+ 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;
+ }
+
+ 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,
+ };
+ }
+ }
+}
diff --git a/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs
new file mode 100644
index 00000000..6ba2b83c
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs
@@ -0,0 +1,138 @@
+using System;
+using System.IO;
+using System.Text;
+using SharpCompress.Compressors.Deflate;
+using SharpCompress.Crypto;
+
+namespace SharpCompress.Common.Arj.Headers
+{
+ public class ArjMainHeader : ArjHeader
+ {
+ private const int FIRST_HDR_SIZE = 34;
+ private const ushort ARJ_MAGIC = 0xEA60;
+
+ public ArchiveEncoding ArchiveEncoding { get; }
+
+ public int ArchiverVersionNumber { get; private set; }
+ public int MinVersionToExtract { get; private set; }
+ public HostOS HostOs { get; private set; }
+ public int SecurityVersion { get; private set; }
+ public DosDateTime CreationDateTime { get; private set; } = new DosDateTime(0);
+ public long CompressedSize { get; private set; }
+ public long ArchiveSize { get; private set; }
+ public long SecurityEnvelope { get; private set; }
+ public int FileSpecPosition { get; private set; }
+ public int SecurityEnvelopeLength { get; private set; }
+ public int EncryptionVersion { get; private set; }
+ public int LastChapter { get; private set; }
+
+ public int ArjProtectionFactor { get; private set; }
+ public int Flags2 { get; private set; }
+ public string Name { get; private set; } = string.Empty;
+ public string Comment { get; private set; } = string.Empty;
+
+ public ArjMainHeader(ArchiveEncoding archiveEncoding)
+ : base(ArjHeaderType.MainHeader)
+ {
+ ArchiveEncoding =
+ archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding));
+ }
+
+ public override ArjHeader? Read(Stream stream)
+ {
+ var body = ReadHeader(stream);
+ ReadExtendedHeaders(stream);
+ return LoadFrom(body);
+ }
+
+ public ArjMainHeader LoadFrom(byte[] headerBytes)
+ {
+ var offset = 1;
+
+ byte ReadByte()
+ {
+ if (offset >= headerBytes.Length)
+ {
+ throw new EndOfStreamException();
+ }
+ return (byte)(headerBytes[offset++] & 0xFF);
+ }
+
+ 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;
+ }
+ string ReadNullTerminatedString(byte[] x, int startIndex)
+ {
+ var result = new StringBuilder();
+ int i = startIndex;
+
+ while (i < x.Length && x[i] != 0)
+ {
+ result.Append((char)x[i]);
+ i++;
+ }
+
+ // Skip the null terminator
+ i++;
+ if (i < x.Length)
+ {
+ byte[] remainder = new byte[x.Length - i];
+ Array.Copy(x, i, remainder, 0, remainder.Length);
+ x = remainder;
+ }
+
+ return result.ToString();
+ }
+
+ 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
new file mode 100644
index 00000000..acdf2ce9
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpCompress.Common.Arj.Headers
+{
+ public enum CompressionMethod
+ {
+ 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
new file mode 100644
index 00000000..d51045ad
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/DosDateTime.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace SharpCompress.Common.Arj.Headers
+{
+ public class DosDateTime
+ {
+ public DateTime DateTime { get; }
+
+ public DosDateTime(long dosValue)
+ {
+ // 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;
+ }
+ }
+
+ 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
new file mode 100644
index 00000000..39c529f0
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/FileType.cs
@@ -0,0 +1,13 @@
+namespace SharpCompress.Common.Arj.Headers
+{
+ public enum FileType : byte
+ {
+ 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
new file mode 100644
index 00000000..a74e7b0f
--- /dev/null
+++ b/src/SharpCompress/Common/Arj/Headers/HostOS.cs
@@ -0,0 +1,19 @@
+namespace SharpCompress.Common.Arj.Headers
+{
+ 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,
+ }
+}
diff --git a/src/SharpCompress/Common/CompressionType.cs b/src/SharpCompress/Common/CompressionType.cs
index 76ada667..57f65b05 100644
--- a/src/SharpCompress/Common/CompressionType.cs
+++ b/src/SharpCompress/Common/CompressionType.cs
@@ -29,4 +29,5 @@ public enum CompressionType
Crushed,
Distilled,
ZStandard,
+ ArjLZ77,
}
diff --git a/src/SharpCompress/Compressors/Arj/DecodeFastest.cs b/src/SharpCompress/Compressors/Arj/DecodeFastest.cs
new file mode 100644
index 00000000..f9f4ef44
--- /dev/null
+++ b/src/SharpCompress/Compressors/Arj/DecodeFastest.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace SharpCompress.Compressors.Arj
+{
+ [CLSCompliant(true)]
+ public class BitReader
+ {
+ private readonly byte[] data;
+ private int bytePos = 0;
+ private int bitPos = 0;
+
+ public BitReader(byte[] input)
+ {
+ data = input;
+ }
+
+ public int ReadBits(int count)
+ {
+ int result = 0;
+ for (int i = 0; i < count; i++)
+ {
+ if (bytePos >= data.Length)
+ {
+ throw new EndOfStreamException();
+ }
+
+ int bit = (data[bytePos] >> (7 - bitPos)) & 1;
+ result = (result << 1) | bit;
+
+ bitPos++;
+ if (bitPos == 8)
+ {
+ bitPos = 0;
+ bytePos++;
+ }
+ }
+ return result;
+ }
+ }
+
+ [CLSCompliant(true)]
+ public static class LHDecoder
+ {
+ private const int THRESHOLD = 3;
+
+ private static int DecodeVal(BitReader r, int from, int to)
+ {
+ int add = 0;
+ int bit = from;
+
+ while (bit < to && r.ReadBits(1) == 1)
+ {
+ add |= 1 << bit;
+ bit++;
+ }
+
+ int res = bit > 0 ? r.ReadBits(bit) : 0;
+ return res + add;
+ }
+
+ public static byte[] DecodeFastest(byte[] data, int originalSize)
+ {
+ var res = new List(originalSize);
+ var r = new BitReader(data);
+
+ while (res.Count < originalSize)
+ {
+ int len = DecodeVal(r, 0, 7);
+ if (len == 0)
+ {
+ byte nextChar = (byte)r.ReadBits(8);
+ res.Add(nextChar);
+ }
+ else
+ {
+ int repCount = len + THRESHOLD - 1;
+ int backPtr = DecodeVal(r, 9, 13);
+
+ if (backPtr >= res.Count)
+ {
+ throw new InvalidDataException("invalid back_ptr");
+ }
+
+ int i = res.Count - 1 - backPtr;
+ for (int j = 0; j < repCount; j++)
+ {
+ res.Add(res[i]);
+ i++;
+ }
+ }
+ }
+
+ return res.ToArray();
+ }
+ }
+}
diff --git a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs
index a7a8583d..199f277b 100644
--- a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs
@@ -2,6 +2,8 @@ using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Compressors.LZMA.Utilites;
using SharpCompress.IO;
@@ -283,5 +285,70 @@ internal sealed class AesDecoderStream : DecoderStream2, IStreamStack
return count;
}
+ public override async Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (count == 0 || mWritten == mLimit)
+ {
+ return 0;
+ }
+
+ if (mUnderflow > 0)
+ {
+ return HandleUnderflow(buffer, offset, count);
+ }
+
+ // Need at least 16 bytes to proceed.
+ if (mEnding - mOffset < 16)
+ {
+ Buffer.BlockCopy(mBuffer, mOffset, mBuffer, 0, mEnding - mOffset);
+ mEnding -= mOffset;
+ mOffset = 0;
+
+ do
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var read = await mStream
+ .ReadAsync(mBuffer, mEnding, mBuffer.Length - mEnding, cancellationToken)
+ .ConfigureAwait(false);
+ if (read == 0)
+ {
+ // We are not done decoding and have less than 16 bytes.
+ throw new EndOfStreamException();
+ }
+
+ mEnding += read;
+ } while (mEnding - mOffset < 16);
+ }
+
+ // We shouldn't return more data than we are limited to.
+ if (count > mLimit - mWritten)
+ {
+ count = (int)(mLimit - mWritten);
+ }
+
+ // We cannot transform less than 16 bytes into the target buffer,
+ // but we also cannot return zero, so we need to handle this.
+ if (count < 16)
+ {
+ return HandleUnderflow(buffer, offset, count);
+ }
+
+ if (count > mEnding - mOffset)
+ {
+ count = mEnding - mOffset;
+ }
+
+ // Otherwise we transform directly into the target buffer.
+ var processed = mDecoder.TransformBlock(mBuffer, mOffset, count & ~15, buffer, offset);
+ mOffset += processed;
+ mWritten += processed;
+ return processed;
+ }
+
#endregion
}
diff --git a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs
index de2d284f..05fa8b9a 100644
--- a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA;
@@ -191,6 +193,18 @@ internal class Bcj2DecoderStream : DecoderStream2, IStreamStack
return count;
}
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ // Bcj2DecoderStream uses complex state machine with multiple streams
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+
public override int ReadByte()
{
if (_mFinished)
diff --git a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs
index 6b9c525f..c65cc683 100644
--- a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs
+++ b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs
@@ -3,6 +3,8 @@
using System;
using System.Buffers;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace SharpCompress.Compressors.LZMA.LZ;
@@ -85,6 +87,12 @@ internal class OutWindow : IDisposable
_stream = null;
}
+ public async Task ReleaseStreamAsync(CancellationToken cancellationToken = default)
+ {
+ await FlushAsync(cancellationToken).ConfigureAwait(false);
+ _stream = null;
+ }
+
private void Flush()
{
if (_stream is null)
@@ -104,6 +112,27 @@ internal class OutWindow : IDisposable
_streamPos = _pos;
}
+ private async Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ if (_stream is null)
+ {
+ return;
+ }
+ var size = _pos - _streamPos;
+ if (size == 0)
+ {
+ return;
+ }
+ await _stream
+ .WriteAsync(_buffer, _streamPos, size, cancellationToken)
+ .ConfigureAwait(false);
+ if (_pos >= _windowSize)
+ {
+ _pos = 0;
+ }
+ _streamPos = _pos;
+ }
+
public void CopyPending()
{
if (_pendingLen < 1)
@@ -124,6 +153,26 @@ internal class OutWindow : IDisposable
_pendingLen = rem;
}
+ public async Task CopyPendingAsync(CancellationToken cancellationToken = default)
+ {
+ if (_pendingLen < 1)
+ {
+ return;
+ }
+ var rem = _pendingLen;
+ var pos = (_pendingDist < _pos ? _pos : _pos + _windowSize) - _pendingDist - 1;
+ while (rem > 0 && HasSpace)
+ {
+ if (pos >= _windowSize)
+ {
+ pos = 0;
+ }
+ await PutByteAsync(_buffer[pos++], cancellationToken).ConfigureAwait(false);
+ rem--;
+ }
+ _pendingLen = rem;
+ }
+
public void CopyBlock(int distance, int len)
{
var rem = len;
@@ -157,6 +206,43 @@ internal class OutWindow : IDisposable
_pendingDist = distance;
}
+ public async Task CopyBlockAsync(
+ int distance,
+ int len,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var rem = len;
+ var pos = (distance < _pos ? _pos : _pos + _windowSize) - distance - 1;
+ var targetSize = HasSpace ? (int)Math.Min(rem, _limit - _total) : 0;
+ var sizeUntilWindowEnd = Math.Min(_windowSize - _pos, _windowSize - pos);
+ var sizeUntilOverlap = Math.Abs(pos - _pos);
+ var fastSize = Math.Min(Math.Min(sizeUntilWindowEnd, sizeUntilOverlap), targetSize);
+ if (fastSize >= 2)
+ {
+ _buffer.AsSpan(pos, fastSize).CopyTo(_buffer.AsSpan(_pos, fastSize));
+ _pos += fastSize;
+ pos += fastSize;
+ _total += fastSize;
+ if (_pos >= _windowSize)
+ {
+ await FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ rem -= fastSize;
+ }
+ while (rem > 0 && HasSpace)
+ {
+ if (pos >= _windowSize)
+ {
+ pos = 0;
+ }
+ await PutByteAsync(_buffer[pos++], cancellationToken).ConfigureAwait(false);
+ rem--;
+ }
+ _pendingLen = rem;
+ _pendingDist = distance;
+ }
+
public void PutByte(byte b)
{
_buffer[_pos++] = b;
@@ -167,6 +253,16 @@ internal class OutWindow : IDisposable
}
}
+ public async Task PutByteAsync(byte b, CancellationToken cancellationToken = default)
+ {
+ _buffer[_pos++] = b;
+ _total++;
+ if (_pos >= _windowSize)
+ {
+ await FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
public byte GetByte(int distance)
{
var pos = _pos - distance - 1;
@@ -207,6 +303,44 @@ internal class OutWindow : IDisposable
return len - size;
}
+ public async Task CopyStreamAsync(
+ Stream stream,
+ int len,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var size = len;
+ while (size > 0 && _pos < _windowSize && _total < _limit)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var curSize = _windowSize - _pos;
+ if (curSize > _limit - _total)
+ {
+ curSize = (int)(_limit - _total);
+ }
+ if (curSize > size)
+ {
+ curSize = size;
+ }
+ var numReadBytes = await stream
+ .ReadAsync(_buffer, _pos, curSize, cancellationToken)
+ .ConfigureAwait(false);
+ if (numReadBytes == 0)
+ {
+ throw new DataErrorException();
+ }
+ size -= numReadBytes;
+ _pos += numReadBytes;
+ _total += numReadBytes;
+ if (_pos >= _windowSize)
+ {
+ await FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+ return len - size;
+ }
+
public void SetLimit(long size) => _limit = _total + size;
public bool HasSpace => _pos < _windowSize && _total < _limit;
diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.cs
index 26d34d3a..e7059003 100644
--- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs
@@ -1,6 +1,8 @@
using System;
using System.Buffers.Binary;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Crypto;
using SharpCompress.IO;
@@ -157,6 +159,11 @@ public sealed class LZipStream : Stream, IStreamStack
#if !NETFRAMEWORK && !NETSTANDARD2_0
+ public override ValueTask ReadAsync(
+ Memory buffer,
+ CancellationToken cancellationToken = default
+ ) => _stream.ReadAsync(buffer, cancellationToken);
+
public override int Read(Span buffer) => _stream.Read(buffer);
public override void Write(ReadOnlySpan buffer)
@@ -179,6 +186,25 @@ public sealed class LZipStream : Stream, IStreamStack
++_writeCount;
}
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken = default
+ ) => _stream.ReadAsync(buffer, offset, count, cancellationToken);
+
+ public override async Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ await _stream.WriteAsync(buffer, offset, count, cancellationToken);
+ _writeCount += count;
+ }
+
#endregion
///
diff --git a/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs b/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs
index 4baa9061..fed40533 100644
--- a/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs
+++ b/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs
@@ -1,6 +1,7 @@
#nullable disable
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using SharpCompress.Compressors.LZMA.LZ;
using SharpCompress.Compressors.LZMA.RangeCoder;
@@ -199,6 +200,9 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
}
}
+#if !NETFRAMEWORK && !NETSTANDARD2_0
+ [MemberNotNull(nameof(_outWindow))]
+#endif
private void CreateDictionary()
{
if (_dictionarySize < 0)
@@ -309,6 +313,42 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
_outWindow = null;
}
+ public async System.Threading.Tasks.Task CodeAsync(
+ Stream inStream,
+ Stream outStream,
+ long inSize,
+ long outSize,
+ ICodeProgress progress,
+ System.Threading.CancellationToken cancellationToken = default
+ )
+ {
+ if (_outWindow is null)
+ {
+ CreateDictionary();
+ }
+ _outWindow.Init(outStream);
+ if (outSize > 0)
+ {
+ _outWindow.SetLimit(outSize);
+ }
+ else
+ {
+ _outWindow.SetLimit(long.MaxValue - _outWindow.Total);
+ }
+
+ var rangeDecoder = new RangeCoder.Decoder();
+ rangeDecoder.Init(inStream);
+
+ await CodeAsync(_dictionarySize, _outWindow, rangeDecoder, cancellationToken)
+ .ConfigureAwait(false);
+
+ await _outWindow.ReleaseStreamAsync(cancellationToken).ConfigureAwait(false);
+ rangeDecoder.ReleaseStream();
+
+ _outWindow.Dispose();
+ _outWindow = null;
+ }
+
internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder)
{
var dictionarySizeCheck = Math.Max(dictionarySize, 1);
@@ -435,6 +475,143 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
return false;
}
+ internal async System.Threading.Tasks.Task CodeAsync(
+ int dictionarySize,
+ OutWindow outWindow,
+ RangeCoder.Decoder rangeDecoder,
+ System.Threading.CancellationToken cancellationToken = default
+ )
+ {
+ var dictionarySizeCheck = Math.Max(dictionarySize, 1);
+
+ await outWindow.CopyPendingAsync(cancellationToken).ConfigureAwait(false);
+
+ while (outWindow.HasSpace)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var posState = (uint)outWindow.Total & _posStateMask;
+ if (
+ _isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState]
+ .Decode(rangeDecoder) == 0
+ )
+ {
+ byte b;
+ var prevByte = outWindow.GetByte(0);
+ if (!_state.IsCharState())
+ {
+ b = _literalDecoder.DecodeWithMatchByte(
+ rangeDecoder,
+ (uint)outWindow.Total,
+ prevByte,
+ outWindow.GetByte((int)_rep0)
+ );
+ }
+ else
+ {
+ b = _literalDecoder.DecodeNormal(rangeDecoder, (uint)outWindow.Total, prevByte);
+ }
+ await outWindow.PutByteAsync(b, cancellationToken).ConfigureAwait(false);
+ _state.UpdateChar();
+ }
+ else
+ {
+ uint len;
+ if (_isRepDecoders[_state._index].Decode(rangeDecoder) == 1)
+ {
+ if (_isRepG0Decoders[_state._index].Decode(rangeDecoder) == 0)
+ {
+ if (
+ _isRep0LongDecoders[
+ (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState
+ ]
+ .Decode(rangeDecoder) == 0
+ )
+ {
+ _state.UpdateShortRep();
+ await outWindow
+ .PutByteAsync(outWindow.GetByte((int)_rep0), cancellationToken)
+ .ConfigureAwait(false);
+ continue;
+ }
+ }
+ else
+ {
+ uint distance;
+ if (_isRepG1Decoders[_state._index].Decode(rangeDecoder) == 0)
+ {
+ distance = _rep1;
+ }
+ else
+ {
+ if (_isRepG2Decoders[_state._index].Decode(rangeDecoder) == 0)
+ {
+ distance = _rep2;
+ }
+ else
+ {
+ distance = _rep3;
+ _rep3 = _rep2;
+ }
+ _rep2 = _rep1;
+ }
+ _rep1 = _rep0;
+ _rep0 = distance;
+ }
+ len = _repLenDecoder.Decode(rangeDecoder, posState) + Base.K_MATCH_MIN_LEN;
+ _state.UpdateRep();
+ }
+ else
+ {
+ _rep3 = _rep2;
+ _rep2 = _rep1;
+ _rep1 = _rep0;
+ len = Base.K_MATCH_MIN_LEN + _lenDecoder.Decode(rangeDecoder, posState);
+ _state.UpdateMatch();
+ var posSlot = _posSlotDecoder[Base.GetLenToPosState(len)].Decode(rangeDecoder);
+ if (posSlot >= Base.K_START_POS_MODEL_INDEX)
+ {
+ var numDirectBits = (int)((posSlot >> 1) - 1);
+ _rep0 = ((2 | (posSlot & 1)) << numDirectBits);
+ if (posSlot < Base.K_END_POS_MODEL_INDEX)
+ {
+ _rep0 += BitTreeDecoder.ReverseDecode(
+ _posDecoders,
+ _rep0 - posSlot - 1,
+ rangeDecoder,
+ numDirectBits
+ );
+ }
+ else
+ {
+ _rep0 += (
+ rangeDecoder.DecodeDirectBits(numDirectBits - Base.K_NUM_ALIGN_BITS)
+ << Base.K_NUM_ALIGN_BITS
+ );
+ _rep0 += _posAlignDecoder.ReverseDecode(rangeDecoder);
+ }
+ }
+ else
+ {
+ _rep0 = posSlot;
+ }
+ }
+ if (_rep0 >= outWindow.Total || _rep0 >= dictionarySizeCheck)
+ {
+ if (_rep0 == 0xFFFFFFFF)
+ {
+ return true;
+ }
+ throw new DataErrorException();
+ }
+ await outWindow
+ .CopyBlockAsync((int)_rep0, (int)len, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
+ return false;
+ }
+
public void SetDecoderProperties(byte[] properties)
{
if (properties.Length < 1)
@@ -470,29 +647,4 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
}
_outWindow.Train(stream);
}
-
- /*
- public override bool CanRead { get { return true; }}
- public override bool CanWrite { get { return true; }}
- public override bool CanSeek { get { return true; }}
- public override long Length { get { return 0; }}
- public override long Position
- {
- get { return 0; }
- set { }
- }
- public override void Flush() { }
- public override int Read(byte[] buffer, int offset, int count)
- {
- return 0;
- }
- public override void Write(byte[] buffer, int offset, int count)
- {
- }
- public override long Seek(long offset, System.IO.SeekOrigin origin)
- {
- return 0;
- }
- public override void SetLength(long value) {}
- */
}
diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs
index 4d80704e..eaef5fd3 100644
--- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs
@@ -3,6 +3,8 @@
using System;
using System.Buffers.Binary;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Compressors.LZMA.LZ;
using SharpCompress.IO;
@@ -423,6 +425,82 @@ public class LzmaStream : Stream, IStreamStack
}
}
+ private async Task DecodeChunkHeaderAsync(CancellationToken cancellationToken = default)
+ {
+ var controlBuffer = new byte[1];
+ await _inputStream.ReadAsync(controlBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
+ var control = controlBuffer[0];
+ _inputPosition++;
+
+ if (control == 0x00)
+ {
+ _endReached = true;
+ return;
+ }
+
+ if (control >= 0xE0 || control == 0x01)
+ {
+ _needProps = true;
+ _needDictReset = false;
+ _outWindow.Reset();
+ }
+ else if (_needDictReset)
+ {
+ throw new DataErrorException();
+ }
+
+ if (control >= 0x80)
+ {
+ _uncompressedChunk = false;
+
+ _availableBytes = (control & 0x1F) << 16;
+ var buffer = new byte[2];
+ await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
+ _availableBytes += (buffer[0] << 8) + buffer[1] + 1;
+ _inputPosition += 2;
+
+ await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
+ _rangeDecoderLimit = (buffer[0] << 8) + buffer[1] + 1;
+ _inputPosition += 2;
+
+ if (control >= 0xC0)
+ {
+ _needProps = false;
+ await _inputStream
+ .ReadAsync(controlBuffer, 0, 1, cancellationToken)
+ .ConfigureAwait(false);
+ Properties[0] = controlBuffer[0];
+ _inputPosition++;
+
+ _decoder = new Decoder();
+ _decoder.SetDecoderProperties(Properties);
+ }
+ else if (_needProps)
+ {
+ throw new DataErrorException();
+ }
+ else if (control >= 0xA0)
+ {
+ _decoder = new Decoder();
+ _decoder.SetDecoderProperties(Properties);
+ }
+
+ _rangeDecoder.Init(_inputStream);
+ }
+ else if (control > 0x02)
+ {
+ throw new DataErrorException();
+ }
+ else
+ {
+ _uncompressedChunk = true;
+ var buffer = new byte[2];
+ await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false);
+ _availableBytes = (buffer[0] << 8) + buffer[1] + 1;
+ _inputPosition += 2;
+ }
+ }
+
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
@@ -435,5 +513,128 @@ public class LzmaStream : Stream, IStreamStack
}
}
+ public override async Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ )
+ {
+ if (_endReached)
+ {
+ return 0;
+ }
+
+ var total = 0;
+ while (total < count)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (_availableBytes == 0)
+ {
+ if (_isLzma2)
+ {
+ await DecodeChunkHeaderAsync(cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ _endReached = true;
+ }
+ if (_endReached)
+ {
+ break;
+ }
+ }
+
+ var toProcess = count - total;
+ if (toProcess > _availableBytes)
+ {
+ toProcess = (int)_availableBytes;
+ }
+
+ _outWindow.SetLimit(toProcess);
+ if (_uncompressedChunk)
+ {
+ _inputPosition += await _outWindow
+ .CopyStreamAsync(_inputStream, toProcess, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ else if (
+ await _decoder
+ .CodeAsync(_dictionarySize, _outWindow, _rangeDecoder, cancellationToken)
+ .ConfigureAwait(false)
+ && _outputSize < 0
+ )
+ {
+ _availableBytes = _outWindow.AvailableBytes;
+ }
+
+ var read = _outWindow.Read(buffer, offset, toProcess);
+ total += read;
+ offset += read;
+ _position += read;
+ _availableBytes -= read;
+
+ if (_availableBytes == 0 && !_uncompressedChunk)
+ {
+ if (
+ !_rangeDecoder.IsFinished
+ || (_rangeDecoderLimit >= 0 && _rangeDecoder._total != _rangeDecoderLimit)
+ )
+ {
+ _outWindow.SetLimit(toProcess + 1);
+ if (
+ !await _decoder
+ .CodeAsync(
+ _dictionarySize,
+ _outWindow,
+ _rangeDecoder,
+ cancellationToken
+ )
+ .ConfigureAwait(false)
+ )
+ {
+ _rangeDecoder.ReleaseStream();
+ throw new DataErrorException();
+ }
+ }
+
+ _rangeDecoder.ReleaseStream();
+
+ _inputPosition += _rangeDecoder._total;
+ if (_outWindow.HasPending)
+ {
+ throw new DataErrorException();
+ }
+ }
+ }
+
+ if (_endReached)
+ {
+ if (_inputSize >= 0 && _inputPosition != _inputSize)
+ {
+ throw new DataErrorException();
+ }
+ if (_outputSize >= 0 && _position != _outputSize)
+ {
+ throw new DataErrorException();
+ }
+ }
+
+ return total;
+ }
+
+ public override Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ Write(buffer, offset, count);
+ return Task.CompletedTask;
+ }
+
public byte[] Properties { get; } = new byte[5];
}
diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs
index 6b379792..015ff744 100644
--- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA.Utilites;
@@ -101,4 +103,22 @@ internal class CrcBuilderStream : Stream, IStreamStack
_mCrc = Crc.Update(_mCrc, buffer, offset, count);
_mTarget.Write(buffer, offset, count);
}
+
+ public override async Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (_mFinished)
+ {
+ throw new InvalidOperationException("CRC calculation has been finished.");
+ }
+
+ Processed += count;
+ _mCrc = Crc.Update(_mCrc, buffer, offset, count);
+ await _mTarget.WriteAsync(buffer, offset, count, cancellationToken);
+ }
}
diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs
index c10efda1..c90ebfda 100644
--- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs
@@ -1,6 +1,9 @@
using System;
+using System.Buffers;
using System.Diagnostics;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace SharpCompress.Compressors.LZMA.Utilites;
@@ -11,7 +14,7 @@ public class CrcCheckStream : Stream
private uint _mCurrentCrc;
private bool _mClosed;
- private readonly long[] _mBytes = new long[256];
+ private readonly long[] _mBytes = ArrayPool.Shared.Rent(256);
private long _mLength;
public CrcCheckStream(uint crc)
@@ -65,6 +68,7 @@ public class CrcCheckStream : Stream
finally
{
base.Dispose(disposing);
+ ArrayPool.Shared.Return(_mBytes);
}
}
@@ -101,4 +105,16 @@ public class CrcCheckStream : Stream
_mCurrentCrc = Crc.Update(_mCurrentCrc, buffer, offset, count);
}
+
+ public override Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ Write(buffer, offset, count);
+ return Task.CompletedTask;
+ }
}
diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs
new file mode 100644
index 00000000..7499946b
--- /dev/null
+++ b/src/SharpCompress/Factories/ArjFactory.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Common;
+using SharpCompress.Common.Arj.Headers;
+using SharpCompress.Readers;
+using SharpCompress.Readers.Arj;
+
+namespace SharpCompress.Factories
+{
+ public class ArjFactory : Factory, IReaderFactory
+ {
+ public override string Name => "Arj";
+
+ public override ArchiveType? KnownArchiveType => ArchiveType.Arj;
+
+ public override IEnumerable GetSupportedExtensions()
+ {
+ yield return "arj";
+ }
+
+ public override bool IsArchive(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ )
+ {
+ var arjHeader = new ArjMainHeader(new ArchiveEncoding());
+ if (arjHeader.Read(stream) == null)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public IReader OpenReader(Stream stream, ReaderOptions? options) =>
+ ArjReader.Open(stream, options);
+ }
+}
diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs
index 2462f614..dba20177 100644
--- a/src/SharpCompress/Factories/Factory.cs
+++ b/src/SharpCompress/Factories/Factory.cs
@@ -18,6 +18,7 @@ public abstract class Factory : IFactory
RegisterFactory(new GZipFactory());
RegisterFactory(new TarFactory());
RegisterFactory(new ArcFactory());
+ RegisterFactory(new ArjFactory());
}
private static readonly HashSet _factories = new();
diff --git a/src/SharpCompress/Readers/Arj/ArjReader.cs b/src/SharpCompress/Readers/Arj/ArjReader.cs
new file mode 100644
index 00000000..d5441e27
--- /dev/null
+++ b/src/SharpCompress/Readers/Arj/ArjReader.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Common;
+using SharpCompress.Common.Arj;
+using SharpCompress.Common.Arj.Headers;
+using SharpCompress.Common.Zip;
+using SharpCompress.Common.Zip.Headers;
+
+namespace SharpCompress.Readers.Arj
+{
+ public class ArjReader : AbstractReader
+ {
+ private ArjReader(Stream stream, ReaderOptions options)
+ : base(options, ArchiveType.Arj) => Volume = new ArjVolume(stream, options, 0);
+
+ public override ArjVolume Volume { get; }
+
+ ///
+ /// Opens an ArjReader for Non-seeking usage with a single volume
+ ///
+ ///
+ ///
+ ///
+ public static ArjReader Open(Stream stream, ReaderOptions? options = null)
+ {
+ stream.NotNull(nameof(stream));
+ return new ArjReader(stream, options ?? new ReaderOptions());
+ }
+
+ protected override IEnumerable GetEntries(Stream stream)
+ {
+ ArchiveEncoding encoding = new ArchiveEncoding();
+ var mainHeaderReader = new ArjMainHeader(encoding);
+ var localHeaderReader = new ArjLocalHeader(encoding);
+ var mainHeader = mainHeaderReader.Read(stream);
+
+ while (true)
+ {
+ var localHeader = localHeaderReader.Read(stream);
+ if (localHeader == null)
+ break;
+
+ yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream));
+ }
+ }
+ }
+}
diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs
index 97846380..80995e41 100644
--- a/src/SharpCompress/Readers/ReaderFactory.cs
+++ b/src/SharpCompress/Readers/ReaderFactory.cs
@@ -70,7 +70,7 @@ public static class ReaderFactory
}
throw new InvalidFormatException(
- "Cannot determine compressed stream type. Supported Reader Formats: Arc, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
+ "Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
);
}
}
diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj
index 7c8294c5..97a31080 100644
--- a/src/SharpCompress/SharpCompress.csproj
+++ b/src/SharpCompress/SharpCompress.csproj
@@ -38,6 +38,9 @@
+
+
+
diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json
index b85a38f7..3d0d443d 100644
--- a/src/SharpCompress/packages.lock.json
+++ b/src/SharpCompress/packages.lock.json
@@ -335,9 +335,9 @@
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
- "requested": "[8.0.20, )",
- "resolved": "8.0.20",
- "contentHash": "Rhcto2AjGvTO62+/VTmBpumBOmqIGp7nYEbTbmEXkCq4yPGxV8whju3/HsIA/bKyo2+DggaYk5+/8sxb1AbPTw=="
+ "requested": "[8.0.21, )",
+ "resolved": "8.0.21",
+ "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
diff --git a/tests/SharpCompress.Test/Arj/ArjReaderTests.cs b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs
new file mode 100644
index 00000000..ee9cecca
--- /dev/null
+++ b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Common;
+using SharpCompress.Readers;
+using Xunit;
+
+namespace SharpCompress.Test.Arj
+{
+ public class ArjReaderTests : ReaderTests
+ {
+ public ArjReaderTests()
+ {
+ UseExtensionInsteadOfNameToVerify = true;
+ UseCaseInsensitiveToVerify = true;
+ }
+
+ [Fact]
+ public void Arj_Uncompressed_Read() => Read("Arj.store.arj", CompressionType.None);
+
+ [Fact]
+ public void Arj_Method4_Read() => Read("Arj.method4.arj");
+ }
+}
diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs
index b919331d..c5539fc8 100644
--- a/tests/SharpCompress.Test/ReaderTests.cs
+++ b/tests/SharpCompress.Test/ReaderTests.cs
@@ -13,29 +13,53 @@ namespace SharpCompress.Test;
public abstract class ReaderTests : TestBase
{
+ protected void Read(string testArchive, ReaderOptions? options = null)
+ {
+ ReadCore(testArchive, options, ReadImpl);
+ }
+
protected void Read(
string testArchive,
CompressionType expectedCompression,
ReaderOptions? options = null
)
{
- testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
+ ReadCore(testArchive, options, (path, opts) => ReadImpl(path, expectedCompression, opts));
+ }
- options ??= new ReaderOptions() { BufferSize = 0x20000 }; //test larger buffer size (need test rather than eyeballing debug logs :P)
+ private void ReadCore(
+ string testArchive,
+ ReaderOptions? options,
+ Action readImpl
+ )
+ {
+ testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
+ options ??= new ReaderOptions { BufferSize = 0x20000 };
options.LeaveStreamOpen = true;
- ReadImpl(testArchive, expectedCompression, options);
+ readImpl(testArchive, options);
options.LeaveStreamOpen = false;
- ReadImpl(testArchive, expectedCompression, options);
+ readImpl(testArchive, options);
+
VerifyFiles();
}
+ private void ReadImpl(string testArchive, ReaderOptions options)
+ {
+ ReadImplCore(testArchive, options, UseReader);
+ }
+
private void ReadImpl(
string testArchive,
CompressionType expectedCompression,
ReaderOptions options
)
+ {
+ ReadImplCore(testArchive, options, r => UseReader(r, expectedCompression));
+ }
+
+ private void ReadImplCore(string testArchive, ReaderOptions options, Action useReader)
{
using var file = File.OpenRead(testArchive);
using var protectedStream = SharpCompressStream.Create(
@@ -47,19 +71,17 @@ public abstract class ReaderTests : TestBase
using var testStream = new TestStream(protectedStream);
using (var reader = ReaderFactory.Open(testStream, options))
{
- UseReader(reader, expectedCompression);
+ useReader(reader);
protectedStream.ThrowOnDispose = false;
Assert.False(testStream.IsDisposed, $"{nameof(testStream)} prematurely closed");
}
- // Boolean XOR -- If the stream should be left open (true), then the stream should not be diposed (false)
- // and if the stream should be closed (false), then the stream should be disposed (true)
var message =
$"{nameof(options.LeaveStreamOpen)} is set to '{options.LeaveStreamOpen}', so {nameof(testStream.IsDisposed)} should be set to '{!testStream.IsDisposed}', but is set to {testStream.IsDisposed}";
Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message);
}
- public void UseReader(IReader reader, CompressionType expectedCompression)
+ protected void UseReader(IReader reader, CompressionType expectedCompression)
{
while (reader.MoveToNextEntry())
{
@@ -74,6 +96,20 @@ public abstract class ReaderTests : TestBase
}
}
+ private void UseReader(IReader reader)
+ {
+ while (reader.MoveToNextEntry())
+ {
+ if (!reader.Entry.IsDirectory)
+ {
+ reader.WriteEntryToDirectory(
+ SCRATCH_FILES_PATH,
+ new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
+ );
+ }
+ }
+ }
+
protected async Task ReadAsync(
string testArchive,
CompressionType expectedCompression,
diff --git a/tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs
new file mode 100644
index 00000000..9f19401d
--- /dev/null
+++ b/tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs
@@ -0,0 +1,606 @@
+using System;
+using System.Buffers;
+using System.IO;
+using System.Threading.Tasks;
+using SharpCompress.Compressors.LZMA;
+using Xunit;
+
+namespace SharpCompress.Test.Streams;
+
+public class LzmaStreamAsyncTests
+{
+ [Fact]
+ public async Task TestLzma2Decompress1ByteAsync()
+ {
+ var properties = new byte[] { 0x01 };
+ var compressedData = new byte[] { 0x01, 0x00, 0x00, 0x58, 0x00 };
+ var lzma2Stream = new MemoryStream(compressedData);
+
+ var decompressor = new LzmaStream(properties, lzma2Stream, 5, 1);
+ var buffer = new byte[1];
+ var bytesRead = await decompressor.ReadAsync(buffer, 0, 1).ConfigureAwait(false);
+ Assert.Equal(1, bytesRead);
+ Assert.Equal((byte)'X', buffer[0]);
+ }
+
+ private static byte[] LzmaData { get; } =
+ [
+ 0x5D,
+ 0x00,
+ 0x20,
+ 0x00,
+ 0x00,
+ 0x48,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x80,
+ 0x24,
+ 0x18,
+ 0x2F,
+ 0xEB,
+ 0x20,
+ 0x78,
+ 0xBA,
+ 0x78,
+ 0x70,
+ 0xDC,
+ 0x43,
+ 0x2C,
+ 0x32,
+ 0xC9,
+ 0xC3,
+ 0x97,
+ 0x4D,
+ 0x10,
+ 0x74,
+ 0xE2,
+ 0x20,
+ 0xBF,
+ 0x5A,
+ 0xB4,
+ 0xB3,
+ 0xC4,
+ 0x31,
+ 0x80,
+ 0x26,
+ 0x3E,
+ 0x6A,
+ 0xEA,
+ 0x51,
+ 0xFC,
+ 0xE4,
+ 0x8D,
+ 0x54,
+ 0x96,
+ 0x05,
+ 0xCC,
+ 0x78,
+ 0x59,
+ 0xAC,
+ 0xD4,
+ 0x21,
+ 0x65,
+ 0x8F,
+ 0xA9,
+ 0xC8,
+ 0x0D,
+ 0x9B,
+ 0xE2,
+ 0xC2,
+ 0xF9,
+ 0x7C,
+ 0x3C,
+ 0xDD,
+ 0x4D,
+ 0x38,
+ 0x04,
+ 0x0B,
+ 0xF8,
+ 0x0B,
+ 0x68,
+ 0xA5,
+ 0x93,
+ 0x6C,
+ 0x64,
+ 0xAC,
+ 0xCF,
+ 0x71,
+ 0x68,
+ 0xE8,
+ 0x69,
+ 0x25,
+ 0xC6,
+ 0x17,
+ 0x28,
+ 0xF1,
+ 0x7C,
+ 0xF1,
+ 0xDC,
+ 0x47,
+ 0x51,
+ 0x4D,
+ 0x1E,
+ 0x0E,
+ 0x0B,
+ 0x80,
+ 0x37,
+ 0x24,
+ 0x58,
+ 0x80,
+ 0xF7,
+ 0xB4,
+ 0xAC,
+ 0x54,
+ 0xF1,
+ 0x0F,
+ 0x7F,
+ 0x0F,
+ 0x0F,
+ 0xF5,
+ 0x9C,
+ 0xDE,
+ 0x54,
+ 0x4F,
+ 0xA3,
+ 0x7B,
+ 0x20,
+ 0xC5,
+ 0xA8,
+ 0x18,
+ 0x3B,
+ 0xED,
+ 0xDC,
+ 0x04,
+ 0xF6,
+ 0xFB,
+ 0x86,
+ 0xE0,
+ 0xAB,
+ 0xB6,
+ 0x87,
+ 0x99,
+ 0x92,
+ 0x43,
+ 0x7B,
+ 0x2C,
+ 0xCC,
+ 0x31,
+ 0x83,
+ 0x90,
+ 0xFF,
+ 0xF1,
+ 0x76,
+ 0x03,
+ 0x90,
+ ];
+
+ ///
+ /// The decoded data for .
+ ///
+ private static byte[] LzmaResultData { get; } =
+ [
+ 0x01,
+ 0x00,
+ 0xFD,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFA,
+ 0x61,
+ 0x18,
+ 0x5F,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0xB4,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3D,
+ 0x61,
+ 0xE5,
+ 0x5E,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x12,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0xB4,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xE2,
+ 0x61,
+ 0x18,
+ 0x5F,
+ 0x04,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x12,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x29,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0xFD,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x14,
+ 0x62,
+ 0x18,
+ 0x5F,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x09,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0xB4,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x61,
+ 0xE5,
+ 0x5E,
+ 0x05,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3B,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xCB,
+ 0x15,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0xB4,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x7F,
+ 0x61,
+ 0xE5,
+ 0x5E,
+ 0x06,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3B,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xCB,
+ 0x15,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0xB4,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3D,
+ 0x61,
+ 0xE5,
+ 0x5E,
+ 0x07,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x12,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0xB4,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFC,
+ 0x96,
+ 0x40,
+ 0x5C,
+ 0x08,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x60,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xF8,
+ 0x83,
+ 0x12,
+ 0x00,
+ 0xD4,
+ 0x99,
+ 0x00,
+ 0x00,
+ 0x43,
+ 0x95,
+ 0x00,
+ 0x00,
+ 0xEB,
+ 0x7A,
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x6F,
+ 0x00,
+ 0x00,
+ 0xD2,
+ 0x6F,
+ 0x00,
+ 0x00,
+ 0x67,
+ 0x74,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x69,
+ 0x00,
+ 0x00,
+ 0x76,
+ 0x79,
+ 0x00,
+ 0x00,
+ 0x98,
+ 0x66,
+ 0x00,
+ 0x00,
+ 0x23,
+ 0x25,
+ 0x00,
+ 0x00,
+ 0x01,
+ 0x00,
+ 0xFD,
+ 0x01,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x3B,
+ 0x2F,
+ 0xC0,
+ 0x5F,
+ 0x09,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x69,
+ 0x00,
+ 0x3D,
+ 0x00,
+ 0x0A,
+ 0x00,
+ 0x00,
+ 0x00,
+ ];
+
+ [Fact]
+ public async Task TestLzmaBufferAsync()
+ {
+ var input = new MemoryStream(LzmaData);
+ using var output = new MemoryStream();
+ var properties = new byte[5];
+ await input.ReadAsync(properties, 0, 5).ConfigureAwait(false);
+
+ var fileLengthBytes = new byte[8];
+ await input.ReadAsync(fileLengthBytes, 0, 8).ConfigureAwait(false);
+ var fileLength = BitConverter.ToInt64(fileLengthBytes, 0);
+
+ var coder = new Decoder();
+ coder.SetDecoderProperties(properties);
+ coder.Code(input, output, input.Length, fileLength, null);
+
+ Assert.Equal(output.ToArray(), LzmaResultData);
+ }
+
+ [Fact]
+ public async Task TestLzmaStreamEncodingWritesDataAsync()
+ {
+ using var inputStream = new MemoryStream(LzmaResultData);
+ using MemoryStream outputStream = new();
+ using var lzmaStream = new LzmaStream(LzmaEncoderProperties.Default, false, outputStream);
+ await inputStream.CopyToAsync(lzmaStream).ConfigureAwait(false);
+ lzmaStream.Close();
+ Assert.NotEqual(0, outputStream.Length);
+ }
+
+ [Fact]
+ public async Task TestLzmaEncodingAccuracyAsync()
+ {
+ var input = new MemoryStream(LzmaResultData);
+ var compressed = new MemoryStream();
+ var lzmaEncodingStream = new LzmaStream(LzmaEncoderProperties.Default, false, compressed);
+ await input.CopyToAsync(lzmaEncodingStream).ConfigureAwait(false);
+ lzmaEncodingStream.Close();
+ compressed.Position = 0;
+
+ var output = new MemoryStream();
+ await DecompressLzmaStreamAsync(
+ lzmaEncodingStream.Properties,
+ compressed,
+ compressed.Length,
+ output,
+ LzmaResultData.LongLength
+ )
+ .ConfigureAwait(false);
+
+ Assert.Equal(output.ToArray(), LzmaResultData);
+ }
+
+ private static async Task DecompressLzmaStreamAsync(
+ byte[] properties,
+ Stream compressedStream,
+ long compressedSize,
+ Stream decompressedStream,
+ long decompressedSize
+ )
+ {
+ var lzmaStream = new LzmaStream(
+ properties,
+ compressedStream,
+ compressedSize,
+ -1,
+ null,
+ false
+ );
+
+ var buffer = new byte[1024];
+ long totalRead = 0;
+ while (totalRead < decompressedSize)
+ {
+ var toRead = (int)Math.Min(buffer.Length, decompressedSize - totalRead);
+ var read = await lzmaStream.ReadAsync(buffer, 0, toRead).ConfigureAwait(false);
+ if (read > 0)
+ {
+ await decompressedStream.WriteAsync(buffer, 0, read).ConfigureAwait(false);
+ totalRead += read;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+}
diff --git a/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs b/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs
index fd909b79..5b00140b 100644
--- a/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs
+++ b/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs
@@ -581,7 +581,7 @@ public class LzmaStreamTests
false
);
- var buffer = ArrayPool.Shared.Rent(1024);
+ var buffer = new byte[1024];
long totalRead = 0;
while (totalRead < decompressedSize)
{
@@ -597,6 +597,5 @@ public class LzmaStreamTests
break;
}
}
- ArrayPool.Shared.Return(buffer);
}
}
diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs b/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs
new file mode 100644
index 00000000..3893f539
--- /dev/null
+++ b/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs
@@ -0,0 +1,96 @@
+using System.IO;
+using System.Threading.Tasks;
+using SharpCompress.IO;
+using Xunit;
+
+namespace SharpCompress.Test.Streams;
+
+public class RewindableStreamAsyncTest
+{
+ [Fact]
+ public async Task TestRewindAsync()
+ {
+ var ms = new MemoryStream();
+ var bw = new BinaryWriter(ms);
+ bw.Write(1);
+ bw.Write(2);
+ bw.Write(3);
+ bw.Write(4);
+ bw.Write(5);
+ bw.Write(6);
+ bw.Write(7);
+ bw.Flush();
+ ms.Position = 0;
+ var stream = new SharpCompressStream(ms, bufferSize: 0x10000);
+
+ Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
+
+ ((IStreamStack)stream).StackSeek(0);
+ long pos = stream.Position;
+ Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
+
+ ((IStreamStack)stream).StackSeek(pos);
+ Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
+ }
+
+ [Fact]
+ public async Task TestIncompleteRewindAsync()
+ {
+ var ms = new MemoryStream();
+ var bw = new BinaryWriter(ms);
+ bw.Write(1);
+ bw.Write(2);
+ bw.Write(3);
+ bw.Write(4);
+ bw.Write(5);
+ bw.Write(6);
+ bw.Write(7);
+ bw.Flush();
+ ms.Position = 0;
+ var stream = new SharpCompressStream(ms, bufferSize: 0x10000);
+
+ Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
+ ((IStreamStack)stream).StackSeek(0);
+
+ Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
+ long pos = stream.Position;
+
+ Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
+ ((IStreamStack)stream).StackSeek(pos);
+
+ Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
+ Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
+ }
+
+ private static async Task ReadInt32Async(Stream stream)
+ {
+ var buffer = new byte[4];
+ var bytesRead = await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
+ if (bytesRead != 4)
+ {
+ throw new EndOfStreamException();
+ }
+ return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
+ }
+}
diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs
index a33560a6..d806246d 100644
--- a/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs
+++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs
@@ -28,148 +28,111 @@ public class SharpCompressStreamAsyncTests
[Fact]
public async Task BufferReadAsyncTest()
{
- byte[] data = ArrayPool.Shared.Rent(0x100000);
- byte[] test = ArrayPool.Shared.Rent(0x1000);
- try
+ byte[] data = new byte[0x100000];
+ byte[] test = new byte[0x1000];
+ using (MemoryStream ms = new MemoryStream(data))
{
- using (MemoryStream ms = new MemoryStream(data))
+ CreateData(ms);
+
+ using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
- CreateData(ms);
+ scs.Seek(0x1000, SeekOrigin.Begin);
+ Assert.Equal(0x1000, scs.Position); // position in the SharpCompressionStream
+ Assert.Equal(0x1000, ms.Position); // initial seek + full buffer read
- using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
- {
- IStreamStack stack = (IStreamStack)scs;
+ await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000
+ Assert.Equal(0x2000, scs.Position); // stream has correct position
+ Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct
+ Assert.Equal(0x11000, ms.Position); // seek plus read bytes
- scs.Seek(0x1000, SeekOrigin.Begin);
- Assert.Equal(0x1000, scs.Position); // position in the SharpCompressionStream
- Assert.Equal(0x1000, ms.Position); // initial seek + full buffer read
-
- await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000
- Assert.Equal(0x2000, scs.Position); // stream has correct position
- Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct
- Assert.Equal(0x11000, ms.Position); // seek plus read bytes
-
- scs.Seek(0x500, SeekOrigin.Begin); // seek before the buffer start
- await scs.ReadAsync(test, 0, test.Length); // read bytes 0x500 to 0x1500
- Assert.Equal(0x1500, scs.Position); // stream has correct position
- Assert.True(data.Skip(0x500).Take(test.Length).SequenceEqual(test)); // is the data correct
- Assert.Equal(0x10500, ms.Position); // seek plus read bytes
- }
+ scs.Seek(0x500, SeekOrigin.Begin); // seek before the buffer start
+ await scs.ReadAsync(test, 0, test.Length); // read bytes 0x500 to 0x1500
+ Assert.Equal(0x1500, scs.Position); // stream has correct position
+ Assert.True(data.Skip(0x500).Take(test.Length).SequenceEqual(test)); // is the data correct
+ Assert.Equal(0x10500, ms.Position); // seek plus read bytes
}
}
- finally
- {
- ArrayPool.Shared.Return(data);
- ArrayPool.Shared.Return(test);
- }
}
[Fact]
public async Task BufferReadAndSeekAsyncTest()
{
- byte[] data = ArrayPool.Shared.Rent(0x100000);
- byte[] test = ArrayPool.Shared.Rent(0x1000);
- try
+ byte[] data = new byte[0x100000];
+ byte[] test = new byte[0x1000];
+ using (MemoryStream ms = new MemoryStream(data))
{
- using (MemoryStream ms = new MemoryStream(data))
+ CreateData(ms);
+
+ using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
- CreateData(ms);
+ IStreamStack stack = (IStreamStack)scs;
- using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
- {
- IStreamStack stack = (IStreamStack)scs;
+ await scs.ReadAsync(test, 0, test.Length); // read bytes 0 to 0x1000
+ Assert.True(data.Take(test.Length).SequenceEqual(test)); // is the data correct
+ Assert.Equal(0x1000, scs.Position); // stream has correct position
+ Assert.Equal(0x10000, ms.Position); // moved the base stream on by buffer size
- await scs.ReadAsync(test, 0, test.Length); // read bytes 0 to 0x1000
- Assert.True(data.Take(test.Length).SequenceEqual(test)); // is the data correct
- Assert.Equal(0x1000, scs.Position); // stream has correct position
- Assert.Equal(0x10000, ms.Position); // moved the base stream on by buffer size
+ await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000
+ Assert.Equal(0x2000, scs.Position); // stream has correct position
+ Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct
+ Assert.Equal(0x10000, ms.Position); // the base stream has not moved
- await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000
- Assert.Equal(0x2000, scs.Position); // stream has correct position
- Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct
- Assert.Equal(0x10000, ms.Position); // the base stream has not moved
+ // rewind the buffer
+ stack.Rewind(0x1000); // rewind buffer back by 0x1000 bytes
- // rewind the buffer
- stack.Rewind(0x1000); // rewind buffer back by 0x1000 bytes
-
- // repeat the previous test
- await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000
- Assert.Equal(0x2000, scs.Position); // stream has correct position
- Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct
- Assert.Equal(0x10000, ms.Position); // the base stream has not moved
- }
+ // repeat the previous test
+ await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000
+ Assert.Equal(0x2000, scs.Position); // stream has correct position
+ Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct
+ Assert.Equal(0x10000, ms.Position); // the base stream has not moved
}
}
- finally
- {
- ArrayPool.Shared.Return(data);
- ArrayPool.Shared.Return(test);
- }
}
[Fact]
public async Task MultipleAsyncReadsTest()
{
- byte[] data = ArrayPool.Shared.Rent(0x100000);
- byte[] test1 = ArrayPool.Shared.Rent(0x800);
- byte[] test2 = ArrayPool.Shared.Rent(0x800);
- try
+ byte[] data = new byte[0x100000];
+ byte[] test1 = new byte[0x800];
+ byte[] test2 = new byte[0x800];
+ using (MemoryStream ms = new MemoryStream(data))
{
- using (MemoryStream ms = new MemoryStream(data))
+ CreateData(ms);
+
+ using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
- CreateData(ms);
+ // Read first chunk
+ await scs.ReadAsync(test1, 0, test1.Length);
+ Assert.Equal(0x800, scs.Position);
+ Assert.True(data.Take(test1.Length).SequenceEqual(test1)); // first read is correct
- using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
- {
- // Read first chunk
- await scs.ReadAsync(test1, 0, test1.Length);
- Assert.Equal(0x800, scs.Position);
- Assert.True(data.Take(test1.Length).SequenceEqual(test1)); // first read is correct
-
- // Read second chunk
- await scs.ReadAsync(test2, 0, test2.Length);
- Assert.Equal(0x1000, scs.Position);
- Assert.True(data.Skip(test1.Length).Take(test2.Length).SequenceEqual(test2)); // second read is correct
- }
+ // Read second chunk
+ await scs.ReadAsync(test2, 0, test2.Length);
+ Assert.Equal(0x1000, scs.Position);
+ Assert.True(data.Skip(test1.Length).Take(test2.Length).SequenceEqual(test2)); // second read is correct
}
}
- finally
- {
- ArrayPool.Shared.Return(data);
- ArrayPool.Shared.Return(test1);
- ArrayPool.Shared.Return(test2);
- }
}
[Fact]
public async Task LargeBufferAsyncReadTest()
{
- byte[] data = ArrayPool.Shared.Rent(0x200000);
- byte[] test = ArrayPool.Shared.Rent(0x8000);
- try
+ byte[] data = new byte[0x200000];
+ byte[] test = new byte[0x8000];
+ using (MemoryStream ms = new MemoryStream(data))
{
- using (MemoryStream ms = new MemoryStream(data))
- {
- CreateData(ms);
+ CreateData(ms);
- using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
+ using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
+ {
+ for (int i = 0; i < 10; i++)
{
- for (int i = 0; i < 10; i++)
- {
- await scs.ReadAsync(test, 0, test.Length);
- long expectedPosition = (long)(i + 1) * test.Length;
- Assert.Equal(expectedPosition, scs.Position);
- Assert.True(
- data.Skip(i * test.Length).Take(test.Length).SequenceEqual(test)
- );
- }
+ await scs.ReadAsync(test, 0, test.Length);
+ long expectedPosition = (long)(i + 1) * test.Length;
+ Assert.Equal(expectedPosition, scs.Position);
+ Assert.True(data.Skip(i * test.Length).Take(test.Length).SequenceEqual(test));
}
}
}
- finally
- {
- ArrayPool.Shared.Return(data);
- ArrayPool.Shared.Return(test);
- }
}
}
diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs
index 5161076b..1c4a8b57 100644
--- a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs
+++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs
@@ -27,8 +27,8 @@ public class SharpCompressStreamTests
[Fact]
public void BufferReadTest()
{
- byte[] data = ArrayPool.Shared.Rent(0x100000);
- byte[] test = ArrayPool.Shared.Rent(0x1000);
+ byte[] data = new byte[0x100000];
+ byte[] test = new byte[0x1000];
using (MemoryStream ms = new MemoryStream(data))
{
createData(ms);
@@ -53,16 +53,13 @@ public class SharpCompressStreamTests
Assert.Equal(0x10500, ms.Position); //seek plus read bytes
}
}
-
- ArrayPool.Shared.Return(data);
- ArrayPool.Shared.Return(test);
}
[Fact]
public void BufferReadAndSeekTest()
{
- byte[] data = ArrayPool.Shared.Rent(0x100000);
- byte[] test = ArrayPool.Shared.Rent(0x1000);
+ byte[] data = new byte[0x100000];
+ byte[] test = new byte[0x1000];
using (MemoryStream ms = new MemoryStream(data))
{
createData(ms);
@@ -91,8 +88,5 @@ public class SharpCompressStreamTests
Assert.Equal(0x10000, ms.Position); //the base stream has not moved
}
}
-
- ArrayPool.Shared.Return(data);
- ArrayPool.Shared.Return(test);
}
}
diff --git a/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs
new file mode 100644
index 00000000..3512b8b7
--- /dev/null
+++ b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs
@@ -0,0 +1,112 @@
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using AwesomeAssertions;
+using SharpCompress.Compressors;
+using SharpCompress.Compressors.Deflate;
+using SharpCompress.IO;
+using Xunit;
+
+namespace SharpCompress.Test.Streams;
+
+public class ZLibBaseStreamAsyncTests
+{
+ [Fact]
+ public async Task TestChunkedZlibCompressesEverythingAsync()
+ {
+ var plainData = new byte[]
+ {
+ 0xf7,
+ 0x1b,
+ 0xda,
+ 0x0f,
+ 0xb6,
+ 0x2b,
+ 0x3d,
+ 0x91,
+ 0xd7,
+ 0xe1,
+ 0xb5,
+ 0x11,
+ 0x34,
+ 0x5a,
+ 0x51,
+ 0x3f,
+ 0x8b,
+ 0xce,
+ 0x49,
+ 0xd2,
+ };
+ var buf = new byte[plainData.Length * 2];
+
+ var plainStream1 = new MemoryStream(plainData);
+ var compressor1 = new DeflateStream(plainStream1, CompressionMode.Compress);
+ // This is enough to read the entire data
+ var realCompressedSize = await compressor1
+ .ReadAsync(buf, 0, plainData.Length * 2)
+ .ConfigureAwait(false);
+
+ var plainStream2 = new MemoryStream(plainData);
+ var compressor2 = new DeflateStream(plainStream2, CompressionMode.Compress);
+ var total = 0;
+ var r = -1; // Jumpstart
+ while (r != 0)
+ {
+ // Reading in chunks
+ r = await compressor2.ReadAsync(buf, 0, plainData.Length).ConfigureAwait(false);
+ total += r;
+ }
+
+ Assert.Equal(total, realCompressedSize);
+ }
+
+ [Fact]
+ public async Task Zlib_should_read_the_previously_written_message_async()
+ {
+ var message = new string('a', 131073); // 131073 causes the failure, but 131072 (-1) doesn't
+ var bytes = Encoding.ASCII.GetBytes(message);
+
+ using var inputStream = new MemoryStream(bytes);
+ using var compressedStream = new MemoryStream();
+ using var byteBufferStream = new BufferedStream(inputStream); // System.IO
+ await CompressAsync(byteBufferStream, compressedStream, compressionLevel: 1)
+ .ConfigureAwait(false);
+ compressedStream.Position = 0;
+
+ using var decompressedStream = new MemoryStream();
+ await DecompressAsync(compressedStream, decompressedStream).ConfigureAwait(false);
+
+ byteBufferStream.Position = 0;
+ var result = Encoding.ASCII.GetString(
+ await GetBytesAsync(byteBufferStream).ConfigureAwait(false)
+ );
+ result.Should().Be(message);
+ }
+
+ private async Task CompressAsync(Stream input, Stream output, int compressionLevel)
+ {
+ using var zlibStream = new ZlibStream(
+ SharpCompressStream.Create(output, leaveOpen: true),
+ CompressionMode.Compress,
+ (CompressionLevel)compressionLevel
+ );
+ zlibStream.FlushMode = FlushType.Sync;
+ await input.CopyToAsync(zlibStream).ConfigureAwait(false);
+ }
+
+ private async Task DecompressAsync(Stream input, Stream output)
+ {
+ using var zlibStream = new ZlibStream(
+ SharpCompressStream.Create(input, leaveOpen: true),
+ CompressionMode.Decompress
+ );
+ await zlibStream.CopyToAsync(output).ConfigureAwait(false);
+ }
+
+ private async Task GetBytesAsync(BufferedStream stream)
+ {
+ var bytes = new byte[stream.Length];
+ await stream.ReadAsync(bytes, 0, (int)stream.Length).ConfigureAwait(false);
+ return bytes;
+ }
+}
diff --git a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs
index 7d7ed430..0bc93d83 100644
--- a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs
+++ b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs
@@ -207,13 +207,10 @@ public class TarReaderAsyncTests : ReaderTests
Assert.Throws(() => reader.MoveToNextEntry());
}
-#if !NETFRAMEWORK
+#if LINUX
[Fact]
public async Task Tar_GZip_With_Symlink_Entries_Async()
{
- var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
- System.Runtime.InteropServices.OSPlatform.Windows
- );
using Stream stream = File.OpenRead(
Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz")
);
@@ -232,38 +229,32 @@ public class TarReaderAsyncTests : ReaderTests
Overwrite = true,
WriteSymbolicLink = (sourcePath, targetPath) =>
{
- if (!isWindows)
+ var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath);
+ if (File.Exists(sourcePath))
{
- var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath);
- if (File.Exists(sourcePath))
- {
- link.Delete(); // equivalent to ln -s -f
- }
- link.CreateSymbolicLinkTo(targetPath);
+ link.Delete(); // equivalent to ln -s -f
}
+ link.CreateSymbolicLinkTo(targetPath);
},
}
);
- if (!isWindows)
+ if (reader.Entry.LinkTarget != null)
{
- if (reader.Entry.LinkTarget != null)
+ var path = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key.NotNull());
+ var link = new Mono.Unix.UnixSymbolicLinkInfo(path);
+ if (link.HasContents)
{
- var path = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key.NotNull());
- var link = new Mono.Unix.UnixSymbolicLinkInfo(path);
- if (link.HasContents)
- {
- // need to convert the link to an absolute path for comparison
- var target = reader.Entry.LinkTarget;
- var realTarget = Path.GetFullPath(
- Path.Combine($"{Path.GetDirectoryName(path)}", target)
- );
+ // need to convert the link to an absolute path for comparison
+ var target = reader.Entry.LinkTarget;
+ var realTarget = Path.GetFullPath(
+ Path.Combine($"{Path.GetDirectoryName(path)}", target)
+ );
- Assert.Equal(realTarget, link.GetContents().ToString());
- }
- else
- {
- Assert.True(false, "Symlink has no target");
- }
+ Assert.Equal(realTarget, link.GetContents().ToString());
+ }
+ else
+ {
+ Assert.True(false, "Symlink has no target");
}
}
}
diff --git a/tests/TestArchives/Archives/Arj.method4.arj b/tests/TestArchives/Archives/Arj.method4.arj
new file mode 100644
index 00000000..5f3bbbf6
Binary files /dev/null and b/tests/TestArchives/Archives/Arj.method4.arj differ
diff --git a/tests/TestArchives/Archives/Arj.store.arj b/tests/TestArchives/Archives/Arj.store.arj
new file mode 100644
index 00000000..b6fa515c
Binary files /dev/null and b/tests/TestArchives/Archives/Arj.store.arj differ