Compare commits

...

2 Commits

Author SHA1 Message Date
Adam Hathcock
dfc303ff6c start of create pattern 2026-01-19 07:14:08 +00:00
Adam Hathcock
dea635e39e Intermediate commit of rar async reading 2026-01-18 16:29:03 +00:00
11 changed files with 1319 additions and 12 deletions

View File

@@ -0,0 +1,49 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Compressors.Rar;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar;
internal class AsyncRarCrcBinaryReader : AsyncMarkingBinaryReader
{
private uint _currentCrc;
public AsyncRarCrcBinaryReader(Stream stream, CancellationToken ct = default)
: base(stream, ct) { }
public uint GetCrc32() => ~_currentCrc;
public void ResetCrc() => _currentCrc = 0xffffffff;
protected void UpdateCrc(byte b) => _currentCrc = RarCRC.CheckCrc(_currentCrc, b);
protected void UpdateCrc(byte[] bytes, int offset, int count) =>
_currentCrc = RarCRC.CheckCrc(_currentCrc, bytes, offset, count);
protected async ValueTask<byte[]> ReadBytesNoCrcAsync(int count, CancellationToken ct = default)
{
CurrentReadByteCount += count;
var buffer = new byte[count];
await BaseStream.ReadExactAsync(buffer, 0, count, ct).ConfigureAwait(false);
return buffer;
}
public override async ValueTask<byte> ReadByteAsync(CancellationToken ct = default)
{
var b = await base.ReadByteAsync(ct).ConfigureAwait(false);
_currentCrc = RarCRC.CheckCrc(_currentCrc, b);
return b;
}
public override async ValueTask<byte[]> ReadBytesAsync(
int count,
CancellationToken ct = default
)
{
var result = await base.ReadBytesAsync(count, ct).ConfigureAwait(false);
_currentCrc = RarCRC.CheckCrc(_currentCrc, result, 0, result.Length);
return result;
}
}

View File

@@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.Crypto;
namespace SharpCompress.Common.Rar;
internal sealed class AsyncRarCryptoBinaryReader : AsyncRarCrcBinaryReader
{
private readonly Queue<byte> _data = new();
private long _readCount;
private BlockTransformer? _rijndael;
public AsyncRarCryptoBinaryReader(
Stream stream,
ICryptKey cryptKey,
CancellationToken ct = default
)
: base(stream, ct)
{
var salt = base.ReadBytesNoCrcAsync(EncryptionConstV5.SIZE_SALT30, ct)
.GetAwaiter()
.GetResult();
_readCount += EncryptionConstV5.SIZE_SALT30;
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
}
public AsyncRarCryptoBinaryReader(Stream stream, ICryptKey cryptKey, byte[] salt)
: base(stream) => _rijndael = new BlockTransformer(cryptKey.Transformer(salt));
public override long CurrentReadByteCount
{
get => _readCount;
protected set
{
// ignore
}
}
public override void Mark() => _readCount = 0;
public override async ValueTask<byte> ReadByteAsync(CancellationToken ct = default)
{
var result = await ReadAndDecryptBytesAsync(1, ct).ConfigureAwait(false);
return result[0];
}
public override async ValueTask<byte[]> ReadBytesAsync(
int count,
CancellationToken ct = default
) => await ReadAndDecryptBytesAsync(count, ct).ConfigureAwait(false);
private async ValueTask<byte[]> ReadAndDecryptBytesAsync(
int count,
CancellationToken ct = default
)
{
var queueSize = _data.Count;
var sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (var i = 0; i < alignedSize / 16; i++)
{
var cipherText = await ReadBytesNoCrcAsync(16, ct).ConfigureAwait(false);
var readBytes = _rijndael!.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
{
_data.Enqueue(readByte);
}
}
}
var decryptedBytes = new byte[count];
for (var i = 0; i < count; i++)
{
var b = _data.Dequeue();
decryptedBytes[i] = b;
UpdateCrc(b);
}
_readCount += count;
return decryptedBytes;
}
public void ClearQueue() => _data.Clear();
public void SkipQueue()
{
var position = BaseStream.Position;
BaseStream.Position = position + _data.Count;
ClearQueue();
}
}

View File

@@ -1,5 +1,8 @@
#nullable disable
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers;
@@ -13,4 +16,14 @@ internal class ArchiveCryptHeader : RarHeader
protected override void ReadFinish(MarkingBinaryReader reader) =>
CryptInfo = new Rar5CryptoInfo(reader, false);
protected override async ValueTask ReadFinishAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
CryptInfo = await Rar5CryptoInfo
.CreateAsync(reader, false, cancellationToken)
.ConfigureAwait(false);
}
}

View File

