From 6ac97810d0337b385633a2a1c8a8f80c6a71b478 Mon Sep 17 00:00:00 2001 From: koplas Date: Thu, 19 Jun 2025 15:11:45 +0200 Subject: [PATCH 1/2] Use JoinPath This avoids issues where parts of the URL are discarded. --- cmd/csaf_checker/links.go | 3 ++- cmd/csaf_checker/processor.go | 11 ++++++----- cmd/csaf_checker/roliecheck.go | 7 ++++--- cmd/csaf_downloader/downloader.go | 2 +- csaf/advisories.go | 5 +++-- internal/misc/url.go | 21 +++++++++++++++++++++ 6 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 internal/misc/url.go 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 +} From 1098c6add07755d8f628edd90d3d5bc67796f812 Mon Sep 17 00:00:00 2001 From: koplas Date: Fri, 20 Jun 2025 14:46:26 +0200 Subject: [PATCH 2/2] Use correct base URL --- cmd/csaf_checker/processor.go | 5 +---- cmd/csaf_checker/roliecheck.go | 3 ++- cmd/csaf_downloader/downloader.go | 1 + csaf/advisories.go | 1 + 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index c0c4437..bfaf9e1 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -632,7 +632,6 @@ func (p *processor) integrity( if err != nil { return err } - makeAbs := makeAbsolute(b) client := p.httpClient() var data bytes.Buffer @@ -643,7 +642,6 @@ func (p *processor) integrity( lg(ErrorType, "Bad URL %s: %v", f, err) continue } - fp = makeAbs(fp) u := misc.JoinURL(b, fp).String() @@ -777,7 +775,6 @@ func (p *processor) integrity( lg(ErrorType, "Bad URL %s: %v", x.url(), err) continue } - hu = makeAbs(hu) hashFile := misc.JoinURL(b, hu).String() p.checkTLS(hashFile) @@ -827,7 +824,6 @@ func (p *processor) integrity( lg(ErrorType, "Bad URL %s: %v", f.SignURL(), err) continue } - su = makeAbs(su) sigFile := misc.JoinURL(b, su).String() p.checkTLS(sigFile) @@ -1527,6 +1523,7 @@ func (p *processor) checkPGPKeys(_ string) error { if err != nil { return err } + base.Path = "" for i := range keys { key := &keys[i] diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 0a9ff04..ace4d0d 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -222,6 +222,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { if err != nil { return err } + base.Path = "" p.badROLIEFeed.use() advisories := map[*csaf.Feed][]csaf.AdvisoryFile{} @@ -291,7 +292,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { // TODO: Issue a warning if we want check AMBER+ without an // authorizing client. - if err := p.integrity(files, feedBase, rolieMask, p.badProviderMetadata.add); err != nil { + if err := p.integrity(files, base.String(), rolieMask, p.badProviderMetadata.add); err != nil { if err != errContinue { return err } diff --git a/cmd/csaf_downloader/downloader.go b/cmd/csaf_downloader/downloader.go index 90e3ac3..2b08544 100644 --- a/cmd/csaf_downloader/downloader.go +++ b/cmd/csaf_downloader/downloader.go @@ -229,6 +229,7 @@ func (d *downloader) download(ctx context.Context, domain string) error { if err != nil { return fmt.Errorf("invalid URL '%s': %v", lpmd.URL, err) } + base.Path = "" expr := util.NewPathEval() diff --git a/csaf/advisories.go b/csaf/advisories.go index e7bc11a..c5e4fea 100644 --- a/csaf/advisories.go +++ b/csaf/advisories.go @@ -295,6 +295,7 @@ func (afp *AdvisoryFileProcessor) processROLIE( slog.Error("Cannot parse feed base URL", "url", fb, "err", err) continue } + feedBaseURL.Path = "" res, err := afp.client.Get(feedURL.String()) if err != nil {