mirror of
https://github.com/moby/moby.git
synced 2026-06-24 08:48:23 +00:00
115 lines
3.4 KiB
Go
115 lines
3.4 KiB
Go
package dockerversion
|
|
|
|
import (
|
|
"context"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/moby/moby/v2/pkg/parsers/kernel"
|
|
"github.com/moby/moby/v2/pkg/useragent"
|
|
)
|
|
|
|
// uaStringKey is used as key type for user-agent string in net/context struct
|
|
type uaStringKey struct{}
|
|
|
|
// WithUpstreamUserAgent returns a new context carrying the upstream client's
|
|
// User-Agent string.
|
|
func WithUpstreamUserAgent(ctx context.Context, ua string) context.Context {
|
|
if ua == "" {
|
|
return ctx
|
|
}
|
|
return context.WithValue(ctx, uaStringKey{}, ua)
|
|
}
|
|
|
|
// DockerUserAgent is the User-Agent used by the daemon.
|
|
//
|
|
// It consists of the daemon's User-Agent, optional version metadata, and
|
|
// an optional upstream client comment:
|
|
//
|
|
// [daemon user agent] [extra] [UpstreamClient(<upstream-user-agent>)]
|
|
//
|
|
// "UpstreamClient" is a Docker-defined convention. The upstream value is
|
|
// sanitized before inclusion. See [RFC9110], section 10.1.5.
|
|
//
|
|
// [RFC9110]: https://www.rfc-editor.org/rfc/rfc9110#section-10.1.5
|
|
func DockerUserAgent(ctx context.Context, extraVersions ...useragent.VersionInfo) string {
|
|
ua := useragent.AppendVersions(getDaemonUserAgent(), extraVersions...)
|
|
if upstreamUA := getUpstreamUserAgent(ctx); upstreamUA != "" {
|
|
ua += " " + upstreamUA
|
|
}
|
|
return ua
|
|
}
|
|
|
|
var (
|
|
daemonUAOnce sync.Once
|
|
daemonUA string
|
|
)
|
|
|
|
// getDaemonUserAgent returns the user-agent to use for requests made by
|
|
// the daemon.
|
|
//
|
|
// It includes:
|
|
//
|
|
// - the docker version
|
|
// - go version
|
|
// - git-commit
|
|
// - kernel version
|
|
// - os
|
|
// - architecture
|
|
func getDaemonUserAgent() string {
|
|
daemonUAOnce.Do(func() {
|
|
httpVersion := make([]useragent.VersionInfo, 0, 6)
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
|
|
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
|
|
}
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
|
|
daemonUA = useragent.AppendVersions("", httpVersion...)
|
|
})
|
|
return daemonUA
|
|
}
|
|
|
|
// getUpstreamUserAgent returns the previously saved user-agent context stored
|
|
// in ctx, if one exists, and formats it as:
|
|
//
|
|
// UpstreamClient(<upstream user agent string>)
|
|
//
|
|
// It returns an empty string if no user-agent is present in the context.
|
|
func getUpstreamUserAgent(ctx context.Context) string {
|
|
upstreamUA, ok := ctx.Value(uaStringKey{}).(string)
|
|
if !ok || upstreamUA == "" {
|
|
return ""
|
|
}
|
|
|
|
return "UpstreamClient(" + escapeStr(upstreamUA) + ")"
|
|
}
|
|
|
|
// escapeStr escapes and sanitizes s for use in a User-Agent comment ([RFC9110]).
|
|
//
|
|
// [RFC9110]: https://www.rfc-editor.org/rfc/rfc9110#section-10.1.5
|
|
func escapeStr(s string) string {
|
|
var b strings.Builder
|
|
b.Grow(len(s))
|
|
|
|
for i := range len(s) {
|
|
switch c := s[i]; c {
|
|
// TODO(thaJeztah): remove redundant escaping semicolons; see https://github.com/moby/moby/pull/52356#discussion_r3234266285
|
|
case '(', ')', ';', '\\':
|
|
b.WriteByte('\\')
|
|
b.WriteByte(c)
|
|
case '\t':
|
|
b.WriteByte(c)
|
|
default:
|
|
if c >= 0x20 && c != 0x7f {
|
|
b.WriteByte(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|