@@ -1,3 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers;
@@ -33,6 +36,37 @@ internal sealed class ArchiveHeader : RarHeader
}
}
protected override async ValueTask ReadFinishAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
if (IsRar5)
{
Flags = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
if (HasFlag(ArchiveFlagsV5.HAS_VOLUME_NUMBER))
{
VolumeNumber = (int)await reader.ReadRarVIntUInt32Async().ConfigureAwait(false);
}
// later: we may have a locator record if we need it
//if (ExtraSize != 0) {
// ReadLocator(reader);
//}
}
else
{
Flags = HeaderFlags;
HighPosAv = await reader.ReadInt16Async(cancellationToken).ConfigureAwait(false);
PosAv = await reader.ReadInt32Async(cancellationToken).ConfigureAwait(false);
if (HasFlag(ArchiveFlagsV4.ENCRYPT_VER))
{
EncryptionVersion = await reader
.ReadByteAsync(cancellationToken)
.ConfigureAwait(false);
}
}
}
private ushort Flags { get; set; }
private bool HasFlag(ushort flag) => (Flags & flag) == flag;

View File

@@ -1,4 +1,6 @@
using SharpCompress.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers;
@@ -27,6 +29,29 @@ internal class EndArchiveHeader : RarHeader
}
}
protected override async ValueTask ReadFinishAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
if (IsRar5)
{
Flags = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
}
else
{
Flags = HeaderFlags;
if (HasFlag(EndArchiveFlagsV4.DATA_CRC))
{
ArchiveCrc = await reader.ReadInt32Async(cancellationToken).ConfigureAwait(false);
}
if (HasFlag(EndArchiveFlagsV4.VOLUME_NUMBER))
{
VolumeNumber = await reader.ReadInt16Async(cancellationToken).ConfigureAwait(false);
}
}
}
private ushort Flags { get; set; }
private bool HasFlag(ushort flag) => (Flags & flag) == flag;

View File

