mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-10-01 11:22:44 +00:00
257 lines
8.9 KiB
C#
257 lines
8.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using SharpCompress.Common.Rar;
|
|
using SharpCompress.IO;
|
|
using SharpCompress.Readers;
|
|
|
|
namespace SharpCompress.Common.Rar.Headers;
|
|
|
|
public partial class RarHeaderFactory
|
|
{
|
|
public async IAsyncEnumerable<IRarHeader> ReadHeadersAsync(Stream stream)
|
|
{
|
|
var markHeader = await MarkHeader
|
|
.ReadAsync(
|
|
stream,
|
|
Options.LeaveStreamOpen,
|
|
Options.LookForHeader,
|
|
CancellationToken.None
|
|
)
|
|
.ConfigureAwait(false);
|
|
_isRar5 = markHeader.IsRar5;
|
|
yield return markHeader;
|
|
|
|
RarHeader? header;
|
|
while (
|
|
(
|
|
header = await TryReadNextHeaderAsync(stream, CancellationToken.None)
|
|
.ConfigureAwait(false)
|
|
) != null
|
|
)
|
|
{
|
|
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 async ValueTask<RarHeader?> TryReadNextHeaderAsync(
|
|
Stream stream,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
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)
|
|
{
|
|
await _cryptInfo
|
|
.ReadInitVAsync(new AsyncMarkingBinaryReader(stream))
|
|
.ConfigureAwait(false);
|
|
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
|
|
|
|
reader = await AsyncRarCryptoBinaryReader
|
|
.Create(stream, _headerKey, _cryptInfo.Salt)
|
|
.ConfigureAwait(false);
|
|
}
|
|
else
|
|
{
|
|
var key = new CryptKey3(Options.Password);
|
|
reader = await AsyncRarCryptoBinaryReader.Create(stream, key).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
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
|
|
IsEncrypted = true;
|
|
}
|
|
return ah;
|
|
}
|
|
|
|
case HeaderCodeV.RAR4_PROTECT_HEADER:
|
|
{
|
|
var ph = await ProtectHeader
|
|
.CreateAsync(header, reader, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
// skip the recovery record data, we do not use it.
|
|
switch (StreamingMode)
|
|
{
|
|
case StreamingMode.Seekable:
|
|
{
|
|
reader.BaseStream.Position += ph.DataSize;
|
|
}
|
|
break;
|
|
case StreamingMode.Streaming:
|
|
{
|
|
await reader
|
|
.BaseStream.SkipAsync(ph.DataSize, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
throw new InvalidFormatException("Invalid StreamingMode");
|
|
}
|
|
}
|
|
return ph;
|
|
}
|
|
|
|
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);
|
|
switch (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");
|
|
}
|
|
}
|
|
return fh;
|
|
}
|
|
case HeaderCodeV.RAR5_END_ARCHIVE_HEADER:
|
|
case HeaderCodeV.RAR4_END_ARCHIVE_HEADER:
|
|
{
|
|
return await EndArchiveHeader
|
|
.CreateAsync(header, 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 cryptoHeader;
|
|
}
|
|
default:
|
|
{
|
|
throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async ValueTask SkipDataAsync(
|
|
FileHeader fh,
|
|
AsyncRarCrcBinaryReader reader,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
switch (StreamingMode)
|
|
{
|
|
case StreamingMode.Seekable:
|
|
{
|
|
fh.DataStartPosition = reader.BaseStream.Position;
|
|
reader.BaseStream.Position += fh.CompressedSize;
|
|
}
|
|
break;
|
|
case StreamingMode.Streaming:
|
|
{
|
|
//skip the data because it's useless?
|
|
await reader
|
|
.BaseStream.SkipAsync(fh.CompressedSize, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
throw new InvalidFormatException("Invalid StreamingMode");
|
|
}
|
|
}
|
|
}
|
|
}
|