Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,46 @@ private void Verify_Extract_SpecialFiles(string destination, PosixTarEntry entry

AssertFileModeEquals(destination, TestPermission1);
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows))]
[InlineData(TarEntryFormat.Ustar)]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
public void Archive_And_Extract_Executable_PreservesExecutableBit(TarEntryFormat format)
{
using TempDirectory root = new TempDirectory();

string executableFileName = "testexecutable.sh";
string executableFilePath = Path.Join(root.Path, executableFileName);

File.WriteAllText(executableFilePath, "#!/bin/bash\necho 'test'\n");

UnixFileMode executableMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherExecute;
File.SetUnixFileMode(executableFilePath, executableMode);

using MemoryStream archiveStream = new MemoryStream();
using (TarWriter writer = new TarWriter(archiveStream, format, leaveOpen: true))
{
writer.WriteEntry(executableFilePath, executableFileName);
}

string extractedFileName = "extracted_executable.sh";
string extractedFilePath = Path.Join(root.Path, extractedFileName);

archiveStream.Seek(0, SeekOrigin.Begin);
using (TarReader reader = new TarReader(archiveStream))
{
TarEntry entry = reader.GetNextEntry();
Assert.NotNull(entry);
entry.ExtractToFile(extractedFilePath, overwrite: false);
}

UnixFileMode extractedMode = File.GetUnixFileMode(extractedFilePath);
Assert.True((extractedMode & UnixFileMode.UserExecute) != 0, "User execute bit should be set");
Assert.True((extractedMode & UnixFileMode.GroupExecute) != 0, "Group execute bit should be set");
Assert.True((extractedMode & UnixFileMode.OtherExecute) != 0, "Other execute bit should be set");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -413,5 +413,64 @@ public void GetNextEntry_UnseekableArchive_DisposedDataStream_NotRead_DoesNotThr

Assert.Null(reader.GetNextEntry());
}

[Theory]
[InlineData(TarEntryType.MultiVolume)]
[InlineData(TarEntryType.SparseFile)]
[InlineData(TarEntryType.TapeVolume)]
public void Read_Archive_With_Unsupported_EntryType(TarEntryType unsupportedType)
{
using MemoryStream archiveStream = new MemoryStream();

byte[] header = new byte[512];

byte[] nameBytes = System.Text.Encoding.UTF8.GetBytes("unsupported_entry");
nameBytes.CopyTo(header.AsSpan(0, nameBytes.Length));

// Set mode field (octal 644 = rw-r--r--)
System.Text.Encoding.UTF8.GetBytes("0000644 ").CopyTo(header.AsSpan(100, 8));
// Set uid field
System.Text.Encoding.UTF8.GetBytes("0000000 ").CopyTo(header.AsSpan(108, 8));
// Set gid field
System.Text.Encoding.UTF8.GetBytes("0000000 ").CopyTo(header.AsSpan(116, 8));
// Set size field
System.Text.Encoding.UTF8.GetBytes("00000000000 ").CopyTo(header.AsSpan(124, 12));
// Set mtime field
System.Text.Encoding.UTF8.GetBytes("00000000000 ").CopyTo(header.AsSpan(136, 12));

header[156] = (byte)unsupportedType;

System.Text.Encoding.UTF8.GetBytes("ustar ").CopyTo(header.AsSpan(257, 6));
System.Text.Encoding.UTF8.GetBytes(" \0").CopyTo(header.AsSpan(263, 2));

// Calculate checksum - the checksum field itself should be treated as spaces
int checksum = 0;
for (int i = 0; i < header.Length; i++)
{
if (i >= 148 && i < 156)
{
checksum += (byte)' ';
}
else
{
checksum += header[i];
}
}

string checksumStr = Convert.ToString(checksum, 8).PadLeft(6, '0') + "\0 ";
System.Text.Encoding.UTF8.GetBytes(checksumStr).CopyTo(header.AsSpan(148, 8));

archiveStream.Write(header);
archiveStream.Write(new byte[1024]);

archiveStream.Seek(0, SeekOrigin.Begin);

using TarReader reader = new TarReader(archiveStream);
TarEntry entry = reader.GetNextEntry();

Assert.NotNull(entry);
Assert.Equal(unsupportedType, entry.EntryType);
Assert.Equal("unsupported_entry", entry.Name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,5 +304,39 @@ public void CreateEntryFromFileOwnedByNonExistentGroupAndUser(TarEntryFormat f)
}
}, f.ToString(), new RemoteInvokeOptions { RunAsSudo = true }).Dispose();
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows))]
[InlineData(TarEntryFormat.V7)]
[InlineData(TarEntryFormat.Ustar)]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
public void Create_Entry_From_HiddenFile(TarEntryFormat format)
{
using TempDirectory root = new TempDirectory();

string hiddenFileName = ".hidden_file";
string hiddenFilePath = Path.Join(root.Path, hiddenFileName);

File.WriteAllText(hiddenFilePath, "This is a hidden file");

using MemoryStream archiveStream = new MemoryStream();
using (TarWriter writer = new TarWriter(archiveStream, format, leaveOpen: true))
{
writer.WriteEntry(hiddenFilePath, hiddenFileName);
}

archiveStream.Seek(0, SeekOrigin.Begin);
using (TarReader reader = new TarReader(archiveStream))
{
TarEntry entry = reader.GetNextEntry();
Assert.NotNull(entry);
Assert.Equal(hiddenFileName, entry.Name);
Assert.NotNull(entry.DataStream);

using StreamReader sr = new StreamReader(entry.DataStream);
string content = sr.ReadToEnd();
Assert.Equal("This is a hidden file", content);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,5 +206,40 @@ public void Add_SymbolicLink(TarEntryFormat format, bool createTarget)
Assert.Null(reader.GetNextEntry());
}
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))]
[InlineData(TarEntryFormat.V7)]
[InlineData(TarEntryFormat.Ustar)]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
public void WindowsPath_WithBackslashes_ConvertedToForwardSlashes(TarEntryFormat format)
{
using TempDirectory root = new TempDirectory();

string subDir = Path.Join(root.Path, "subdir");
Directory.CreateDirectory(subDir);

string fileName = "testfile.txt";
string filePath = Path.Join(subDir, fileName);
File.WriteAllText(filePath, "test content");

using MemoryStream archiveStream = new MemoryStream();
using (TarWriter writer = new TarWriter(archiveStream, format, leaveOpen: true))
{
string windowsStylePath = $"subdir\\{fileName}";
writer.WriteEntry(filePath, windowsStylePath);
}

archiveStream.Seek(0, SeekOrigin.Begin);
using (TarReader reader = new TarReader(archiveStream))
{
TarEntry entry = reader.GetNextEntry();
Assert.NotNull(entry);

Assert.DoesNotContain("\\", entry.Name);
Assert.Contains("/", entry.Name);
Assert.Equal("subdir/testfile.txt", entry.Name);
}
}
}
}
Loading