@@ -2,6 +2,8 @@ using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
#if !Rar2017_64bit
using size_t = System.UInt32;
@@ -32,6 +34,33 @@ internal class FileHeader : RarHeader
}
}
public static new async ValueTask<FileHeader> CreateAsync(
RarHeader header,
AsyncRarCrcBinaryReader reader,
HeaderType headerType,
CancellationToken cancellationToken = default
)
{
var fh = new FileHeader(header, reader, headerType);
await fh.ReadFinishAsync(reader, cancellationToken).ConfigureAwait(false);
return fh;
}
protected override async ValueTask ReadFinishAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
if (IsRar5)
{
await ReadFromReaderV5Async(reader, cancellationToken).ConfigureAwait(false);
}
else
{
await ReadFromReaderV4Async(reader, cancellationToken).ConfigureAwait(false);
}
}
private void ReadFromReaderV5(MarkingBinaryReader reader)
{
Flags = reader.ReadRarVIntUInt16();
@@ -205,6 +234,162 @@ internal class FileHeader : RarHeader
}
}
private async Task ReadFromReaderV5Async(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
Flags = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
var lvalue = checked((long)await reader.ReadRarVIntAsync().ConfigureAwait(false));
UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue;
FileAttributes = await reader.ReadRarVIntUInt32Async().ConfigureAwait(false);
if (HasFlag(FileFlagsV5.HAS_MOD_TIME))
{
var value = await reader.ReadUInt32Async(cancellationToken).ConfigureAwait(false);
FileLastModifiedTime = Utility.UnixTimeToDateTime(value);
}
if (HasFlag(FileFlagsV5.HAS_CRC32))
{
FileCrc = await reader.ReadBytesAsync(4, cancellationToken).ConfigureAwait(false);
}
var compressionInfo = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50);
IsSolid = (compressionInfo & 0x40) == 0x40;
CompressionMethod = (byte)((compressionInfo >> 7) & 0x7);
WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo >> 10) & 0xf);
HostOs = await reader.ReadRarVIntByteAsync().ConfigureAwait(false);
var nameSize = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
var b = await reader.ReadBytesAsync(nameSize, cancellationToken).ConfigureAwait(false);
FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length));
if (ExtraSize != RemainingHeaderBytes(reader))
{
throw new InvalidFormatException("rar5 header size / extra size inconsistency");
}
const ushort FHEXTRA_CRYPT = 0x01;
const ushort FHEXTRA_HASH = 0x02;
const ushort FHEXTRA_HTIME = 0x03;
const ushort FHEXTRA_REDIR = 0x05;
while (RemainingHeaderBytes(reader) > 0)
{
var size = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
var n = RemainingHeaderBytes(reader);
var type = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
switch (type)
{
case FHEXTRA_CRYPT:
{
Rar5CryptoInfo = new Rar5CryptoInfo(reader, true);
if (Rar5CryptoInfo.PswCheck.All(singleByte => singleByte == 0))
{
Rar5CryptoInfo = null;
}
}
break;
case FHEXTRA_HASH:
{
const uint FHEXTRA_HASH_BLAKE2 = 0x0;
const int BLAKE2_DIGEST_SIZE = 0x20;
if (
(uint)await reader.ReadRarVIntAsync().ConfigureAwait(false)
== FHEXTRA_HASH_BLAKE2
)
{
_hash = await reader
.ReadBytesAsync(BLAKE2_DIGEST_SIZE, cancellationToken)
.ConfigureAwait(false);
}
}
break;
case FHEXTRA_HTIME:
{
var flags = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
var isWindowsTime = (flags & 1) == 0;
if ((flags & 0x2) == 0x2)
{
FileLastModifiedTime = await ReadExtendedTimeV5Async(
reader,
isWindowsTime,
cancellationToken
);
}
if ((flags & 0x4) == 0x4)
{
FileCreatedTime = await ReadExtendedTimeV5Async(
reader,
isWindowsTime,
cancellationToken
);
}
if ((flags & 0x8) == 0x8)
{
FileLastAccessedTime = await ReadExtendedTimeV5Async(
reader,
isWindowsTime,
cancellationToken
);
}
}
break;
case FHEXTRA_REDIR:
{
RedirType = await reader.ReadRarVIntByteAsync().ConfigureAwait(false);
RedirFlags = await reader.ReadRarVIntByteAsync().ConfigureAwait(false);
var nn = await reader.ReadRarVIntUInt16Async().ConfigureAwait(false);
var bb = await reader
.ReadBytesAsync(nn, cancellationToken)
.ConfigureAwait(false);
RedirTargetName = ConvertPathV5(Encoding.UTF8.GetString(bb, 0, bb.Length));
}
break;
default:
break;
}
var did = n - RemainingHeaderBytes(reader);
var drain = size - did;
if (drain > 0)
{
await reader.ReadBytesAsync(drain, cancellationToken).ConfigureAwait(false);
}
}
if (AdditionalDataSize != 0)
{
CompressedSize = AdditionalDataSize;
}
}
private static async ValueTask<DateTime> ReadExtendedTimeV5Async(
AsyncMarkingBinaryReader reader,
bool isWindowsTime,
CancellationToken cancellationToken = default
)
{
if (isWindowsTime)
{
var value = await reader.ReadInt64Async(cancellationToken).ConfigureAwait(false);
return DateTime.FromFileTime(value);
}
else
{
var value = await reader.ReadUInt32Async(cancellationToken).ConfigureAwait(false);
return Utility.UnixTimeToDateTime(value);
}
}
private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime)
{
if (isWindowsTime)
@@ -217,6 +402,169 @@ internal class FileHeader : RarHeader
}
}
private async Task ReadFromReaderV4Async(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
Flags = HeaderFlags;
IsSolid = HasFlag(FileFlagsV4.SOLID);
WindowSize = IsDirectory
? 0U
: ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5);
var lowUncompressedSize = await reader
.ReadUInt32Async(cancellationToken)
.ConfigureAwait(false);
HostOs = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
FileCrc = await reader.ReadBytesAsync(4, cancellationToken).ConfigureAwait(false);
var value = await reader.ReadUInt32Async(cancellationToken).ConfigureAwait(false);
FileLastModifiedTime = Utility.DosDateToDateTime(value);
CompressionAlgorithm = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
var value1 = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
CompressionMethod = (byte)(value1 - 0x30);
var nameSize = await reader.ReadInt16Async(cancellationToken).ConfigureAwait(false);
FileAttributes = await reader.ReadUInt32Async(cancellationToken).ConfigureAwait(false);
uint highCompressedSize = 0;
uint highUncompressedkSize = 0;
if (HasFlag(FileFlagsV4.LARGE))
{
highCompressedSize = await reader
.ReadUInt32Async(cancellationToken)
.ConfigureAwait(false);
highUncompressedkSize = await reader
.ReadUInt32Async(cancellationToken)
.ConfigureAwait(false);
}
else
{
if (lowUncompressedSize == 0xffffffff)
{
lowUncompressedSize = 0xffffffff;
highUncompressedkSize = int.MaxValue;
}
}
CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize));
UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize);
nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize;
var fileNameBytes = await reader
.ReadBytesAsync(nameSize, cancellationToken)
.ConfigureAwait(false);
const int newLhdSize = 32;
switch (HeaderCode)
{
case HeaderCodeV.RAR4_FILE_HEADER:
{
if (HasFlag(FileFlagsV4.UNICODE))
{
var length = 0;
while (length < fileNameBytes.Length && fileNameBytes[length] != 0)
{
length++;
}
if (length != nameSize)
{
length++;
}
FileName = FileNameDecoder.Decode(fileNameBytes, length);
}
else
{
FileName = ArchiveEncoding.Decode(fileNameBytes);
}
FileName = ConvertPathV4(FileName);
}
break;
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
{
var datasize = HeaderSize - newLhdSize - nameSize;
if (HasFlag(FileFlagsV4.SALT))
{
datasize -= EncryptionConstV5.SIZE_SALT30;
}
if (datasize > 0)
{
SubData = await reader
.ReadBytesAsync(datasize, cancellationToken)
.ConfigureAwait(false);
}
if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
{
if (SubData is null)
{
throw new InvalidFormatException();
}
RecoverySectors =
SubData[8]
+ (SubData[9] << 8)
+ (SubData[10] << 16)
+ (SubData[11] << 24);
}
}
break;
}
if (HasFlag(FileFlagsV4.SALT))
{
R4Salt = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_SALT30, cancellationToken)
.ConfigureAwait(false);
}
if (HasFlag(FileFlagsV4.EXT_TIME))
{
if (RemainingHeaderBytes(reader) >= 2)
{
var extendedFlags = await reader
.ReadUInt16Async(cancellationToken)
.ConfigureAwait(false);
if (FileLastModifiedTime is null)
{
FileLastModifiedTime = await ProcessExtendedTimeV4Async(
extendedFlags,
FileLastModifiedTime,
reader,
0,
cancellationToken
);
}
FileCreatedTime = await ProcessExtendedTimeV4Async(
extendedFlags,
null,
reader,
1,
cancellationToken
);
FileLastAccessedTime = await ProcessExtendedTimeV4Async(
extendedFlags,
null,
reader,
2,
cancellationToken
);
FileArchivedTime = await ProcessExtendedTimeV4Async(
extendedFlags,
null,
reader,
3,
cancellationToken
);
}
}
}
private static string ConvertPathV5(string path)
{
if (Path.DirectorySeparatorChar == '\\')
@@ -365,6 +713,43 @@ internal class FileHeader : RarHeader
return l + y;
}
private static async ValueTask<DateTime?> ProcessExtendedTimeV4Async(
ushort extendedFlags,
DateTime? time,
AsyncMarkingBinaryReader reader,
int i,
CancellationToken cancellationToken = default
)
{
var rmode = (uint)extendedFlags >> ((3 - i) * 4);
if ((rmode & 8) == 0)
{
return null;
}
if (i != 0)
{
var dosTime = await reader.ReadUInt32Async(cancellationToken).ConfigureAwait(false);
time = Utility.DosDateToDateTime(dosTime);
}
if ((rmode & 4) == 0 && time is not null)
{
time = time.Value.AddSeconds(1);
}
uint nanosecondHundreds = 0;
var count = (int)rmode & 3;
for (var j = 0; j < count; j++)
{
var b = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8));
}
if (time is not null)
{
return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
}
return null;
}
private static DateTime? ProcessExtendedTimeV4(
ushort extendedFlags,
DateTime? time,

View File

@@ -1,3 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers;
@@ -22,6 +25,17 @@ internal sealed class ProtectHeader : RarHeader
Mark = reader.ReadBytes(8);
}
protected override async ValueTask ReadFinishAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
)
{
Version = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
RecSectors = await reader.ReadUInt16Async(cancellationToken).ConfigureAwait(false);
TotalBlocks = await reader.ReadUInt32Async(cancellationToken).ConfigureAwait(false);
Mark = await reader.ReadBytesAsync(8, cancellationToken).ConfigureAwait(false);
}
internal uint DataSize => checked((uint)AdditionalDataSize);
internal byte Version { get; private set; }
internal ushort RecSectors { get; private set; }

