public void ReadAndWriteMultipleGlobalExtendedAttributesEntries(TarEntryFormat format) { Dictionary <string, string> attrs = new Dictionary <string, string>() { { "hello", "world" }, { "dotnet", "runtime" } }; using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: true)) { PaxGlobalExtendedAttributesTarEntry gea1 = new PaxGlobalExtendedAttributesTarEntry(attrs); writer.WriteEntry(gea1); TarEntry entry1 = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir1"); writer.WriteEntry(entry1); PaxGlobalExtendedAttributesTarEntry gea2 = new PaxGlobalExtendedAttributesTarEntry(attrs); writer.WriteEntry(gea2); TarEntry entry2 = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir2"); writer.WriteEntry(entry2); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream, leaveOpen: false)) { VerifyGlobalExtendedAttributesEntry(reader.GetNextEntry(), attrs); VerifyDirectory(reader.GetNextEntry(), format, "dir1"); VerifyGlobalExtendedAttributesEntry(reader.GetNextEntry(), attrs); VerifyDirectory(reader.GetNextEntry(), format, "dir2"); Assert.Null(reader.GetNextEntry()); } }
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 void Extract_AllSegmentsOfPath() { 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 segment1 = new PaxTarEntry(TarEntryType.Directory, "segment1"); writer.WriteEntry(segment1); PaxTarEntry segment2 = new PaxTarEntry(TarEntryType.Directory, "segment1/segment2"); writer.WriteEntry(segment2); PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "segment1/segment2/file.txt"); writer.WriteEntry(file); } TarFile.ExtractToDirectory(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 void EntryName_NullOrEmpty() { 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(); using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: true)) { writer.WriteEntry(file1Path, null); writer.WriteEntry(file2Path, string.Empty); } archiveStream.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archiveStream)) { TarEntry first = reader.GetNextEntry(); Assert.NotNull(first); Assert.Equal(file1Name, first.Name); TarEntry second = reader.GetNextEntry(); Assert.NotNull(second); Assert.Equal(file2Name, second.Name); Assert.Null(reader.GetNextEntry()); } }
public void Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries(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); writer.WriteEntry(entry1); var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); writer.WriteEntry(entry2); } archive.Position = 0; using var decompressor = new GZipStream(archive, CompressionMode.Decompress); using var reader = new TarReader(decompressor); using TempDirectory destination = new TempDirectory(); TarFile.ExtractToDirectory(decompressor, destination.Path, overwriteFiles: true); Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); }
public void FileName_NullOrEmpty() { using MemoryStream archiveStream = new MemoryStream(); using TarWriter writer = new TarWriter(archiveStream); Assert.Throws <ArgumentNullException>(() => writer.WriteEntry(null, "entryName")); Assert.Throws <ArgumentException>(() => writer.WriteEntry(string.Empty, "entryName")); }
public void WritePaxAttributes_Timestamps() { Dictionary <string, string> extendedAttributes = new(); extendedAttributes.Add("atime", ConvertDateTimeOffsetToDouble(TestAccessTime).ToString("F6", CultureInfo.InvariantCulture)); extendedAttributes.Add("ctime", ConvertDateTimeOffsetToDouble(TestChangeTime).ToString("F6", CultureInfo.InvariantCulture)); using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName, extendedAttributes); SetRegularFile(regularFile); VerifyRegularFile(regularFile, isWritable: true); writer.WriteEntry(regularFile); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { PaxTarEntry regularFile = reader.GetNextEntry() as PaxTarEntry; VerifyRegularFile(regularFile, isWritable: false); Assert.NotNull(regularFile.ExtendedAttributes); Assert.True(regularFile.ExtendedAttributes.Count >= 4); Assert.Contains("path", regularFile.ExtendedAttributes); VerifyExtendedAttributeTimestamp(regularFile, "mtime", TestModificationTime); VerifyExtendedAttributeTimestamp(regularFile, "atime", TestAccessTime); VerifyExtendedAttributeTimestamp(regularFile, "ctime", TestChangeTime); } }
public void Constructor_ConversionFromUstar_From_UnseekableTarReader(TarEntryFormat writerFormat) { using MemoryStream source = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.ustar, "file"); using WrappedStream wrappedSource = new WrappedStream(source, canRead: true, canWrite: false, canSeek: false); using TarReader sourceReader = new TarReader(wrappedSource, leaveOpen: true); UstarTarEntry ustarEntry = sourceReader.GetNextEntry(copyData: false) as UstarTarEntry; V7TarEntry v7Entry = new V7TarEntry(other: ustarEntry); // Convert, and avoid advancing wrappedSource position using MemoryStream destination = new MemoryStream(); using (TarWriter writer = new TarWriter(destination, writerFormat, leaveOpen: true)) { writer.WriteEntry(v7Entry); // Write DataStream exactly where the wrappedSource position was left } destination.Position = 0; // Rewind using (TarReader destinationReader = new TarReader(destination, leaveOpen: false)) { V7TarEntry resultEntry = destinationReader.GetNextEntry() as V7TarEntry; Assert.NotNull(resultEntry); using (StreamReader streamReader = new StreamReader(resultEntry.DataStream)) { Assert.Equal("Hello file", streamReader.ReadToEnd()); } } }
// 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 void Extract_LinkEntry_TargetInsideDirectory_Internal(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(); using MemoryStream archive = new MemoryStream(); using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true)) { TarEntry entry = InvokeTarEntryCreationConstructor(format, entryType, linkName); entry.LinkName = targetPath; writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); TarFile.ExtractToDirectory(archive, baseDir, overwriteFiles: false); Assert.Equal(2, Directory.GetFileSystemEntries(baseDir).Count()); }
public void WriteEntry_RespectDefaultWriterFormat(TarEntryFormat expectedFormat) { using TempDirectory root = new TempDirectory(); string path = Path.Join(root.Path, "file.txt"); File.Create(path).Dispose(); using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, expectedFormat, leaveOpen: true)) { writer.WriteEntry(path, "file.txt"); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream, leaveOpen: false)) { TarEntry entry = reader.GetNextEntry(); Assert.Equal(expectedFormat, entry.Format); Type expectedType = GetTypeForFormat(expectedFormat); Assert.Equal(expectedType, entry.GetType()); } }
public void TarGz_TarWriter_TarReader() { 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 using (FileStream streamToCompress = File.Create(archivePath)) { using GZipStream compressorStream = new GZipStream(streamToCompress, CompressionMode.Compress); using TarWriter writer = new TarWriter(compressorStream); writer.WriteEntry(fileName: filePath, entryName: fileName); } FileInfo fileInfo = new FileInfo(archivePath); Assert.True(fileInfo.Exists); Assert.True(fileInfo.Length > 0); // Verify tar.gz archive contents using (FileStream streamToDecompress = File.OpenRead(archivePath)) { using GZipStream decompressorStream = new GZipStream(streamToDecompress, CompressionMode.Decompress); using TarReader reader = new TarReader(decompressorStream); TarEntry entry = reader.GetNextEntry(); Assert.Equal(TarEntryFormat.Pax, reader.Format); Assert.Equal(fileName, entry.Name); Assert.Null(reader.GetNextEntry()); } }
public void Write_RegularFileEntry_As_V7RegularFileEntry(TarFormat entryFormat) { using MemoryStream archive = new MemoryStream(); using (TarWriter writer = new TarWriter(archive, archiveFormat: TarFormat.V7, leaveOpen: true)) { TarEntry entry = entryFormat switch { TarFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), TarFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), TarFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), _ => throw new FormatException() }; // Should be written as V7RegularFile writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { TarEntry entry = reader.GetNextEntry(); Assert.True(entry is V7TarEntry); Assert.Equal(TarEntryType.V7RegularFile, entry.EntryType); Assert.Null(reader.GetNextEntry()); } }
public void WritePaxAttributes_Timestamps_UserProvided() { Dictionary <string, string> extendedAttributes = new(); extendedAttributes.Add(PaxEaATime, GetTimestampStringFromDateTimeOffset(TestAccessTime)); extendedAttributes.Add(PaxEaCTime, GetTimestampStringFromDateTimeOffset(TestChangeTime)); using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName, extendedAttributes); regularFile.ModificationTime = TestModificationTime; writer.WriteEntry(regularFile); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { PaxTarEntry regularFile = reader.GetNextEntry() as PaxTarEntry; AssertExtensions.GreaterThanOrEqualTo(regularFile.ExtendedAttributes.Count, 4); VerifyExtendedAttributeTimestamp(regularFile, PaxEaMTime, TestModificationTime); VerifyExtendedAttributeTimestamp(regularFile, PaxEaATime, TestAccessTime); VerifyExtendedAttributeTimestamp(regularFile, PaxEaCTime, TestChangeTime); } }
public void WriteEntry_FromUnseekableStream_AdvanceDataStream_WriteFromThatPosition() { 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(); using (TarReader reader = new TarReader(unseekable)) { TarEntry entry = reader.GetNextEntry(); Assert.NotNull(entry); Assert.NotNull(entry.DataStream); entry.DataStream.ReadByte(); // Advance one byte, now the expected string would be "ello file" using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Ustar, leaveOpen: true)) { writer.WriteEntry(entry); } } destination.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(destination)) { TarEntry entry = reader.GetNextEntry(); 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 void ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries() { using TempDirectory root = new TempDirectory(); string firstSegment = "a"; string secondSegment = Path.Join(firstSegment, "b"); string fileWithTwoSegments = Path.Join(secondSegment, "c.txt"); using MemoryStream archive = new MemoryStream(); using (TarWriter writer = new TarWriter(archive, TarFormat.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); writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); TarFile.ExtractToDirectory(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 void GetNextEntry_CopyDataTrue_UnseekableArchive() { string expectedText = "Hello world!"; MemoryStream archive = new MemoryStream(); 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); writer.WriteEntry(entry1); UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir"); writer.WriteEntry(entry2); } archive.Seek(0, SeekOrigin.Begin); using WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false); UstarTarEntry entry; using (TarReader reader = new TarReader(wrapped, leaveOpen: true)) // Unseekable { entry = reader.GetNextEntry(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(reader.GetNextEntry()); Assert.Null(reader.GetNextEntry()); 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 void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposing(bool copyData) { MemoryStream archive = new MemoryStream(); 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 writer.WriteEntry(entry1); UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir"); writer.WriteEntry(entry2); } archive.Seek(0, SeekOrigin.Begin); using WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false); UstarTarEntry entry; Stream oldStream; using (TarReader reader = new TarReader(wrapped)) // Unseekable { entry = reader.GetNextEntry(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 void ThrowIf_AddFile_AfterDispose() { using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream); writer.Dispose(); Assert.Throws <ObjectDisposedException>(() => writer.WriteEntry("fileName", "entryName")); }
public void WriteEntry_AfterDispose_Throws() { using MemoryStream archiveStream = new MemoryStream(); TarWriter writer = new TarWriter(archiveStream); writer.Dispose(); PaxTarEntry entry = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); Assert.Throws <ObjectDisposedException>(() => writer.WriteEntry(entry)); }
public void GetNextEntry_CopyDataFalse_UnseekableArchive_Exceptions() { MemoryStream archive = new MemoryStream(); 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 writer.WriteEntry(entry1); UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir"); writer.WriteEntry(entry2); } archive.Seek(0, SeekOrigin.Begin); using WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false); UstarTarEntry entry; using (TarReader reader = new TarReader(wrapped)) // Unseekable { entry = reader.GetNextEntry(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(reader.GetNextEntry()); Assert.Null(reader.GetNextEntry()); // 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 void WritePaxAttributes_LongLinkName_AutomaticallyAdded() { using MemoryStream archiveStream = new MemoryStream(); string longSymbolicLinkName = new string('a', 101); string longHardLinkName = new string('b', 101); using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry symlink = new PaxTarEntry(TarEntryType.SymbolicLink, "symlink"); symlink.LinkName = longSymbolicLinkName; writer.WriteEntry(symlink); PaxTarEntry hardlink = new PaxTarEntry(TarEntryType.HardLink, "hardlink"); hardlink.LinkName = longHardLinkName; writer.WriteEntry(hardlink); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { PaxTarEntry symlink = reader.GetNextEntry() 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 = reader.GetNextEntry() 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]); } }
// 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 void WriteTimestampsBeyondOctalLimitInPax() { 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(); using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: true)) { writer.WriteEntry(entry); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { PaxTarEntry readEntry = reader.GetNextEntry() 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); } }
public void BlockAlignmentPadding_DoesNotAffectNextEntries(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); writer.WriteEntry(entry1); var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); writer.WriteEntry(entry2); } archive.Position = 0; using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); using var reader = new TarReader(unseekable); TarEntry e = reader.GetNextEntry(copyData); Assert.Equal(contentSize, e.Length); byte[] buffer = new byte[contentSize]; while (e.DataStream.Read(buffer) > 0) { ; } AssertExtensions.SequenceEqual(fileContents, buffer); e = reader.GetNextEntry(copyData); Assert.Equal(0, e.Length); e = reader.GetNextEntry(copyData); Assert.Null(e); }
// 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 void WriteTimestampsBeyondEpochalypseInPax() { 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(); using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: true)) { writer.WriteEntry(entry); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { PaxTarEntry readEntry = reader.GetNextEntry() 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 void VerifyChecksumV7() { using MemoryStream archive = new MemoryStream(); 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 entry.DataStream.Write(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 ' writer.WriteEntry(entry); Assert.Equal(2664, entry.Checksum); } archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { TarEntry entry = reader.GetNextEntry(); Assert.Equal(2664, entry.Checksum); } }
public void WriteHardLink() { using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) { V7TarEntry hardLink = new V7TarEntry(TarEntryType.HardLink, InitialEntryName); SetHardLink(hardLink); VerifyHardLink(hardLink); writer.WriteEntry(hardLink); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { V7TarEntry hardLink = reader.GetNextEntry() as V7TarEntry; VerifyHardLink(hardLink); } }
public void WriteRegularFile() { using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) { V7TarEntry oldRegularFile = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); SetRegularFile(oldRegularFile); VerifyRegularFile(oldRegularFile, isWritable: true); writer.WriteEntry(oldRegularFile); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { V7TarEntry oldRegularFile = reader.GetNextEntry() as V7TarEntry; VerifyRegularFile(oldRegularFile, isWritable: false); } }
public void WriteDirectory() { using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) { V7TarEntry directory = new V7TarEntry(TarEntryType.Directory, InitialEntryName); SetDirectory(directory); VerifyDirectory(directory); writer.WriteEntry(directory); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { V7TarEntry directory = reader.GetNextEntry() as V7TarEntry; VerifyDirectory(directory); } }
public void Extract_LinkEntry_TargetOutsideDirectory(TarEntryType entryType) { using MemoryStream archive = new MemoryStream(); using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry = new UstarTarEntry(entryType, "link"); entry.LinkName = PlatformDetection.IsWindows ? @"C:\Windows\System32\notepad.exe" : "/usr/bin/nano"; writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); using TempDirectory root = new TempDirectory(); Assert.Throws <IOException>(() => TarFile.ExtractToDirectory(archive, root.Path, overwriteFiles: false)); Assert.Equal(0, Directory.GetFileSystemEntries(root.Path).Count()); }
public void WriteCharacterDevice() { using MemoryStream archiveStream = new MemoryStream(); using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry charDevice = new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName); SetCharacterDevice(charDevice); VerifyCharacterDevice(charDevice); writer.WriteEntry(charDevice); } archiveStream.Position = 0; using (TarReader reader = new TarReader(archiveStream)) { GnuTarEntry charDevice = reader.GetNextEntry() as GnuTarEntry; VerifyCharacterDevice(charDevice); } }