mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-07 05:34:35 +00:00
Compare commits
2 Commits
master
...
adam/async
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfc303ff6c | ||
|
|
dea635e39e |
49
src/SharpCompress/Common/Rar/AsyncRarCrcBinaryReader.cs
Normal file
49
src/SharpCompress/Common/Rar/AsyncRarCrcBinaryReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
98
src/SharpCompress/Common/Rar/AsyncRarCryptoBinaryReader.cs
Normal file
98
src/SharpCompress/Common/Rar/AsyncRarCryptoBinaryReader.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
186
src/SharpCompress/IO/AsyncMarkingBinaryReader.cs
Normal file
186
src/SharpCompress/IO/AsyncMarkingBinaryReader.cs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user