View File

@@ -1,4 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers;
@@ -18,7 +21,9 @@ internal class RarHeader : IRarHeader
{
try
{
return new RarHeader(reader, isRar5, archiveEncoding);
var header = new RarHeader(isRar5, archiveEncoding);
header.Initialize(reader);
return header;
}
catch (InvalidFormatException)
{
@@ -26,11 +31,91 @@ internal class RarHeader : IRarHeader
}
}
private RarHeader(RarCrcBinaryReader reader, bool isRar5, IArchiveEncoding archiveEncoding)
internal static async ValueTask<RarHeader?> TryReadBaseAsync(
AsyncRarCrcBinaryReader reader,
bool isRar5,
IArchiveEncoding archiveEncoding,
CancellationToken cancellationToken = default
)
{
try
{
return await CreateBaseAsync(reader, isRar5, archiveEncoding, cancellationToken)
.ConfigureAwait(false);
}
catch (InvalidFormatException)
{
return null;
}
}
private static async ValueTask<RarHeader> CreateBaseAsync(
AsyncRarCrcBinaryReader reader,
bool isRar5,
IArchiveEncoding archiveEncoding,
CancellationToken cancellationToken = default
)
{
var header = new RarHeader(HeaderType.Null, isRar5) { ArchiveEncoding = archiveEncoding };
if (isRar5)
{
header.HeaderCrc = await reader
.ReadUInt32Async(cancellationToken)
.ConfigureAwait(false);
reader.ResetCrc();
header.HeaderSize = (int)
await reader.ReadRarVIntUInt32Async(3, cancellationToken).ConfigureAwait(false);
reader.Mark();
header.HeaderCode = await reader.ReadRarVIntByteAsync(2).ConfigureAwait(false);
header.HeaderFlags = await reader
.ReadRarVIntUInt16Async(2, cancellationToken)
.ConfigureAwait(false);
if (header.HasHeaderFlag(HeaderFlagsV5.HAS_EXTRA))
{
header.ExtraSize = await reader.ReadRarVIntUInt32Async().ConfigureAwait(false);
}
if (header.HasHeaderFlag(HeaderFlagsV5.HAS_DATA))
{
header.AdditionalDataSize = (long)
await reader.ReadRarVIntAsync().ConfigureAwait(false);
}
}
else
{
reader.Mark();
header.HeaderCrc = await reader
.ReadUInt16Async(cancellationToken)
.ConfigureAwait(false);
reader.ResetCrc();
header.HeaderCode = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
header.HeaderFlags = await reader
.ReadUInt16Async(cancellationToken)
.ConfigureAwait(false);
header.HeaderSize = await reader
.ReadInt16Async(cancellationToken)
.ConfigureAwait(false);
if (header.HasHeaderFlag(HeaderFlagsV4.HAS_DATA))
{
header.AdditionalDataSize = await reader
.ReadUInt32Async(cancellationToken)
.ConfigureAwait(false);
}
}
return header;
}
private RarHeader( bool isRar5, IArchiveEncoding archiveEncoding)
{
_headerType = HeaderType.Null;
_isRar5 = isRar5;
ArchiveEncoding = archiveEncoding;
}
private void Initialize(RarCrcBinaryReader reader)
{
if (IsRar5)
{
HeaderCrc = reader.ReadUInt32();
@@ -64,6 +149,14 @@ internal class RarHeader : IRarHeader
}
}
private RarHeader(HeaderType headerType, bool isRar5)
{
_headerType = headerType;
_isRar5 = isRar5;
ArchiveEncoding = null!;
}
protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
{
_headerType = headerType;
@@ -86,12 +179,50 @@ internal class RarHeader : IRarHeader
VerifyHeaderCrc(reader.GetCrc32());
}
public static async ValueTask<RarHeader> CreateAsync(
RarHeader header,
AsyncRarCrcBinaryReader reader,
HeaderType headerType,
CancellationToken cancellationToken = default
)
{
var result = new RarHeader(headerType, header.IsRar5)
{
HeaderCrc = header.HeaderCrc,
HeaderCode = header.HeaderCode,
HeaderFlags = header.HeaderFlags,
HeaderSize = header.HeaderSize,
ExtraSize = header.ExtraSize,
AdditionalDataSize = header.AdditionalDataSize,
ArchiveEncoding = header.ArchiveEncoding,
};
await result.ReadFinishAsync(reader, cancellationToken).ConfigureAwait(false);
var n = result.RemainingHeaderBytes(reader);
if (n > 0)
{
await reader.ReadBytesAsync(n, cancellationToken).ConfigureAwait(false);
}
result.VerifyHeaderCrc(reader.GetCrc32());
return result;
}
protected int RemainingHeaderBytes(AsyncMarkingBinaryReader reader) =>
checked(HeaderSize - (int)reader.CurrentReadByteCount);
protected int RemainingHeaderBytes(MarkingBinaryReader reader) =>
checked(HeaderSize - (int)reader.CurrentReadByteCount);
protected virtual void ReadFinish(MarkingBinaryReader reader) =>
throw new NotImplementedException();
protected virtual async ValueTask ReadFinishAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
) => throw new NotImplementedException();
private void VerifyHeaderCrc(uint crc32)
{
var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc;
@@ -105,25 +236,25 @@ internal class RarHeader : IRarHeader
protected bool IsRar5 => _isRar5;
protected uint HeaderCrc { get; }
protected uint HeaderCrc { get; private set; }
internal byte HeaderCode { get; }
internal byte HeaderCode { get; private set; }
protected ushort HeaderFlags { get; }
protected ushort HeaderFlags { get; private set; }
protected bool HasHeaderFlag(ushort flag) => (HeaderFlags & flag) == flag;
protected int HeaderSize { get; }
protected int HeaderSize { get; private set; }
internal IArchiveEncoding ArchiveEncoding { get; }
internal IArchiveEncoding ArchiveEncoding { get; private set; }
/// <summary>
/// Extra header size.
/// </summary>
protected uint ExtraSize { get; }
protected uint ExtraSize { get; private set; }
/// <summary>
/// Size of additional data (eg file contents)
/// </summary>
protected long AdditionalDataSize { get; }
protected long AdditionalDataSize { get; private set; }
}

