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 ReadAndWriteMultipleGlobalExtendedAttributesEntries_Async(TarEntryFormat format) { Dictionary <string, string> attrs = new Dictionary <string, string>() { { "hello", "world" }, { "dotnet", "runtime" } }; using MemoryStream archiveStream = new MemoryStream(); await using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: true)) { PaxGlobalExtendedAttributesTarEntry gea1 = new PaxGlobalExtendedAttributesTarEntry(attrs); await writer.WriteEntryAsync(gea1); TarEntry entry1 = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir1"); await writer.WriteEntryAsync(entry1); PaxGlobalExtendedAttributesTarEntry gea2 = new PaxGlobalExtendedAttributesTarEntry(attrs); await writer.WriteEntryAsync(gea2); TarEntry entry2 = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir2"); await writer.WriteEntryAsync(entry2); } archiveStream.Position = 0; await using (TarReader reader = new TarReader(archiveStream, leaveOpen: false)) { VerifyGlobalExtendedAttributesEntry(await reader.GetNextEntryAsync(), attrs); VerifyDirectory(await reader.GetNextEntryAsync(), format, "dir1"); VerifyGlobalExtendedAttributesEntry(await reader.GetNextEntryAsync(), attrs); VerifyDirectory(await reader.GetNextEntryAsync(), format, "dir2"); Assert.Null(await reader.GetNextEntryAsync()); } }
public async Task FileName_NullOrEmpty_Async() { using MemoryStream archiveStream = new MemoryStream(); await using (TarWriter writer = new TarWriter(archiveStream)) { await Assert.ThrowsAsync <ArgumentNullException>(() => writer.WriteEntryAsync(null, "entryName")); await Assert.ThrowsAsync <ArgumentException>(() => writer.WriteEntryAsync(string.Empty, "entryName")); } }
public async Task GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposing_Async(bool copyData) { await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); using (StreamWriter streamWriter = new StreamWriter(entry1.DataStream, leaveOpen: true)) { streamWriter.WriteLine("Hello world!"); } entry1.DataStream.Seek(0, SeekOrigin.Begin); // Rewind to ensure it gets written from the beginning await writer.WriteEntryAsync(entry1); UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir"); await writer.WriteEntryAsync(entry2); } archive.Seek(0, SeekOrigin.Begin); await using (WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false)) { UstarTarEntry entry; Stream oldStream; await using (TarReader reader = new TarReader(wrapped)) // Unseekable { entry = await reader.GetNextEntryAsync(copyData) as UstarTarEntry; Assert.NotNull(entry); Assert.Equal(TarEntryType.RegularFile, entry.EntryType); oldStream = entry.DataStream; entry.DataStream = new MemoryStream(); // Substitution, setter should dispose the previous stream using (StreamWriter streamWriter = new StreamWriter(entry.DataStream, leaveOpen: true)) { streamWriter.WriteLine("Substituted"); } } // Disposing reader should not dispose the substituted DataStream Assert.Throws <ObjectDisposedException>(() => oldStream.Read(new byte[1])); entry.DataStream.Seek(0, SeekOrigin.Begin); using (StreamReader streamReader = new StreamReader(entry.DataStream)) { Assert.Equal("Substituted", streamReader.ReadLine()); } } } }
public async Task GetNextEntry_CopyDataTrue_UnseekableArchive_Async() { string expectedText = "Hello world!"; await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); using (StreamWriter streamWriter = new StreamWriter(entry1.DataStream, leaveOpen: true)) { streamWriter.WriteLine(expectedText); } entry1.DataStream.Seek(0, SeekOrigin.Begin); await writer.WriteEntryAsync(entry1); UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir"); await writer.WriteEntryAsync(entry2); } archive.Seek(0, SeekOrigin.Begin); await using (WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false)) { UstarTarEntry entry; await using (TarReader reader = new TarReader(wrapped, leaveOpen: true)) // Unseekable { entry = await reader.GetNextEntryAsync(copyData : true) as UstarTarEntry; Assert.NotNull(entry); Assert.Equal(TarEntryType.RegularFile, entry.EntryType); // Force reading the next entry to advance the underlying stream position Assert.NotNull(await reader.GetNextEntryAsync()); Assert.Null(await reader.GetNextEntryAsync()); Assert.NotNull(entry.DataStream); entry.DataStream.Seek(0, SeekOrigin.Begin); // Should not throw: This is a new stream, not the archive's disposed stream using (StreamReader streamReader = new StreamReader(entry.DataStream)) { string actualText = streamReader.ReadLine(); Assert.Equal(expectedText, actualText); } } // The reader must stay alive because it's in charge of disposing all the entries it collected Assert.Throws <ObjectDisposedException>(() => entry.DataStream.Read(new byte[1])); } } }
public async Task Write_LongName_And_LongLinkName_Async(TarEntryType entryType) { // Both the Name and LinkName fields in header only fit 100 bytes string longName = new string('a', 101); string longLinkName = new string('a', 101); await using (MemoryStream archiveStream = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); entry.LinkName = longLinkName; await writer.WriteEntryAsync(entry); } archiveStream.Position = 0; await using (TarReader reader = new TarReader(archiveStream)) { GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; Assert.Equal(entryType, entry.EntryType); Assert.Equal(longName, entry.Name); Assert.Equal(longLinkName, entry.LinkName); } } }
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))); } } }
// 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 Write_V7RegularFileEntry_In_OtherFormatsWriter_Async(TarEntryFormat writerFormat) { using MemoryStream archive = new MemoryStream(); TarWriter writer = new TarWriter(archive, format: writerFormat, leaveOpen: true); await using (writer) { V7TarEntry entry = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); // Should be written in the format of the entry await writer.WriteEntryAsync(entry); } archive.Seek(0, SeekOrigin.Begin); TarReader reader = new TarReader(archive); await using (reader) { TarEntry entry = await reader.GetNextEntryAsync(); Assert.NotNull(entry); Assert.Equal(TarEntryFormat.V7, entry.Format); Assert.True(entry is V7TarEntry); Assert.Null(await reader.GetNextEntryAsync()); } }
public async Task WriteEntryAsync_Cancel(TarEntryFormat format) { CancellationTokenSource cs = new CancellationTokenSource(); cs.Cancel(); await using (MemoryStream archiveStream = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: false)) { TarEntry entry = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir"); await Assert.ThrowsAsync <TaskCanceledException>(() => writer.WriteEntryAsync(entry, cs.Token)); await Assert.ThrowsAsync <TaskCanceledException>(() => writer.WriteEntryAsync("file.txt", "file.txt", cs.Token)); } } }
public async Task Add_Empty_GlobalExtendedAttributes_Async() { using MemoryStream archive = new MemoryStream(); TarWriter writer = new TarWriter(archive, leaveOpen: true); await using (writer) { PaxGlobalExtendedAttributesTarEntry gea = new PaxGlobalExtendedAttributesTarEntry(new Dictionary <string, string>()); await writer.WriteEntryAsync(gea); } archive.Seek(0, SeekOrigin.Begin); TarReader reader = new TarReader(archive); await using (reader) { PaxGlobalExtendedAttributesTarEntry gea = await reader.GetNextEntryAsync() as PaxGlobalExtendedAttributesTarEntry; Assert.NotNull(gea); Assert.Equal(TarEntryFormat.Pax, gea.Format); Assert.Equal(TarEntryType.GlobalExtendedAttributes, gea.EntryType); Assert.Equal(0, gea.GlobalExtendedAttributes.Count); Assert.Null(await reader.GetNextEntryAsync()); } }
public async Task WriteEntry_RespectDefaultWriterFormat_Async(TarEntryFormat expectedFormat) { using (TempDirectory root = new TempDirectory()) { string path = Path.Join(root.Path, "file.txt"); File.Create(path).Dispose(); await using (MemoryStream archiveStream = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archiveStream, expectedFormat, leaveOpen: true)) { await writer.WriteEntryAsync(path, "file.txt"); } archiveStream.Position = 0; await using (TarReader reader = new TarReader(archiveStream, leaveOpen: false)) { TarEntry entry = await reader.GetNextEntryAsync(); Assert.Equal(expectedFormat, entry.Format); Type expectedType = GetTypeForFormat(expectedFormat); Assert.Equal(expectedType, entry.GetType()); } } } }
public async Task WritePaxAttributes_Timestamps_UserProvided_Async() { Dictionary <string, string> extendedAttributes = new(); extendedAttributes.Add(PaxEaATime, GetTimestampStringFromDateTimeOffset(TestAccessTime)); extendedAttributes.Add(PaxEaCTime, GetTimestampStringFromDateTimeOffset(TestChangeTime)); await using (MemoryStream archiveStream = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName, extendedAttributes); regularFile.ModificationTime = TestModificationTime; await writer.WriteEntryAsync(regularFile); } archiveStream.Position = 0; await using (TarReader reader = new TarReader(archiveStream)) { PaxTarEntry regularFile = await reader.GetNextEntryAsync() as PaxTarEntry; AssertExtensions.GreaterThanOrEqualTo(regularFile.ExtendedAttributes.Count, 4); VerifyExtendedAttributeTimestamp(regularFile, PaxEaMTime, TestModificationTime); VerifyExtendedAttributeTimestamp(regularFile, PaxEaATime, TestAccessTime); VerifyExtendedAttributeTimestamp(regularFile, PaxEaCTime, TestChangeTime); } } }
public async void Write_To_UnseekableStream_Async() { await using (MemoryStream inner = new MemoryStream()) { await using (WrappedStream wrapped = new WrappedStream(inner, canRead: true, canWrite: true, canSeek: false)) { await using (TarWriter writer = new TarWriter(wrapped, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry paxEntry = new PaxTarEntry(TarEntryType.RegularFile, "file.txt"); await writer.WriteEntryAsync(paxEntry); } // The final records should get written, and the length should not be set because position cannot be read inner.Seek(0, SeekOrigin.Begin); // Rewind the base stream (wrapped cannot be rewound) await using (TarReader reader = new TarReader(wrapped)) { TarEntry entry = await reader.GetNextEntryAsync(); Assert.Equal(TarEntryFormat.Pax, entry.Format); Assert.Equal(TarEntryType.RegularFile, entry.EntryType); Assert.Null(await reader.GetNextEntryAsync()); } } } }
public async Task WriteEntry_FromUnseekableStream_AdvanceDataStream_WriteFromThatPosition_Async() { using MemoryStream source = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.ustar, "file"); using WrappedStream unseekable = new WrappedStream(source, canRead: true, canWrite: true, canSeek: false); using MemoryStream destination = new MemoryStream(); await using (TarReader reader1 = new TarReader(unseekable)) { TarEntry entry = await reader1.GetNextEntryAsync(); Assert.NotNull(entry); Assert.NotNull(entry.DataStream); entry.DataStream.ReadByte(); // Advance one byte, now the expected string would be "ello file" await using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Ustar, leaveOpen: true)) { await writer.WriteEntryAsync(entry); } } destination.Seek(0, SeekOrigin.Begin); await using (TarReader reader2 = new TarReader(destination)) { TarEntry entry = await reader2.GetNextEntryAsync(); Assert.NotNull(entry); Assert.NotNull(entry.DataStream); using (StreamReader streamReader = new StreamReader(entry.DataStream, leaveOpen: true)) { string contents = streamReader.ReadLine(); Assert.Equal("ello file", contents); } } }
public async Task LongEndMarkers_DoNotAdvanceStream_Async() { await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry = new UstarTarEntry(TarEntryType.Directory, "dir"); await writer.WriteEntryAsync(entry); } byte[] buffer = new byte[2048]; // Four additional end markers (512 each) Array.Fill <byte>(buffer, 0x0); archive.Write(buffer); archive.Seek(0, SeekOrigin.Begin); await using (TarReader reader = new TarReader(archive)) { Assert.NotNull(await reader.GetNextEntryAsync()); Assert.Null(await reader.GetNextEntryAsync()); long expectedPosition = archive.Position; // After reading the first null entry, should not advance more Assert.Null(await reader.GetNextEntryAsync()); Assert.Equal(expectedPosition, archive.Position); } } }
public async Task ThrowIf_AddFile_AfterDispose_Async() { using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream); await writer.DisposeAsync(); await Assert.ThrowsAsync <ObjectDisposedException>(() => writer.WriteEntryAsync("fileName", "entryName")); }
public async Task WriteEntry_AfterDispose_Throws_Async() { using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream); await writer.DisposeAsync(); PaxTarEntry entry = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); await Assert.ThrowsAsync <ObjectDisposedException>(() => writer.WriteEntryAsync(entry)); }
public async Task TarGz_TarWriter_TarReader_Async() { using (TempDirectory root = new TempDirectory()) { string archivePath = Path.Join(root.Path, "compressed.tar.gz"); string fileName = "file.txt"; string filePath = Path.Join(root.Path, fileName); File.Create(filePath).Dispose(); // Create tar.gz archive FileStreamOptions createOptions = new() { Mode = FileMode.CreateNew, Access = FileAccess.Write, Options = FileOptions.Asynchronous }; await using (FileStream streamToCompress = new FileStream(archivePath, createOptions)) { await using (GZipStream compressorStream = new GZipStream(streamToCompress, CompressionMode.Compress)) { await using (TarWriter writer = new TarWriter(compressorStream)) { await writer.WriteEntryAsync(fileName : filePath, entryName : fileName); } } } FileInfo fileInfo = new FileInfo(archivePath); Assert.True(fileInfo.Exists); Assert.True(fileInfo.Length > 0); // Verify tar.gz archive contents 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 using (TarReader reader = new TarReader(decompressorStream)) { TarEntry entry = await reader.GetNextEntryAsync(); Assert.Equal(TarEntryFormat.Pax, entry.Format); Assert.Equal(fileName, entry.Name); Assert.Null(await reader.GetNextEntryAsync()); } } } } }
public async Task GetNextEntry_CopyDataFalse_UnseekableArchive_Exceptions_Async() { await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); using (StreamWriter streamWriter = new StreamWriter(entry1.DataStream, leaveOpen: true)) { streamWriter.WriteLine("Hello world!"); } entry1.DataStream.Seek(0, SeekOrigin.Begin); // Rewind to ensure it gets written from the beginning await writer.WriteEntryAsync(entry1); UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir"); await writer.WriteEntryAsync(entry2); } archive.Seek(0, SeekOrigin.Begin); await using (WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false)) { UstarTarEntry entry; await using (TarReader reader = new TarReader(wrapped)) // Unseekable { entry = await reader.GetNextEntryAsync(copyData : false) as UstarTarEntry; Assert.NotNull(entry); Assert.Equal(TarEntryType.RegularFile, entry.EntryType); entry.DataStream.ReadByte(); // Reading is possible as long as we don't move to the next entry // Attempting to read the next entry should automatically move the position pointer to the beginning of the next header Assert.NotNull(await reader.GetNextEntryAsync()); Assert.Null(await reader.GetNextEntryAsync()); // This is not possible because the position of the main stream is already past the data Assert.Throws <EndOfStreamException>(() => entry.DataStream.Read(new byte[1])); } // The reader must stay alive because it's in charge of disposing all the entries it collected Assert.Throws <ObjectDisposedException>(() => entry.DataStream.Read(new byte[1])); } } }
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 WritePaxAttributes_LongLinkName_AutomaticallyAdded_Async() { using MemoryStream archiveStream = new MemoryStream(); string longSymbolicLinkName = new string('a', 101); string longHardLinkName = new string('b', 101); TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true); await using (writer) { PaxTarEntry symlink = new PaxTarEntry(TarEntryType.SymbolicLink, "symlink"); symlink.LinkName = longSymbolicLinkName; await writer.WriteEntryAsync(symlink); PaxTarEntry hardlink = new PaxTarEntry(TarEntryType.HardLink, "hardlink"); hardlink.LinkName = longHardLinkName; await writer.WriteEntryAsync(hardlink); } archiveStream.Position = 0; TarReader reader = new TarReader(archiveStream); await using (reader) { PaxTarEntry symlink = await reader.GetNextEntryAsync() as PaxTarEntry; AssertExtensions.GreaterThanOrEqualTo(symlink.ExtendedAttributes.Count, 5); Assert.Contains(PaxEaName, symlink.ExtendedAttributes); Assert.Equal("symlink", symlink.ExtendedAttributes[PaxEaName]); Assert.Contains(PaxEaLinkName, symlink.ExtendedAttributes); Assert.Equal(longSymbolicLinkName, symlink.ExtendedAttributes[PaxEaLinkName]); PaxTarEntry hardlink = await reader.GetNextEntryAsync() as PaxTarEntry; AssertExtensions.GreaterThanOrEqualTo(hardlink.ExtendedAttributes.Count, 5); Assert.Contains(PaxEaName, hardlink.ExtendedAttributes); Assert.Equal("hardlink", hardlink.ExtendedAttributes[PaxEaName]); Assert.Contains(PaxEaLinkName, hardlink.ExtendedAttributes); Assert.Equal(longHardLinkName, hardlink.ExtendedAttributes[PaxEaLinkName]); } }
protected async Task WriteEntry_Null_Throws_Async_Internal(TarEntryFormat format) { await using (MemoryStream archiveStream = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archiveStream, format, leaveOpen: false)) { await Assert.ThrowsAsync <ArgumentNullException>(() => writer.WriteEntryAsync(null)); } } }
// The fixed size fields for mtime, atime and ctime can fit 12 ASCII characters, but the last character is reserved for an ASCII space. // We internally use long to represent the seconds since Unix epoch, not int. // If the max allowed value is 77,777,777,777 in octal, then the max allowed seconds since the Unix epoch are 8,589,934,591, // which represents the date "2242/03/16 12:56:32 +00:00". // Pax should survive after this date because it stores the timestamps in the extended attributes dictionary // without size restrictions. public async Task WriteTimestampsBeyondOctalLimitInPax_Async() { DateTimeOffset overLimitTimestamp = new DateTimeOffset(2242, 3, 16, 12, 56, 33, TimeSpan.Zero); // One second past the octal limit string strOverLimitTimestamp = GetTimestampStringFromDateTimeOffset(overLimitTimestamp); Dictionary <string, string> ea = new Dictionary <string, string>() { { PaxEaATime, strOverLimitTimestamp }, { PaxEaCTime, strOverLimitTimestamp } }; PaxTarEntry entry = new PaxTarEntry(TarEntryType.Directory, "dir", ea); entry.ModificationTime = overLimitTimestamp; Assert.Equal(overLimitTimestamp, entry.ModificationTime); Assert.Contains(PaxEaATime, entry.ExtendedAttributes); DateTimeOffset atime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaATime); Assert.Equal(overLimitTimestamp, atime); Assert.Contains(PaxEaCTime, entry.ExtendedAttributes); DateTimeOffset ctime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaCTime); Assert.Equal(overLimitTimestamp, ctime); using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream, leaveOpen: true); await using (writer) { await writer.WriteEntryAsync(entry); } archiveStream.Position = 0; TarReader reader = new TarReader(archiveStream); await using (reader) { PaxTarEntry readEntry = await reader.GetNextEntryAsync() as PaxTarEntry; Assert.NotNull(readEntry); Assert.Equal(overLimitTimestamp, readEntry.ModificationTime); Assert.Contains(PaxEaATime, readEntry.ExtendedAttributes); DateTimeOffset actualATime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaATime); Assert.Equal(overLimitTimestamp, actualATime); Assert.Contains(PaxEaCTime, readEntry.ExtendedAttributes); DateTimeOffset actualCTime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaCTime); Assert.Equal(overLimitTimestamp, actualCTime); } }
// Y2K38 will happen one second after "2038/19/01 03:14:07 +00:00". This timestamp represents the seconds since the Unix epoch with a // value of int.MaxValue: 2,147,483,647. // The fixed size fields for mtime, atime and ctime can fit 12 ASCII characters, but the last character is reserved for an ASCII space. // All our entry types should survive the Epochalypse because we internally use long to represent the seconds since Unix epoch, not int. // So if the max allowed value is 77,777,777,777 in octal, then the max allowed seconds since the Unix epoch are 8,589,934,591, which // is way past int MaxValue, but still within the long limits. That number represents the date "2242/16/03 12:56:32 +00:00". public async Task WriteTimestampsBeyondEpochalypseInPax_Async() { DateTimeOffset epochalypse = new DateTimeOffset(2038, 1, 19, 3, 14, 8, TimeSpan.Zero); string strEpochalypse = GetTimestampStringFromDateTimeOffset(epochalypse); Dictionary <string, string> ea = new Dictionary <string, string>() { { PaxEaATime, strEpochalypse }, { PaxEaCTime, strEpochalypse } }; PaxTarEntry entry = new PaxTarEntry(TarEntryType.Directory, "dir", ea); entry.ModificationTime = epochalypse; Assert.Equal(epochalypse, entry.ModificationTime); Assert.Contains(PaxEaATime, entry.ExtendedAttributes); DateTimeOffset atime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaATime); Assert.Equal(epochalypse, atime); Assert.Contains(PaxEaCTime, entry.ExtendedAttributes); DateTimeOffset ctime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaCTime); Assert.Equal(epochalypse, ctime); using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream, leaveOpen: true); await using (writer) { await writer.WriteEntryAsync(entry); } archiveStream.Position = 0; TarReader reader = new TarReader(archiveStream); await using (reader) { PaxTarEntry readEntry = await reader.GetNextEntryAsync() as PaxTarEntry; Assert.NotNull(readEntry); Assert.Equal(epochalypse, readEntry.ModificationTime); Assert.Contains(PaxEaATime, readEntry.ExtendedAttributes); DateTimeOffset actualATime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaATime); Assert.Equal(epochalypse, actualATime); Assert.Contains(PaxEaCTime, readEntry.ExtendedAttributes); DateTimeOffset actualCTime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaCTime); Assert.Equal(epochalypse, actualCTime); } }
public async Task EntryName_NullOrEmpty_Async() { using (TempDirectory root = new TempDirectory()) { string file1Name = "file1.txt"; string file2Name = "file2.txt"; string file1Path = Path.Join(root.Path, file1Name); string file2Path = Path.Join(root.Path, file2Name); File.Create(file1Path).Dispose(); File.Create(file2Path).Dispose(); await using (MemoryStream archiveStream = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { await writer.WriteEntryAsync(file1Path, null); await writer.WriteEntryAsync(file2Path, string.Empty); } archiveStream.Seek(0, SeekOrigin.Begin); await using (TarReader reader = new TarReader(archiveStream)) { TarEntry first = await reader.GetNextEntryAsync(); Assert.NotNull(first); Assert.Equal(file1Name, first.Name); TarEntry second = await reader.GetNextEntryAsync(); Assert.NotNull(second); Assert.Equal(file2Name, second.Name); Assert.Null(await reader.GetNextEntryAsync()); } } } }
public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize, bool copyData) { byte[] fileContents = new byte[contentSize]; Array.Fill <byte>(fileContents, 0x1); using var archive = new MemoryStream(); using (var writer = new TarWriter(archive, leaveOpen: true)) { 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 unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); using var reader = new TarReader(unseekable); TarEntry e = await reader.GetNextEntryAsync(copyData); Assert.Equal(contentSize, e.Length); byte[] buffer = new byte[contentSize]; while (e.DataStream.Read(buffer) > 0) { ; } AssertExtensions.SequenceEqual(fileContents, buffer); e = await reader.GetNextEntryAsync(copyData); Assert.Equal(0, e.Length); e = await reader.GetNextEntryAsync(copyData); Assert.Null(e); }
public async Task VerifyChecksumV7_Async() { await using (MemoryStream archive = new MemoryStream()) { await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.V7, leaveOpen: true)) { V7TarEntry entry = new V7TarEntry( // '\0' = 0 TarEntryType.V7RegularFile, // 'a.b' = 97 + 46 + 98 = 241 entryName: "a.b"); // '0000744\0' = 48 + 48 + 48 + 48 + 55 + 52 + 52 + 0 = 351 entry.Mode = AssetMode; // octal 744 = u+rxw, g+r, o+r // '0017351\0' = 48 + 48 + 49 + 55 + 51 + 53 + 49 + 0 = 353 entry.Uid = AssetUid; // decimal 7913, octal 17351 // '0006773\0' = 48 + 48 + 48 + 54 + 55 + 55 + 51 + 0 = 359 entry.Gid = AssetGid; // decimal 3579, octal 6773 // '14164217674\0' = 49 + 52 + 49 + 54 + 52 + 50 + 49 + 55 + 54 + 55 + 52 + 0 = 571 DateTimeOffset mtime = new DateTimeOffset(2022, 1, 2, 3, 45, 00, TimeSpan.Zero); // ToUnixTimeSeconds() = decimal 1641095100, octal 14164217674 entry.ModificationTime = mtime; entry.DataStream = new MemoryStream(); byte[] buffer = new byte[] { 72, 101, 108, 108, 111 }; // '0000000005\0' = 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 53 + 0 = 533 await entry.DataStream.WriteAsync(buffer); // Data length: decimal 5 entry.DataStream.Seek(0, SeekOrigin.Begin); // Rewind to ensure it gets written from the beginning // Sum so far: 0 + 241 + 351 + 353 + 359 + 571 + 533 = decimal 2408 // Add 8 spaces to the sum: 2408 + (8 x 32) = octal 5150, decimal 2664 (final) // Checksum: '005150\0 ' await writer.WriteEntryAsync(entry); Assert.Equal(2664, entry.Checksum); } archive.Seek(0, SeekOrigin.Begin); await using (TarReader reader = new TarReader(archive)) { TarEntry entry = await reader.GetNextEntryAsync(); Assert.Equal(2664, entry.Checksum); } } }
public async Task WritePaxAttributes_LongGroupName_LongUserName_Async() { string userName = "******"; string groupName = "IAmAGroupNameWhoseLengthIsWayBeyondTheThirtyTwoByteLimit"; using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true); await using (writer) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); SetRegularFile(regularFile); VerifyRegularFile(regularFile, isWritable: true); regularFile.UserName = userName; regularFile.GroupName = groupName; await writer.WriteEntryAsync(regularFile); } archiveStream.Position = 0; TarReader reader = new TarReader(archiveStream); await using (reader) { PaxTarEntry regularFile = await reader.GetNextEntryAsync() as PaxTarEntry; VerifyRegularFile(regularFile, isWritable: false); Assert.NotNull(regularFile.ExtendedAttributes); // path, mtime, atime and ctime are always collected by default AssertExtensions.GreaterThanOrEqualTo(regularFile.ExtendedAttributes.Count, 6); Assert.Contains(PaxEaName, regularFile.ExtendedAttributes); Assert.Contains(PaxEaMTime, regularFile.ExtendedAttributes); Assert.Contains(PaxEaATime, regularFile.ExtendedAttributes); Assert.Contains(PaxEaCTime, regularFile.ExtendedAttributes); Assert.Contains(PaxEaUName, regularFile.ExtendedAttributes); Assert.Equal(userName, regularFile.ExtendedAttributes[PaxEaUName]); Assert.Contains(PaxEaGName, regularFile.ExtendedAttributes); Assert.Equal(groupName, regularFile.ExtendedAttributes[PaxEaGName]); // They should also get exposed via the regular properties Assert.Equal(groupName, regularFile.GroupName); Assert.Equal(userName, regularFile.UserName); } }
public async Task Write_RegularFileEntry_In_V7Writer_Async(TarEntryFormat entryFormat) { using MemoryStream archive = new MemoryStream(); TarWriter writer = new TarWriter(archive, format: TarEntryFormat.V7, leaveOpen: true); await using (writer) { TarEntry entry = entryFormat switch { TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), _ => throw new FormatException($"Unexpected format: {entryFormat}") }; // Should be written in the format of the entry await writer.WriteEntryAsync(entry); } archive.Seek(0, SeekOrigin.Begin); TarReader reader = new TarReader(archive); await using (reader) { TarEntry entry = await reader.GetNextEntryAsync(); Assert.NotNull(entry); Assert.Equal(entryFormat, entry.Format); switch (entryFormat) { case TarEntryFormat.Ustar: Assert.True(entry is UstarTarEntry); break; case TarEntryFormat.Pax: Assert.True(entry is PaxTarEntry); break; case TarEntryFormat.Gnu: Assert.True(entry is GnuTarEntry); break; } Assert.Null(await reader.GetNextEntryAsync()); } }