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 10f3f2524d1ca5..915cb81caab664 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 @@ -225,10 +225,10 @@ internal void AdvanceDataStreamIfNeeded() { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; TarHelpers.AdvanceStream(_archiveStream, bytesToSkip); - TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } + TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); } } @@ -267,10 +267,10 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; await TarHelpers.AdvanceStreamAsync(_archiveStream, bytesToSkip, cancellationToken).ConfigureAwait(false); - await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } + await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs index 5a68d5a95d5d26..d53f354ee408d1 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using Xunit; @@ -145,5 +146,36 @@ private void Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType entry Assert.Equal(2, Directory.GetFileSystemEntries(baseDir).Count()); } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public void Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) + { + using var writer = new TarWriter(compressor); + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + writer.WriteEntry(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + writer.WriteEntry(entry2); + } + + archive.Position = 0; + using var decompressor = new GZipStream(archive, CompressionMode.Decompress); + using var reader = new TarReader(decompressor); + + using TempDirectory destination = new TempDirectory(); + TarFile.ExtractToDirectory(decompressor, destination.Path, overwriteFiles: true); + + Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs index a35a22c1050f56..741ce8e102deb4 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -174,5 +175,36 @@ private async Task Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEnt } } } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public async Task Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) + { + using var writer = new TarWriter(compressor); + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + await writer.WriteEntryAsync(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + await writer.WriteEntryAsync(entry2); + } + + archive.Position = 0; + using var decompressor = new GZipStream(archive, CompressionMode.Decompress); + using var reader = new TarReader(decompressor); + + using TempDirectory destination = new TempDirectory(); + await TarFile.ExtractToDirectoryAsync(decompressor, destination.Path, overwriteFiles: true); + + Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); + } } } 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 bad9bf8fa179bf..1c8dfeea19fce5 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 @@ -250,5 +250,43 @@ public void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposin Assert.Equal("Substituted", streamReader.ReadLine()); } } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public void BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var writer = new TarWriter(archive, leaveOpen: true)) + { + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + writer.WriteEntry(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + writer.WriteEntry(entry2); + } + + archive.Position = 0; + using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); + using var reader = new TarReader(unseekable); + + TarEntry e = reader.GetNextEntry(); + Assert.Equal(contentSize, e.Length); + + byte[] buffer = new byte[contentSize]; + while (e.DataStream.Read(buffer) > 0) ; + AssertExtensions.SequenceEqual(fileContents, buffer); + + e = reader.GetNextEntry(); + Assert.Equal(0, e.Length); + + e = reader.GetNextEntry(); + 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 77ddf1f1320621..a2e14598557c95 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 @@ -288,5 +288,43 @@ 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) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var writer = new TarWriter(archive, leaveOpen: true)) + { + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + await writer.WriteEntryAsync(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + await writer.WriteEntryAsync(entry2); + } + + archive.Position = 0; + using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); + using var reader = new TarReader(unseekable); + + TarEntry e = await reader.GetNextEntryAsync(); + Assert.Equal(contentSize, e.Length); + + byte[] buffer = new byte[contentSize]; + while (e.DataStream.Read(buffer) > 0) ; + AssertExtensions.SequenceEqual(fileContents, buffer); + + e = await reader.GetNextEntryAsync(); + Assert.Equal(0, e.Length); + + e = await reader.GetNextEntryAsync(); + Assert.Null(e); + } } }