View File

@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
using SharpCompress.Readers;
@@ -40,6 +43,35 @@ public class RarHeaderFactory
}
}
public async IAsyncEnumerable<IRarHeader> ReadHeadersAsync(
Stream stream,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
var markHeader = await MarkHeader
.ReadAsync(stream, Options.LeaveStreamOpen, Options.LookForHeader, cancellationToken)
.ConfigureAwait(false);
_isRar5 = markHeader.IsRar5;
yield return markHeader;
RarHeader? header;
while (
(header = await TryReadNextHeaderAsync(stream, cancellationToken).ConfigureAwait(false))
!= null
)
{
cancellationToken.ThrowIfCancellationRequested();
yield return header;
if (header.HeaderType == HeaderType.EndArchive)
{
// End of archive marker. RAR does not read anything after this header letting to use third
// party tools to add extra information such as a digital signature to archive.
yield break;
}
}
}
private RarHeader? TryReadNextHeader(Stream stream)
{
RarCrcBinaryReader reader;
@@ -92,7 +124,7 @@ public class RarHeaderFactory
case HeaderCodeV.RAR4_PROTECT_HEADER:
{
var ph = new ProtectHeader(header, reader);
// skip the recovery record data, we do not use it.
// skip for recovery record data, we do not use it.
switch (StreamingMode)
{
case StreamingMode.Seekable:
@@ -198,6 +230,181 @@ public class RarHeaderFactory
}
}
private async ValueTask<RarHeader?> TryReadNextHeaderAsync(
Stream stream,
CancellationToken cancellationToken = default
)
{
AsyncRarCrcBinaryReader reader;
if (!IsEncrypted)
{
reader = new AsyncRarCrcBinaryReader(stream);
}
else
{
if (Options.Password is null)
{
throw new CryptographicException(
"Encrypted Rar archive has no password specified."
);
}
if (_isRar5 && _cryptInfo != null)
{
var markingReader = new MarkingBinaryReader(stream);
_cryptInfo.ReadInitV(markingReader);
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
reader = new AsyncRarCrcBinaryReader(stream, _headerKey, _cryptInfo.Salt);
}
else
{
var key = new CryptKey3(Options.Password);
reader = new AsyncRarCrcBinaryReader(stream, key);
}
}
var header = await RarHeader
.TryReadBaseAsync(reader, _isRar5, Options.ArchiveEncoding, cancellationToken)
.ConfigureAwait(false);
if (header is null)
{
return null;
}
switch (header.HeaderCode)
{
case HeaderCodeV.RAR5_ARCHIVE_HEADER:
case HeaderCodeV.RAR4_ARCHIVE_HEADER:
{
var ah = await ArchiveHeader
.CreateAsync(header, reader, cancellationToken)
.ConfigureAwait(false);
if (ah.IsEncrypted == true)
{
//!!! rar5 we don't know yet
return await CreateRarHeaderAsync(header, ah, reader, cancellationToken)
.ConfigureAwait(false);
}
return ah;
}
case HeaderCodeV.RAR4_PROTECT_HEADER:
{
var ph = await ProtectHeader
.CreateAsync(header, reader, cancellationToken)
.ConfigureAwait(false);
await SkipProtectHeaderDataAsync(ph, reader, cancellationToken)
.ConfigureAwait(false);
return await CreateRarHeaderAsync(header, ph, reader, cancellationToken)
.ConfigureAwait(false);
}
case HeaderCodeV.RAR5_SERVICE_HEADER:
{
var fh = await FileHeader
.CreateAsync(header, reader, HeaderType.Service, cancellationToken)
.ConfigureAwait(false);
if (fh.FileName == "CMT")
{
fh.PackedStream = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
}
else
{
await SkipDataAsync(fh, reader, cancellationToken).ConfigureAwait(false);
}
return fh;
}
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
{
var fh = await FileHeader
.CreateAsync(header, reader, HeaderType.NewSub, cancellationToken)
.ConfigureAwait(false);
await SkipDataAsync(fh, reader, cancellationToken).ConfigureAwait(false);
return fh;
}
case HeaderCodeV.RAR5_FILE_HEADER:
case HeaderCodeV.RAR4_FILE_HEADER:
{
var fh = await FileHeader
.CreateAsync(header, reader, HeaderType.File, cancellationToken)
.ConfigureAwait(false);
await ProcessFileHeaderAsync(fh, reader, cancellationToken).ConfigureAwait(false);
return fh;
}
case HeaderCodeV.RAR5_END_ARCHIVE_HEADER:
case HeaderCodeV.RAR4_END_ARCHIVE_HEADER:
{
var eh = await EndArchiveHeader
.CreateAsync(header, reader, cancellationToken)
.ConfigureAwait(false);
return await CreateRarHeaderAsync(header, eh, reader, cancellationToken)
.ConfigureAwait(false);
}
case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER:
{
var cryptoHeader = await ArchiveCryptHeader
.CreateAsync(header, reader, cancellationToken)
.ConfigureAwait(false);
IsEncrypted = true;
_cryptInfo = cryptoHeader.CryptInfo;
return await CreateRarHeaderAsync(header, cryptoHeader, reader, cancellationToken)
.ConfigureAwait(false);
}
default:
{
throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode);
}
}
}
private static async ValueTask<RarHeader> CreateRarHeaderAsync(
RarHeader header,
RarHeader result,
RarCrcBinaryReader reader,
CancellationToken cancellationToken = default
)
{
await result.ReadFinishAsync(reader, cancellationToken).ConfigureAwait(false);
var n = result.RemainingHeaderBytes(reader);
if (n > 0)
{
await reader.ReadBytesAsync(n, cancellationToken).ConfigureAwait(false);
}
result.VerifyHeaderCrc(reader.GetCrc32());
return result;
}
private static async ValueTask SkipProtectHeaderDataAsync(
ProtectHeader ph,
RarCrcBinaryReader reader,
CancellationToken cancellationToken = default
)
{
switch (ph.StreamingMode)
{
case StreamingMode.Seekable:
{
reader.BaseStream.Position += ph.DataSize;
}
break;
case StreamingMode.Streaming:
{
reader.BaseStream.Skip(ph.DataSize);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
private void SkipData(FileHeader fh, RarCrcBinaryReader reader)
{
switch (StreamingMode)
@@ -210,7 +417,6 @@ public class RarHeaderFactory
break;
case StreamingMode.Streaming:
{
//skip the data because it's useless?
reader.BaseStream.Skip(fh.CompressedSize);
}
break;
@@ -220,4 +426,108 @@ public class RarHeaderFactory
}
}
}
private static void ProcessFileHeader(FileHeader fh, RarCrcBinaryReader reader)
{
switch (fh.StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
if (fh.R4Salt is null && fh.Rar5CryptoInfo is null)
{
fh.PackedStream = ms;
}
else
{
fh.PackedStream = new RarCryptoWrapper(
ms,
fh.R4Salt is null ? fh.Rar5CryptoInfo.NotNull().Salt : fh.R4Salt,
fh.R4Salt is null
? new CryptKey5(Options.Password, fh.Rar5CryptoInfo.NotNull())
: new CryptKey3(Options.Password)
);
}
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
private static async ValueTask SkipDataAsync(
FileHeader fh,
RarCrcBinaryReader reader,
CancellationToken cancellationToken = default
)
{
switch (fh.StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
await reader
.BaseStream.SkipAsync((int)fh.CompressedSize, cancellationToken)
.ConfigureAwait(false);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
private static async ValueTask ProcessFileHeaderAsync(
FileHeader fh,
RarCrcBinaryReader reader,
CancellationToken cancellationToken = default
)
{
switch (fh.StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
if (fh.R4Salt is null && fh.Rar5CryptoInfo is null)
{
fh.PackedStream = ms;
}
else
{
fh.PackedStream = new RarCryptoWrapper(
ms,
fh.R4Salt is null ? fh.Rar5CryptoInfo.NotNull().Salt : fh.R4Salt,
fh.R4Salt is null
? new CryptKey5(Options.Password, fh.Rar5CryptoInfo.NotNull())
: new CryptKey3(Options.Password)
);
}
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
@@ -9,6 +11,58 @@ internal class Rar5CryptoInfo
{
public Rar5CryptoInfo() { }
public static async ValueTask<Rar5CryptoInfo> CreateAsync(
AsyncMarkingBinaryReader reader,
bool readInitV,
CancellationToken cancellationToken = default
)
{
var info = new Rar5CryptoInfo();
var cryptVersion = await reader.ReadRarVIntUInt32Async().ConfigureAwait(false);
if (cryptVersion > EncryptionConstV5.VERSION)
{
throw new CryptographicException($"Unsupported crypto version of {cryptVersion}");
}
var encryptionFlags = await reader.ReadRarVIntUInt32Async().ConfigureAwait(false);
info.UsePswCheck = FlagUtility.HasFlag(
encryptionFlags,
EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK
);
info.LG2Count = await reader.ReadRarVIntByteAsync(1).ConfigureAwait(false);
if (info.LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
{
throw new CryptographicException($"Unsupported LG2 count of {info.LG2Count}.");
}
info.Salt = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_SALT50, cancellationToken)
.ConfigureAwait(false);
if (readInitV)
{
await info.ReadInitVAsync(reader, cancellationToken).ConfigureAwait(false);
}
if (info.UsePswCheck)
{
info.PswCheck = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_PSWCHECK, cancellationToken)
.ConfigureAwait(false);
var _pswCheckCsm = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_PSWCHECK_CSUM, cancellationToken)
.ConfigureAwait(false);
var sha = SHA256.Create();
info.UsePswCheck = sha.ComputeHash(info.PswCheck)
.AsSpan()
.StartsWith(_pswCheckCsm.AsSpan());
}
return info;
}
public Rar5CryptoInfo(MarkingBinaryReader reader, bool readInitV)
{
var cryptVersion = reader.ReadRarVIntUInt32();
@@ -45,6 +99,14 @@ internal class Rar5CryptoInfo
public void ReadInitV(MarkingBinaryReader reader) =>
InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV);
public async ValueTask ReadInitVAsync(
AsyncMarkingBinaryReader reader,
CancellationToken cancellationToken = default
) =>
InitV = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_INITV, cancellationToken)
.ConfigureAwait(false);
public bool UsePswCheck = false;
public int LG2Count = 0;

View File

@@ -0,0 +1,186 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.IO;
internal class AsyncMarkingBinaryReader : IDisposable
{
private readonly AsyncBinaryReader _asyncReader;
private readonly bool _leaveOpen;
private bool _disposed;
public AsyncMarkingBinaryReader(Stream stream, CancellationToken cancellationToken = default)
{
_asyncReader = new AsyncBinaryReader(stream, leaveOpen: false);
}
public Stream BaseStream => _asyncReader.BaseStream;
public virtual long CurrentReadByteCount { get; protected set; }
public virtual void Mark() => CurrentReadByteCount = 0;
public virtual async ValueTask<bool> ReadBooleanAsync(CancellationToken ct = default)
{
var b = await ReadByteAsync(ct).ConfigureAwait(false);
return b != 0;
}
public virtual async ValueTask<byte> ReadByteAsync(CancellationToken ct = default)
{
CurrentReadByteCount++;
return await _asyncReader.ReadByteAsync(ct).ConfigureAwait(false);
}
public virtual async ValueTask<byte[]> ReadBytesAsync(int count, CancellationToken ct = default)
{
CurrentReadByteCount += count;
var buffer = new byte[count];
await _asyncReader.ReadBytesAsync(buffer, 0, count, ct).ConfigureAwait(false);
return buffer;
}
public virtual async ValueTask<short> ReadInt16Async(CancellationToken ct = default)
{
var bytes = await ReadBytesAsync(2, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadInt16LittleEndian(bytes);
}
public virtual async ValueTask<int> ReadInt32Async(CancellationToken ct = default)
{
var bytes = await ReadBytesAsync(4, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadInt32LittleEndian(bytes);
}
public virtual async ValueTask<long> ReadInt64Async(CancellationToken ct = default)
{
var bytes = await ReadBytesAsync(8, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadInt64LittleEndian(bytes);
}
public virtual async ValueTask<sbyte> ReadSByteAsync(CancellationToken ct = default)
{
var b = await ReadByteAsync(ct).ConfigureAwait(false);
return (sbyte)b;
}
public virtual async ValueTask<ushort> ReadUInt16Async(CancellationToken ct = default)
{
var bytes = await ReadBytesAsync(2, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadUInt16LittleEndian(bytes);
}
public virtual async ValueTask<uint> ReadUInt32Async(CancellationToken ct = default)
{
var bytes = await ReadBytesAsync(4, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
}
public virtual async ValueTask<ulong> ReadUInt64Async(CancellationToken ct = default)
{
var bytes = await ReadBytesAsync(8, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
}
public virtual async ValueTask<ulong> ReadRarVIntAsync(
int maxBytes = 10,
CancellationToken ct = default
) => await DoReadRarVIntAsync((maxBytes - 1) * 7, ct).ConfigureAwait(false);
private async ValueTask<ulong> DoReadRarVIntAsync(int maxShift, CancellationToken ct)
{
var shift = 0;
ulong result = 0;
do
{
var b0 = await ReadByteAsync(ct).ConfigureAwait(false);
var b1 = ((uint)b0) & 0x7f;
ulong n = b1;
var shifted = n << shift;
if (n != shifted >> shift)
{
break;
}
result |= shifted;
if (b0 == b1)
{
return result;
}
shift += 7;
} while (shift <= maxShift);
throw new FormatException("malformed vint");
}
public virtual async ValueTask<uint> ReadRarVIntUInt32Async(
int maxBytes = 5,
CancellationToken ct = default
) => await DoReadRarVIntUInt32Async((maxBytes - 1) * 7, ct).ConfigureAwait(false);
private async ValueTask<uint> DoReadRarVIntUInt32Async(int maxShift, CancellationToken ct)
{
var shift = 0;
uint result = 0;
do
{
var b0 = await ReadByteAsync(ct).ConfigureAwait(false);
var b1 = ((uint)b0) & 0x7f;
var n = b1;
var shifted = n << shift;
if (n != shifted >> shift)
{
break;
}
result |= shifted;
if (b0 == b1)
{
return result;
}
shift += 7;
} while (shift <= maxShift);
throw new FormatException("malformed vint");
}
public virtual async ValueTask<ushort> ReadRarVIntUInt16Async(
int maxBytes = 3,
CancellationToken ct = default
) =>
checked(
(ushort)await DoReadRarVIntUInt32Async((maxBytes - 1) * 7, ct).ConfigureAwait(false)
);
public virtual async ValueTask<byte> ReadRarVIntByteAsync(
int maxBytes = 2,
CancellationToken ct = default
) =>
checked((byte)await DoReadRarVIntUInt32Async((maxBytes - 1) * 7, ct).ConfigureAwait(false));
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_asyncReader.Dispose();
}
#if NET6_0_OR_GREATER
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
_disposed = true;
await _asyncReader.DisposeAsync().ConfigureAwait(false);
}
#endif
}