From c7ba9a83e6f3d05a1dd366c4483edb6b29b1a6d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 05:48:21 +0000 Subject: [PATCH 1/5] Initial plan From ce9b952636346aa620136ae576c3358fb1c3c78a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 05:51:48 +0000 Subject: [PATCH 2/5] Initial plan for adding FileMetadata to FileEntry Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- nuget.config | 2 +- nuget.config.bak | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 nuget.config.bak diff --git a/nuget.config b/nuget.config index 227ad0ce..248a5bb5 100644 --- a/nuget.config +++ b/nuget.config @@ -2,6 +2,6 @@ - + \ No newline at end of file diff --git a/nuget.config.bak b/nuget.config.bak new file mode 100644 index 00000000..227ad0ce --- /dev/null +++ b/nuget.config.bak @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 0caf31e1f57f8a60407196c4fc9747a6d6a0cb21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:01:01 +0000 Subject: [PATCH 3/5] Add FileEntryMetadata class and populate metadata in Tar, Zip, Rar, 7Zip, and Ar extractors Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- .../ExtractorTests/FileMetadataTests.cs | 143 ++++++++++++++++++ RecursiveExtractor/ArFile.cs | 90 +++++++++-- RecursiveExtractor/Extractors/RarExtractor.cs | 16 ++ .../Extractors/SevenZipExtractor.cs | 17 +++ RecursiveExtractor/Extractors/TarExtractor.cs | 10 +- RecursiveExtractor/Extractors/ZipExtractor.cs | 28 ++++ RecursiveExtractor/FileEntry.cs | 6 + RecursiveExtractor/FileEntryMetadata.cs | 47 ++++++ nuget.config | 2 +- nuget.config.bak | 7 - 10 files changed, 346 insertions(+), 20 deletions(-) create mode 100644 RecursiveExtractor.Tests/ExtractorTests/FileMetadataTests.cs create mode 100644 RecursiveExtractor/FileEntryMetadata.cs delete mode 100644 nuget.config.bak diff --git a/RecursiveExtractor.Tests/ExtractorTests/FileMetadataTests.cs b/RecursiveExtractor.Tests/ExtractorTests/FileMetadataTests.cs new file mode 100644 index 00000000..1700217f --- /dev/null +++ b/RecursiveExtractor.Tests/ExtractorTests/FileMetadataTests.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. + +using Microsoft.CST.RecursiveExtractor; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace RecursiveExtractor.Tests.ExtractorTests; + +public class FileMetadataTests +{ + [Fact] + public async Task TarEntries_HaveMetadata() + { + var extractor = new Extractor(); + var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", "TestData.tar"); + var results = await extractor.ExtractAsync(path, new ExtractorOptions() { Recurse = false }).ToListAsync(); + + Assert.NotEmpty(results); + foreach (var entry in results) + { + Assert.NotNull(entry.Metadata); + Assert.NotNull(entry.Metadata!.Mode); + // Regular files in TestData.tar have mode 0644 (octal) = 420 (decimal) + Assert.Equal(420, entry.Metadata.Mode); + Assert.False(entry.Metadata.IsExecutable); + Assert.False(entry.Metadata.IsSetUid); + Assert.False(entry.Metadata.IsSetGid); + Assert.NotNull(entry.Metadata.Uid); + Assert.NotNull(entry.Metadata.Gid); + } + } + + [Fact] + public void TarEntries_HaveMetadata_Sync() + { + var extractor = new Extractor(); + var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", "TestData.tar"); + var results = extractor.Extract(path, new ExtractorOptions() { Recurse = false }).ToList(); + + Assert.NotEmpty(results); + foreach (var entry in results) + { + Assert.NotNull(entry.Metadata); + Assert.NotNull(entry.Metadata!.Mode); + Assert.Equal(420, entry.Metadata.Mode); + Assert.False(entry.Metadata.IsExecutable); + Assert.NotNull(entry.Metadata.Uid); + Assert.NotNull(entry.Metadata.Gid); + } + } + + [Fact] + public async Task ArEntries_HaveMetadata() + { + var extractor = new Extractor(); + var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", "TestData.a"); + var results = await extractor.ExtractAsync(path, new ExtractorOptions() { Recurse = false }).ToListAsync(); + + Assert.NotEmpty(results); + foreach (var entry in results) + { + Assert.NotNull(entry.Metadata); + Assert.NotNull(entry.Metadata!.Mode); + // ar files in TestData.a have mode 0644 (octal) = 420 (decimal) + Assert.Equal(420, entry.Metadata.Mode); + Assert.False(entry.Metadata.IsExecutable); + Assert.NotNull(entry.Metadata.Uid); + Assert.Equal(0L, entry.Metadata.Uid); + Assert.NotNull(entry.Metadata.Gid); + Assert.Equal(0L, entry.Metadata.Gid); + } + } + + [Fact] + public void ArEntries_HaveMetadata_Sync() + { + var extractor = new Extractor(); + var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", "TestData.a"); + var results = extractor.Extract(path, new ExtractorOptions() { Recurse = false }).ToList(); + + Assert.NotEmpty(results); + foreach (var entry in results) + { + Assert.NotNull(entry.Metadata); + Assert.NotNull(entry.Metadata!.Mode); + Assert.Equal(420, entry.Metadata.Mode); + Assert.NotNull(entry.Metadata.Uid); + Assert.NotNull(entry.Metadata.Gid); + } + } + + [Fact] + public void MetadataDefaults_AreNull() + { + var metadata = new FileEntryMetadata(); + Assert.Null(metadata.Mode); + Assert.Null(metadata.Uid); + Assert.Null(metadata.Gid); + Assert.Null(metadata.IsExecutable); + Assert.Null(metadata.IsSetUid); + Assert.Null(metadata.IsSetGid); + } + + [Fact] + public void IsExecutable_DerivedFromMode() + { + // 0755 (octal) = 493 (decimal) + var metadata = new FileEntryMetadata { Mode = 493 }; + Assert.True(metadata.IsExecutable); + Assert.False(metadata.IsSetUid); + Assert.False(metadata.IsSetGid); + + // 0644 (octal) = 420 (decimal) + metadata = new FileEntryMetadata { Mode = 420 }; + Assert.False(metadata.IsExecutable); + } + + [Fact] + public void SetUidSetGid_DerivedFromMode() + { + // 04755 (octal) = 2541 (decimal) — setuid + rwxr-xr-x + var metadata = new FileEntryMetadata { Mode = 2541 }; + Assert.True(metadata.IsSetUid); + Assert.False(metadata.IsSetGid); + Assert.True(metadata.IsExecutable); + + // 02755 (octal) = 1517 (decimal) — setgid + rwxr-xr-x + metadata = new FileEntryMetadata { Mode = 1517 }; + Assert.False(metadata.IsSetUid); + Assert.True(metadata.IsSetGid); + Assert.True(metadata.IsExecutable); + } + + [Fact] + public void FileEntry_MetadataDefaultsToNull() + { + using var stream = new MemoryStream(new byte[] { 0 }); + var entry = new FileEntry("test.txt", stream); + Assert.Null(entry.Metadata); + } +} diff --git a/RecursiveExtractor/ArFile.cs b/RecursiveExtractor/ArFile.cs index 66728061..45cc1fc6 100644 --- a/RecursiveExtractor/ArFile.cs +++ b/RecursiveExtractor/ArFile.cs @@ -83,7 +83,10 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract // The name length is included in the total size reported in the header CopyStreamBytes(fileEntry.Content, entryStream, size - nameLength); - yield return new FileEntry(Encoding.ASCII.GetString(nameSpan).TrimEnd('/'), entryStream, fileEntry, true, memoryStreamCutoff: options.MemoryStreamCutoff); + yield return new FileEntry(Encoding.ASCII.GetString(nameSpan).TrimEnd('/'), entryStream, fileEntry, true, memoryStreamCutoff: options.MemoryStreamCutoff) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } else if (filename.Equals('/')) @@ -149,7 +152,10 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, innerSize); CopyStreamBytes(fileEntry.Content, entryStream, innerSize); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } fileEntry.Content.Position = fileEntry.Content.Length - 1; @@ -220,7 +226,10 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, innerSize); CopyStreamBytes(fileEntry.Content, entryStream, innerSize); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } fileEntry.Content.Position = fileEntry.Content.Length - 1; @@ -241,14 +250,20 @@ public static IEnumerable GetFileEntries(FileEntry fileEntry, Extract var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, size); CopyStreamBytes(fileEntry.Content, entryStream, size); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); ; + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } else { var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, size); CopyStreamBytes(fileEntry.Content, entryStream, size); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } else @@ -329,7 +344,10 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi // The name length is included in the total size reported in the header await CopyStreamBytesAsync(fileEntry.Content, entryStream, size - nameLength).ConfigureAwait(false); - yield return new FileEntry(Encoding.ASCII.GetString(nameSpan).TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(Encoding.ASCII.GetString(nameSpan).TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } else if (filename.Equals('/')) @@ -394,7 +412,10 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi } var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, innerSize); await CopyStreamBytesAsync(fileEntry.Content, entryStream, innerSize).ConfigureAwait(false); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } fileEntry.Content.Position = fileEntry.Content.Length - 1; @@ -465,7 +486,10 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, innerSize); await CopyStreamBytesAsync(fileEntry.Content, entryStream, innerSize).ConfigureAwait(false); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } fileEntry.Content.Position = fileEntry.Content.Length - 1; @@ -485,13 +509,19 @@ public static async IAsyncEnumerable GetFileEntriesAsync(FileEntry fi } var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, size); CopyStreamBytes(fileEntry.Content, entryStream, size); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } else { var entryStream = StreamFactory.GenerateAppropriateBackingStream(options, size); await CopyStreamBytesAsync(fileEntry.Content, entryStream, size).ConfigureAwait(false); - yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true); + yield return new FileEntry(filename.TrimEnd('/'), entryStream, fileEntry, true) + { + Metadata = ParseArMetadata(headerBuffer) + }; } } else @@ -570,6 +600,46 @@ internal static async Task CopyStreamBytesAsync(Stream input, Stream outpu private const int bufferSize = 4096; + /// + /// Parse file metadata (UID, GID, mode) from an ar file header buffer. + /// + /// The 60-byte ar header + /// A with parsed values, or null if parsing fails. + internal static FileEntryMetadata? ParseArMetadata(byte[] headerBuffer) + { + var metadata = new FileEntryMetadata(); + var hasData = false; + + // ar_uid: bytes 28-33 (6 bytes), decimal + if (int.TryParse(Encoding.ASCII.GetString(headerBuffer[28..34]).Trim(), out var uid)) + { + metadata.Uid = uid; + hasData = true; + } + + // ar_gid: bytes 34-39 (6 bytes), decimal + if (int.TryParse(Encoding.ASCII.GetString(headerBuffer[34..40]).Trim(), out var gid)) + { + metadata.Gid = gid; + hasData = true; + } + + // ar_mode: bytes 40-47 (8 bytes), octal + var modeString = Encoding.ASCII.GetString(headerBuffer[40..48]).Trim(); + try + { + if (!string.IsNullOrEmpty(modeString)) + { + metadata.Mode = Convert.ToInt64(modeString, 8); + hasData = true; + } + } + catch (FormatException) { } + catch (OverflowException) { } + + return hasData ? metadata : null; + } + private readonly static NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); } } \ No newline at end of file diff --git a/RecursiveExtractor/Extractors/RarExtractor.cs b/RecursiveExtractor/Extractors/RarExtractor.cs index e1096583..0e571672 100644 --- a/RecursiveExtractor/Extractors/RarExtractor.cs +++ b/RecursiveExtractor/Extractors/RarExtractor.cs @@ -108,6 +108,14 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra var newFileEntry = await FileEntry.FromStreamAsync(name, entry.OpenEntryStream(), fileEntry, entry.CreatedTime, entry.LastModifiedTime, entry.LastAccessedTime, memoryStreamCutoff: options.MemoryStreamCutoff).ConfigureAwait(false); if (newFileEntry != null) { + try + { + if (entry.Attrib.HasValue) + { + newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; + } + } + catch (Exception) { } if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -158,6 +166,14 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti } if (newFileEntry != null) { + try + { + if (entry.Attrib.HasValue) + { + newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; + } + } + catch (Exception) { } if (options.Recurse || topLevel) { foreach (var innerEntry in Context.Extract(newFileEntry, options, governor, false)) diff --git a/RecursiveExtractor/Extractors/SevenZipExtractor.cs b/RecursiveExtractor/Extractors/SevenZipExtractor.cs index 2f1e3a53..43504800 100644 --- a/RecursiveExtractor/Extractors/SevenZipExtractor.cs +++ b/RecursiveExtractor/Extractors/SevenZipExtractor.cs @@ -46,6 +46,14 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra if (newFileEntry != null) { + try + { + if (entry.Attrib.HasValue) + { + newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; + } + } + catch (Exception) { } if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -157,6 +165,15 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti var name = (entry.Key ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); var newFileEntry = new FileEntry(name, entry.OpenEntryStream(), fileEntry, createTime: entry.CreatedTime, modifyTime: entry.LastModifiedTime, accessTime: entry.LastAccessedTime, memoryStreamCutoff: options.MemoryStreamCutoff); + try + { + if (entry.Attrib.HasValue) + { + newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; + } + } + catch (Exception) { } + if (options.Recurse || topLevel) { foreach (var innerEntry in Context.Extract(newFileEntry, options, governor, false)) diff --git a/RecursiveExtractor/Extractors/TarExtractor.cs b/RecursiveExtractor/Extractors/TarExtractor.cs index 9de51798..4d5a2dc5 100644 --- a/RecursiveExtractor/Extractors/TarExtractor.cs +++ b/RecursiveExtractor/Extractors/TarExtractor.cs @@ -75,7 +75,10 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra name = name[2..]; } - var newFileEntry = new FileEntry(name, fs, fileEntry, true, memoryStreamCutoff: options.MemoryStreamCutoff); + var newFileEntry = new FileEntry(name, fs, fileEntry, true, memoryStreamCutoff: options.MemoryStreamCutoff) + { + Metadata = new FileEntryMetadata { Mode = tarEntry.Mode, Uid = tarEntry.UserID, Gid = tarEntry.GroupId } + }; if (options.Recurse || topLevel) { @@ -144,7 +147,10 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti { name = name[2..]; } - var newFileEntry = new FileEntry(name, fs, fileEntry, true, memoryStreamCutoff: options.MemoryStreamCutoff); + var newFileEntry = new FileEntry(name, fs, fileEntry, true, memoryStreamCutoff: options.MemoryStreamCutoff) + { + Metadata = new FileEntryMetadata { Mode = tarEntry.Mode, Uid = tarEntry.UserID, Gid = tarEntry.GroupId } + }; if (options.Recurse || topLevel) { diff --git a/RecursiveExtractor/Extractors/ZipExtractor.cs b/RecursiveExtractor/Extractors/ZipExtractor.cs index 5b706b44..9831a378 100644 --- a/RecursiveExtractor/Extractors/ZipExtractor.cs +++ b/RecursiveExtractor/Extractors/ZipExtractor.cs @@ -142,6 +142,20 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra var name = zipEntry.Key?.Replace('/', Path.DirectorySeparatorChar) ?? ""; var newFileEntry = new FileEntry(name, target, fileEntry, modifyTime: zipEntry.LastModifiedTime, memoryStreamCutoff: options.MemoryStreamCutoff); + try + { + if (zipEntry.Attrib.HasValue) + { + // For ZIP files, Unix permissions are stored in the upper 16 bits of the external attributes + var unixMode = (zipEntry.Attrib.Value >> 16) & 0xFFFF; + if (unixMode != 0) + { + newFileEntry.Metadata = new FileEntryMetadata { Mode = unixMode }; + } + } + } + catch (Exception) { } + if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -237,6 +251,20 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti var name = zipEntry.Key?.Replace('/', Path.DirectorySeparatorChar) ?? ""; var newFileEntry = new FileEntry(name, fs, fileEntry, modifyTime: zipEntry.LastModifiedTime, memoryStreamCutoff: options.MemoryStreamCutoff); + try + { + if (zipEntry.Attrib.HasValue) + { + // For ZIP files, Unix permissions are stored in the upper 16 bits of the external attributes + var unixMode = (zipEntry.Attrib.Value >> 16) & 0xFFFF; + if (unixMode != 0) + { + newFileEntry.Metadata = new FileEntryMetadata { Mode = unixMode }; + } + } + } + catch (Exception) { } + if (options.Recurse || topLevel) { foreach (var innerEntry in Context.Extract(newFileEntry, options, governor, false)) diff --git a/RecursiveExtractor/FileEntry.cs b/RecursiveExtractor/FileEntry.cs index 921486fb..7e0fd55c 100644 --- a/RecursiveExtractor/FileEntry.cs +++ b/RecursiveExtractor/FileEntry.cs @@ -173,6 +173,12 @@ public FileEntry(string name, Stream inputStream, FileEntry? parent = null, bool /// ExtractionStatus metadata. /// public FileEntryStatus EntryStatus { get; set; } + + /// + /// Optional metadata about the file such as permissions, ownership, and special bits. + /// Null when the archive format does not provide this information. + /// + public FileEntryMetadata? Metadata { get; set; } /// /// Regular expression to find characters that are not valid in filenames/paths on this system. diff --git a/RecursiveExtractor/FileEntryMetadata.cs b/RecursiveExtractor/FileEntryMetadata.cs new file mode 100644 index 00000000..4254c442 --- /dev/null +++ b/RecursiveExtractor/FileEntryMetadata.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. + +namespace Microsoft.CST.RecursiveExtractor +{ + /// + /// Metadata about a file extracted from an archive, such as permissions and ownership. + /// Properties are nullable to indicate when the metadata was not available from the archive format. + /// + public class FileEntryMetadata + { + /// + /// The Unix file mode (permissions) as an integer (e.g., 0x1FF for 0777, 0x1ED for 0755). + /// Null if not available from the archive format. + /// + public long? Mode { get; set; } + + /// + /// Whether the file has any executable permission bits set (owner, group, or other). + /// Derived from when available, otherwise null. + /// + public bool? IsExecutable => Mode.HasValue ? (Mode.Value & 0x49) != 0 : null; // 0x49 = 0111 in octal + + /// + /// Whether the SetUID bit is set on this file. + /// Derived from when available, otherwise null. + /// + public bool? IsSetUid => Mode.HasValue ? (Mode.Value & 0x800) != 0 : null; // 04000 in octal + + /// + /// Whether the SetGID bit is set on this file. + /// Derived from when available, otherwise null. + /// + public bool? IsSetGid => Mode.HasValue ? (Mode.Value & 0x400) != 0 : null; // 02000 in octal + + /// + /// The User ID (UID) of the file owner. + /// Null if not available from the archive format. + /// + public long? Uid { get; set; } + + /// + /// The Group ID (GID) of the file owner. + /// Null if not available from the archive format. + /// + public long? Gid { get; set; } + } +} diff --git a/nuget.config b/nuget.config index 248a5bb5..227ad0ce 100644 --- a/nuget.config +++ b/nuget.config @@ -2,6 +2,6 @@ - + \ No newline at end of file diff --git a/nuget.config.bak b/nuget.config.bak deleted file mode 100644 index 227ad0ce..00000000 --- a/nuget.config.bak +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From 6da62dc9c1ac4b454421459dfa4210293b666c1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:02:11 +0000 Subject: [PATCH 4/5] Add logging to catch blocks for Attrib property access in extractors Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- RecursiveExtractor/Extractors/RarExtractor.cs | 4 ++-- RecursiveExtractor/Extractors/SevenZipExtractor.cs | 4 ++-- RecursiveExtractor/Extractors/ZipExtractor.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/RecursiveExtractor/Extractors/RarExtractor.cs b/RecursiveExtractor/Extractors/RarExtractor.cs index 0e571672..6335dfa0 100644 --- a/RecursiveExtractor/Extractors/RarExtractor.cs +++ b/RecursiveExtractor/Extractors/RarExtractor.cs @@ -115,7 +115,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception) { } + catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -173,7 +173,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception) { } + catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } if (options.Recurse || topLevel) { foreach (var innerEntry in Context.Extract(newFileEntry, options, governor, false)) diff --git a/RecursiveExtractor/Extractors/SevenZipExtractor.cs b/RecursiveExtractor/Extractors/SevenZipExtractor.cs index 43504800..8a2af1f3 100644 --- a/RecursiveExtractor/Extractors/SevenZipExtractor.cs +++ b/RecursiveExtractor/Extractors/SevenZipExtractor.cs @@ -53,7 +53,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception) { } + catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -172,7 +172,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception) { } + catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } if (options.Recurse || topLevel) { diff --git a/RecursiveExtractor/Extractors/ZipExtractor.cs b/RecursiveExtractor/Extractors/ZipExtractor.cs index 9831a378..1a0a0ce7 100644 --- a/RecursiveExtractor/Extractors/ZipExtractor.cs +++ b/RecursiveExtractor/Extractors/ZipExtractor.cs @@ -154,7 +154,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra } } } - catch (Exception) { } + catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } if (options.Recurse || topLevel) { @@ -263,7 +263,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti } } } - catch (Exception) { } + catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } if (options.Recurse || topLevel) { From 536c46a17d89c09887b4c9e6dfdeb62645c79e51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:17:12 +0000 Subject: [PATCH 5/5] Include archive type and file path in trace messages for failed attribute reads Co-authored-by: gfs <98900+gfs@users.noreply.github.com> --- RecursiveExtractor/Extractors/RarExtractor.cs | 4 ++-- RecursiveExtractor/Extractors/SevenZipExtractor.cs | 4 ++-- RecursiveExtractor/Extractors/ZipExtractor.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/RecursiveExtractor/Extractors/RarExtractor.cs b/RecursiveExtractor/Extractors/RarExtractor.cs index 6335dfa0..829b3d30 100644 --- a/RecursiveExtractor/Extractors/RarExtractor.cs +++ b/RecursiveExtractor/Extractors/RarExtractor.cs @@ -115,7 +115,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } + catch (Exception e) { Logger.Trace("Failed to read file attributes for {0} in {1} archive {2}: {3}", entry.Key, ArchiveFileType.RAR, fileEntry.FullPath, e.Message); } if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -173,7 +173,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } + catch (Exception e) { Logger.Trace("Failed to read file attributes for {0} in {1} archive {2}: {3}", entry.Key, ArchiveFileType.RAR, fileEntry.FullPath, e.Message); } if (options.Recurse || topLevel) { foreach (var innerEntry in Context.Extract(newFileEntry, options, governor, false)) diff --git a/RecursiveExtractor/Extractors/SevenZipExtractor.cs b/RecursiveExtractor/Extractors/SevenZipExtractor.cs index 8a2af1f3..c3a7bf4c 100644 --- a/RecursiveExtractor/Extractors/SevenZipExtractor.cs +++ b/RecursiveExtractor/Extractors/SevenZipExtractor.cs @@ -53,7 +53,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } + catch (Exception e) { Logger.Trace("Failed to read file attributes for {0} in {1} archive {2}: {3}", entry.Key, ArchiveFileType.P7ZIP, fileEntry.FullPath, e.Message); } if (options.Recurse || topLevel) { await foreach (var innerEntry in Context.ExtractAsync(newFileEntry, options, governor, false)) @@ -172,7 +172,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti newFileEntry.Metadata = new FileEntryMetadata { Mode = entry.Attrib.Value }; } } - catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } + catch (Exception e) { Logger.Trace("Failed to read file attributes for {0} in {1} archive {2}: {3}", entry.Key, ArchiveFileType.P7ZIP, fileEntry.FullPath, e.Message); } if (options.Recurse || topLevel) { diff --git a/RecursiveExtractor/Extractors/ZipExtractor.cs b/RecursiveExtractor/Extractors/ZipExtractor.cs index 1a0a0ce7..d5795e18 100644 --- a/RecursiveExtractor/Extractors/ZipExtractor.cs +++ b/RecursiveExtractor/Extractors/ZipExtractor.cs @@ -154,7 +154,7 @@ public async IAsyncEnumerable ExtractAsync(FileEntry fileEntry, Extra } } } - catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } + catch (Exception e) { Logger.Trace("Failed to read file attributes for {0} in {1} archive {2}: {3}", zipEntry.Key, ArchiveFileType.ZIP, fileEntry.FullPath, e.Message); } if (options.Recurse || topLevel) { @@ -263,7 +263,7 @@ public IEnumerable Extract(FileEntry fileEntry, ExtractorOptions opti } } } - catch (Exception e) { Logger.Trace("Failed to read file attributes: {0}", e.Message); } + catch (Exception e) { Logger.Trace("Failed to read file attributes for {0} in {1} archive {2}: {3}", zipEntry.Key, ArchiveFileType.ZIP, fileEntry.FullPath, e.Message); } if (options.Recurse || topLevel) {