Files

76 lines
2.4 KiB
Go

package credhelper
import (
"fmt"
"net/http"
"runtime"
"sync"
"time"
)
// userAgent returns the User-Agent string for outgoing HTTP requests.
//
// Format: <binary-name>/<version> (<os>/<arch>; commit <short>)
//
// Format follows the convention Docker's own clients use, so it parses
// cleanly with the same regexes server-side log analyzers already
// understand. The commit suffix lets users on the device-approval page
// distinguish two devices on the same version line if they ever need to.
func userAgent() string {
short := cfg.Commit
if len(short) > 7 {
short = short[:7]
}
return fmt.Sprintf("%s/%s (%s/%s; commit %s)",
cfg.BinaryName, cfg.Version, runtime.GOOS, runtime.GOARCH, short)
}
// uaTransport wraps another RoundTripper and sets the User-Agent header
// on every request that doesn't already carry one. Used as the default
// transport for the helper's shared http.Client so we can't forget to
// set the UA on a future call site.
type uaTransport struct {
base http.RoundTripper
}
func (t *uaTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if req.Header.Get("User-Agent") == "" {
// Clone before mutating: net/http may retry a request and the
// caller could be using the same *Request elsewhere.
clone := req.Clone(req.Context())
clone.Header.Set("User-Agent", userAgent())
req = clone
}
base := t.base
if base == nil {
base = http.DefaultTransport
}
return base.RoundTrip(req)
}
var sharedHTTPClient = sync.OnceValue(func() *http.Client {
return &http.Client{
Transport: &uaTransport{base: http.DefaultTransport},
}
})
// httpClient returns the shared UA-tagged http.Client used for all of
// the helper's outgoing HTTP requests. It carries no per-request
// timeout — call sites that want one should use httpClientWithTimeout.
func httpClient() *http.Client {
return sharedHTTPClient()
}
// httpClientWithTimeout returns a fresh client that shares the shared
// transport (so connection pooling and the UA header are preserved) but
// scopes a per-client timeout. CheckRedirect can be supplied for cases
// like fetchLatestVersion that need to inspect a redirect rather than
// follow it.
func httpClientWithTimeout(timeout time.Duration, checkRedirect func(*http.Request, []*http.Request) error) *http.Client {
return &http.Client{
Transport: sharedHTTPClient().Transport,
Timeout: timeout,
CheckRedirect: checkRedirect,
}
}