diff --git a/cmd/csaf_checker/links.go b/cmd/csaf_checker/links.go index a323661..c7aec57 100644 --- a/cmd/csaf_checker/links.go +++ b/cmd/csaf_checker/links.go @@ -9,6 +9,7 @@ package main import ( + "github.com/gocsaf/csaf/v3/internal/misc" "io" "net/http" "net/url" @@ -93,7 +94,7 @@ func (pgs pages) listed( return err } // Links may be relative - abs := baseURL.ResolveReference(u).String() + abs := misc.JoinURL(baseURL, u).String() content.links.Add(abs) return nil }) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index ae79133..c0c4437 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/gocsaf/csaf/v3/internal/misc" "io" "log" "net/http" @@ -644,7 +645,7 @@ func (p *processor) integrity( } fp = makeAbs(fp) - u := b.ResolveReference(fp).String() + u := misc.JoinURL(b, fp).String() // Should this URL be ignored? if p.cfg.ignoreURL(u) { @@ -777,7 +778,7 @@ func (p *processor) integrity( continue } hu = makeAbs(hu) - hashFile := b.ResolveReference(hu).String() + hashFile := misc.JoinURL(b, hu).String() p.checkTLS(hashFile) if res, err = client.Get(hashFile); err != nil { @@ -827,7 +828,7 @@ func (p *processor) integrity( continue } su = makeAbs(su) - sigFile := b.ResolveReference(su).String() + sigFile := misc.JoinURL(b, su).String() p.checkTLS(sigFile) p.badSignatures.use() @@ -1374,7 +1375,7 @@ func (p *processor) checkSecurityFolder(folder string) string { return err.Error() } - u = base.ResolveReference(up).String() + u = misc.JoinURL(base, up).String() p.checkTLS(u) if res, err = client.Get(u); err != nil { return fmt.Sprintf("Cannot fetch %s from security.txt: %v", u, err) @@ -1539,7 +1540,7 @@ func (p *processor) checkPGPKeys(_ string) error { continue } - u := base.ResolveReference(up).String() + u := misc.JoinURL(base, up).String() p.checkTLS(u) res, err := client.Get(u) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 28bd437..0a9ff04 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -10,6 +10,7 @@ package main import ( "errors" + "github.com/gocsaf/csaf/v3/internal/misc" "net/http" "net/url" "sort" @@ -237,7 +238,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { p.badProviderMetadata.error("Invalid URL %s in feed: %v.", *feed.URL, err) continue } - feedBase := base.ResolveReference(up) + feedBase := misc.JoinURL(base, up) feedURL := feedBase.String() p.checkTLS(feedURL) @@ -270,7 +271,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { continue } - feedURL := base.ResolveReference(up) + feedURL := misc.JoinURL(base, up) feedBase, err := util.BaseURL(feedURL) if err != nil { p.badProviderMetadata.error("Bad base path: %v", err) @@ -325,7 +326,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { continue } - feedBase := base.ResolveReference(up) + feedBase := misc.JoinURL(base, up) makeAbs := makeAbsolute(feedBase) label := defaults(feed.TLPLabel, csaf.TLPLabelUnlabeled) diff --git a/cmd/csaf_downloader/downloader.go b/cmd/csaf_downloader/downloader.go index bcef357..90e3ac3 100644 --- a/cmd/csaf_downloader/downloader.go +++ b/cmd/csaf_downloader/downloader.go @@ -343,7 +343,7 @@ func (d *downloader) loadOpenPGPKeys( continue } - u := base.ResolveReference(up).String() + u := base.JoinPath(up.Path).String() res, err := client.Get(u) if err != nil { diff --git a/csaf/advisories.go b/csaf/advisories.go index ef3fea8..e7bc11a 100644 --- a/csaf/advisories.go +++ b/csaf/advisories.go @@ -12,6 +12,7 @@ import ( "context" "encoding/csv" "fmt" + "github.com/gocsaf/csaf/v3/internal/misc" "io" "log/slog" "net/http" @@ -281,7 +282,7 @@ func (afp *AdvisoryFileProcessor) processROLIE( slog.Error("Invalid URL in feed", "feed", *feed.URL, "err", err) continue } - feedURL := afp.base.ResolveReference(up) + feedURL := misc.JoinURL(afp.base, up) slog.Info("Got feed URL", "feed", feedURL) fb, err := util.BaseURL(feedURL) @@ -325,7 +326,7 @@ func (afp *AdvisoryFileProcessor) processROLIE( slog.Error("Invalid URL", "url", u, "err", err) return "" } - return feedBaseURL.ResolveReference(p).String() + return misc.JoinURL(feedBaseURL, p).String() } rfeed.Entries(func(entry *Entry) { diff --git a/internal/misc/url.go b/internal/misc/url.go new file mode 100644 index 0000000..2256a94 --- /dev/null +++ b/internal/misc/url.go @@ -0,0 +1,21 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2025 German Federal Office for Information Security (BSI) +// Software-Engineering: 2025 Intevation GmbH + +package misc + +import "net/url" + +// JoinURL joins the two URLs while preserving the query and fragment part of the latter. +func JoinURL(baseURL *url.URL, relativeURL *url.URL) *url.URL { + u := baseURL.JoinPath(relativeURL.Path) + u.RawQuery = relativeURL.RawQuery + u.RawFragment = relativeURL.RawFragment + // Enforce https, this is required if the base url was only a domain + u.Scheme = "https" + return u +}