mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Fix encrypted RAR header state when rescanning archives
Reset scan-specific encryption state in sync and async header readers while retaining the KDF cache. Add RAR4/RAR5 rescan and solid encrypted extraction regression coverage. Fixes #1422
This commit is contained in:
@@ -14,6 +14,7 @@ public partial class RarHeaderFactory
|
||||
{
|
||||
public async IAsyncEnumerable<IRarHeader> ReadHeadersAsync(Stream stream)
|
||||
{
|
||||
ResetHeaderScanState();
|
||||
var markHeader = await MarkHeader
|
||||
.ReadAsync(
|
||||
stream,
|
||||
|
||||
@@ -39,8 +39,20 @@ public partial class RarHeaderFactory
|
||||
public StreamingMode StreamingMode { get; }
|
||||
public bool IsEncrypted { get; private set; }
|
||||
|
||||
private void ResetHeaderScanState()
|
||||
{
|
||||
// Metadata probes rewind the stream. Each new scan must rediscover encryption
|
||||
// from the plaintext archive headers, rather than reuse the previous scan's state.
|
||||
IsEncrypted = false;
|
||||
_cryptInfo = null;
|
||||
_headerKey = null;
|
||||
_headerPassword = null;
|
||||
// Keep the password/salt/iteration-keyed KDF cache across scans.
|
||||
}
|
||||
|
||||
public IEnumerable<IRarHeader> ReadHeaders(Stream stream)
|
||||
{
|
||||
ResetHeaderScanState();
|
||||
var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader);
|
||||
_isRar5 = markHeader.IsRar5;
|
||||
yield return markHeader;
|
||||
|
||||
140
tests/SharpCompress.Test/Rar/RarEncryptedHeaderScanTests.cs
Normal file
140
tests/SharpCompress.Test/Rar/RarEncryptedHeaderScanTests.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Rar;
|
||||
|
||||
public class RarEncryptedHeaderScanTests : TestBase
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Rar.encrypted_filesAndHeader.rar", false)]
|
||||
[InlineData("Rar.encrypted_filesAndHeader.rar", true)]
|
||||
[InlineData("Rar5.encrypted_filesAndHeader.rar", false)]
|
||||
[InlineData("Rar5.encrypted_filesAndHeader.rar", true)]
|
||||
public async Task Rar_EncryptedHeaders_Rescan(string filename, bool useAsync)
|
||||
{
|
||||
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
|
||||
var factory = new RarHeaderFactory(
|
||||
StreamingMode.Seekable,
|
||||
ReaderOptions.ForExternalStream.WithPassword("test")
|
||||
);
|
||||
var expected = await ReadNames(false);
|
||||
Assert.Equal(6, expected.Count);
|
||||
stream.Position = 0;
|
||||
Assert.Single(await ReadNames(true));
|
||||
stream.Position = 0;
|
||||
Assert.Equal(expected, await ReadNames(false));
|
||||
|
||||
async Task<List<string>> ReadNames(bool stopAfterFirst)
|
||||
{
|
||||
var names = new List<string>();
|
||||
if (useAsync)
|
||||
{
|
||||
await foreach (var header in factory.ReadHeadersAsync(stream))
|
||||
{
|
||||
if (header is FileHeader file && header.HeaderType == HeaderType.File)
|
||||
{
|
||||
Assert.NotNull(file.FileName);
|
||||
names.Add(file.FileName);
|
||||
if (stopAfterFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var header in factory.ReadHeaders(stream))
|
||||
{
|
||||
if (header is FileHeader file && header.HeaderType == HeaderType.File)
|
||||
{
|
||||
Assert.NotNull(file.FileName);
|
||||
names.Add(file.FileName);
|
||||
if (stopAfterFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void Rar5_Solid_EncryptedHeaders_ExtractAll(bool readMetadataFirst)
|
||||
{
|
||||
using var stream = File.OpenRead(
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.encrypted_headers.rar")
|
||||
);
|
||||
using var archive = RarArchive.OpenArchive(
|
||||
stream,
|
||||
ReaderOptions.ForExternalStream.WithPassword("test")
|
||||
);
|
||||
if (readMetadataFirst)
|
||||
{
|
||||
Assert.True(archive.IsSolid);
|
||||
Assert.Equal(3, archive.Entries.Count());
|
||||
}
|
||||
using var reader = archive.ExtractAllEntries();
|
||||
var count = 0;
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
Assert.Equal($"f{count:D3}.txt", reader.Entry.Key);
|
||||
using var entryStream = reader.OpenEntryStream();
|
||||
using var textReader = new StreamReader(entryStream);
|
||||
Assert.Equal("Solid encrypted header regression test.\n", textReader.ReadToEnd());
|
||||
count++;
|
||||
}
|
||||
Assert.Equal(3, count);
|
||||
Assert.Equal(3, archive.Entries.Count());
|
||||
Assert.True(stream.CanRead);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task Rar5_Solid_EncryptedHeaders_ExtractAll_Async(bool readMetadataFirst)
|
||||
{
|
||||
using var stream = File.OpenRead(
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.encrypted_headers.rar")
|
||||
);
|
||||
await using var archive = await RarArchive.OpenAsyncArchive(
|
||||
stream,
|
||||
ReaderOptions.ForExternalStream.WithPassword("test"),
|
||||
CancellationToken.None
|
||||
);
|
||||
if (readMetadataFirst)
|
||||
{
|
||||
Assert.True(await archive.IsSolidAsync());
|
||||
Assert.Equal(3, (await archive.EntriesAsync.ToListAsync()).Count);
|
||||
}
|
||||
await using var reader = await archive.ExtractAllEntriesAsync();
|
||||
var count = 0;
|
||||
while (await reader.MoveToNextEntryAsync(CancellationToken.None))
|
||||
{
|
||||
Assert.Equal($"f{count:D3}.txt", reader.Entry.Key);
|
||||
await using var entryStream = await reader.OpenEntryStreamAsync(CancellationToken.None);
|
||||
using var textReader = new StreamReader(entryStream);
|
||||
Assert.Equal(
|
||||
"Solid encrypted header regression test.\n",
|
||||
await textReader.ReadToEndAsync()
|
||||
);
|
||||
count++;
|
||||
}
|
||||
Assert.Equal(3, count);
|
||||
Assert.Equal(3, (await archive.EntriesAsync.ToListAsync()).Count);
|
||||
Assert.True(stream.CanRead);
|
||||
}
|
||||
}
|
||||
BIN
tests/TestArchives/Archives/Rar5.solid.encrypted_headers.rar
Normal file
BIN
tests/TestArchives/Archives/Rar5.solid.encrypted_headers.rar
Normal file
Binary file not shown.
Reference in New Issue
Block a user