From 9af3c75f377c51a7e3f285704025385104d8f428 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 24 Aug 2026 20:26:23 +0200 Subject: [PATCH] chore: set User-Agent on all outgoing HTTP requests (#10867) It's only polite, and it enables better troubleshooting in situations like weird-ass requests to the upgrades download service. --------- Signed-off-by: Jakob Borg --- cmd/infra/stupgrades/main.go | 2 +- cmd/syncthing/cli/client.go | 2 ++ cmd/syncthing/crash_reporting.go | 3 +++ cmd/syncthing/main.go | 2 ++ lib/build/build.go | 4 ++++ lib/discover/global.go | 3 +++ lib/relay/client/dynamic.go | 2 ++ lib/upgrade/upgrade_supported.go | 10 +++++----- lib/upnp/upnp.go | 9 +++++++-- lib/ur/failurereporting.go | 1 + lib/ur/usage_report.go | 1 + 11 files changed, 31 insertions(+), 8 deletions(-) diff --git a/cmd/infra/stupgrades/main.go b/cmd/infra/stupgrades/main.go index 76daee578..a2e97bef8 100644 --- a/cmd/infra/stupgrades/main.go +++ b/cmd/infra/stupgrades/main.go @@ -224,7 +224,7 @@ func filterForLatest(rels []upgrade.Release) []upgrade.Release { return filtered } -var userAgentOSArchExp = regexp.MustCompile(`^syncthing.*\(.+ (\w+)-(\w+)\)$`) +var userAgentOSArchExp = regexp.MustCompile(`^[Ss]yncthing.*\(.+ (\w+)-(\w+)\)$`) func filterForCompatibility(rels []upgrade.Release, ua, osv string) []upgrade.Release { osArch := userAgentOSArchExp.FindStringSubmatch(ua) diff --git a/cmd/syncthing/cli/client.go b/cmd/syncthing/cli/client.go index ace852b85..90ac542ab 100644 --- a/cmd/syncthing/cli/client.go +++ b/cmd/syncthing/cli/client.go @@ -18,6 +18,7 @@ import ( "net/http" "strings" + "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/locations" @@ -115,6 +116,7 @@ func (c *apiClient) Endpoint() string { func (c *apiClient) Do(req *http.Request) (*http.Response, error) { req.Header.Set("X-Api-Key", c.apikey) + req.Header.Set("User-Agent", build.UserAgent()) resp, err := c.Client.Do(req) if err != nil { return nil, err diff --git a/cmd/syncthing/crash_reporting.go b/cmd/syncthing/crash_reporting.go index d9788d83b..e261c83e0 100644 --- a/cmd/syncthing/crash_reporting.go +++ b/cmd/syncthing/crash_reporting.go @@ -20,6 +20,7 @@ import ( "time" "github.com/syncthing/syncthing/internal/slogutil" + "github.com/syncthing/syncthing/lib/build" ) const ( @@ -81,6 +82,7 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error { if err != nil { return err } + headReq.Header.Set("User-Agent", build.UserAgent()) // Set a reasonable timeout on the HEAD request headCtx, headCancel := context.WithTimeout(ctx, headRequestTimeout) @@ -101,6 +103,7 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error { if err != nil { return err } + putReq.Header.Set("User-Agent", build.UserAgent()) // Set a reasonable timeout on the PUT request putCtx, putCancel := context.WithTimeout(ctx, putRequestTimeout) diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 8714b6fa4..14b850e64 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -374,6 +374,7 @@ func upgradeViaRest() error { target := u.String() r, _ := http.NewRequest(http.MethodPost, target, nil) r.Header.Set("X-Api-Key", cfg.GUI().APIKey) + r.Header.Set("User-Agent", build.UserAgent()) tr := &http.Transport{ DialContext: dialer.DialContext, @@ -955,6 +956,7 @@ func (c browserCmd) Run() error { if err != nil { return err } + req.Header.Set("User-Agent", build.UserAgent()) _, err = http.DefaultClient.Do(req) //nolint:bodyclose // we're exiting in a millisecond if err != nil { slog.Error("GUI not available", slogutil.Error(err)) diff --git a/lib/build/build.go b/lib/build/build.go index f4908ea9e..2adf2dd1c 100644 --- a/lib/build/build.go +++ b/lib/build/build.go @@ -127,6 +127,10 @@ func LongVersionFor(program string) string { return v } +func UserAgent() string { + return fmt.Sprintf(`Syncthing/%s (%s %s-%s)`, strings.TrimPrefix(Version, "v"), runtime.Version(), runtime.GOOS, runtime.GOARCH) +} + func TagsList() []string { tags := strings.Split(Tags, ",") if len(tags) == 1 && tags[0] == "" { diff --git a/lib/discover/global.go b/lib/discover/global.go index c944f0a2c..414b44c14 100644 --- a/lib/discover/global.go +++ b/lib/discover/global.go @@ -25,6 +25,7 @@ import ( "golang.org/x/net/http2" "github.com/syncthing/syncthing/internal/slogutil" + "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/connections/registry" "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/events" @@ -454,6 +455,7 @@ func (c *contextClient) Get(ctx context.Context, url string) (*http.Response, er if err != nil { return nil, err } + req.Header.Set("User-Agent", build.UserAgent()) return c.Client.Do(req) } @@ -463,6 +465,7 @@ func (c *contextClient) Post(ctx context.Context, url, ctype string, data io.Rea return nil, err } req.Header.Set("Content-Type", ctype) + req.Header.Set("User-Agent", build.UserAgent()) return c.Client.Do(req) } diff --git a/lib/relay/client/dynamic.go b/lib/relay/client/dynamic.go index fc18104d5..ae39bd3e1 100644 --- a/lib/relay/client/dynamic.go +++ b/lib/relay/client/dynamic.go @@ -14,6 +14,7 @@ import ( "sync" "time" + "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/osutil" "github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/relay/protocol" @@ -53,6 +54,7 @@ func (c *dynamicClient) serve(ctx context.Context) error { l.Debugln(c, "failed to lookup dynamic relays", err) return err } + req.Header.Set("User-Agent", build.UserAgent()) data, err := http.DefaultClient.Do(req) if err != nil { l.Debugln(c, "failed to lookup dynamic relays", err) diff --git a/lib/upgrade/upgrade_supported.go b/lib/upgrade/upgrade_supported.go index 92994bbb5..a41749150 100644 --- a/lib/upgrade/upgrade_supported.go +++ b/lib/upgrade/upgrade_supported.go @@ -15,20 +15,19 @@ import ( "compress/gzip" "encoding/json" "errors" - "fmt" "io" "log/slog" "net/http" "os" "path" "path/filepath" - "runtime" "sort" "strings" "time" "github.com/shirou/gopsutil/v4/host" "github.com/syncthing/syncthing/internal/slogutil" + "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/signature" "github.com/syncthing/syncthing/lib/tlsutil" @@ -79,13 +78,13 @@ func init() { osVersion = strings.TrimSpace(osVersion) } -func upgradeClientGet(url, version string) (*http.Response, error) { +func upgradeClientGet(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } - req.Header.Set("User-Agent", fmt.Sprintf(`syncthing %s (%s %s-%s)`, version, runtime.Version(), runtime.GOOS, runtime.GOARCH)) + req.Header.Set("User-Agent", build.UserAgent()) if osVersion != "" { req.Header.Set("Syncthing-Os-Version", osVersion) } @@ -95,7 +94,7 @@ func upgradeClientGet(url, version string) (*http.Response, error) { // FetchLatestReleases returns the latest releases. The "current" parameter // is used for setting the User-Agent only. func FetchLatestReleases(releasesURL, current string) []Release { - resp, err := upgradeClientGet(releasesURL, current) + resp, err := upgradeClientGet(releasesURL) if err != nil { slog.Warn("Failed to fetch latest release information", slogutil.Error(err)) return nil @@ -227,6 +226,7 @@ func readRelease(archiveName, dir, url string) (string, error) { } req.Header.Add("Accept", "application/octet-stream") + req.Header.Set("User-Agent", build.UserAgent()) resp, err := upgradeClient.Do(req) if err != nil { return "", err diff --git a/lib/upnp/upnp.go b/lib/upnp/upnp.go index 292baa27d..730272eb1 100644 --- a/lib/upnp/upnp.go +++ b/lib/upnp/upnp.go @@ -337,7 +337,12 @@ func parseResponse(ctx context.Context, deviceType string, addr *net.UDPAddr, re } deviceUUID := strings.TrimPrefix(strings.Split(deviceUSN, "::")[0], "uuid:") - response, err = http.Get(deviceDescriptionLocation) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, deviceDescriptionLocation, nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", build.UserAgent()) + response, err = http.DefaultClient.Do(req) if err != nil { return nil, err } @@ -582,7 +587,7 @@ func soapRequestWithIP(ctx context.Context, url, service, function, message stri } req.Close = true req.Header.Set("Content-Type", `text/xml; charset="utf-8"`) - req.Header.Set("User-Agent", "syncthing/1.0") + req.Header.Set("User-Agent", build.UserAgent()) req.Header["SOAPAction"] = []string{fmt.Sprintf(`"%s#%s"`, service, function)} // Enforce capitalization in header-entry for sensitive routers. See issue #1696 req.Header.Set("Connection", "Close") req.Header.Set("Cache-Control", "no-cache") diff --git a/lib/ur/failurereporting.go b/lib/ur/failurereporting.go index bbf2c53ec..f9be60d71 100644 --- a/lib/ur/failurereporting.go +++ b/lib/ur/failurereporting.go @@ -214,6 +214,7 @@ func sendFailureReports(ctx context.Context, reports []contract.FailureReport, u return } req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", build.UserAgent()) resp, err := client.Do(req) if err != nil { diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go index 71c38fd85..65424bace 100644 --- a/lib/ur/usage_report.go +++ b/lib/ur/usage_report.go @@ -373,6 +373,7 @@ func (s *Service) sendUsageReport(ctx context.Context) error { return err } req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", build.UserAgent()) resp, err := client.Do(req) if err != nil { return err