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 @@ -565,9 +565,11 @@ private void ReadUstarAttributes(ReadOnlySpan<byte> buffer)
// Name, if the full path did not fit in the Name byte array.
if (!string.IsNullOrEmpty(_prefix))
{
// Prefix never has a leading separator, so we add it.
// It should always be a forward slash for compatibility
_name = $"{_prefix}/{_name}";
// Prefix should not have a trailing separator, but to avoid producing a
// synthesized double slash for archives that do include one, only add
// the separator when it is not already present.
// It should always be a forward slash for compatibility.
_name = _prefix.EndsWith('/') ? $"{_prefix}{_name}" : $"{_prefix}/{_name}";
Comment on lines +568 to +572
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,44 @@ public void Read_PaxEntryWithOnlyLinkpath_PreservesUstarPrefix()
Assert.Null(reader2.GetNextEntry());
}

[Fact]
public void Read_UstarEntry_PrefixWithTrailingSlash_DoesNotDuplicateSeparator()
{
// Some archives may write the ustar prefix field with a trailing slash already
// included. TarReader must not synthesize a double slash when combining prefix and name.
string prefix = "./sdk/";
string nameField = "file.txt";
string expectedName = "./sdk/file.txt";

using MemoryStream archiveStream = new MemoryStream();

byte[] entryHeader = new byte[512];
Encoding.UTF8.GetBytes(nameField).CopyTo(entryHeader.AsSpan(0));
Encoding.UTF8.GetBytes("0000644\0").CopyTo(entryHeader.AsSpan(100, 8));
Encoding.UTF8.GetBytes("0000000\0").CopyTo(entryHeader.AsSpan(108, 8));
Encoding.UTF8.GetBytes("0000000\0").CopyTo(entryHeader.AsSpan(116, 8));
Encoding.UTF8.GetBytes("00000000000\0").CopyTo(entryHeader.AsSpan(124, 12));
Encoding.UTF8.GetBytes("00000000000\0").CopyTo(entryHeader.AsSpan(136, 12));
entryHeader[156] = (byte)'0'; // RegularFile
Encoding.UTF8.GetBytes("ustar\0").CopyTo(entryHeader.AsSpan(257, 6));
Encoding.UTF8.GetBytes("00").CopyTo(entryHeader.AsSpan(263, 2));
Encoding.UTF8.GetBytes(prefix).CopyTo(entryHeader.AsSpan(345));

WriteHeaderChecksum(entryHeader);
archiveStream.Write(entryHeader);

// End-of-archive markers.
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(expectedName, entry.Name);
Assert.DoesNotContain("//", entry.Name);
Assert.Null(reader.GetNextEntry());
}

[Theory]
[InlineData("PaxExtendedAttributes", MaxMetadataBlockSize - 100)]
[InlineData("GnuLongPath", MaxMetadataBlockSize)]
Expand Down