Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,48 @@ private void Verify_Extract_SpecialFiles(string destination, PosixTarEntry entry

AssertFileModeEquals(destination, TestPermission1);
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows))]
[InlineData(TarEntryFormat.V7)]
[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);
UnixFileMode executeBitsMask = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
UnixFileMode expectedExecutableBits = executableMode & ~UMask & executeBitsMask;
UnixFileMode actualExecutableBits = extractedMode & executeBitsMask;
Assert.Equal(expectedExecutableBits, actualExecutableBits);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposin
oldStream = entry.DataStream;

entry.DataStream = new MemoryStream(); // Substitution, setter should dispose the previous stream
using(StreamWriter streamWriter = new StreamWriter(entry.DataStream, leaveOpen: true))
using (StreamWriter streamWriter = new StreamWriter(entry.DataStream, leaveOpen: true))
{
streamWriter.WriteLine("Substituted");
}
Expand Down Expand Up @@ -413,5 +413,57 @@ public void GetNextEntry_UnseekableArchive_DisposedDataStream_NotRead_DoesNotThr

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

[Fact]
public void Read_Archive_With_Unsupported_EntryType()
{
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)TarEntryType.SparseFile; // Unsupported entry type

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);
Assert.Throws<NotSupportedException>(() => reader.GetNextEntry());
}
}
}
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();
}

[Theory]
[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);
}
}
}
}
Loading