diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs index 2afcfb8a9f23df..e1166a066814b3 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs @@ -391,6 +391,9 @@ private int WritePosixName(Span buffer) // Writes all the common fields shared by all formats into the specified spans. private int WriteCommonFields(Span buffer, long actualLength, TarEntryType actualEntryType) { + // Don't write an empty LinkName if the entry is a hardlink or symlink + Debug.Assert(!string.IsNullOrEmpty(_linkName) ^ (_typeFlag is not TarEntryType.SymbolicLink and not TarEntryType.HardLink)); + int checksum = 0; if (_mode > 0) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs index 7d3977eda68df4..ee5da966c29947 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs @@ -209,6 +209,7 @@ private async Task ReadFileFromDiskAndWriteToArchiveStreamAsEntryAsync(string fu /// /// /// + /// The entry type is or and the is or empty. /// The archive stream is disposed. /// The entry type of the is not supported for writing. /// An I/O problem occurred. @@ -216,6 +217,7 @@ public void WriteEntry(TarEntry entry) { ObjectDisposedException.ThrowIf(_isDisposed, this); ArgumentNullException.ThrowIfNull(entry); + ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName); WriteEntryInternal(entry); } @@ -250,6 +252,7 @@ public void WriteEntry(TarEntry entry) /// /// /// + /// The entry type is or and the is or empty. /// The archive stream is disposed. /// The entry type of the is not supported for writing. /// An I/O problem occurred. @@ -262,6 +265,7 @@ public Task WriteEntryAsync(TarEntry entry, CancellationToken cancellationToken ObjectDisposedException.ThrowIf(_isDisposed, this); ArgumentNullException.ThrowIfNull(entry); + ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName); return WriteEntryAsyncInternal(entry, cancellationToken); } @@ -365,5 +369,16 @@ private async ValueTask WriteFinalRecordsAsync() return (fullPath, actualEntryName); } + + private static void ValidateEntryLinkName(TarEntryType entryType, string? linkName) + { + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + if (string.IsNullOrEmpty(linkName)) + { + throw new ArgumentException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty, "entry"); + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs index d591ba9b6542a6..925daef99d5167 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs @@ -167,6 +167,10 @@ public void Write_Long_Name(TarEntryType entryType) using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + entry.LinkName = "linktarget"; + } writer.WriteEntry(entry); } @@ -231,5 +235,15 @@ public void Write_LongName_And_LongLinkName(TarEntryType entryType) Assert.Equal(longLinkName, entry.LinkName); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new GnuTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs index 6c9e79d83a1eff..1e81fb7b1e8a01 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs @@ -485,5 +485,15 @@ public void WriteTimestampsBeyondOctalLimitInPax() Assert.Equal(overLimitTimestamp, actualCTime); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new PaxTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs index da3b69051ab347..c1f2d07562d388 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs @@ -152,5 +152,15 @@ public void WriteFifo() VerifyFifo(fifo); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new UstarTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs index fb0cbb980ee6e2..51be90c74b07c9 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs @@ -92,5 +92,15 @@ public void WriteDirectory() VerifyDirectory(directory); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new V7TarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs index b33e2bf7add22a..1e77e548d4a6f7 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs @@ -183,6 +183,10 @@ public async Task Write_Long_Name_Async(TarEntryType entryType) await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + entry.LinkName = "linktarget"; + } await writer.WriteEntryAsync(entry); } @@ -252,5 +256,15 @@ public async Task Write_LongName_And_LongLinkName_Async(TarEntryType entryType) } } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new GnuTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs index 688c918baedf15..b0c9d636420b52 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs @@ -505,5 +505,15 @@ public async Task WriteTimestampsBeyondOctalLimitInPax_Async() Assert.Equal(overLimitTimestamp, actualCTime); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new PaxTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs index 1266e7216a0126..b3e207d434f78e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs @@ -153,5 +153,15 @@ public async Task WriteFifo_Async() VerifyFifo(fifo); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new UstarTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs index 4408477ee00006..3e41f263ef02e4 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs @@ -93,5 +93,15 @@ public async Task WriteDirectory_Async() VerifyDirectory(directory); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new V7TarEntry(entryType, "link"))); + } } }