From 24b03e16800d570b6b7f05b55353d953df0fa83d Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 28 Aug 2025 23:32:08 +0500 Subject: [PATCH 01/13] Fix directory detection to handle both forward and backward slashes --- src/Ramstack.FileProviders/ZipFileProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 3dc3854..28ee319 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -77,7 +77,7 @@ private static void Initialize(ZipArchive archive, Dictionary // Since we cannot rely on all archivers to represent directory entries within the archive, // it's simpler to assume their absence and disregard entries ending with a forward slash '/' - if (entry.FullName.EndsWith('/')) + if (FilePath.HasTrailingSlash(entry.FullName)) continue; var path = FilePath.Normalize(entry.FullName); From 84a48bc1be03cdc72a1115c5754ea7c7d31f88b6 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 28 Aug 2025 23:36:00 +0500 Subject: [PATCH 02/13] Include directory entries in zip file provider --- src/Ramstack.FileProviders/ZipFileProvider.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 28ee319..57f7dec 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -71,21 +71,24 @@ private static void Initialize(ZipArchive archive, Dictionary { foreach (var entry in archive.Entries) { - // Skip directories. - // Directory entries are represented by a trailing slash in their names. - // - // Since we cannot rely on all archivers to represent directory entries within the archive, - // it's simpler to assume their absence and disregard entries ending with a forward slash '/' + var path = FilePath.Normalize(entry.FullName); if (FilePath.HasTrailingSlash(entry.FullName)) + { + GetDirectory(path); continue; + } - var path = FilePath.Normalize(entry.FullName); var directory = GetDirectory(FilePath.GetDirectoryName(path)); var file = new ZipFileInfo(entry); - directory.RegisterFile(file); - cache.Add(path, file); + // + // Archives legitimately may contain entries with identical names, + // so skip if a file with this name has already been added, + // avoiding duplicates in the directory file list. + // + if (cache.TryAdd(path, file)) + directory.RegisterFile(file); } ZipDirectoryInfo GetDirectory(string path) From cc829ceb515e1d553b27cd241ba4bea30641ecb1 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 28 Aug 2025 23:37:04 +0500 Subject: [PATCH 03/13] Add input validation for ZipFileProvider constructors --- src/Ramstack.FileProviders/ZipFileProvider.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 57f7dec..7c5bbfa 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -29,8 +29,12 @@ public ZipFileProvider(string path) /// to leave the stream open /// after the object is disposed; otherwise, . public ZipFileProvider(Stream stream, bool leaveOpen = false) - : this(new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen)) { + if (!stream.CanSeek) + throw new ArgumentException("Stream does not support seeking.", nameof(stream)); + + _archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen); + Initialize(_archive, _directories); } /// @@ -41,6 +45,11 @@ public ZipFileProvider(Stream stream, bool leaveOpen = false) /// to use for providing access to ZIP archive content. public ZipFileProvider(ZipArchive archive) { + if (archive.Mode != ZipArchiveMode.Read) + throw new ArgumentException( + "Archive must be opened in read mode (ZipArchiveMode.Read).", + nameof(archive)); + _archive = archive; Initialize(archive, _directories); } From ceba15312118015d9e1853c59a601fb993c72772 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 28 Aug 2025 23:37:35 +0500 Subject: [PATCH 04/13] Clean up and formatting --- src/Ramstack.FileProviders.Extensions/DirectoryNode.cs | 2 +- src/Ramstack.FileProviders/ZipFileProvider.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ramstack.FileProviders.Extensions/DirectoryNode.cs b/src/Ramstack.FileProviders.Extensions/DirectoryNode.cs index e3fbdc8..4cec2e1 100644 --- a/src/Ramstack.FileProviders.Extensions/DirectoryNode.cs +++ b/src/Ramstack.FileProviders.Extensions/DirectoryNode.cs @@ -166,7 +166,7 @@ private sealed class DirectoryFileInfoContents(DirectoryNode directory) : IFileI /// public Stream CreateReadStream() => - throw new NotSupportedException("Cannot create a stream for a directory"); + throw new NotSupportedException("Cannot create a read stream for a directory."); /// public IEnumerator GetEnumerator() => diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 7c5bbfa..9b01c18 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -150,7 +150,7 @@ private sealed class ZipDirectoryInfo(string path) : IDirectoryContents, IFileIn /// public Stream CreateReadStream() => - throw new NotSupportedException("Cannot create a stream for a directory"); + throw new NotSupportedException("Cannot create a read stream for a directory."); /// public IEnumerator GetEnumerator() => @@ -217,7 +217,7 @@ public Stream CreateReadStream() => /// A string containing information about the current instance. /// private string ToStringDebugger() => - $"/{entry.FullName}"; + entry.FullName; } #endregion From fdacfb328f79998c56fc8315b1fde0884f4ec526 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 28 Aug 2025 23:42:35 +0500 Subject: [PATCH 05/13] Strip common absolute path prefixes from zip entries --- src/Ramstack.FileProviders/ZipFileProvider.cs | 29 +++- .../ZipFileProviderTests.cs | 152 ++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 9b01c18..83746ab 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -1,4 +1,5 @@ using System.IO.Compression; +using System.Runtime.CompilerServices; namespace Ramstack.FileProviders; @@ -80,7 +81,8 @@ private static void Initialize(ZipArchive archive, Dictionary { foreach (var entry in archive.Entries) { - var path = FilePath.Normalize(entry.FullName); + var path = FilePath.Normalize( + entry.FullName[GetPrefixLength(entry.FullName)..]); if (FilePath.HasTrailingSlash(entry.FullName)) { @@ -114,6 +116,31 @@ ZipDirectoryInfo GetDirectory(string path) } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static int GetPrefixLength(string path) + { + if (path.StartsWith(@"\\?\UNC\", StringComparison.OrdinalIgnoreCase) + || path.StartsWith(@"\\.\UNC\", StringComparison.OrdinalIgnoreCase) + || path.StartsWith("//?/UNC/", StringComparison.OrdinalIgnoreCase) + || path.StartsWith("//./UNC/", StringComparison.OrdinalIgnoreCase)) + return 8; + + if (path.StartsWith(@"\\?\", StringComparison.Ordinal) + || path.StartsWith(@"\\.\", StringComparison.Ordinal) + || path.StartsWith("//?/", StringComparison.Ordinal) + || path.StartsWith("//./", StringComparison.Ordinal)) + return path.Length >= 6 && IsAsciiLetter(path[4]) && path[5] == ':' ? 6 : 4; + + if (path.Length >= 2 + && IsAsciiLetter(path[0]) && path[1] == ':') + return 2; + + return 0; + + static bool IsAsciiLetter(char ch) => + (uint)((ch | 0x20) - 'a') <= 'z' - 'a'; + } + #region Inner type: ZipDirectoryInfo /// diff --git a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs index eee0c4e..623ea17 100644 --- a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs +++ b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs @@ -31,6 +31,158 @@ public void Cleanup() File.Delete(_path); } + [Test] + public void ZipArchive_WithIdenticalNameEntries() + { + using var provider = new ZipFileProvider(CreateArchive()); + + var list = provider + .EnumerateFiles("/1") + .ToArray(); + + Assert.That( + list.Length, + Is.EqualTo(1)); + + Assert.That( + list[0].ReadAllBytes(), + Is.EquivalentTo("Hello, World!"u8.ToArray())); + + static MemoryStream CreateArchive() + { + var stream = new MemoryStream(); + using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) + { + var a = archive.CreateEntry("1/text.txt"); + using (var writer = a.Open()) + writer.Write("Hello, World!"u8); + + archive.CreateEntry("1/text.txt"); + archive.CreateEntry(@"1\text.txt"); + } + + stream.Position = 0; + return stream; + } + } + + [Test] + public void ZipArchive_PrefixedEntries() + { + using var provider = new ZipFileProvider(CreateArchive()); + + var directories = provider + .EnumerateDirectories("/", "**") + .Select(f => + f.FullName) + .OrderBy(f => f) + .ToArray(); + + var files = provider + .EnumerateFiles("/", "**") + .Select(f => + f.FullName) + .OrderBy(f => f) + .ToArray(); + + Assert.That(files, Is.EquivalentTo( + [ + "/1/text.txt", + "/2/text.txt", + "/3/text.txt", + "/4/text.txt", + "/5/text.txt", + "/localhost/backup/text.txt", + "/localhost/share/text.txt", + "/server/backup/text.txt", + "/server/share/text.txt", + "/text.txt", + "/text.xml" + ])); + + Assert.That(directories, Is.EquivalentTo( + [ + "/1", + "/2", + "/3", + "/4", + "/5", + "/localhost", + "/localhost/backup", + "/localhost/share", + "/server", + "/server/backup", + "/server/share" + ])); + + static MemoryStream CreateArchive() + { + var stream = new MemoryStream(); + using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) + { + archive.CreateEntry(@"D:\1/text.txt"); + archive.CreateEntry(@"D:2\text.txt"); + + archive.CreateEntry(@"\\?\D:\text.txt"); + archive.CreateEntry(@"\\?\D:text.xml"); + archive.CreateEntry(@"\\.\D:\3\text.txt"); + archive.CreateEntry(@"//?/D:/4\text.txt"); + archive.CreateEntry(@"//./D:\5/text.txt"); + + archive.CreateEntry(@"\\?\UNC\localhost\share\text.txt"); + archive.CreateEntry(@"\\.\unc\server\share\text.txt"); + archive.CreateEntry(@"//?/UNC/localhost/backup\text.txt"); + archive.CreateEntry(@"//./unc/server/backup\text.txt"); + } + + stream.Position = 0; + return stream; + } + } + + [Test] + public void ZipArchive_Directories() + { + using var provider = new ZipFileProvider(CreateArchive()); + + var directories = provider + .EnumerateDirectories("/", "**") + .Select(f => + f.FullName) + .OrderBy(f => f) + .ToArray(); + + Assert.That(directories, Is.EquivalentTo( + [ + "/1", + "/2", + "/2/3", + "/4", + "/4/5", + "/4/5/6" + ])); + + static MemoryStream CreateArchive() + { + var stream = new MemoryStream(); + using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true)) + { + archive.CreateEntry(@"\1/"); + archive.CreateEntry(@"\2/"); + archive.CreateEntry(@"/2\"); + archive.CreateEntry(@"/2\"); + archive.CreateEntry(@"/2\"); + archive.CreateEntry(@"/2\3/"); + archive.CreateEntry(@"/2\3/"); + archive.CreateEntry(@"/2\3/"); + archive.CreateEntry(@"4\5/6\"); + } + + stream.Position = 0; + return stream; + } + } + protected override IFileProvider GetFileProvider() => new ZipFileProvider(_path); From e719a958f308d3390c43a15f60139a6b03bec085 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 28 Aug 2025 23:52:57 +0500 Subject: [PATCH 06/13] Strip common absolute path prefixes from zip entries --- src/Ramstack.FileProviders/ZipFileProvider.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 83746ab..19a1d64 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -81,6 +81,10 @@ private static void Initialize(ZipArchive archive, Dictionary { foreach (var entry in archive.Entries) { + // + // Strip common path prefixes from zip entries to handle archives + // saved with absolute paths. + // var path = FilePath.Normalize( entry.FullName[GetPrefixLength(entry.FullName)..]); @@ -119,6 +123,12 @@ ZipDirectoryInfo GetDirectory(string path) [MethodImpl(MethodImplOptions.NoInlining)] private static int GetPrefixLength(string path) { + // + // Check only well-known prefixes. + // Note: Since entry names can be arbitrary, + // we specifically target only common absolute path patterns. + // + if (path.StartsWith(@"\\?\UNC\", StringComparison.OrdinalIgnoreCase) || path.StartsWith(@"\\.\UNC\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("//?/UNC/", StringComparison.OrdinalIgnoreCase) From 025f2d7bc734eab2e7974295f94d28beb3795e41 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 01:00:33 +0500 Subject: [PATCH 07/13] Debug CI test failures on Ubuntu * Tests pass locally and on other Ubuntu machines * Temporary commit for CI debugging purposes --- .../ZipFileProviderTests.cs | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs index 623ea17..c9f23e1 100644 --- a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs +++ b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs @@ -31,7 +31,7 @@ public void Cleanup() File.Delete(_path); } - [Test] + // [Test] public void ZipArchive_WithIdenticalNameEntries() { using var provider = new ZipFileProvider(CreateArchive()); @@ -69,7 +69,8 @@ static MemoryStream CreateArchive() [Test] public void ZipArchive_PrefixedEntries() { - using var provider = new ZipFileProvider(CreateArchive()); + var archive = new ZipArchive(CreateArchive(), ZipArchiveMode.Read, leaveOpen: true); + using var provider = new ZipFileProvider(archive); var directories = provider .EnumerateDirectories("/", "**") @@ -85,35 +86,42 @@ public void ZipArchive_PrefixedEntries() .OrderBy(f => f) .ToArray(); - Assert.That(files, Is.EquivalentTo( - [ - "/1/text.txt", - "/2/text.txt", - "/3/text.txt", - "/4/text.txt", - "/5/text.txt", - "/localhost/backup/text.txt", - "/localhost/share/text.txt", - "/server/backup/text.txt", - "/server/share/text.txt", - "/text.txt", - "/text.xml" - ])); - - Assert.That(directories, Is.EquivalentTo( - [ - "/1", - "/2", - "/3", - "/4", - "/5", - "/localhost", - "/localhost/backup", - "/localhost/share", - "/server", - "/server/backup", - "/server/share" - ])); + foreach (var name in files) Console.WriteLine(name); + Console.WriteLine("--- directories ---"); + foreach (var name in directories) Console.WriteLine(name); + + Console.WriteLine("--- archive ---"); + foreach (var entry in archive.Entries) Console.WriteLine(entry.FullName); + + // Assert.That(files, Is.EquivalentTo( + // [ + // "/1/text.txt", + // "/2/text.txt", + // "/3/text.txt", + // "/4/text.txt", + // "/5/text.txt", + // "/localhost/backup/text.txt", + // "/localhost/share/text.txt", + // "/server/backup/text.txt", + // "/server/share/text.txt", + // "/text.txt", + // "/text.xml" + // ])); + // + // Assert.That(directories, Is.EquivalentTo( + // [ + // "/1", + // "/2", + // "/3", + // "/4", + // "/5", + // "/localhost", + // "/localhost/backup", + // "/localhost/share", + // "/server", + // "/server/backup", + // "/server/share" + // ])); static MemoryStream CreateArchive() { @@ -140,7 +148,7 @@ static MemoryStream CreateArchive() } } - [Test] + // [Test] public void ZipArchive_Directories() { using var provider = new ZipFileProvider(CreateArchive()); From 14dda50c0424a7de83aa18ec3595e62a7da2f8cc Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 01:11:41 +0500 Subject: [PATCH 08/13] Debug CI test failures on Ubuntu * Tests pass locally and on other Ubuntu machines * Temporary commit for CI debugging purposes --- .github/workflows/test.yml | 4 ++-- .../ZipFileProviderTests.cs | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad9629e..d658e08 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,10 +38,10 @@ jobs: run: dotnet build -c Release - name: Test (Debug) - run: dotnet test -c Debug --no-build + run: dotnet test -c Debug --no-build --verbosity detailed - name: Test (Release) - run: dotnet test -c Release --no-build + run: dotnet test -c Release --no-build --verbosity detailed - name: Test (Release, AVX2=0) env: diff --git a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs index c9f23e1..966b4dc 100644 --- a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs +++ b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs @@ -86,12 +86,13 @@ public void ZipArchive_PrefixedEntries() .OrderBy(f => f) .ToArray(); - foreach (var name in files) Console.WriteLine(name); - Console.WriteLine("--- directories ---"); - foreach (var name in directories) Console.WriteLine(name); + Console.Error.WriteLine("--- files ---"); + foreach (var name in files) Console.Error.WriteLine(name); + Console.Error.WriteLine("--- directories ---"); + foreach (var name in directories) Console.Error.WriteLine(name); - Console.WriteLine("--- archive ---"); - foreach (var entry in archive.Entries) Console.WriteLine(entry.FullName); + Console.Error.WriteLine("--- archive ---"); + foreach (var entry in archive.Entries) Console.Error.WriteLine(entry.FullName); // Assert.That(files, Is.EquivalentTo( // [ From 9947020c4b49fc417495df2aaf505e95467859fc Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 02:00:53 +0500 Subject: [PATCH 09/13] Use custom platform-agnostic zip entry name resolution --- .github/workflows/test.yml | 4 +- src/Ramstack.FileProviders/ZipFileProvider.cs | 31 ++------ .../ZipFileProviderTests.cs | 73 ++++++++++--------- 3 files changed, 47 insertions(+), 61 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d658e08..ad9629e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,10 +38,10 @@ jobs: run: dotnet build -c Release - name: Test (Debug) - run: dotnet test -c Debug --no-build --verbosity detailed + run: dotnet test -c Debug --no-build - name: Test (Release) - run: dotnet test -c Release --no-build --verbosity detailed + run: dotnet test -c Release --no-build - name: Test (Release, AVX2=0) env: diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index 19a1d64..d65467e 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -95,7 +95,7 @@ private static void Initialize(ZipArchive archive, Dictionary } var directory = GetDirectory(FilePath.GetDirectoryName(path)); - var file = new ZipFileInfo(entry); + var file = new ZipFileInfo(Path.GetFileName(path), entry); // // Archives legitimately may contain entries with identical names, @@ -111,7 +111,7 @@ ZipDirectoryInfo GetDirectory(string path) if (cache.TryGetValue(path, out var di)) return (ZipDirectoryInfo)di; - di = new ZipDirectoryInfo(path); + di = new ZipDirectoryInfo(FilePath.GetFileName(path)); var parent = GetDirectory(FilePath.GetDirectoryName(path)); parent.RegisterFile(di); cache.Add(path, di); @@ -157,10 +157,10 @@ static bool IsAsciiLetter(char ch) => /// Represents directory contents and file information within a ZIP archive for the specified path. /// This class is used to provide both and interfaces for directory entries in the ZIP archive. /// - /// The path of the directory within the ZIP archive. - [DebuggerDisplay("{ToStringDebugger(),nq}")] + /// The name of the directory within the ZIP archive. + [DebuggerDisplay("{Name,nq}")] [DebuggerTypeProxy(typeof(ZipDirectoryInfoDebuggerProxy))] - private sealed class ZipDirectoryInfo(string path) : IDirectoryContents, IFileInfo + private sealed class ZipDirectoryInfo(string name) : IDirectoryContents, IFileInfo { /// /// The list of the within this directory. @@ -177,7 +177,7 @@ private sealed class ZipDirectoryInfo(string path) : IDirectoryContents, IFileIn public string? PhysicalPath => null; /// - public string Name => Path.GetFileName(path); + public string Name => name; /// public DateTimeOffset LastModified => default; @@ -203,15 +203,6 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => /// The file associated with this directory. public void RegisterFile(IFileInfo file) => _files.Add(file); - - /// - /// Returns a string representation of the current instance's state, intended for debugging purposes. - /// - /// - /// A string containing information about the current instance. - /// - private string ToStringDebugger() => - path; } #endregion @@ -223,7 +214,7 @@ private string ToStringDebugger() => /// /// The ZIP archive entry representing the file. [DebuggerDisplay("{ToStringDebugger(),nq}")] - private sealed class ZipFileInfo(ZipArchiveEntry entry) : IFileInfo + private sealed class ZipFileInfo(string name, ZipArchiveEntry entry) : IFileInfo { /// public bool Exists => true; @@ -241,18 +232,12 @@ private sealed class ZipFileInfo(ZipArchiveEntry entry) : IFileInfo public string? PhysicalPath => null; /// - public string Name => entry.Name; + public string Name => name; /// public Stream CreateReadStream() => entry.Open(); - /// - /// Returns a string representation of the current instance's state, intended for debugging purposes. - /// - /// - /// A string containing information about the current instance. - /// private string ToStringDebugger() => entry.FullName; } diff --git a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs index 966b4dc..94021a6 100644 --- a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs +++ b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs @@ -86,43 +86,44 @@ public void ZipArchive_PrefixedEntries() .OrderBy(f => f) .ToArray(); - Console.Error.WriteLine("--- files ---"); - foreach (var name in files) Console.Error.WriteLine(name); - Console.Error.WriteLine("--- directories ---"); - foreach (var name in directories) Console.Error.WriteLine(name); - - Console.Error.WriteLine("--- archive ---"); - foreach (var entry in archive.Entries) Console.Error.WriteLine(entry.FullName); - - // Assert.That(files, Is.EquivalentTo( - // [ - // "/1/text.txt", - // "/2/text.txt", - // "/3/text.txt", - // "/4/text.txt", - // "/5/text.txt", - // "/localhost/backup/text.txt", - // "/localhost/share/text.txt", - // "/server/backup/text.txt", - // "/server/share/text.txt", - // "/text.txt", - // "/text.xml" - // ])); + // Console.Error.WriteLine("--- files ---"); + // foreach (var name in files) Console.Error.WriteLine(name); // - // Assert.That(directories, Is.EquivalentTo( - // [ - // "/1", - // "/2", - // "/3", - // "/4", - // "/5", - // "/localhost", - // "/localhost/backup", - // "/localhost/share", - // "/server", - // "/server/backup", - // "/server/share" - // ])); + // Console.Error.WriteLine("--- directories ---"); + // foreach (var name in directories) Console.Error.WriteLine(name); + // + // Console.Error.WriteLine("--- archive ---"); + // foreach (var entry in archive.Entries) Console.Error.WriteLine(entry.FullName); + + Assert.That(files, Is.EquivalentTo( + [ + "/1/text.txt", + "/2/text.txt", + "/3/text.txt", + "/4/text.txt", + "/5/text.txt", + "/localhost/backup/text.txt", + "/localhost/share/text.txt", + "/server/backup/text.txt", + "/server/share/text.txt", + "/text.txt", + "/text.xml" + ])); + + Assert.That(directories, Is.EquivalentTo( + [ + "/1", + "/2", + "/3", + "/4", + "/5", + "/localhost", + "/localhost/backup", + "/localhost/share", + "/server", + "/server/backup", + "/server/share" + ])); static MemoryStream CreateArchive() { From 8c60f78fbc6dbc937fba72014c86ffdc4fdfd532 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 02:06:24 +0500 Subject: [PATCH 10/13] Use FilePath for consistency --- src/Ramstack.FileProviders/ZipFileProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index d65467e..c432a7a 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -57,7 +57,7 @@ public ZipFileProvider(ZipArchive archive) /// public IFileInfo GetFileInfo(string subpath) => - Find(subpath) ?? new NotFoundFileInfo(Path.GetFileName(subpath)); + Find(subpath) ?? new NotFoundFileInfo(FilePath.GetFileName(subpath)); /// public IDirectoryContents GetDirectoryContents(string subpath) => @@ -95,7 +95,7 @@ private static void Initialize(ZipArchive archive, Dictionary } var directory = GetDirectory(FilePath.GetDirectoryName(path)); - var file = new ZipFileInfo(Path.GetFileName(path), entry); + var file = new ZipFileInfo(FilePath.GetFileName(path), entry); // // Archives legitimately may contain entries with identical names, From 866dd4b7b15acd0204ca59be0d16465ee5c5f270 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 02:11:45 +0500 Subject: [PATCH 11/13] Rename _directories to _cache for clarity --- src/Ramstack.FileProviders/ZipFileProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index c432a7a..e49932b 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -9,7 +9,7 @@ namespace Ramstack.FileProviders; public sealed class ZipFileProvider : IFileProvider, IDisposable { private readonly ZipArchive _archive; - private readonly Dictionary _directories = + private readonly Dictionary _cache = new() { ["/"] = new ZipDirectoryInfo("/") }; /// @@ -35,7 +35,7 @@ public ZipFileProvider(Stream stream, bool leaveOpen = false) throw new ArgumentException("Stream does not support seeking.", nameof(stream)); _archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen); - Initialize(_archive, _directories); + Initialize(_archive, _cache); } /// @@ -52,7 +52,7 @@ public ZipFileProvider(ZipArchive archive) nameof(archive)); _archive = archive; - Initialize(archive, _directories); + Initialize(archive, _cache); } /// @@ -72,7 +72,7 @@ public void Dispose() => _archive.Dispose(); private IFileInfo? Find(string path) => - _directories.GetValueOrDefault(FilePath.Normalize(path)); + _cache.GetValueOrDefault(FilePath.Normalize(path)); /// /// Initializes the current provider by populating it with entries from the underlying ZIP archive. From 9952a5ecddfe441b24906cebfa0de45264b37e26 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 02:12:11 +0500 Subject: [PATCH 12/13] Update xml-comments --- src/Ramstack.FileProviders/ZipFileProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ramstack.FileProviders/ZipFileProvider.cs b/src/Ramstack.FileProviders/ZipFileProvider.cs index e49932b..d639a26 100644 --- a/src/Ramstack.FileProviders/ZipFileProvider.cs +++ b/src/Ramstack.FileProviders/ZipFileProvider.cs @@ -157,7 +157,7 @@ static bool IsAsciiLetter(char ch) => /// Represents directory contents and file information within a ZIP archive for the specified path. /// This class is used to provide both and interfaces for directory entries in the ZIP archive. /// - /// The name of the directory within the ZIP archive. + /// The name of the directory, not including any path. [DebuggerDisplay("{Name,nq}")] [DebuggerTypeProxy(typeof(ZipDirectoryInfoDebuggerProxy))] private sealed class ZipDirectoryInfo(string name) : IDirectoryContents, IFileInfo @@ -212,6 +212,7 @@ public void RegisterFile(IFileInfo file) => /// /// Represents a file within a ZIP archive as an implementation of the interface. /// + /// The name of the file, not including any path. /// The ZIP archive entry representing the file. [DebuggerDisplay("{ToStringDebugger(),nq}")] private sealed class ZipFileInfo(string name, ZipArchiveEntry entry) : IFileInfo From 0b40056fe602af0cf12115c1ce1d4b54a52aae20 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 29 Aug 2025 02:15:59 +0500 Subject: [PATCH 13/13] Re-enable tests --- .../ZipFileProviderTests.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs index 94021a6..4a3bfa6 100644 --- a/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs +++ b/tests/Ramstack.FileProviders.Tests/ZipFileProviderTests.cs @@ -31,7 +31,7 @@ public void Cleanup() File.Delete(_path); } - // [Test] + [Test] public void ZipArchive_WithIdenticalNameEntries() { using var provider = new ZipFileProvider(CreateArchive()); @@ -86,15 +86,6 @@ public void ZipArchive_PrefixedEntries() .OrderBy(f => f) .ToArray(); - // Console.Error.WriteLine("--- files ---"); - // foreach (var name in files) Console.Error.WriteLine(name); - // - // Console.Error.WriteLine("--- directories ---"); - // foreach (var name in directories) Console.Error.WriteLine(name); - // - // Console.Error.WriteLine("--- archive ---"); - // foreach (var entry in archive.Entries) Console.Error.WriteLine(entry.FullName); - Assert.That(files, Is.EquivalentTo( [ "/1/text.txt", @@ -150,7 +141,7 @@ static MemoryStream CreateArchive() } } - // [Test] + [Test] public void ZipArchive_Directories() { using var provider = new ZipFileProvider(CreateArchive());