mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Resolve RAR companion volumes with unambiguous case-insensitive fallback
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
### Rar Format Notes
|
||||
|
||||
- RAR5 encrypted headers and data accept KDF counts from 0 through 24 (the binary logarithm of the PBKDF2 iteration count), matching UnRAR's acceptance limit. Larger counts are rejected before key derivation.
|
||||
- RAR companion filenames use the same exact-match-first, case-insensitive fallback as ZIP, with ambiguous fallback matches rejected.
|
||||
- Encrypted ciphertext must contain complete AES blocks. Truncation throws `InvalidFormatException` on synchronous reads or `IncompleteArchiveException` on asynchronous reads.
|
||||
|
||||
### 7Zip Format Notes
|
||||
|
||||
@@ -28,11 +28,32 @@ internal abstract class ArchiveVolumeFactory
|
||||
);
|
||||
}
|
||||
|
||||
if (item != null && item.Exists)
|
||||
return item is null ? null : ResolveFilePart(item);
|
||||
}
|
||||
|
||||
internal static FileInfo? ResolveFilePart(FileInfo item)
|
||||
{
|
||||
if (item.Exists)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!item.Directory!.Exists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
FileInfo? match = null;
|
||||
foreach (var file in item.Directory.EnumerateFiles())
|
||||
{
|
||||
if (!string.Equals(file.Name, item.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (match is not null)
|
||||
{
|
||||
throw new InvalidFormatException($"Ambiguous archive volume name: {item.Name}");
|
||||
}
|
||||
match = file;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,6 @@ internal static class RarArchiveVolumeFactory
|
||||
}
|
||||
}
|
||||
|
||||
if (item != null && item.Exists)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
return null; //no more items
|
||||
return item is null ? null : ArchiveVolumeFactory.ResolveFilePart(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,31 +32,6 @@ internal static class ZipArchiveVolumeFactory
|
||||
return ArchiveVolumeFactory.GetFilePart(index, part1);
|
||||
}
|
||||
|
||||
if (item != null && item.Exists)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
// Generated suffixes are lowercase, but split archives can use any on-disk casing.
|
||||
// Prefer the exact path above and never choose arbitrarily between fallback matches.
|
||||
if (item is not null && item.Directory!.Exists)
|
||||
{
|
||||
FileInfo? match = null;
|
||||
foreach (var file in item.Directory!.EnumerateFiles())
|
||||
{
|
||||
if (!string.Equals(file.Name, item.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (match is not null)
|
||||
{
|
||||
throw new InvalidFormatException($"Ambiguous ZIP volume name: {item.Name}");
|
||||
}
|
||||
match = file;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
return null; //no more items
|
||||
return item is null ? null : ArchiveVolumeFactory.ResolveFilePart(item);
|
||||
}
|
||||
}
|
||||
|
||||
45
tests/SharpCompress.Test/Rar/RarVolumeCasingTests.cs
Normal file
45
tests/SharpCompress.Test/Rar/RarVolumeCasingTests.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Rar;
|
||||
|
||||
public class RarVolumeCasingTests : TestBase
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("sample.RAR", "SAMPLE.R00")]
|
||||
[InlineData("sample.part01.RAR", "SAMPLE.PART02.rar")]
|
||||
[InlineData("sample.rar.001", "SAMPLE.RAR.002")]
|
||||
public void Companion_Lookup_Is_Case_Insensitive(string firstName, string nextName)
|
||||
{
|
||||
var first = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, firstName));
|
||||
var next = Path.Combine(SCRATCH_FILES_PATH, nextName);
|
||||
File.WriteAllBytes(next, []);
|
||||
Assert.Equal(
|
||||
next,
|
||||
RarArchiveVolumeFactory.GetFilePart(1, first)!.FullName,
|
||||
ignoreCase: true
|
||||
);
|
||||
Assert.Null(RarArchiveVolumeFactory.GetFilePart(2, first));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Companion_Lookup_Prefers_Exact_Path_And_Rejects_Ambiguity()
|
||||
{
|
||||
var first = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "sample.rar"));
|
||||
var upper = Path.Combine(SCRATCH_FILES_PATH, "SAMPLE.R00");
|
||||
var mixed = Path.Combine(SCRATCH_FILES_PATH, "Sample.r00");
|
||||
File.WriteAllBytes(upper, []);
|
||||
if (File.Exists(mixed))
|
||||
{
|
||||
return; // Case-insensitive filesystems cannot represent ambiguous companion names.
|
||||
}
|
||||
File.WriteAllBytes(mixed, []);
|
||||
Assert.Throws<InvalidFormatException>(() => RarArchiveVolumeFactory.GetFilePart(1, first));
|
||||
var exact = Path.Combine(SCRATCH_FILES_PATH, "sample.r00");
|
||||
File.WriteAllBytes(exact, []);
|
||||
Assert.Equal(exact, RarArchiveVolumeFactory.GetFilePart(1, first)!.FullName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user