diff --git a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx
index 001d85b764bcaf..5308e9153c9791 100644
--- a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx
+++ b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx
@@ -205,6 +205,9 @@
The entry is a symbolic link or a hard link but the LinkName field is null or empty.
+ Entry type '{0}' not supported.
+
+
Entry type '{0}' not supported in format '{1}'.
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs
index e43ae6973607dc..5578707d64eb18 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs
@@ -20,7 +20,7 @@ internal sealed partial class TarHeader
// Attempts to retrieve the next header from the specified tar archive stream.
// Throws if end of stream is reached or if any data type conversion fails.
// Returns a valid TarHeader object if the attributes were read successfully, null otherwise.
- internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat)
+ internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock)
{
// The four supported formats have a header that fits in the default record size
Span buffer = stackalloc byte[TarHelpers.RecordSize];
@@ -28,7 +28,7 @@ internal sealed partial class TarHeader
archiveStream.ReadExactly(buffer);
TarHeader? header = TryReadAttributes(initialFormat, buffer);
- if (header != null)
+ if (header != null && processDataBlock)
{
header.ProcessDataBlock(archiveStream, copyData);
}
@@ -39,7 +39,7 @@ internal sealed partial class TarHeader
// Asynchronously attempts read all the fields of the next header.
// Throws if end of stream is reached or if any data type conversion fails.
// Returns true if all the attributes were read successfully, false otherwise.
- internal static async ValueTask TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, CancellationToken cancellationToken)
+ internal static async ValueTask TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -50,7 +50,7 @@ internal sealed partial class TarHeader
await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false);
TarHeader? header = TryReadAttributes(initialFormat, buffer.Span);
- if (header != null)
+ if (header != null && processDataBlock)
{
await header.ProcessDataBlockAsync(archiveStream, copyData, cancellationToken).ConfigureAwait(false);
}
@@ -180,7 +180,7 @@ internal void ReplaceNormalAttributesWithExtended(Dictionary? di
// will get all the data section read and the stream pointer positioned at the beginning of the next header.
// - Block, Character, Directory, Fifo, HardLink and SymbolicLink typeflag entries have no data section so the archive stream pointer will be positioned at the beginning of the next header.
// - All other typeflag entries with a data section will generate a stream wrapping the data section: SeekableSubReadStream for seekable archive streams, and SubReadStream for unseekable archive streams.
- private void ProcessDataBlock(Stream archiveStream, bool copyData)
+ internal void ProcessDataBlock(Stream archiveStream, bool copyData)
{
bool skipBlockAlignmentPadding = true;
@@ -199,6 +199,10 @@ private void ProcessDataBlock(Stream archiveStream, bool copyData)
case TarEntryType.HardLink:
case TarEntryType.SymbolicLink:
// No data section
+ if (_size > 0)
+ {
+ throw new FormatException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag));
+ }
break;
case TarEntryType.RegularFile:
case TarEntryType.V7RegularFile: // Treated as regular file
@@ -257,6 +261,10 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca
case TarEntryType.HardLink:
case TarEntryType.SymbolicLink:
// No data section
+ if (_size > 0)
+ {
+ throw new FormatException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag));
+ }
break;
case TarEntryType.RegularFile:
case TarEntryType.V7RegularFile: // Treated as regular file
@@ -311,6 +319,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca
{
MemoryStream copiedData = new MemoryStream();
TarHelpers.CopyBytes(archiveStream, copiedData, _size);
+ // Reset position pointer so the user can do the first DataStream read from the beginning
+ copiedData.Position = 0;
return copiedData;
}
@@ -336,6 +346,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca
{
MemoryStream copiedData = new MemoryStream();
await TarHelpers.CopyBytesAsync(archiveStream, copiedData, size, cancellationToken).ConfigureAwait(false);
+ // Reset position pointer so the user can do the first DataStream read from the beginning
+ copiedData.Position = 0;
return copiedData;
}
@@ -396,12 +408,13 @@ TarEntryType.LongLink or
TarEntryType.LongPath or
TarEntryType.MultiVolume or
TarEntryType.RenamedOrSymlinked or
- TarEntryType.SparseFile or
TarEntryType.TapeVolume => TarEntryFormat.Gnu,
// V7 is the only one that uses 'V7RegularFile'.
TarEntryType.V7RegularFile => TarEntryFormat.V7,
+ TarEntryType.SparseFile => throw new NotSupportedException(string.Format(SR.TarEntryTypeNotSupported, header._typeFlag)),
+
// We can quickly determine the *minimum* possible format if the entry type
// is the POSIX 'RegularFile', although later we could upgrade it to PAX or GNU
_ => (header._typeFlag == TarEntryType.RegularFile) ? TarEntryFormat.Ustar : TarEntryFormat.V7
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs
index 898daf1e6c3ed7..c2a1b3b854c14b 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs
@@ -203,6 +203,12 @@ internal static TarEntryType GetCorrectTypeFlagForFormat(TarEntryFormat format,
internal static T ParseOctal(ReadOnlySpan buffer) where T : struct, INumber
{
buffer = TrimEndingNullsAndSpaces(buffer);
+ buffer = TrimLeadingNullsAndSpaces(buffer);
+
+ if (buffer.Length == 0)
+ {
+ return T.Zero;
+ }
T octalFactor = T.CreateTruncating(8u);
T value = T.Zero;
@@ -243,6 +249,17 @@ internal static ReadOnlySpan TrimEndingNullsAndSpaces(ReadOnlySpan b
return buffer.Slice(0, trimmedLength);
}
+ private static ReadOnlySpan TrimLeadingNullsAndSpaces(ReadOnlySpan buffer)
+ {
+ int newStart = 0;
+ while (newStart < buffer.Length && buffer[newStart] is 0 or 32)
+ {
+ newStart++;
+ }
+
+ return buffer.Slice(newStart);
+ }
+
// Returns the ASCII string contained in the specified buffer of bytes,
// removing the trailing null or space chars.
internal static string GetTrimmedAsciiString(ReadOnlySpan buffer) => GetTrimmedString(buffer, Encoding.ASCII);
@@ -351,7 +368,7 @@ TarEntryType.RegularFile or
throw new FormatException(string.Format(SR.TarInvalidFormat, archiveFormat));
}
- throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupported, entryType, archiveFormat));
+ throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupportedInFormat, entryType, archiveFormat));
}
}
}
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs
index 915cb81caab664..5328d61190280b 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs
@@ -19,7 +19,6 @@ public sealed class TarReader : IDisposable, IAsyncDisposable
private readonly bool _leaveOpen;
private TarEntry? _previouslyReadEntry;
private List? _dataStreamsToDispose;
- private bool _readFirstEntry;
private bool _reachedEndMarkers;
internal Stream _archiveStream;
@@ -44,7 +43,6 @@ public TarReader(Stream archiveStream, bool leaveOpen = false)
_previouslyReadEntry = null;
_isDisposed = false;
- _readFirstEntry = false;
_reachedEndMarkers = false;
}
@@ -124,11 +122,6 @@ public async ValueTask DisposeAsync()
TarHeader? header = TryGetNextEntryHeader(copyData);
if (header != null)
{
- if (!_readFirstEntry)
- {
- _readFirstEntry = true;
- }
-
TarEntry entry = header._format switch
{
TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ?
@@ -282,11 +275,6 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel
TarHeader? header = await TryGetNextEntryHeaderAsync(copyData, cancellationToken).ConfigureAwait(false);
if (header != null)
{
- if (!_readFirstEntry)
- {
- _readFirstEntry = true;
- }
-
TarEntry entry = header._format switch
{
TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ?
@@ -319,7 +307,7 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel
{
Debug.Assert(!_reachedEndMarkers);
- TarHeader? header = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Unknown);
+ TarHeader? header = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Unknown, processDataBlock: true);
if (header == null)
{
@@ -361,7 +349,7 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel
Debug.Assert(!_reachedEndMarkers);
- TarHeader? header = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Unknown, cancellationToken).ConfigureAwait(false);
+ TarHeader? header = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Unknown, processDataBlock: true, cancellationToken).ConfigureAwait(false);
if (header == null)
{
return null;
@@ -397,9 +385,10 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel
// and returns the actual entry with the processed extended attributes saved in the _extendedAttributes dictionary.
private bool TryProcessExtendedAttributesHeader(TarHeader extendedAttributesHeader, bool copyData, [NotNullWhen(returnValue: true)] out TarHeader? actualHeader)
{
- actualHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Pax);
+ // Don't process the data block of the actual entry just yet, because there's a slim chance
+ // that the extended attributes contain a size that we need to override in the header
+ actualHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Pax, processDataBlock: false);
- // Now get the actual entry
if (actualHeader == null)
{
return false;
@@ -417,6 +406,9 @@ TarEntryType.LongLink or
// Replace all the attributes representing standard fields with the extended ones, if any
actualHeader.ReplaceNormalAttributesWithExtended(extendedAttributesHeader.ExtendedAttributes);
+ // We retrieved the extended attributes, now we can read the data, and always with the right size
+ actualHeader.ProcessDataBlock(_archiveStream, copyData);
+
return true;
}
@@ -426,8 +418,9 @@ TarEntryType.LongLink or
{
cancellationToken.ThrowIfCancellationRequested();
- // Now get the actual entry
- TarHeader? actualHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Pax, cancellationToken).ConfigureAwait(false);
+ // Don't process the data block of the actual entry just yet, because there's a slim chance
+ // that the extended attributes contain a size that we need to override in the header
+ TarHeader? actualHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Pax, processDataBlock: false, cancellationToken).ConfigureAwait(false);
if (actualHeader == null)
{
return null;
@@ -451,6 +444,9 @@ TarEntryType.LongLink or
// Replace all the attributes representing standard fields with the extended ones, if any
actualHeader.ReplaceNormalAttributesWithExtended(extendedAttributesHeader.ExtendedAttributes);
+ // We retrieved the extended attributes, now we can read the data, and always with the right size
+ actualHeader.ProcessDataBlock(_archiveStream, copyData);
+
return actualHeader;
}
@@ -460,7 +456,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta
{
finalHeader = new(TarEntryFormat.Gnu);
- TarHeader? secondHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu);
+ TarHeader? secondHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true);
// Get the second entry, which is the actual entry
if (secondHeader == null)
@@ -478,7 +474,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta
if ((header._typeFlag is TarEntryType.LongLink && secondHeader._typeFlag is TarEntryType.LongPath) ||
(header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink))
{
- TarHeader? thirdHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu);
+ TarHeader? thirdHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true);
// Get the third entry, which is the actual entry
if (thirdHeader == null)
@@ -537,7 +533,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta
cancellationToken.ThrowIfCancellationRequested();
// Get the second entry, which is the actual entry
- TarHeader? secondHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, cancellationToken).ConfigureAwait(false);
+ TarHeader? secondHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true, cancellationToken).ConfigureAwait(false);
if (secondHeader == null)
{
return null;
@@ -556,7 +552,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta
(header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink))
{
// Get the third entry, which is the actual entry
- TarHeader? thirdHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, cancellationToken).ConfigureAwait(false);
+ TarHeader? thirdHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true, cancellationToken).ConfigureAwait(false);
if (thirdHeader == null)
{
return null;
diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs
index 14087fb030d020..69a543a206d4b9 100644
--- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
@@ -125,5 +126,245 @@ public Task Read_Archive_LongFileName_Over100_Under255_Async(TarEntryFormat form
[InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)]
public Task Read_Archive_LongPath_Over255_Async(TarEntryFormat format, TestTarFormat testFormat) =>
Read_Archive_LongPath_Over255_Async_Internal(format, testFormat);
+
+ [Theory]
+ [MemberData(nameof(GetV7TestCaseNames))]
+ public Task ReadDataStreamOfTarGzV7Async(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.v7, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetUstarTestCaseNames))]
+ public Task ReadDataStreamOfTarGzUstarAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.ustar, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadDataStreamOfTarGzPaxAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadDataStreamOfTarGzPaxGeaAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax_gea, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadDataStreamOfTarGzOldGnuAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.oldgnu, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadDataStreamOfTarGzGnuAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.gnu, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetV7TestCaseNames))]
+ public Task ReadCopiedDataStreamOfTarGzV7Async(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.v7, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetUstarTestCaseNames))]
+ public Task ReadCopiedDataStreamOfTarGzUstarAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.ustar, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadCopiedDataStreamOfTarGzPaxAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadCopiedDataStreamOfTarGzPaxGeaAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax_gea, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadCopiedDataStreamOfTarGzOldGnuAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.oldgnu, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public Task ReadCopiedDataStreamOfTarGzGnuAsync(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.gnu, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetGoLangTarTestCaseNames))]
+ public Task ReadDataStreamOfExternalAssetsGoLangAsync(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternalAsync("golang_tar", testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetNodeTarTestCaseNames))]
+ public Task ReadDataStreamOfExternalAssetsNodeAsync(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternalAsync("node-tar", testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetRsTarTestCaseNames))]
+ public Task ReadDataStreamOfExternalAssetsRsAsync(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternalAsync("tar-rs", testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetGoLangTarTestCaseNames))]
+ public Task ReadCopiedDataStreamOfExternalAssetsGoLangAsync(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternalAsync("golang_tar", testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetNodeTarTestCaseNames))]
+ public Task ReadCopiedDataStreamOfExternalAssetsNodeAsync(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternalAsync("node-tar", testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetRsTarTestCaseNames))]
+ public Task ReadCopiedDataStreamOfExternalAssetsRsAsync(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternalAsync("tar-rs", testCaseName, copyData: true);
+
+ [Fact]
+ public async Task Throw_FifoContainsNonZeroDataSectionAsync()
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "hdr-only");
+ await using TarReader reader = new TarReader(archiveStream);
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ Assert.NotNull(await reader.GetNextEntryAsync());
+ await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync());
+ }
+
+ [Fact]
+ public async Task Throw_SingleExtendedAttributesEntryWithNoActualEntryAsync()
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "pax-path-hdr");
+ await using TarReader reader = new TarReader(archiveStream);
+ await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync());
+ }
+
+ [Theory]
+ [InlineData("tar-rs", "spaces")]
+ [InlineData("golang_tar", "v7")]
+ public async Task AllowSpacesInOctalFieldsAsync(string folderName, string testCaseName)
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, folderName, testCaseName);
+ await using TarReader reader = new TarReader(archiveStream);
+ TarEntry entry;
+ while ((entry = await reader.GetNextEntryAsync()) != null)
+ {
+ AssertExtensions.GreaterThan(entry.Checksum, 0);
+ AssertExtensions.GreaterThan((int)entry.Mode, 0);
+ }
+ }
+
+ [Theory]
+ [InlineData("pax-multi-hdrs")] // Multiple consecutive PAX metadata entries
+ [InlineData("gnu-multi-hdrs")] // Multiple consecutive GNU metadata entries
+ [InlineData("neg-size")] // Garbage chars
+ [InlineData("invalid-go17")] // Many octal fields are all zero chars
+ [InlineData("issue11169")] // Checksum with null in the middle
+ [InlineData("issue10968")] // Garbage chars
+ [InlineData("writer-big")] // The size field contains an euro char
+ public async Task Throw_ArchivesWithRandomCharsAsync(string testCaseName)
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", testCaseName);
+ await using TarReader reader = new TarReader(archiveStream);
+ await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync());
+ }
+
+ [Fact]
+ public async Task GarbageEntryChecksumZeroReturnNullAsync()
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "issue12435");
+ await using TarReader reader = new TarReader(archiveStream);
+ Assert.Null(await reader.GetNextEntryAsync());
+ }
+
+ [Theory]
+ [InlineData("golang_tar", "gnu-nil-sparse-data")]
+ [InlineData("golang_tar", "gnu-nil-sparse-hole")]
+ [InlineData("golang_tar", "gnu-sparse-big")]
+ [InlineData("golang_tar", "sparse-formats")]
+ [InlineData("tar-rs", "sparse-1")]
+ [InlineData("tar-rs", "sparse")]
+ public async Task SparseEntryNotSupportedAsync(string testFolderName, string testCaseName)
+ {
+ // Currently sparse entries are not supported.
+
+ // There are PAX archives archives in the golang folder that have extended attributes for treating a regular file as a sparse file.
+ // Sparse entries were created for the GNU format, so they are very rare entry types which are excluded from this test method:
+ // pax-nil-sparse-data, pax-nil-sparse-hole, pax-sparse-big
+
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName);
+ await using TarReader reader = new TarReader(archiveStream);
+ await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync());
+ }
+
+ [Fact]
+ public async Task DirectoryListRegularFileAndSparseAsync()
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "gnu-incremental");
+ await using TarReader reader = new TarReader(archiveStream);
+ TarEntry directoryList = await reader.GetNextEntryAsync();
+
+ Assert.Equal(TarEntryType.DirectoryList, directoryList.EntryType);
+ Assert.NotNull(directoryList.DataStream);
+ Assert.Equal(14, directoryList.Length);
+
+ Assert.NotNull(await reader.GetNextEntryAsync()); // Just a regular file
+
+ await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); // Sparse
+ }
+
+ [Fact]
+ public async Task PaxSizeLargerThanMaxAllowedByStreamAsync()
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "writer-big-long");
+ await using TarReader reader = new TarReader(archiveStream);
+ // The extended attribute 'size' has the value 17179869184
+ // Exception message: Stream length must be non-negative and less than 2^31 - 1 - origin
+ await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync());
+ }
+
+ private static async Task VerifyDataStreamOfTarUncompressedInternalAsync(string testFolderName, string testCaseName, bool copyData)
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName);
+ await VerifyDataStreamOfTarInternalAsync(archiveStream, copyData);
+ }
+
+ private static async Task VerifyDataStreamOfTarGzInternalAsync(TestTarFormat testTarFormat, string testCaseName, bool copyData)
+ {
+ await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.GZip, testTarFormat, testCaseName);
+ await using GZipStream decompressor = new GZipStream(archiveStream, CompressionMode.Decompress);
+ await VerifyDataStreamOfTarInternalAsync(decompressor, copyData);
+ }
+
+ private static async Task VerifyDataStreamOfTarInternalAsync(Stream archiveStream, bool copyData)
+ {
+ await using TarReader reader = new TarReader(archiveStream);
+
+ TarEntry entry;
+
+ await using MemoryStream ms = new MemoryStream();
+ while ((entry = await reader.GetNextEntryAsync(copyData)) != null)
+ {
+ if (entry.EntryType is TarEntryType.V7RegularFile or TarEntryType.RegularFile)
+ {
+ if (entry.Length == 0)
+ {
+ Assert.Null(entry.DataStream);
+ }
+ else
+ {
+ Assert.NotNull(entry.DataStream);
+ Assert.Equal(entry.DataStream.Length, entry.Length);
+ if (copyData)
+ {
+ Assert.True(entry.DataStream.CanSeek);
+ Assert.Equal(0, entry.DataStream.Position);
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs
index 5e35c12c3aaac9..298e3e8be47947 100644
--- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs
@@ -4,8 +4,10 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using Xunit;
+using static System.Formats.Tar.Tests.TarTestsBase;
namespace System.Formats.Tar.Tests
{
@@ -125,28 +127,241 @@ public void Read_Archive_LongFileName_Over100_Under255(TarEntryFormat format, Te
public void Read_Archive_LongPath_Over255(TarEntryFormat format, TestTarFormat testFormat) =>
Read_Archive_LongPath_Over255_Internal(format, testFormat);
+ [Theory]
+ [MemberData(nameof(GetV7TestCaseNames))]
+ public void ReadDataStreamOfTarGzV7(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.v7, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetUstarTestCaseNames))]
+ public void ReadDataStreamOfTarGzUstar(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.ustar, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadDataStreamOfTarGzPax(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.pax, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadDataStreamOfTarGzPaxGea(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.pax_gea, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadDataStreamOfTarGzOldGnu(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.oldgnu, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadDataStreamOfTarGzGnu(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.gnu, testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetV7TestCaseNames))]
+ public void ReadCopiedDataStreamOfTarGzV7(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.v7, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetUstarTestCaseNames))]
+ public void ReadCopiedDataStreamOfTarGzUstar(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.ustar, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadCopiedDataStreamOfTarGzPax(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.pax, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadCopiedDataStreamOfTarGzPaxGea(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.pax_gea, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadCopiedDataStreamOfTarGzOldGnu(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.oldgnu, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetPaxAndGnuTestCaseNames))]
+ public void ReadCopiedDataStreamOfTarGzGnu(string testCaseName) =>
+ VerifyDataStreamOfTarGzInternal(TestTarFormat.gnu, testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetGoLangTarTestCaseNames))]
+ public void ReadDataStreamOfExternalAssetsGoLang(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternal("golang_tar", testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetNodeTarTestCaseNames))]
+ public void ReadDataStreamOfExternalAssetsNode(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternal("node-tar", testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetRsTarTestCaseNames))]
+ public void ReadDataStreamOfExternalAssetsRs(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternal("tar-rs", testCaseName, copyData: false);
+
+ [Theory]
+ [MemberData(nameof(GetGoLangTarTestCaseNames))]
+ public void ReadCopiedDataStreamOfExternalAssetsGoLang(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternal("golang_tar", testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetNodeTarTestCaseNames))]
+ public void ReadCopiedDataStreamOfExternalAssetsNode(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternal("node-tar", testCaseName, copyData: true);
+
+ [Theory]
+ [MemberData(nameof(GetRsTarTestCaseNames))]
+ public void ReadCopiedDataStreamOfExternalAssetsRs(string testCaseName) =>
+ VerifyDataStreamOfTarUncompressedInternal("tar-rs", testCaseName, copyData: true);
+
+ [Fact]
+ public void Throw_FifoContainsNonZeroDataSection()
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "hdr-only");
+ using TarReader reader = new TarReader(archiveStream);
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.NotNull(reader.GetNextEntry());
+ Assert.Throws(() => reader.GetNextEntry());
+ }
+
[Fact]
- public void Read_NodeTarArchives_Successfully()
+ public void Throw_SingleExtendedAttributesEntryWithNoActualEntry()
{
- string nodeTarPath = Path.Join(Directory.GetCurrentDirectory(), "tar", "node-tar");
- foreach (string file in Directory.EnumerateFiles(nodeTarPath, "*.tar", SearchOption.AllDirectories))
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "pax-path-hdr");
+ using TarReader reader = new TarReader(archiveStream);
+ Assert.Throws(() => reader.GetNextEntry());
+ }
+
+ [Theory]
+ [InlineData("tar-rs", "spaces")]
+ [InlineData("golang_tar", "v7")]
+ public void AllowSpacesInOctalFields(string folderName, string testCaseName)
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, folderName, testCaseName);
+ using TarReader reader = new TarReader(archiveStream);
+ TarEntry entry;
+ while ((entry = reader.GetNextEntry()) != null)
{
- using FileStream sourceStream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
- using var reader = new TarReader(sourceStream);
+ AssertExtensions.GreaterThan(entry.Checksum, 0);
+ AssertExtensions.GreaterThan((int)entry.Mode, 0);
+ }
+ }
- TarEntry? entry = null;
- while (true)
- {
- Exception ex = Record.Exception(() => entry = reader.GetNextEntry());
- Assert.Null(ex);
+ [Theory]
+ [InlineData("pax-multi-hdrs")] // Multiple consecutive PAX metadata entries
+ [InlineData("gnu-multi-hdrs")] // Multiple consecutive GNU metadata entries
+ [InlineData("neg-size")] // Garbage chars
+ [InlineData("invalid-go17")] // Many octal fields are all zero chars
+ [InlineData("issue11169")] // Checksum with null in the middle
+ [InlineData("issue10968")] // Garbage chars
+ [InlineData("writer-big")] // The size field contains an euro char
+ public void Throw_ArchivesWithRandomChars(string testCaseName)
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", testCaseName);
+ using TarReader reader = new TarReader(archiveStream);
+ Assert.Throws(() => reader.GetNextEntry());
+ }
+
+ [Fact]
+ public void GarbageEntryChecksumZeroReturnNull()
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "issue12435");
+ using TarReader reader = new TarReader(archiveStream);
+ Assert.Null(reader.GetNextEntry());
+ }
+
+ [Theory]
+ [InlineData("golang_tar", "gnu-nil-sparse-data")]
+ [InlineData("golang_tar", "gnu-nil-sparse-hole")]
+ [InlineData("golang_tar", "gnu-sparse-big")]
+ [InlineData("golang_tar", "sparse-formats")]
+ [InlineData("tar-rs", "sparse-1")]
+ [InlineData("tar-rs", "sparse")]
+ public void SparseEntryNotSupported(string testFolderName, string testCaseName)
+ {
+ // Currently sparse entries are not supported.
+
+ // There are PAX archives archives in the golang folder that have extended attributes for treating a regular file as a sparse file.
+ // Sparse entries were created for the GNU format, so they are very rare entry types which are excluded from this test method:
+ // pax-nil-sparse-data, pax-nil-sparse-hole, pax-sparse-big
+
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName);
+ using TarReader reader = new TarReader(archiveStream);
+ Assert.Throws(() => reader.GetNextEntry());
+ }
- if (entry is null) break;
+ [Fact]
+ public void DirectoryListRegularFileAndSparse()
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "gnu-incremental");
+ using TarReader reader = new TarReader(archiveStream);
+ TarEntry directoryList = reader.GetNextEntry();
+
+ Assert.Equal(TarEntryType.DirectoryList, directoryList.EntryType);
+ Assert.NotNull(directoryList.DataStream);
+ Assert.Equal(14, directoryList.Length);
+
+ Assert.NotNull(reader.GetNextEntry()); // Just a regular file
+
+ Assert.Throws(() => reader.GetNextEntry()); // Sparse
+ }
+
+ [Fact]
+ public void PaxSizeLargerThanMaxAllowedByStream()
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "writer-big-long");
+ using TarReader reader = new TarReader(archiveStream);
+ // The extended attribute 'size' has the value 17179869184
+ // Exception message: Stream length must be non-negative and less than 2^31 - 1 - origin
+ Assert.Throws(() => reader.GetNextEntry());
+ }
- ex = Record.Exception(() => entry.Name);
- Assert.Null(ex);
+ private static void VerifyDataStreamOfTarUncompressedInternal(string testFolderName, string testCaseName, bool copyData)
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName);
+ VerifyDataStreamOfTarInternal(archiveStream, copyData);
+ }
+
+ private static void VerifyDataStreamOfTarGzInternal(TestTarFormat testTarFormat, string testCaseName, bool copyData)
+ {
+ using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.GZip, testTarFormat, testCaseName);
+ using GZipStream decompressor = new GZipStream(archiveStream, CompressionMode.Decompress);
+ VerifyDataStreamOfTarInternal(decompressor, copyData);
+ }
- ex = Record.Exception(() => entry.Length);
- Assert.Null(ex);
+ private static void VerifyDataStreamOfTarInternal(Stream archiveStream, bool copyData)
+ {
+ using TarReader reader = new TarReader(archiveStream);
+
+ TarEntry entry;
+
+ while ((entry = reader.GetNextEntry(copyData)) != null)
+ {
+ if (entry.EntryType is TarEntryType.V7RegularFile or TarEntryType.RegularFile)
+ {
+ if (entry.Length == 0)
+ {
+ Assert.Null(entry.DataStream);
+ }
+ else
+ {
+ Assert.NotNull(entry.DataStream);
+ Assert.Equal(entry.DataStream.Length, entry.Length);
+ if (copyData)
+ {
+ Assert.True(entry.DataStream.CanSeek);
+ Assert.Equal(0, entry.DataStream.Position);
+ }
+ }
}
}
}
diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs
index 1c8dfeea19fce5..dda0cae56b69f8 100644
--- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs
@@ -252,10 +252,13 @@ public void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposin
}
[Theory]
- [InlineData(512)]
- [InlineData(512 + 1)]
- [InlineData(512 + 512 - 1)]
- public void BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize)
+ [InlineData(512, false)]
+ [InlineData(512, true)]
+ [InlineData(512 + 1, false)]
+ [InlineData(512 + 1, true)]
+ [InlineData(512 + 512 - 1, false)]
+ [InlineData(512 + 512 - 1, true)]
+ public void BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize, bool copyData)
{
byte[] fileContents = new byte[contentSize];
Array.Fill(fileContents, 0x1);
@@ -275,17 +278,17 @@ public void BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize)
using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false);
using var reader = new TarReader(unseekable);
- TarEntry e = reader.GetNextEntry();
+ 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();
+ e = reader.GetNextEntry(copyData);
Assert.Equal(0, e.Length);
- e = reader.GetNextEntry();
+ e = reader.GetNextEntry(copyData);
Assert.Null(e);
}
}
diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs
index a2e14598557c95..1dcd9326ee81ee 100644
--- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs
@@ -290,10 +290,13 @@ public async Task GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDi
}
[Theory]
- [InlineData(512)]
- [InlineData(512 + 1)]
- [InlineData(512 + 512 - 1)]
- public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize)
+ [InlineData(512, false)]
+ [InlineData(512, true)]
+ [InlineData(512 + 1, false)]
+ [InlineData(512 + 1, true)]
+ [InlineData(512 + 512 - 1, false)]
+ [InlineData(512 + 512 - 1, true)]
+ public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize, bool copyData)
{
byte[] fileContents = new byte[contentSize];
Array.Fill(fileContents, 0x1);
@@ -313,17 +316,17 @@ public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int conte
using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false);
using var reader = new TarReader(unseekable);
- TarEntry e = await reader.GetNextEntryAsync();
+ TarEntry e = await reader.GetNextEntryAsync(copyData);
Assert.Equal(contentSize, e.Length);
byte[] buffer = new byte[contentSize];
while (e.DataStream.Read(buffer) > 0) ;
AssertExtensions.SequenceEqual(fileContents, buffer);
- e = await reader.GetNextEntryAsync();
+ e = await reader.GetNextEntryAsync(copyData);
Assert.Equal(0, e.Length);
- e = await reader.GetNextEntryAsync();
+ e = await reader.GetNextEntryAsync(copyData);
Assert.Null(e);
}
}
diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
index 04d034ce8764f8..23dc653c83a165 100644
--- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
@@ -3,6 +3,8 @@
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;
@@ -88,6 +90,94 @@ public abstract partial class TarTestsBase : FileCleanupTestBase
protected const string PaxEaDevMajor = "devmajor";
protected const string PaxEaDevMinor = "devminor";
+ private static readonly string[] V7TestCaseNames = new[]
+ {
+ "file",
+ "file_hardlink",
+ "file_symlink",
+ "folder_file",
+ "folder_file_utf8",
+ "folder_subfolder_file",
+ "foldersymlink_folder_subfolder_file",
+ "many_small_files"
+ };
+
+ private static readonly string[] UstarTestCaseNames = new[]
+ {
+ "longpath_splitable_under255",
+ "specialfiles" };
+
+ private static readonly string[] PaxAndGnuTestCaseNames = new[]
+ {
+ "file_longsymlink",
+ "longfilename_over100_under255",
+ "longpath_over255"
+ };
+
+ private static readonly string[] GoLangTestCaseNames = new[]
+ {
+ "empty",
+ "file-and-dir",
+ "gnu-long-nul",
+ "gnu-not-utf8",
+ "gnu-utf8",
+ "gnu",
+ "hardlink",
+ "nil-uid",
+ "pax-bad-hdr-file",
+ "pax-bad-mtime-file",
+ "pax-global-records",
+ "pax-nul-path",
+ "pax-nul-xattrs",
+ "pax-pos-size-file",
+ "pax-records",
+ "pax",
+ "star",
+ "trailing-slash",
+ "ustar-file-devs",
+ "ustar-file-reg",
+ "ustar",
+ "writer",
+ "xattrs"
+ };
+
+ private static readonly string[] NodeTarTestCaseNames = new[]
+ {
+ "bad-cksum",
+ "body-byte-counts",
+ "dir",
+ "emptypax",
+ "file",
+ "global-header",
+ "links-invalid",
+ "links-strip",
+ "links",
+ "long-paths",
+ "long-pax",
+ "next-file-has-long",
+ "null-byte",
+ "path-missing",
+ "trailing-slash-corner-case",
+ "utf8"
+ };
+
+ private static readonly string[] RsTarTestCaseNames = new[]
+ {
+ "7z_long_path",
+ "directory",
+ "duplicate_dirs",
+ "empty_filename",
+ "file_times",
+ "link",
+ "pax_size",
+ "pax",
+ "pax2",
+ "reading_files",
+ "simple_missing_last_header",
+ "simple",
+ "xattrs"
+ };
+
protected enum CompressionMethod
{
// Archiving only, no compression
@@ -121,30 +211,41 @@ public enum TestTarFormat
protected TarTestsBase()
{
CreateDirectoryDefaultMode = Directory.CreateDirectory(GetRandomDirPath()).UnixFileMode; // '0777 & ~umask'
- UMask = ~CreateDirectoryDefaultMode & (UnixFileMode)Convert.ToInt32("777", 8);
+ UMask = ~CreateDirectoryDefaultMode & (UnixFileMode)Convert.ToInt32("777",
+ 8);
}
protected static string GetTestCaseUnarchivedFolderPath(string testCaseName) =>
- Path.Join(Directory.GetCurrentDirectory(), "unarchived", testCaseName);
+ Path.Join(Directory.GetCurrentDirectory(), "unarchived",
+ testCaseName);
protected static string GetTarFilePath(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName)
+ => GetTarFilePath(compressionMethod, format.ToString(), testCaseName);
+
+ protected static string GetTarFilePath(CompressionMethod compressionMethod, string testFolderName, string testCaseName)
{
(string compressionMethodFolder, string fileExtension) = compressionMethod switch
{
- CompressionMethod.Uncompressed => ("tar", ".tar"),
- CompressionMethod.GZip => ("targz", ".tar.gz"),
+ CompressionMethod.Uncompressed => ("tar",
+ ".tar"),
+ CompressionMethod.GZip => ("targz",
+ ".tar.gz"),
_ => throw new InvalidOperationException($"Unexpected compression method: {compressionMethod}"),
};
- return Path.Join(Directory.GetCurrentDirectory(), compressionMethodFolder, format.ToString(), testCaseName + fileExtension);
+ return Path.Join(Directory.GetCurrentDirectory(), compressionMethodFolder, testFolderName, testCaseName + fileExtension);
}
// MemoryStream containing the copied contents of the specified file. Meant for reading and writing.
protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) =>
- GetMemoryStream(GetTarFilePath(compressionMethod, format, testCaseName));
+ GetTarMemoryStream(compressionMethod, format.ToString(), testCaseName);
+
+ protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, string testFolderName, string testCaseName) =>
+ GetMemoryStream(GetTarFilePath(compressionMethod, testFolderName, testCaseName));
protected static string GetStrangeTarFilePath(string testCaseName) =>
- Path.Join(Directory.GetCurrentDirectory(), "strange", testCaseName + ".tar");
+ Path.Join(Directory.GetCurrentDirectory(), "strange",
+ testCaseName + ".tar");
protected static MemoryStream GetStrangeTarMemoryStream(string testCaseName) =>
GetMemoryStream(GetStrangeTarFilePath(testCaseName));
@@ -462,5 +563,53 @@ protected void Verify_Extract(string destination, TarEntry entry, TarEntryType e
AssertFileModeEquals(destination, TestPermission1);
}
+
+ public static IEnumerable