// This test would not pass for the V7 and Ustar formats in some OSs like MacCatalyst, tvOSSimulator and OSX, because the TempDirectory gets created in // a folder with a path longer than 100 bytes, and those tar formats have no way of handling pathnames and linknames longer than that length. // The rest of the OSs create the TempDirectory in a path that does not surpass the 100 bytes, so the 'subfolder' parameter gives a chance to extend // the base directory past that length, to ensure this scenario is tested everywhere. private async Task Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEntryType entryType, TarEntryFormat format, string subfolder) { using (TempDirectory root = new TempDirectory()) { string baseDir = string.IsNullOrEmpty(subfolder) ? root.Path : Path.Join(root.Path, subfolder); Directory.CreateDirectory(baseDir); string linkName = "link"; string targetName = "target"; string targetPath = Path.Join(baseDir, targetName); File.Create(targetPath).Dispose(); await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true)) { TarEntry entry = InvokeTarEntryCreationConstructor(format, entryType, linkName); entry.LinkName = targetPath; await writer.WriteEntryAsync(entry); } archive.Seek(0, SeekOrigin.Begin); await TarFile.ExtractToDirectoryAsync(archive, baseDir, overwriteFiles : false); Assert.Equal(2, Directory.GetFileSystemEntries(baseDir).Count()); } } }
public async Task Extract_Archive_File_OverwriteTrue_Async() { string testCaseName = "file"; string archivePath = GetTarFilePath(CompressionMethod.Uncompressed, TestTarFormat.pax, testCaseName); using (TempDirectory destination = new TempDirectory()) { string filePath = Path.Join(destination.Path, "file.txt"); using (FileStream fileStream = File.Create(filePath)) { using StreamWriter writer = new StreamWriter(fileStream, leaveOpen: false); writer.WriteLine("Original text"); } await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles : true); Assert.True(File.Exists(filePath)); using (FileStream fileStream = File.Open(filePath, FileMode.Open)) { using StreamReader reader = new StreamReader(fileStream); string actualContents = reader.ReadLine(); Assert.Equal($"Hello {testCaseName}", actualContents); // Confirm overwrite } } }
public async Task ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries_Async() { using (TempDirectory root = new TempDirectory()) { string firstSegment = "a"; string secondSegment = Path.Join(firstSegment, "b"); string fileWithTwoSegments = Path.Join(secondSegment, "c.txt"); await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { // No preceding directory entries for the segments UstarTarEntry entry = new UstarTarEntry(TarEntryType.RegularFile, fileWithTwoSegments); entry.DataStream = new MemoryStream(); entry.DataStream.Write(new byte[] { 0x1 }); entry.DataStream.Seek(0, SeekOrigin.Begin); await writer.WriteEntryAsync(entry); } archive.Seek(0, SeekOrigin.Begin); await TarFile.ExtractToDirectoryAsync(archive, root.Path, overwriteFiles : false); Assert.True(Directory.Exists(Path.Join(root.Path, firstSegment))); Assert.True(Directory.Exists(Path.Join(root.Path, secondSegment))); Assert.True(File.Exists(Path.Join(root.Path, fileWithTwoSegments))); } } }
public async Task SetsLastModifiedTimeOnExtractedFiles() { using TempDirectory root = new TempDirectory(); string inDir = Path.Join(root.Path, "indir"); string inFile = Path.Join(inDir, "file"); string tarFile = Path.Join(root.Path, "file.tar"); string outDir = Path.Join(root.Path, "outdir"); string outFile = Path.Join(outDir, "file"); Directory.CreateDirectory(inDir); File.Create(inFile).Dispose(); var dt = new DateTime(2001, 1, 2, 3, 4, 5, DateTimeKind.Local); File.SetLastWriteTime(inFile, dt); await TarFile.CreateFromDirectoryAsync(sourceDirectoryName : inDir, destinationFileName : tarFile, includeBaseDirectory : false); Directory.CreateDirectory(outDir); await TarFile.ExtractToDirectoryAsync(sourceFileName : tarFile, destinationDirectoryName : outDir, overwriteFiles : false); Assert.True(File.Exists(outFile)); Assert.InRange(File.GetLastWriteTime(outFile).Ticks, dt.AddSeconds(-3).Ticks, dt.AddSeconds(3).Ticks); // include some slop for filesystem granularity }
public async Task UnixFileModes_RestrictiveParentDir_Async() { using TempDirectory source = new TempDirectory(); using TempDirectory destination = new TempDirectory(); string archivePath = Path.Join(source.Path, "archive.tar"); using FileStream archiveStream = File.Create(archivePath); using (TarWriter writer = new TarWriter(archiveStream)) { PaxTarEntry dir = new PaxTarEntry(TarEntryType.Directory, "dir"); dir.Mode = UnixFileMode.None; // Restrict permissions. writer.WriteEntry(dir); PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "dir/file"); file.Mode = TestPermission1; writer.WriteEntry(file); } await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles : false); string dirPath = Path.Join(destination.Path, "dir"); Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); AssertFileModeEquals(dirPath, UnixFileMode.None); // Set dir permissions so we can access file. SetUnixFileMode(dirPath, UserAll); string filePath = Path.Join(dirPath, "file"); Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); AssertFileModeEquals(filePath, TestPermission1); }
public Task ExtractToDirectoryAsync_Cancel() { CancellationTokenSource cs = new CancellationTokenSource(); cs.Cancel(); return(Assert.ThrowsAsync <TaskCanceledException>(() => TarFile.ExtractToDirectoryAsync("file.tar", "directory", overwriteFiles: true, cs.Token))); }
public async Task Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize) { byte[] fileContents = new byte[contentSize]; Array.Fill <byte>(fileContents, 0x1); using var archive = new MemoryStream(); using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) { using var writer = new TarWriter(compressor); var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); entry1.DataStream = new MemoryStream(fileContents); await writer.WriteEntryAsync(entry1); var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); await writer.WriteEntryAsync(entry2); } archive.Position = 0; using var decompressor = new GZipStream(archive, CompressionMode.Decompress); using var reader = new TarReader(decompressor); using TempDirectory destination = new TempDirectory(); await TarFile.ExtractToDirectoryAsync(decompressor, destination.Path, overwriteFiles : true); Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); }
public async Task InvalidPath_Throws_Async() { using (MemoryStream archive = new MemoryStream()) { await Assert.ThrowsAsync <ArgumentNullException>(() => TarFile.ExtractToDirectoryAsync(archive, destinationDirectoryName: null, overwriteFiles: false)); await Assert.ThrowsAsync <ArgumentException>(() => TarFile.ExtractToDirectoryAsync(archive, destinationDirectoryName: string.Empty, overwriteFiles: false)); } }
public async Task ExtractToDirectoryAsync_Cancel() { CancellationTokenSource cs = new CancellationTokenSource(); cs.Cancel(); using (MemoryStream archiveStream = new MemoryStream()) { await Assert.ThrowsAsync <TaskCanceledException>(() => TarFile.ExtractToDirectoryAsync(archiveStream, "directory", overwriteFiles: true, cs.Token)); } }
public async Task InvalidPaths_Throw() { await Assert.ThrowsAsync <ArgumentNullException>(() => TarFile.ExtractToDirectoryAsync(sourceFileName: null, destinationDirectoryName: "path", overwriteFiles: false)); await Assert.ThrowsAsync <ArgumentException>(() => TarFile.ExtractToDirectoryAsync(sourceFileName: string.Empty, destinationDirectoryName: "path", overwriteFiles: false)); await Assert.ThrowsAsync <ArgumentNullException>(() => TarFile.ExtractToDirectoryAsync(sourceFileName: "path", destinationDirectoryName: null, overwriteFiles: false)); await Assert.ThrowsAsync <ArgumentException>(() => TarFile.ExtractToDirectoryAsync(sourceFileName: "path", destinationDirectoryName: string.Empty, overwriteFiles: false)); }
public async Task UnreadableStream_Throws_Async() { using (MemoryStream archive = new MemoryStream()) { using (WrappedStream unreadable = new WrappedStream(archive, canRead: false, canWrite: true, canSeek: true)) { await Assert.ThrowsAsync <IOException>(() => TarFile.ExtractToDirectoryAsync(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); } } }
public async Task TarGz_TarFile_CreateFromDir_ExtractToDir_Async() { using (TempDirectory root = new TempDirectory()) { string archivePath = Path.Join(root.Path, "compressed.tar.gz"); string sourceDirectory = Path.Join(root.Path, "source"); Directory.CreateDirectory(sourceDirectory); string destinationDirectory = Path.Join(root.Path, "destination"); Directory.CreateDirectory(destinationDirectory); string fileName = "file.txt"; string filePath = Path.Join(sourceDirectory, fileName); File.Create(filePath).Dispose(); FileStreamOptions createOptions = new() { Mode = FileMode.CreateNew, Access = FileAccess.Write, Options = FileOptions.Asynchronous }; await using (FileStream streamToCompress = File.Open(archivePath, createOptions)) { await using (GZipStream compressorStream = new GZipStream(streamToCompress, CompressionMode.Compress)) { await TarFile.CreateFromDirectoryAsync(sourceDirectory, compressorStream, includeBaseDirectory : false); } } FileInfo fileInfo = new FileInfo(archivePath); Assert.True(fileInfo.Exists); Assert.True(fileInfo.Length > 0); FileStreamOptions readOptions = new() { Mode = FileMode.Open, Access = FileAccess.Read, Options = FileOptions.Asynchronous }; await using (FileStream streamToDecompress = File.Open(archivePath, readOptions)) { await using (GZipStream decompressorStream = new GZipStream(streamToDecompress, CompressionMode.Decompress)) { await TarFile.ExtractToDirectoryAsync(decompressorStream, destinationDirectory, overwriteFiles : true); Assert.True(File.Exists(filePath)); } } } } } }
public async Task NonExistentDirectory_Throws_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 <DirectoryNotFoundException>(() => TarFile.ExtractToDirectoryAsync(sourceFileName: filePath, destinationDirectoryName: dirPath, overwriteFiles: false)); } }
public async Task NonExistentDirectory_Throws_Async() { using (TempDirectory root = new TempDirectory()) { string dirPath = Path.Join(root.Path, "dir"); using (MemoryStream archive = new MemoryStream()) { await Assert.ThrowsAsync <DirectoryNotFoundException>(() => TarFile.ExtractToDirectoryAsync(archive, destinationDirectoryName: dirPath, overwriteFiles: false)); } } }
public async Task Extract_Archive_File_OverwriteFalse_Async() { using (TempDirectory destination = new TempDirectory()) { string sourceArchiveFileName = GetTarFilePath(CompressionMethod.Uncompressed, TestTarFormat.pax, "file"); string filePath = Path.Join(destination.Path, "file.txt"); File.Create(filePath).Dispose(); await Assert.ThrowsAsync <IOException>(() => TarFile.ExtractToDirectoryAsync(sourceArchiveFileName, destination.Path, overwriteFiles: false)); } }
public async Task Extract_Archive_File_Async(TestTarFormat testFormat) { string sourceArchiveFileName = GetTarFilePath(CompressionMethod.Uncompressed, testFormat, "file"); using (TempDirectory destination = new TempDirectory()) { string filePath = Path.Join(destination.Path, "file.txt"); await TarFile.ExtractToDirectoryAsync(sourceArchiveFileName, destination.Path, overwriteFiles : false); Assert.True(File.Exists(filePath)); } }
public async Task Extract_SpecialFiles_Unix_Unelevated_ThrowsUnauthorizedAccess_Async() { using (TempDirectory root = new TempDirectory()) { string originalFileName = GetTarFilePath(CompressionMethod.Uncompressed, TestTarFormat.ustar, "specialfiles"); string archive = Path.Join(root.Path, "input.tar"); string destination = Path.Join(root.Path, "dir"); // Copying the tar to reduce the chance of other tests failing due to being used by another process File.Copy(originalFileName, archive); Directory.CreateDirectory(destination); await Assert.ThrowsAsync <UnauthorizedAccessException>(() => TarFile.ExtractToDirectoryAsync(archive, destination, overwriteFiles: false)); Assert.Equal(0, Directory.GetFileSystemEntries(destination).Count()); } }
public async Task Extract_AllSegmentsOfPath_Async() { using (TempDirectory source = new TempDirectory()) { string archivePath = Path.Join(source.Path, "archive.tar"); using (TempDirectory destination = new TempDirectory()) { FileStreamOptions fileOptions = new() { Access = FileAccess.Write, Mode = FileMode.CreateNew, Share = FileShare.None, Options = FileOptions.Asynchronous }; await using (FileStream archiveStream = new FileStream(archivePath, fileOptions)) { await using (TarWriter writer = new TarWriter(archiveStream)) { PaxTarEntry segment1 = new PaxTarEntry(TarEntryType.Directory, "segment1"); await writer.WriteEntryAsync(segment1); PaxTarEntry segment2 = new PaxTarEntry(TarEntryType.Directory, "segment1/segment2"); await writer.WriteEntryAsync(segment2); PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "segment1/segment2/file.txt"); await writer.WriteEntryAsync(file); } } await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles : false); string segment1Path = Path.Join(destination.Path, "segment1"); Assert.True(Directory.Exists(segment1Path), $"{segment1Path}' does not exist."); string segment2Path = Path.Join(segment1Path, "segment2"); Assert.True(Directory.Exists(segment2Path), $"{segment2Path}' does not exist."); string filePath = Path.Join(segment2Path, "file.txt"); Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); } } }
public async Task Extract_LinkEntry_TargetOutsideDirectory_Async(TarEntryType entryType) { await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry = new UstarTarEntry(entryType, "link"); entry.LinkName = PlatformDetection.IsWindows ? @"C:\Windows\System32\notepad.exe" : "/usr/bin/nano"; await writer.WriteEntryAsync(entry); } archive.Seek(0, SeekOrigin.Begin); using (TempDirectory root = new TempDirectory()) { await Assert.ThrowsAsync <IOException>(() => TarFile.ExtractToDirectoryAsync(archive, root.Path, overwriteFiles: false)); Assert.Equal(0, Directory.GetFileSystemEntries(root.Path).Count()); } } }
public async Task ExtractArchiveWithEntriesThatStartWithSlashDotPrefix_Async() { using (TempDirectory root = new TempDirectory()) { await using (MemoryStream archiveStream = GetStrangeTarMemoryStream("prefixDotSlashAndCurrentFolderEntry")) { await TarFile.ExtractToDirectoryAsync(archiveStream, root.Path, overwriteFiles : true); archiveStream.Position = 0; await using (TarReader reader = new TarReader(archiveStream, leaveOpen: false)) { TarEntry entry; while ((entry = await reader.GetNextEntryAsync()) != null) { // Normalize the path (remove redundant segments), remove trailing separators // this is so the first entry can be skipped if it's the same as the root directory string entryPath = Path.TrimEndingDirectorySeparator(Path.GetFullPath(Path.Join(root.Path, entry.Name))); Assert.True(Path.Exists(entryPath), $"Entry was not extracted: {entryPath}"); } } } } }
public async Task UnixFileModes_Async(bool overwrite) { using TempDirectory source = new TempDirectory(); using TempDirectory destination = new TempDirectory(); string archivePath = Path.Join(source.Path, "archive.tar"); using FileStream archiveStream = File.Create(archivePath); using (TarWriter writer = new TarWriter(archiveStream)) { PaxTarEntry dir = new PaxTarEntry(TarEntryType.Directory, "dir"); dir.Mode = TestPermission1; writer.WriteEntry(dir); PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "file"); file.Mode = TestPermission2; writer.WriteEntry(file); // Archive has no entry for missing_parent. PaxTarEntry missingParentDir = new PaxTarEntry(TarEntryType.Directory, "missing_parent/dir"); missingParentDir.Mode = TestPermission3; writer.WriteEntry(missingParentDir); // out_of_order_parent/file entry comes before out_of_order_parent entry. PaxTarEntry outOfOrderFile = new PaxTarEntry(TarEntryType.RegularFile, "out_of_order_parent/file"); writer.WriteEntry(outOfOrderFile); PaxTarEntry outOfOrderDir = new PaxTarEntry(TarEntryType.Directory, "out_of_order_parent"); outOfOrderDir.Mode = TestPermission4; writer.WriteEntry(outOfOrderDir); } string dirPath = Path.Join(destination.Path, "dir"); string filePath = Path.Join(destination.Path, "file"); string missingParentPath = Path.Join(destination.Path, "missing_parent"); string missingParentDirPath = Path.Join(missingParentPath, "dir"); string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); if (overwrite) { File.OpenWrite(filePath).Dispose(); Directory.CreateDirectory(dirPath); Directory.CreateDirectory(missingParentDirPath); Directory.CreateDirectory(outOfOrderDirPath); } await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles : overwrite); Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); AssertFileModeEquals(dirPath, TestPermission1); Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); AssertFileModeEquals(filePath, TestPermission2); // Missing parents are created with CreateDirectoryDefaultMode. Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); AssertFileModeEquals(missingParentPath, CreateDirectoryDefaultMode); Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); AssertFileModeEquals(missingParentDirPath, TestPermission3); // Directory modes that are out-of-order are still applied. Assert.True(Directory.Exists(outOfOrderDirPath), $"{outOfOrderDirPath}' does not exist."); AssertFileModeEquals(outOfOrderDirPath, TestPermission4); }
public Task NullStream_Throws_Async() => Assert.ThrowsAsync <ArgumentNullException>(() => TarFile.ExtractToDirectoryAsync(source: null, destinationDirectoryName: "path", overwriteFiles: false));