Merge branch 'adam/async-lzma' into adam/async-xz
This commit is contained in:
commit
7dd0da5fd7
39 changed files with 2503 additions and 181 deletions
|
|
@ -14,6 +14,7 @@
|
|||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
|
||||
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.21" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ public enum ArchiveType
|
|||
SevenZip,
|
||||
GZip,
|
||||
Arc,
|
||||
Arj,
|
||||
}
|
||||
|
|
|
|||
58
src/SharpCompress/Common/Arj/ArjEntry.cs
Normal file
58
src/SharpCompress/Common/Arj/ArjEntry.cs
Normal file
|
|
@ -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<FilePart> Parts => _filePart.Empty();
|
||||
}
|
||||
}
|
||||
65
src/SharpCompress/Common/Arj/ArjFilePart.cs
Normal file
65
src/SharpCompress/Common/Arj/ArjFilePart.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
16
src/SharpCompress/Common/Arj/ArjVolume.cs
Normal file
16
src/SharpCompress/Common/Arj/ArjVolume.cs
Normal file
|
|
@ -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) { }
|
||||
}
|
||||
}
|
||||
142
src/SharpCompress/Common/Arj/Headers/ArjHeader.cs
Normal file
142
src/SharpCompress/Common/Arj/Headers/ArjHeader.cs
Normal file
|
|
@ -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<byte> magic = stackalloc byte[2];
|
||||
if (stream.Read(magic) != 2)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
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<byte>();
|
||||
}
|
||||
|
||||
var body = new byte[headerSize];
|
||||
var read = stream.Read(body, 0, headerSize);
|
||||
if (read < headerSize)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
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<byte[]> ReadExtendedHeaders(Stream reader)
|
||||
{
|
||||
List<byte[]> extendedHeader = new List<byte[]>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs
Normal file
161
src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs
Normal file
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs
Normal file
138
src/SharpCompress/Common/Arj/Headers/ArjMainHeader.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs
Normal file
20
src/SharpCompress/Common/Arj/Headers/CompressionMethod.cs
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
37
src/SharpCompress/Common/Arj/Headers/DosDateTime.cs
Normal file
37
src/SharpCompress/Common/Arj/Headers/DosDateTime.cs
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
13
src/SharpCompress/Common/Arj/Headers/FileType.cs
Normal file
13
src/SharpCompress/Common/Arj/Headers/FileType.cs
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
19
src/SharpCompress/Common/Arj/Headers/HostOS.cs
Normal file
19
src/SharpCompress/Common/Arj/Headers/HostOS.cs
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -29,4 +29,5 @@ public enum CompressionType
|
|||
Crushed,
|
||||
Distilled,
|
||||
ZStandard,
|
||||
ArjLZ77,
|
||||
}
|
||||
|
|
|
|||
99
src/SharpCompress/Compressors/Arj/DecodeFastest.cs
Normal file
99
src/SharpCompress/Compressors/Arj/DecodeFastest.cs
Normal file
|
|
@ -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<byte>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<int> 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<int> 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)
|
||||
|
|
|
|||
|
|
@ -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<int> 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;
|
||||
|
|
|
|||
|
|
@ -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<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) => _stream.ReadAsync(buffer, cancellationToken);
|
||||
|
||||
public override int Read(Span<byte> buffer) => _stream.Read(buffer);
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
|
|
@ -179,6 +186,25 @@ public sealed class LZipStream : Stream, IStreamStack
|
|||
++_writeCount;
|
||||
}
|
||||
|
||||
public override Task<int> 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
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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<bool> 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) {}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<int> 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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<long>.Shared.Rent(256);
|
||||
private long _mLength;
|
||||
|
||||
public CrcCheckStream(uint crc)
|
||||
|
|
@ -65,6 +68,7 @@ public class CrcCheckStream : Stream
|
|||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
ArrayPool<long>.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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
src/SharpCompress/Factories/ArjFactory.cs
Normal file
42
src/SharpCompress/Factories/ArjFactory.cs
Normal file
|
|
@ -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<string> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Factory> _factories = new();
|
||||
|
|
|
|||
51
src/SharpCompress/Readers/Arj/ArjReader.cs
Normal file
51
src/SharpCompress/Readers/Arj/ArjReader.cs
Normal file
|
|
@ -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<ArjEntry, ArjVolume>
|
||||
{
|
||||
private ArjReader(Stream stream, ReaderOptions options)
|
||||
: base(options, ArchiveType.Arj) => Volume = new ArjVolume(stream, options, 0);
|
||||
|
||||
public override ArjVolume Volume { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens an ArjReader for Non-seeking usage with a single volume
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public static ArjReader Open(Stream stream, ReaderOptions? options = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ArjReader(stream, options ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
protected override IEnumerable<ArjEntry> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@
|
|||
<PackageReference Include="ZstdSharp.Port" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'net481' ">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
27
tests/SharpCompress.Test/Arj/ArjReaderTests.cs
Normal file
27
tests/SharpCompress.Test/Arj/ArjReaderTests.cs
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string, ReaderOptions> 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<IReader> 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,
|
||||
|
|
|
|||
606
tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs
Normal file
606
tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs
Normal file
|
|
@ -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,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The decoded data for <see cref="LzmaData"/>.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -581,7 +581,7 @@ public class LzmaStreamTests
|
|||
false
|
||||
);
|
||||
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(1024);
|
||||
var buffer = new byte[1024];
|
||||
long totalRead = 0;
|
||||
while (totalRead < decompressedSize)
|
||||
{
|
||||
|
|
@ -597,6 +597,5 @@ public class LzmaStreamTests
|
|||
break;
|
||||
}
|
||||
}
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<int> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,148 +28,111 @@ public class SharpCompressStreamAsyncTests
|
|||
[Fact]
|
||||
public async Task BufferReadAsyncTest()
|
||||
{
|
||||
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
|
||||
byte[] test = ArrayPool<byte>.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<byte>.Shared.Return(data);
|
||||
ArrayPool<byte>.Shared.Return(test);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BufferReadAndSeekAsyncTest()
|
||||
{
|
||||
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
|
||||
byte[] test = ArrayPool<byte>.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<byte>.Shared.Return(data);
|
||||
ArrayPool<byte>.Shared.Return(test);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleAsyncReadsTest()
|
||||
{
|
||||
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
|
||||
byte[] test1 = ArrayPool<byte>.Shared.Rent(0x800);
|
||||
byte[] test2 = ArrayPool<byte>.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<byte>.Shared.Return(data);
|
||||
ArrayPool<byte>.Shared.Return(test1);
|
||||
ArrayPool<byte>.Shared.Return(test2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LargeBufferAsyncReadTest()
|
||||
{
|
||||
byte[] data = ArrayPool<byte>.Shared.Rent(0x200000);
|
||||
byte[] test = ArrayPool<byte>.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<byte>.Shared.Return(data);
|
||||
ArrayPool<byte>.Shared.Return(test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ public class SharpCompressStreamTests
|
|||
[Fact]
|
||||
public void BufferReadTest()
|
||||
{
|
||||
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
|
||||
byte[] test = ArrayPool<byte>.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<byte>.Shared.Return(data);
|
||||
ArrayPool<byte>.Shared.Return(test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferReadAndSeekTest()
|
||||
{
|
||||
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
|
||||
byte[] test = ArrayPool<byte>.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<byte>.Shared.Return(data);
|
||||
ArrayPool<byte>.Shared.Return(test);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
112
tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs
Normal file
112
tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs
Normal file
|
|
@ -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<byte[]> GetBytesAsync(BufferedStream stream)
|
||||
{
|
||||
var bytes = new byte[stream.Length];
|
||||
await stream.ReadAsync(bytes, 0, (int)stream.Length).ConfigureAwait(false);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
|
@ -207,13 +207,10 @@ public class TarReaderAsyncTests : ReaderTests
|
|||
Assert.Throws<IncompleteArchiveException>(() => 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
tests/TestArchives/Archives/Arj.method4.arj
Normal file
BIN
tests/TestArchives/Archives/Arj.method4.arj
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/Arj.store.arj
Normal file
BIN
tests/TestArchives/Archives/Arj.store.arj
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue