using System.IO; using System.Linq; using System.Threading.Tasks; using SharpCompress.Archives; using SharpCompress.Archives.Rar; using SharpCompress.Common; using SharpCompress.Compressors.LZMA.Utilities; using SharpCompress.Readers; using SharpCompress.Test.Mocks; using Xunit; namespace SharpCompress.Test.Rar; public class RarArchiveAsyncTests : ArchiveTests { [Fact] public async ValueTask Rar_EncryptedFileAndHeader_Archive_Async() => await ReadRarPasswordAsync("Rar.encrypted_filesAndHeader.rar", "test"); [Fact] public async ValueTask Rar_EncryptedFileAndHeader_NoPasswordExceptionTest_Async() => await Assert.ThrowsAsync( typeof(CryptographicException), async () => await ReadRarPasswordAsync("Rar.encrypted_filesAndHeader.rar", null) ); [Fact] public async ValueTask Rar5_EncryptedFileAndHeader_Archive_Async() => await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", "test"); [Fact] public async ValueTask Rar5_EncryptedFileAndHeader_Archive_Err_Async() => await Assert.ThrowsAsync( typeof(CryptographicException), async () => await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", "failed") ); [Fact] public async ValueTask Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest_Async() => await Assert.ThrowsAsync( typeof(CryptographicException), async () => await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", null) ); [Fact] public async ValueTask Rar_EncryptedFileOnly_Archive_Async() => await ReadRarPasswordAsync("Rar.encrypted_filesOnly.rar", "test"); [Fact] public async ValueTask Rar_EncryptedFileOnly_Archive_Err_Async() => await Assert.ThrowsAsync( typeof(CryptographicException), async () => await ReadRarPasswordAsync("Rar5.encrypted_filesOnly.rar", "failed") ); [Fact] public async ValueTask Rar5_EncryptedFileOnly_Archive_Async() => await ReadRarPasswordAsync("Rar5.encrypted_filesOnly.rar", "test"); [Fact] public async ValueTask Rar_Encrypted_Archive_Async() => await ReadRarPasswordAsync("Rar.Encrypted.rar", "test"); [Fact] public async ValueTask Rar5_Encrypted_Archive_Async() => await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", "test"); private async ValueTask ReadRarPasswordAsync(string testArchive, string? password) { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive))) await using ( var archive = await RarArchive.OpenAsyncArchive( stream, new ReaderOptions { Password = password, LeaveStreamOpen = true } ) ) { await foreach (var entry in archive.EntriesAsync) { if (!entry.IsDirectory) { Assert.Equal(CompressionType.Rar, entry.CompressionType); await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } } VerifyFiles(); } [Fact] public async ValueTask Rar_Multi_Archive_Encrypted_Async() => await Assert.ThrowsAsync(async () => await ArchiveFileReadPasswordAsync("Rar.EncryptedParts.part01.rar", "test") ); protected async Task ArchiveFileReadPasswordAsync(string archiveName, string password) { using ( var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, archiveName), new ReaderOptions { Password = password, LeaveStreamOpen = true } ) ) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); } [Fact] public async ValueTask Rar_None_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar.none.rar"); [Fact] public async ValueTask Rar5_None_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar5.none.rar"); [Fact] public async ValueTask Rar_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar.rar"); [Fact] public async ValueTask Rar5_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar5.rar"); [Fact] public async ValueTask Rar_test_invalid_exttime_ArchiveStreamRead_Async() => await DoRar_test_invalid_exttime_ArchiveStreamReadAsync("Rar.test_invalid_exttime.rar"); private async ValueTask DoRar_test_invalid_exttime_ArchiveStreamReadAsync(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } [Fact] public async ValueTask Rar_Jpg_ArchiveStreamRead_Async() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg")); using ( var archive = RarArchive.OpenArchive(stream, new ReaderOptions { LookForHeader = true }) ) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); } [Fact] public async ValueTask Rar_IsSolidArchiveCheck_Async() => await DoRar_IsSolidArchiveCheckAsync("Rar.rar"); [Fact] public async ValueTask Rar5_IsSolidArchiveCheck_Async() => await DoRar_IsSolidArchiveCheckAsync("Rar5.rar"); private async ValueTask DoRar_IsSolidArchiveCheckAsync(string filename) { using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { using var archive = RarArchive.OpenArchive(stream); Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); } [Fact] public async ValueTask Rar_IsSolidEntryStreamCheck_Async() => await DoRar_IsSolidEntryStreamCheckAsync("Rar.solid.rar"); private async ValueTask DoRar_IsSolidEntryStreamCheckAsync(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); using var archive = RarArchive.OpenArchive(stream); Assert.True(archive.IsSolid); IArchiveEntry[] entries = archive.Entries.Where(a => !a.IsDirectory).ToArray(); Assert.NotInRange(entries.Length, 0, 1); Assert.False(entries[0].IsSolid); var testEntry = entries[1]; Assert.True(testEntry.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { using (var crcStream = new CrcCheckStream((uint)entry.Crc)) { using var eStream = await entry.OpenEntryStreamAsync(); await eStream.CopyToAsync(crcStream); } if (entry == testEntry) { break; } } } [Fact] public async ValueTask Rar_Solid_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar.solid.rar"); [Fact] public async ValueTask Rar5_Solid_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar5.solid.rar"); [Fact] public async ValueTask Rar_Solid_StreamRead_Extract_All_Async() => await ArchiveStreamReadExtractAllAsync("Rar.solid.rar", CompressionType.Rar); [Fact] public async ValueTask Rar5_Solid_StreamRead_Extract_All_Async() => await ArchiveStreamReadExtractAllAsync("Rar5.solid.rar", CompressionType.Rar); [Fact] public async ValueTask Rar_Multi_ArchiveStreamRead_Async() => await DoRar_Multi_ArchiveStreamReadAsync( [ "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", "Rar.multi.part06.rar", ], false ); [Fact] public async ValueTask Rar5_Multi_ArchiveStreamRead_Async() => await DoRar_Multi_ArchiveStreamReadAsync( [ "Rar5.multi.part01.rar", "Rar5.multi.part02.rar", "Rar5.multi.part03.rar", "Rar5.multi.part04.rar", "Rar5.multi.part05.rar", "Rar5.multi.part06.rar", ], false ); private async ValueTask DoRar_Multi_ArchiveStreamReadAsync(string[] archives, bool isSolid) { using var archive = RarArchive.OpenArchive( archives .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) .Select(File.OpenRead) .ToArray() ); Assert.Equal(archive.IsSolid, isSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } [Fact] public async ValueTask Rar5_MultiSolid_ArchiveStreamRead_Async() => await DoRar_Multi_ArchiveStreamReadAsync( [ "Rar.multi.solid.part01.rar", "Rar.multi.solid.part02.rar", "Rar.multi.solid.part03.rar", "Rar.multi.solid.part04.rar", "Rar.multi.solid.part05.rar", "Rar.multi.solid.part06.rar", ], true ); [Fact] public async ValueTask RarNoneArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar.none.rar"); [Fact] public async ValueTask Rar5NoneArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar5.none.rar"); [Fact] public async ValueTask Rar_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar.rar"); [Fact] public async ValueTask Rar5_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar5.rar"); [Fact] public async ValueTask Rar_ArchiveFileRead_HasDirectories_Async() => await DoRar_ArchiveFileRead_HasDirectoriesAsync("Rar.rar"); [Fact] public async ValueTask Rar5_ArchiveFileRead_HasDirectories_Async() => await DoRar_ArchiveFileRead_HasDirectoriesAsync("Rar5.rar"); private Task DoRar_ArchiveFileRead_HasDirectoriesAsync(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); using var archive = RarArchive.OpenArchive(stream); Assert.False(archive.IsSolid); Assert.Contains(true, archive.Entries.Select(entry => entry.IsDirectory)); return Task.CompletedTask; } [Fact] public async ValueTask Rar_Jpg_ArchiveFileRead_Async() { using ( var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), new ReaderOptions { LookForHeader = true } ) ) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); } [Fact] public async ValueTask Rar_Solid_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar.solid.rar"); [Fact] public async ValueTask Rar5_Solid_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar5.solid.rar"); [Fact] public async ValueTask Rar2_Multi_ArchiveStreamRead_Async() => await DoRar_Multi_ArchiveStreamReadAsync( [ "Rar2.multi.rar", "Rar2.multi.r00", "Rar2.multi.r01", "Rar2.multi.r02", "Rar2.multi.r03", "Rar2.multi.r04", "Rar2.multi.r05", ], false ); [Fact] public async ValueTask Rar2_Multi_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar2.multi.rar"); [Fact] public async ValueTask Rar2_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar2.rar"); [Fact] public async ValueTask Rar15_ArchiveFileRead_Async() { UseExtensionInsteadOfNameToVerify = true; UseCaseInsensitiveToVerify = true; await ArchiveFileReadAsync("Rar15.rar"); } [Fact] public void Rar15_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar15.rar"); using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(1, archive.MinVersion); Assert.Equal(1, archive.MaxVersion); } [Fact] public void Rar2_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar2.rar"); using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(2, archive.MinVersion); Assert.Equal(2, archive.MaxVersion); } [Fact] public void Rar4_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar4.multi.part01.rar"); using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(3, archive.MinVersion); Assert.Equal(4, archive.MaxVersion); } [Fact] public void Rar5_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.rar"); using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(5, archive.MinVersion); Assert.Equal(6, archive.MaxVersion); } [Fact] public async ValueTask Rar4_Multi_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar4.multi.part01.rar"); [Fact] public async ValueTask Rar4_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar4.rar"); [Fact] public void Rar_GetPartsSplit_Async() => ArchiveGetParts( new[] { "Rar4.split.001", "Rar4.split.002", "Rar4.split.003", "Rar4.split.004", "Rar4.split.005", "Rar4.split.006", } ); [Fact] public void Rar_GetPartsOld_Async() => ArchiveGetParts( new[] { "Rar2.multi.rar", "Rar2.multi.r00", "Rar2.multi.r01", "Rar2.multi.r02", "Rar2.multi.r03", "Rar2.multi.r04", "Rar2.multi.r05", } ); [Fact] public void Rar_GetPartsNew_Async() => ArchiveGetParts( new[] { "Rar4.multi.part01.rar", "Rar4.multi.part02.rar", "Rar4.multi.part03.rar", "Rar4.multi.part04.rar", "Rar4.multi.part05.rar", "Rar4.multi.part06.rar", "Rar4.multi.part07.rar", } ); [Fact] public async ValueTask Rar4_Multi_ArchiveStreamRead_Async() => await DoRar_Multi_ArchiveStreamReadAsync( [ "Rar4.multi.part01.rar", "Rar4.multi.part02.rar", "Rar4.multi.part03.rar", "Rar4.multi.part04.rar", "Rar4.multi.part05.rar", "Rar4.multi.part06.rar", "Rar4.multi.part07.rar", ], false ); [Fact] public async ValueTask Rar4_Split_ArchiveStreamRead_Async() => await ArchiveStreamMultiReadAsync( null, [ "Rar4.split.001", "Rar4.split.002", "Rar4.split.003", "Rar4.split.004", "Rar4.split.005", "Rar4.split.006", ] ); [Fact] public async ValueTask Rar4_Multi_ArchiveFirstFileRead_Async() => await ArchiveFileReadAsync("Rar4.multi.part01.rar"); [Fact] public async ValueTask Rar4_Split_ArchiveFirstFileRead_Async() => await ArchiveFileReadAsync("Rar4.split.001"); [Fact] public async ValueTask Rar4_Split_ArchiveStreamFirstFileRead_Async() => await ArchiveStreamMultiReadAsync(null, ["Rar4.split.001"]); [Fact] public async ValueTask Rar4_Split_ArchiveOpen_Async() => await ArchiveOpenStreamReadAsync( null, "Rar4.split.001", "Rar4.split.002", "Rar4.split.003", "Rar4.split.004", "Rar4.split.005", "Rar4.split.006" ); [Fact] public async ValueTask Rar4_Multi_ArchiveOpen_Async() => await ArchiveOpenStreamReadAsync( null, "Rar4.multi.part01.rar", "Rar4.multi.part02.rar", "Rar4.multi.part03.rar", "Rar4.multi.part04.rar", "Rar4.multi.part05.rar", "Rar4.multi.part06.rar", "Rar4.multi.part07.rar" ); [Fact] public void Rar4_Multi_ArchiveOpenEntryVolumeIndexTest_Async() => ArchiveOpenEntryVolumeIndexTest( [ [0, 1], [1, 5], [5, 6], ], null, "Rar4.multi.part01.rar", "Rar4.multi.part02.rar", "Rar4.multi.part03.rar", "Rar4.multi.part04.rar", "Rar4.multi.part05.rar", "Rar4.multi.part06.rar", "Rar4.multi.part07.rar" ); [Fact] public async ValueTask Rar_Multi_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar.multi.part01.rar"); [Fact] public async ValueTask Rar5_Multi_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar5.multi.part01.rar"); [Fact] public void Rar_IsFirstVolume_True_Async() => DoRar_IsFirstVolume_True("Rar.multi.part01.rar"); [Fact] public void Rar5_IsFirstVolume_True_Async() => DoRar_IsFirstVolume_True("Rar5.multi.part01.rar"); private void DoRar_IsFirstVolume_True(string firstFilename) { using var archive = RarArchive.OpenArchive(Path.Combine(TEST_ARCHIVES_PATH, firstFilename)); Assert.True(archive.IsMultipartVolume()); Assert.True(archive.IsFirstVolume()); } [Fact] public void Rar_IsFirstVolume_False_Async() => DoRar_IsFirstVolume_False("Rar.multi.part03.rar"); [Fact] public void Rar5_IsFirstVolume_False_Async() => DoRar_IsFirstVolume_False("Rar5.multi.part03.rar"); private void DoRar_IsFirstVolume_False(string notFirstFilename) { using var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename) ); Assert.True(archive.IsMultipartVolume()); Assert.False(archive.IsFirstVolume()); } [Fact] public async ValueTask Rar5_CRC_Blake2_Archive_Async() => await ArchiveFileReadAsync("Rar5.crc_blake2.rar"); [Fact] void Rar_Iterate_Archive_Async() => ArchiveFileSkip("Rar.rar", "Failure jpg exe Empty jpg\\test.jpg exe\\test.exe тест.txt"); [Fact] public void Rar2_Iterate_Archive_Async() => ArchiveFileSkip("Rar2.rar", "Failure Empty тест.txt jpg\\test.jpg exe\\test.exe jpg exe"); [Fact] public void Rar4_Iterate_Archive_Async() => ArchiveFileSkip("Rar4.rar", "Failure Empty jpg exe тест.txt jpg\\test.jpg exe\\test.exe"); [Fact] public void Rar5_Iterate_Archive_Async() => ArchiveFileSkip("Rar5.rar", "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe"); [Fact] public void Rar_Encrypted_Iterate_Archive_Async() => ArchiveFileSkip( "Rar.encrypted_filesOnly.rar", "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe" ); [Fact] public void Rar5_Encrypted_Iterate_Archive_Async() => ArchiveFileSkip( "Rar5.encrypted_filesOnly.rar", "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe" ); private async ValueTask ArchiveStreamReadAsync(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using var stream = File.OpenRead(testArchive); using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } private async ValueTask ArchiveStreamReadExtractAllAsync( string testArchive, CompressionType compression ) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using var stream = File.OpenRead(testArchive); await using var archive = await ArchiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream) ); Assert.True(await archive.IsSolidAsync()); await using (var reader = await archive.ExtractAllEntriesAsync()) { while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { Assert.Equal(compression, reader.Entry.CompressionType); await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } VerifyFiles(); await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } private async ValueTask ArchiveFileReadAsync(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using var archive = ArchiveFactory.OpenArchive(testArchive); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } private async ValueTask ArchiveStreamMultiReadAsync( ReaderOptions? readerOptions, params string[] testArchives ) { var paths = testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)); using var archive = ArchiveFactory.OpenArchive( paths.Select(a => new FileInfo(a)).ToArray(), readerOptions ); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } /// /// Tests for Issue #1050 - RAR extraction with WriteToDirectoryAsync creates folders /// but places all files at the top level instead of in their subdirectories. /// [Fact] public async ValueTask Rar_Issue1050_WriteToDirectoryAsync_ExtractsToSubdirectories() { var testFile = "Rar.issue1050.rar"; using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile)); await using var archive = await RarArchive.OpenAsyncArchive(fileStream); // Extract using archive.WriteToDirectoryAsync without explicit options await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH); // Verify files are in their subdirectories, not at the root Assert.True( File.Exists(Path.Combine(SCRATCH_FILES_PATH, "PhysicsBraid", "263825.tr11dtp")), "File should be in PhysicsBraid subdirectory" ); Assert.True( File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Animations", "15441.tr11anim")), "File should be in Animations subdirectory" ); Assert.True( File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766728.tr11dtp")), "File should be in Braid subdirectory" ); Assert.True( File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp")), "File should be in Braid subdirectory" ); Assert.True( File.Exists(Path.Combine(SCRATCH_FILES_PATH, "HeadBraid", "321353.tr11modeldata")), "File should be in HeadBraid subdirectory" ); // NOTE: The file size check is omitted because there's a separate pre-existing bug // in the async RAR stream implementation that causes incorrect file sizes. // This test only verifies the directory structure fix. } private async ValueTask ArchiveOpenStreamReadAsync( ReaderOptions? readerOptions, params string[] testArchives ) { var paths = testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)); using var archive = ArchiveFactory.OpenArchive( paths.Select(f => new FileInfo(f)).ToArray(), readerOptions ); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } }