From f59b14a278f2bbc0c5b0fcbafcf16db13306e1df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:39:41 +0000 Subject: [PATCH] WIP: Implement compressed tar detection in TarFactory (seeking clarification) Enhanced TarFactory.IsArchive() to detect compressed tar formats (tar.bz2, tar.lz, etc.) by: - Checking if stream contains tar header directly (for uncompressed) - Testing each compression format to see if it contains a tar file Enhanced TarFactory.Open() methods to decompress compressed tar files to MemoryStream for seekable access. Current blockers: - Some compression stream constructors (LZipStream) don't support leaveOpen parameter - Stream disposal during detection phase causes issues with factory detection flow Awaiting clarification from @adamhathcock on preferred architecture approach. Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/Factories/TarFactory.cs | 229 +++++++++++++++++++++- 1 file changed, 224 insertions(+), 5 deletions(-) diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index d32020fd..aab92194 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -57,19 +57,238 @@ public class TarFactory Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize - ) => TarArchive.IsTarFile(stream); + ) + { + if (!stream.CanSeek) + { + return TarArchive.IsTarFile(stream); // For non-seekable streams, just check if it's a tar file + } + + var startPosition = stream.Position; + + // First check if it's a regular tar file + if (TarArchive.IsTarFile(stream)) + { + stream.Seek(startPosition, SeekOrigin.Begin); // Seek back for consistency + return true; + } + + // Seek back after the tar file check + stream.Seek(startPosition, SeekOrigin.Begin); + + if (compressionOptions == null) + { + return false; + } + + try + { + // Try each compression option to see if it contains a tar file + foreach (var testOption in compressionOptions) + { + if (testOption.Type == CompressionType.None) + { + continue; // Skip uncompressed + } + + stream.Seek(startPosition, SeekOrigin.Begin); + + try + { + if (testOption.CanHandle(stream)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + // Try to decompress and check if it contains a tar archive + // For compression formats that don't support leaveOpen, we need to save/restore position + var positionBeforeDecompress = stream.Position; + Stream? decompressedStream = null; + bool streamWasClosed = false; + + try + { + decompressedStream = testOption.Type switch + { + CompressionType.BZip2 => new BZip2Stream(stream, CompressionMode.Decompress, true), + _ => testOption.CreateStream(stream) // For other types, may close the stream + }; + + if (TarArchive.IsTarFile(decompressedStream)) + { + return true; + } + } + catch (ObjectDisposedException) + { + streamWasClosed = true; + throw; // Stream was closed, can't continue + } + finally + { + decompressedStream?.Dispose(); + + if (!streamWasClosed && stream.CanSeek) + { + try + { + stream.Seek(positionBeforeDecompress, SeekOrigin.Begin); + } + catch + { + // If seek fails, the stream might have been closed + } + } + } + + // Seek back to start after decompression attempt + stream.Seek(startPosition, SeekOrigin.Begin); + } + } + catch + { + // If decompression fails, it's not this format - continue to next option + try + { + stream.Seek(startPosition, SeekOrigin.Begin); + } + catch + { + // Ignore seek failures + } + } + } + + return false; + } + finally + { + try + { + stream.Seek(startPosition, SeekOrigin.Begin); + } + catch + { + // Ignore seek failures + } + } + } #endregion #region IArchiveFactory /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - TarArchive.Open(stream, readerOptions); + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + readerOptions ??= new ReaderOptions(); + + // Try to detect and handle compressed tar formats + if (stream.CanSeek) + { + var startPosition = stream.Position; + + // Try each compression option to see if we can decompress it + foreach (var testOption in compressionOptions) + { + if (testOption.Type == CompressionType.None) + { + continue; // Skip uncompressed + } + + stream.Seek(startPosition, SeekOrigin.Begin); + + if (testOption.CanHandle(stream)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + // Decompress the entire stream into a seekable MemoryStream + using var decompressedStream = testOption.CreateStream(stream); + var memoryStream = new MemoryStream(); + decompressedStream.CopyTo(memoryStream); + memoryStream.Position = 0; + + // Verify it's actually a tar file + if (TarArchive.IsTarFile(memoryStream)) + { + memoryStream.Position = 0; + // Return a TarArchive from the decompressed memory stream + // The TarArchive will own the MemoryStream and dispose it when disposed + var options = new ReaderOptions + { + LeaveStreamOpen = false, // Ensure the MemoryStream is disposed with the archive + ArchiveEncoding = readerOptions?.ArchiveEncoding ?? new ArchiveEncoding() + }; + return TarArchive.Open(memoryStream, options); + } + + memoryStream.Dispose(); + } + } + + stream.Seek(startPosition, SeekOrigin.Begin); + } + + // Fall back to normal tar archive opening + return TarArchive.Open(stream, readerOptions); + } /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - TarArchive.Open(fileInfo, readerOptions); + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + readerOptions ??= new ReaderOptions(); + + // Try to detect and handle compressed tar formats by file extension and content + using var fileStream = fileInfo.OpenRead(); + + // Try each compression option + foreach (var testOption in compressionOptions) + { + if (testOption.Type == CompressionType.None) + { + continue; // Skip uncompressed + } + + // Check if file extension matches + var fileName = fileInfo.Name.ToLowerInvariant(); + if (testOption.KnownExtensions.Any(ext => fileName.EndsWith(ext))) + { + fileStream.Position = 0; + + // Verify it's the right compression format + if (testOption.CanHandle(fileStream)) + { + fileStream.Position = 0; + + // Decompress the entire file into a seekable MemoryStream + using var decompressedStream = testOption.CreateStream(fileStream); + var memoryStream = new MemoryStream(); + decompressedStream.CopyTo(memoryStream); + memoryStream.Position = 0; + + // Verify it's actually a tar file + if (TarArchive.IsTarFile(memoryStream)) + { + memoryStream.Position = 0; + // Return a TarArchive from the decompressed memory stream + // The TarArchive will own the MemoryStream and dispose it when disposed + var options = new ReaderOptions + { + LeaveStreamOpen = false, // Ensure the MemoryStream is disposed with the archive + ArchiveEncoding = readerOptions?.ArchiveEncoding ?? new ArchiveEncoding() + }; + return TarArchive.Open(memoryStream, options); + } + + memoryStream.Dispose(); + } + } + } + + // fileStream will be closed by the using statement + + // Fall back to normal tar archive opening + return TarArchive.Open(fileInfo, readerOptions); + } #endregion