From 1392f071d4050829a28327ace94c2e70ea4fc862 Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Thu, 23 Apr 2026 15:58:06 -0700 Subject: [PATCH 1/2] fix: Parse URL sources that use the rename '#' tag --- internal/rpm/spectool/spectool.go | 12 ++++++++++++ internal/rpm/spectool/spectool_test.go | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/internal/rpm/spectool/spectool.go b/internal/rpm/spectool/spectool.go index a807b1c4..eb21e9e5 100644 --- a/internal/rpm/spectool/spectool.go +++ b/internal/rpm/spectool/spectool.go @@ -13,12 +13,24 @@ import ( // filenameFromURL attempts to parse value as a URL and extract the basename. // Returns the filename and true if value is a URL, or ("", false) if not. +// +// RPM specs use the fragment convention "#/local-name" to specify the local +// filename for URLs whose path doesn't contain a meaningful basename (e.g., +// keyserver lookup URLs). When a fragment starting with "/" is present, its +// basename is used instead of the URL path's basename. func filenameFromURL(value string) (string, bool) { parsed, err := url.Parse(value) if err != nil || parsed.Scheme == "" || parsed.Host == "" { return "", false } + // RPM "#/filename" convention: fragment overrides the path basename. + // Variants: "#/name.asc" and "#./name.asc" both occur in the wild. + // Plain anchors like "#section" are not filename overrides. + if strings.HasPrefix(parsed.Fragment, "/") || strings.HasPrefix(parsed.Fragment, "./") { + return path.Base(parsed.Fragment), true + } + return path.Base(parsed.Path), true } diff --git a/internal/rpm/spectool/spectool_test.go b/internal/rpm/spectool/spectool_test.go index 2800457e..1d52f521 100644 --- a/internal/rpm/spectool/spectool_test.go +++ b/internal/rpm/spectool/spectool_test.go @@ -73,6 +73,21 @@ func TestParseSpectoolOutput(t *testing.T) { input: "Source0: good.tar.gz\nPatch0: /bad/path\nPatch1: ok.patch", expected: []string{"good.tar.gz", "ok.patch"}, }, + { + name: "URL with slash fragment extracts fragment basename", + input: "Source0: https://example.com/lookup?q=abc#/local-name.asc", + expected: []string{"local-name.asc"}, + }, + { + name: "URL with dot-slash fragment extracts fragment basename", + input: "Source0: https://example.com/lookup?q=abc#./local-name.asc", + expected: []string{"local-name.asc"}, + }, + { + name: "URL with fragment rename overrides path basename", + input: "Patch0: https://example.com/pull/33.patch#/renamed.diff", + expected: []string{"renamed.diff"}, + }, } for _, testCase := range tests { @@ -96,7 +111,9 @@ func TestFilenameFromURL(t *testing.T) { }{ {"https URL", "https://example.com/file.tar.gz", "file.tar.gz", true}, {"URL with query", "https://example.com/file.tar.gz?raw=true", "file.tar.gz", true}, - {"URL with fragment", "https://example.com/file.tar.gz#section", "file.tar.gz", true}, + {"URL with non-path fragment", "https://example.com/file.tar.gz#section", "file.tar.gz", true}, + {"RPM fragment slash", "https://example.com/lookup?q=abc#/local-name.asc", "local-name.asc", true}, + {"RPM fragment dot-slash", "https://example.com/lookup?q=abc#./local-name.asc", "local-name.asc", true}, {"nested URL path", "https://example.com/a/b/c/file.tar.gz", "file.tar.gz", true}, {"ftp URL", "ftp://ftp.gnu.org/pub/gnu/sed/sed-4.9.tar.xz", "sed-4.9.tar.xz", true}, {"trailing slash", "https://example.com/", "/", true}, From 5a50e3c16ef0f5a06e8d65aa7a7d3459558fac22 Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Thu, 23 Apr 2026 17:17:26 -0700 Subject: [PATCH 2/2] Update internal/rpm/spectool/spectool.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/rpm/spectool/spectool.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/rpm/spectool/spectool.go b/internal/rpm/spectool/spectool.go index eb21e9e5..09462867 100644 --- a/internal/rpm/spectool/spectool.go +++ b/internal/rpm/spectool/spectool.go @@ -14,10 +14,11 @@ import ( // filenameFromURL attempts to parse value as a URL and extract the basename. // Returns the filename and true if value is a URL, or ("", false) if not. // -// RPM specs use the fragment convention "#/local-name" to specify the local -// filename for URLs whose path doesn't contain a meaningful basename (e.g., -// keyserver lookup URLs). When a fragment starting with "/" is present, its -// basename is used instead of the URL path's basename. +// RPM specs use fragment conventions like "#/local-name" and "#./local-name" +// to specify the local filename for URLs whose path doesn't contain a +// meaningful basename (e.g., keyserver lookup URLs). When a fragment starting +// with "/" or "./" is present, its basename is used instead of the URL path's +// basename. func filenameFromURL(value string) (string, bool) { parsed, err := url.Parse(value) if err != nil || parsed.Scheme == "" || parsed.Host == "" {