diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index 0bc4dba9..b51fd804 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.IO; using SharpCompress.Readers; @@ -72,6 +74,72 @@ public abstract class ReaderTests : TestBase } } + protected async Task ReadAsync( + string testArchive, + CompressionType expectedCompression, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + + options ??= new ReaderOptions() { BufferSize = 0x20000 }; + + options.LeaveStreamOpen = true; + await ReadImplAsync(testArchive, expectedCompression, options, cancellationToken); + + options.LeaveStreamOpen = false; + await ReadImplAsync(testArchive, expectedCompression, options, cancellationToken); + VerifyFiles(); + } + + private async Task ReadImplAsync( + string testArchive, + CompressionType expectedCompression, + ReaderOptions options, + CancellationToken cancellationToken = default + ) + { + using var file = File.OpenRead(testArchive); + using var protectedStream = SharpCompressStream.Create( + new ForwardOnlyStream(file, options.BufferSize), + leaveOpen: true, + throwOnDispose: true, + bufferSize: options.BufferSize + ); + using var testStream = new TestStream(protectedStream); + using (var reader = ReaderFactory.Open(testStream, options)) + { + await UseReaderAsync(reader, expectedCompression, cancellationToken); + protectedStream.ThrowOnDispose = false; + Assert.False(testStream.IsDisposed, $"{nameof(testStream)} prematurely closed"); + } + + var message = + $"{nameof(options.LeaveStreamOpen)} is set to '{options.LeaveStreamOpen}', so {nameof(testStream.IsDisposed)} should be set to '{!testStream.IsDisposed}', but is set to {testStream.IsDisposed}"; + Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message); + } + + public async Task UseReaderAsync( + IReader reader, + CompressionType expectedCompression, + CancellationToken cancellationToken = default + ) + { + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + Assert.Equal(expectedCompression, reader.Entry.CompressionType); + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, + cancellationToken + ); + } + } + } + protected void Iterate( string testArchive, string fileOrder, diff --git a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs new file mode 100644 index 00000000..7d7ed430 --- /dev/null +++ b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Readers; +using SharpCompress.Readers.Tar; +using SharpCompress.Test.Mocks; +using Xunit; + +namespace SharpCompress.Test.Tar; + +public class TarReaderAsyncTests : ReaderTests +{ + public TarReaderAsyncTests() => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public async Task Tar_Reader_Async() => await ReadAsync("Tar.tar", CompressionType.None); + + [Fact] + public async Task Tar_Skip_Async() + { + using Stream stream = new ForwardOnlyStream( + File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")) + ); + using var reader = ReaderFactory.Open(stream); + var x = 0; + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + x++; + if (x % 2 == 0) + { + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); + } + } + } + } + + [Fact] + public async Task Tar_Z_Reader_Async() => await ReadAsync("Tar.tar.Z", CompressionType.Lzw); + + [Fact] + public async Task Tar_BZip2_Reader_Async() => + await ReadAsync("Tar.tar.bz2", CompressionType.BZip2); + + [Fact] + public async Task Tar_GZip_Reader_Async() => + await ReadAsync("Tar.tar.gz", CompressionType.GZip); + + [Fact] + public async Task Tar_ZStandard_Reader_Async() => + await ReadAsync("Tar.tar.zst", CompressionType.ZStandard); + + [Fact] + public async Task Tar_LZip_Reader_Async() => + await ReadAsync("Tar.tar.lz", CompressionType.LZip); + + [Fact] + public async Task Tar_Xz_Reader_Async() => await ReadAsync("Tar.tar.xz", CompressionType.Xz); + + [Fact] + public async Task Tar_GZip_OldGnu_Reader_Async() => + await ReadAsync("Tar.oldgnu.tar.gz", CompressionType.GZip); + + [Fact] + public async Task Tar_BZip2_Entry_Stream_Async() + { + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) + using (var reader = TarReader.Open(stream)) + { + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); + using var entryStream = reader.OpenEntryStream(); + var file = Path.GetFileName(reader.Entry.Key); + var folder = + Path.GetDirectoryName(reader.Entry.Key) + ?? throw new ArgumentNullException(); + var destdir = Path.Combine(SCRATCH_FILES_PATH, folder); + if (!Directory.Exists(destdir)) + { + Directory.CreateDirectory(destdir); + } + var destinationFileName = Path.Combine(destdir, file.NotNull()); + + using var fs = File.OpenWrite(destinationFileName); + await entryStream.CopyToAsync(fs); + } + } + } + VerifyFiles(); + } + + [Fact] + public void Tar_LongNamesWithLongNameExtension_Async() + { + var filePaths = new List(); + + using ( + Stream stream = File.OpenRead( + Path.Combine(TEST_ARCHIVES_PATH, "Tar.LongPathsWithLongNameExtension.tar") + ) + ) + using (var reader = TarReader.Open(stream)) + { + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + filePaths.Add(reader.Entry.Key.NotNull("Entry Key is null")); + } + } + } + + Assert.Equal(3, filePaths.Count); + Assert.Contains("a.txt", filePaths); + Assert.Contains( + "wp-content/plugins/gravityformsextend/lib/Aws/Symfony/Component/ClassLoader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php", + filePaths + ); + Assert.Contains( + "wp-content/plugins/gravityformsextend/lib/Aws/Symfony/Component/ClassLoader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Foo.php", + filePaths + ); + } + + [Fact] + public void Tar_BZip2_Skip_Entry_Stream_Async() + { + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2")); + using var reader = TarReader.Open(stream); + var names = new List(); + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); + using var entryStream = reader.OpenEntryStream(); + entryStream.SkipEntry(); + names.Add(reader.Entry.Key.NotNull()); + } + } + Assert.Equal(3, names.Count); + } + + [Fact] + public void Tar_Containing_Rar_Reader_Async() + { + var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); + using Stream stream = File.OpenRead(archiveFullPath); + using var reader = ReaderFactory.Open(stream); + Assert.True(reader.ArchiveType == ArchiveType.Tar); + } + + [Fact] + public void Tar_With_TarGz_With_Flushed_EntryStream_Async() + { + var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsTarGz.tar"); + using Stream stream = File.OpenRead(archiveFullPath); + using var reader = ReaderFactory.Open(stream); + Assert.True(reader.MoveToNextEntry()); + Assert.Equal("inner.tar.gz", reader.Entry.Key); + + using var entryStream = reader.OpenEntryStream(); + using var flushingStream = new FlushOnDisposeStream(entryStream); + + // Extract inner.tar.gz + using var innerReader = ReaderFactory.Open(flushingStream); + Assert.True(innerReader.MoveToNextEntry()); + Assert.Equal("test", innerReader.Entry.Key); + } + + [Fact] + public async Task Tar_Broken_Stream_Async() + { + var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"); + using Stream stream = File.OpenRead(archiveFullPath); + using var reader = ReaderFactory.Open(stream); + var memoryStream = new MemoryStream(); + + Assert.True(reader.MoveToNextEntry()); + Assert.True(reader.MoveToNextEntry()); + await reader.WriteEntryToAsync(memoryStream); + stream.Close(); + Assert.Throws(() => reader.MoveToNextEntry()); + } + + [Fact] + public async Task Tar_Corrupted_Async() + { + var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "TarCorrupted.tar"); + using Stream stream = File.OpenRead(archiveFullPath); + using var reader = ReaderFactory.Open(stream); + var memoryStream = new MemoryStream(); + + Assert.True(reader.MoveToNextEntry()); + Assert.True(reader.MoveToNextEntry()); + await reader.WriteEntryToAsync(memoryStream); + stream.Close(); + Assert.Throws(() => reader.MoveToNextEntry()); + } + +#if !NETFRAMEWORK + [Fact] + public async Task Tar_GZip_With_Symlink_Entries_Async() + { + var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows + ); + using Stream stream = File.OpenRead( + Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz") + ); + using var reader = TarReader.Open(stream); + while (reader.MoveToNextEntry()) + { + if (reader.Entry.IsDirectory) + { + continue; + } + await reader.WriteEntryToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true, + WriteSymbolicLink = (sourcePath, targetPath) => + { + if (!isWindows) + { + var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath); + if (File.Exists(sourcePath)) + { + link.Delete(); // equivalent to ln -s -f + } + link.CreateSymbolicLinkTo(targetPath); + } + }, + } + ); + if (!isWindows) + { + if (reader.Entry.LinkTarget != null) + { + var path = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key.NotNull()); + var link = new Mono.Unix.UnixSymbolicLinkInfo(path); + if (link.HasContents) + { + // need to convert the link to an absolute path for comparison + var target = reader.Entry.LinkTarget; + var realTarget = Path.GetFullPath( + Path.Combine($"{Path.GetDirectoryName(path)}", target) + ); + + Assert.Equal(realTarget, link.GetContents().ToString()); + } + else + { + Assert.True(false, "Symlink has no target"); + } + } + } + } + } +#endif +}