1package dockerversion // import "github.com/docker/docker/dockerversion"
2
3import (
4	"context"
5	"fmt"
6	"runtime"
7
8	"github.com/docker/docker/pkg/parsers/kernel"
9	"github.com/docker/docker/pkg/useragent"
10)
11
12// UAStringKey is used as key type for user-agent string in net/context struct
13type UAStringKey struct{}
14
15// DockerUserAgent is the User-Agent the Docker client uses to identify itself.
16// In accordance with RFC 7231 (5.5.3) is of the form:
17//    [docker client's UA] UpstreamClient([upstream client's UA])
18func DockerUserAgent(ctx context.Context) string {
19	httpVersion := make([]useragent.VersionInfo, 0, 6)
20	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
21	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
22	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
23	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
24		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
25	}
26	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
27	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
28
29	dockerUA := useragent.AppendVersions("", httpVersion...)
30	upstreamUA := getUserAgentFromContext(ctx)
31	if len(upstreamUA) > 0 {
32		ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
33		return ret
34	}
35	return dockerUA
36}
37
38// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
39func getUserAgentFromContext(ctx context.Context) string {
40	var upstreamUA string
41	if ctx != nil {
42		var ki interface{} = ctx.Value(UAStringKey{})
43		if ki != nil {
44			upstreamUA = ctx.Value(UAStringKey{}).(string)
45		}
46	}
47	return upstreamUA
48}
49
50// escapeStr returns s with every rune in charsToEscape escaped by a backslash
51func escapeStr(s string, charsToEscape string) string {
52	var ret string
53	for _, currRune := range s {
54		appended := false
55		for _, escapableRune := range charsToEscape {
56			if currRune == escapableRune {
57				ret += `\` + string(currRune)
58				appended = true
59				break
60			}
61		}
62		if !appended {
63			ret += string(currRune)
64		}
65	}
66	return ret
67}
68
69// insertUpstreamUserAgent adds the upstream client useragent to create a user-agent
70// string of the form:
71//   $dockerUA UpstreamClient($upstreamUA)
72func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
73	charsToEscape := `();\`
74	upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
75	return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
76}
77