diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs index 978eb75cc6048e..51e3ad13e5b4c7 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs @@ -263,12 +263,12 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string d /// Extracts the contents of a stream that represents a tar archive into the specified directory. /// /// The stream containing the tar archive. - /// The path of the destination directory where the filesystem entries should be extracted. - /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. + /// to overwrite existing files in ; otherwise, to throw if a file to extract already exists. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// or is . - /// The directory path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. /// -or- @@ -283,12 +283,12 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory /// Extracts the contents of a stream that represents a tar archive into the specified directory. /// /// The stream containing the tar archive. - /// The path of the destination directory where the filesystem entries should be extracted. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. /// The options that configure the behavior of the extraction. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// , , or is . - /// The directory path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. /// -or- @@ -307,11 +307,6 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(source)); } - if (!Directory.Exists(destinationDirectoryName)) - { - throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, destinationDirectoryName), destinationDirectoryName); - } - // Rely on Path.GetFullPath for validation of paths destinationDirectoryName = Path.GetFullPath(destinationDirectoryName); destinationDirectoryName = PathInternal.EnsureTrailingSeparator(destinationDirectoryName); @@ -323,14 +318,14 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory /// Asynchronously extracts the contents of a stream that represents a tar archive into the specified directory. /// /// The stream containing the tar archive. - /// The path of the destination directory where the filesystem entries should be extracted. - /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. + /// to overwrite existing files in ; otherwise, to throw if a file to extract already exists. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// or is . - /// The directory path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. /// -or- @@ -345,14 +340,14 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire /// Asynchronously extracts the contents of a stream that represents a tar archive into the specified directory. /// /// The stream containing the tar archive. - /// The path of the destination directory where the filesystem entries should be extracted. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. /// The options that configure the behavior of the extraction. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// , , or is . - /// The directory path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. /// -or- @@ -375,11 +370,6 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire return Task.FromException(new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(source))); } - if (!Directory.Exists(destinationDirectoryName)) - { - return Task.FromException(new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, destinationDirectoryName), destinationDirectoryName)); - } - // Rely on Path.GetFullPath for validation of paths destinationDirectoryName = Path.GetFullPath(destinationDirectoryName); destinationDirectoryName = PathInternal.EnsureTrailingSeparator(destinationDirectoryName); @@ -391,12 +381,12 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire /// Extracts the contents of a tar file into the specified directory. /// /// The path of the tar file to extract. - /// The path of the destination directory where the filesystem entries should be extracted. - /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. + /// to overwrite existing files in ; otherwise, to throw if a file to extract already exists. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// or is . - /// The directory path was not found. /// The file path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. @@ -410,12 +400,12 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD /// Extracts the contents of a tar file into the specified directory. /// /// The path of the tar file to extract. - /// The path of the destination directory where the filesystem entries should be extracted. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. /// The options that configure the behavior of the extraction. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// , , or is . - /// The directory path was not found. /// The file path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. @@ -438,11 +428,6 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD throw new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, sourceFileName)); } - if (!Directory.Exists(destinationDirectoryName)) - { - throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, destinationDirectoryName), destinationDirectoryName); - } - using FileStream archive = File.OpenRead(sourceFileName); ExtractToDirectoryInternal(archive, destinationDirectoryName, options, leaveOpen: false); @@ -452,14 +437,14 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD /// Asynchronously extracts the contents of a tar file into the specified directory. /// /// The path of the tar file to extract. - /// The path of the destination directory where the filesystem entries should be extracted. - /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. + /// to overwrite existing files in ; otherwise, to throw if a file to extract already exists. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// or is . - /// The directory path was not found. /// The file path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. @@ -473,14 +458,14 @@ public static Task ExtractToDirectoryAsync(string sourceFileName, string destina /// Asynchronously extracts the contents of a tar file into the specified directory. /// /// The path of the tar file to extract. - /// The path of the destination directory where the filesystem entries should be extracted. + /// The path of the destination directory where the filesystem entries should be extracted. + /// Missing destination and parent directories are created as needed when entries are extracted. An empty archive does not create any directories. /// The options that configure the behavior of the extraction. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. /// , , or is . - /// The directory path was not found. /// The file path was not found. /// Operation not permitted due to insufficient permissions. /// Extracting tar entry would have resulted in a file outside the specified destination directory. @@ -507,11 +492,6 @@ public static Task ExtractToDirectoryAsync(string sourceFileName, string destina return Task.FromException(new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, sourceFileName))); } - if (!Directory.Exists(destinationDirectoryName)) - { - return Task.FromException(new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, destinationDirectoryName), destinationDirectoryName)); - } - return ExtractToDirectoryInternalAsync(sourceFileName, destinationDirectoryName, options, cancellationToken); } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs index b5a1f111aa6a46..73627a737dcad1 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs @@ -2,11 +2,183 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; namespace System.Formats.Tar.Tests; public abstract class TarFile_ExtractToDirectory_Tests : TarTestsBase { + protected abstract Task ExtractArchive(MemoryStream archive, string destinationDirectoryName, bool overwriteFiles, bool useOptions, bool async, CancellationToken cancellationToken = default); + + public static IEnumerable DestinationDirectory_OptionsAndBooleanData() => GetDataAndBooleanData(GetTwoBooleansData()); + + public static IEnumerable DestinationDirectory_TestDataAndBooleanData() => GetDataAndBooleanData(DestinationDirectory_TestData()); + + public static IEnumerable DestinationDirectory_TestData() + { + foreach (bool overwriteFiles in new[] { false, true }) + { + foreach (bool useOptions in new[] { false, true }) + { + foreach (int missingDirectoryCount in new[] { 0, 1, 3 }) + { + foreach (string entryName in new[] { null, "", "file.txt", "nested/child/file.txt", "nested/empty/", "./" }) + { + yield return new object[] { overwriteFiles, useOptions, missingDirectoryCount, entryName }; + } + } + } + } + } + + [Theory] + [MemberData(nameof(DestinationDirectory_TestDataAndBooleanData))] + public async Task DestinationDirectory_CreatedAsNeeded(bool overwriteFiles, bool useOptions, int missingDirectoryCount, string entryName, bool async) + { + using TempDirectory root = new TempDirectory(); + string destination = root.Path; + for (int i = 0; i < missingDirectoryCount; i++) + { + destination = Path.Join(destination, "missing"); + } + + using MemoryStream archive = CreateArchive(entryName); + await ExtractArchive(archive, destination, overwriteFiles, useOptions, async); + + Assert.True(archive.CanRead); + if (string.IsNullOrEmpty(entryName)) + { + Assert.Equal(missingDirectoryCount == 0, Directory.Exists(destination)); + Assert.Empty(Directory.GetFileSystemEntries(root.Path)); + } + else if (entryName.EndsWith('/')) + { + Assert.True(Directory.Exists(Path.Join(destination, entryName))); + } + else + { + Assert.Equal("archive contents", File.ReadAllText(Path.Join(destination, entryName))); + } + } + + [Theory] + [MemberData(nameof(DestinationDirectory_OptionsAndBooleanData))] + public async Task DestinationDirectory_FileCollision(bool overwriteFiles, bool useOptions, bool async) + { + using TempDirectory root = new TempDirectory(); + string filePath = Path.Join(root.Path, "file.txt"); + File.WriteAllText(filePath, "original contents"); + using MemoryStream archive = CreateArchive("file.txt"); + + if (overwriteFiles) + { + await ExtractArchive(archive, root.Path, overwriteFiles, useOptions, async); + Assert.Equal("archive contents", File.ReadAllText(filePath)); + } + else + { + await Assert.ThrowsAsync(() => ExtractArchive(archive, root.Path, overwriteFiles, useOptions, async)); + Assert.Equal("original contents", File.ReadAllText(filePath)); + } + + Assert.True(archive.CanRead); + } + + [Theory] + [MemberData(nameof(DestinationDirectory_OptionsAndBooleanData))] + public async Task DestinationDirectory_FileInRequiredPath_Throws(bool overwriteFiles, bool useOptions, bool async) + { + using TempDirectory root = new TempDirectory(); + string filePath = Path.Join(root.Path, "file"); + File.WriteAllText(filePath, "original contents"); + + foreach (string destination in new[] { filePath, Path.Join(filePath, "missing") }) + { + using MemoryStream archive = CreateArchive("file.txt"); + await Assert.ThrowsAnyAsync(() => ExtractArchive(archive, destination, overwriteFiles, useOptions, async)); + Assert.True(archive.CanRead); + Assert.Equal("original contents", File.ReadAllText(filePath)); + } + } + + [Theory] + [MemberData(nameof(DestinationDirectory_OptionsAndBooleanData))] + public async Task DestinationDirectory_Traversal_Throws(bool overwriteFiles, bool useOptions, bool async) + { + using TempDirectory root = new TempDirectory(); + string destination = Path.Join(root.Path, "missing"); + + foreach (TarEntryType entryType in new[] { TarEntryType.RegularFile, TarEntryType.SymbolicLink, TarEntryType.HardLink }) + { + using MemoryStream archive = new MemoryStream(); + using (TarWriter writer = new TarWriter(archive, leaveOpen: true)) + { + PaxTarEntry entry = new PaxTarEntry(entryType, entryType == TarEntryType.RegularFile ? "../outside.txt" : "link"); + if (entryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) + { + entry.LinkName = "../outside.txt"; + } + writer.WriteEntry(entry); + } + archive.Position = 0; + + await Assert.ThrowsAsync(() => ExtractArchive(archive, destination, overwriteFiles, useOptions, async)); + Assert.True(archive.CanRead); + Assert.Empty(Directory.GetFileSystemEntries(root.Path)); + } + } + + [Theory] + [MemberData(nameof(GetTwoBooleansData))] + public async Task DestinationDirectory_InvalidPath_Throws(bool useOptions, bool async) + { + using MemoryStream archive = CreateArchive(entryName: null); + await Assert.ThrowsAsync(() => ExtractArchive(archive, "\0", overwriteFiles: false, useOptions, async)); + Assert.True(archive.CanRead); + } + + [Theory] + [MemberData(nameof(GetBooleanData))] + public async Task ExtractToDirectoryAsync_Cancel(bool useOptions) + { + using TempDirectory root = new TempDirectory(); + using CancellationTokenSource cs = new CancellationTokenSource(); + cs.Cancel(); + using MemoryStream archive = new MemoryStream(); + await Assert.ThrowsAnyAsync(() => ExtractArchive(archive, Path.Join(root.Path, "missing", "directory"), overwriteFiles: true, useOptions, async: true, cs.Token)); + Assert.True(archive.CanRead); + Assert.Equal(0, archive.Position); + Assert.Empty(Directory.GetFileSystemEntries(root.Path)); + } + + private static MemoryStream CreateArchive(string entryName) + { + MemoryStream archive = new MemoryStream(); + if (entryName == "") + { + // An empty archive may contain just the two end-of-archive records. + archive.SetLength(1024); + } + else if (entryName is not null) + { + using TarWriter writer = new TarWriter(archive, leaveOpen: true); + bool isDirectory = entryName.EndsWith('/'); + PaxTarEntry entry = new PaxTarEntry(isDirectory ? TarEntryType.Directory : TarEntryType.RegularFile, entryName); + using MemoryStream contents = new MemoryStream(Encoding.UTF8.GetBytes("archive contents")); + if (!isDirectory) + { + entry.DataStream = contents; + } + writer.WriteEntry(entry); + } + archive.Position = 0; + return archive; + } + // TarEntryFormat, TarEntryType, string fileName public static IEnumerable GetExactRootDirMatchCases() { diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Unix.cs index d32d524d8f9a57..9704403bc277a3 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Unix.cs @@ -8,7 +8,7 @@ namespace System.Formats.Tar.Tests { - public partial class TarFile_ExtractToDirectory_File_Tests : TarTestsBase + public partial class TarFile_ExtractToDirectory_File_Tests : TarFile_ExtractToDirectory_Tests { [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotPrivilegedProcess))] [MemberData(nameof(GetBooleanData))] diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Windows.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Windows.cs index 3f2e715a8dafca..2e6b2fda060f66 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Windows.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Windows.cs @@ -8,7 +8,7 @@ namespace System.Formats.Tar.Tests { - public partial class TarFile_ExtractToDirectory_File_Tests : TarTestsBase + public partial class TarFile_ExtractToDirectory_File_Tests : TarFile_ExtractToDirectory_Tests { [Theory] [MemberData(nameof(GetBooleanData))] diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs index 1add00270dc6ae..e9b02a72d26303 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs @@ -10,14 +10,28 @@ namespace System.Formats.Tar.Tests { - public partial class TarFile_ExtractToDirectory_File_Tests : TarTestsBase + public partial class TarFile_ExtractToDirectory_File_Tests : TarFile_ExtractToDirectory_Tests { - [Fact] - public Task ExtractToDirectoryAsync_Cancel() + protected override Task ExtractArchive(MemoryStream archive, string destinationDirectoryName, bool overwriteFiles, bool useOptions, bool async, CancellationToken cancellationToken = default) { - CancellationTokenSource cs = new CancellationTokenSource(); - cs.Cancel(); - return Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync("file.tar", "directory", overwriteFiles: true, cs.Token)); + string archivePath = GetTestFilePath(); + File.WriteAllBytes(archivePath, archive.ToArray()); + if (async) + { + return useOptions + ? TarFile.ExtractToDirectoryAsync(archivePath, destinationDirectoryName, new TarExtractOptions { OverwriteFiles = overwriteFiles }, cancellationToken) + : TarFile.ExtractToDirectoryAsync(archivePath, destinationDirectoryName, overwriteFiles, cancellationToken); + } + + if (useOptions) + { + TarFile.ExtractToDirectory(archivePath, destinationDirectoryName, new TarExtractOptions { OverwriteFiles = overwriteFiles }); + } + else + { + TarFile.ExtractToDirectory(archivePath, destinationDirectoryName, overwriteFiles); + } + return Task.CompletedTask; } [Theory] @@ -44,20 +58,6 @@ public async Task NonExistentFile_Throws(bool async) await Assert.ThrowsAsync(() => ExtractToDirectory(filePath, dirPath, overwriteFiles: false, async)); } - [Theory] - [MemberData(nameof(GetBooleanData))] - public async Task NonExistentDirectory_Throws(bool async) - { - using TempDirectory root = new TempDirectory(); - - string filePath = Path.Join(root.Path, "file.tar"); - string dirPath = Path.Join(root.Path, "dir"); - - File.Create(filePath).Dispose(); - - await Assert.ThrowsAsync(() => ExtractToDirectory(filePath, dirPath, overwriteFiles: false, async)); - } - [Theory] [MemberData(nameof(GetBooleanData))] public async Task SetsLastModifiedTimeOnExtractedFiles(bool async) diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs index a72df655cc3661..ba3768bf425908 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs @@ -14,6 +14,26 @@ namespace System.Formats.Tar.Tests { public class TarFile_ExtractToDirectory_Stream_Tests : TarFile_ExtractToDirectory_Tests { + protected override Task ExtractArchive(MemoryStream archive, string destinationDirectoryName, bool overwriteFiles, bool useOptions, bool async, CancellationToken cancellationToken = default) + { + if (async) + { + return useOptions + ? TarFile.ExtractToDirectoryAsync(archive, destinationDirectoryName, new TarExtractOptions { OverwriteFiles = overwriteFiles }, cancellationToken) + : TarFile.ExtractToDirectoryAsync(archive, destinationDirectoryName, overwriteFiles, cancellationToken); + } + + if (useOptions) + { + TarFile.ExtractToDirectory(archive, destinationDirectoryName, new TarExtractOptions { OverwriteFiles = overwriteFiles }); + } + else + { + TarFile.ExtractToDirectory(archive, destinationDirectoryName, overwriteFiles); + } + return Task.CompletedTask; + } + public static IEnumerable GetLinkEntryTypesAndBooleanData() => GetDataAndBooleanData(new[] { new object[] { TarEntryType.SymbolicLink }, @@ -35,15 +55,6 @@ public static IEnumerable GetNormalizedPathAndBooleanData() => GetData new object[] { "subdir/./readme.txt", Path.Join("subdir", "readme.txt") } }); - [Fact] - public async Task ExtractToDirectoryAsync_Cancel() - { - CancellationTokenSource cs = new CancellationTokenSource(); - cs.Cancel(); - using MemoryStream archiveStream = new MemoryStream(); - await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(archiveStream, "directory", overwriteFiles: true, cs.Token)); - } - [Theory] [MemberData(nameof(GetBooleanData))] public Task NullStream_Throws(bool async) => @@ -67,17 +78,6 @@ public async Task UnreadableStream_Throws(bool async) await Assert.ThrowsAsync(() => ExtractToDirectory(unreadable, destinationDirectoryName: "path", overwriteFiles: false, async)); } - [Theory] - [MemberData(nameof(GetBooleanData))] - public async Task NonExistentDirectory_Throws(bool async) - { - using TempDirectory root = new TempDirectory(); - string dirPath = Path.Join(root.Path, "dir"); - - using MemoryStream archive = new MemoryStream(); - await Assert.ThrowsAsync(() => ExtractToDirectory(archive, destinationDirectoryName: dirPath, overwriteFiles: false, async)); - } - [Theory] [MemberData(nameof(GetBooleanData))] public async Task ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries(bool async)