1package middleware 2 3import ( 4 "context" 5 "fmt" 6 "os" 7 "runtime" 8 "strings" 9 10 "github.com/aws/aws-sdk-go-v2/aws" 11 "github.com/aws/smithy-go/middleware" 12 smithyhttp "github.com/aws/smithy-go/transport/http" 13) 14 15var languageVersion = strings.TrimPrefix(runtime.Version(), "go") 16 17// SDKAgentKeyType is the metadata type to add to the SDK agent string 18type SDKAgentKeyType int 19 20// The set of valid SDKAgentKeyType constants. If an unknown value is assigned for SDKAgentKeyType it will 21// be mapped to AdditionalMetadata. 22const ( 23 _ SDKAgentKeyType = iota 24 APIMetadata 25 OperatingSystemMetadata 26 LanguageMetadata 27 EnvironmentMetadata 28 FeatureMetadata 29 ConfigMetadata 30 FrameworkMetadata 31 AdditionalMetadata 32 ApplicationIdentifier 33) 34 35func (k SDKAgentKeyType) string() string { 36 switch k { 37 case APIMetadata: 38 return "api" 39 case OperatingSystemMetadata: 40 return "os" 41 case LanguageMetadata: 42 return "lang" 43 case EnvironmentMetadata: 44 return "exec-env" 45 case FeatureMetadata: 46 return "ft" 47 case ConfigMetadata: 48 return "cfg" 49 case FrameworkMetadata: 50 return "lib" 51 case ApplicationIdentifier: 52 return "app" 53 case AdditionalMetadata: 54 fallthrough 55 default: 56 return "md" 57 } 58} 59 60const execEnvVar = `AWS_EXECUTION_ENV` 61 62// requestUserAgent is a build middleware that set the User-Agent for the request. 63type requestUserAgent struct { 64 sdkAgent, userAgent *smithyhttp.UserAgentBuilder 65} 66 67// newRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the 68// request. 69// 70// User-Agent example: 71// aws-sdk-go-v2/1.2.3 72// 73// X-Amz-User-Agent example: 74// aws-sdk-go-v2/1.2.3 md/GOOS/linux md/GOARCH/amd64 lang/go/1.15 75func newRequestUserAgent() *requestUserAgent { 76 userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder() 77 addProductName(userAgent) 78 addProductName(sdkAgent) 79 80 r := &requestUserAgent{ 81 sdkAgent: sdkAgent, 82 userAgent: userAgent, 83 } 84 85 addSDKMetadata(r) 86 87 return r 88} 89 90func getNormalizedOSName() (os string) { 91 switch runtime.GOOS { 92 case "android": 93 os = "android" 94 case "linux": 95 os = "linux" 96 case "windows": 97 os = "windows" 98 case "darwin": 99 // Due to Apple M1 we can't distinguish between macOS and iOS when GOOS/GOARCH is darwin/amd64 100 // For now declare this as "other" until we have a better detection mechanism. 101 fallthrough 102 default: 103 os = "other" 104 } 105 return os 106} 107 108func addSDKMetadata(r *requestUserAgent) { 109 r.AddSDKAgentKey(OperatingSystemMetadata, getNormalizedOSName()) 110 r.AddSDKAgentKeyValue(LanguageMetadata, "go", languageVersion) 111 r.AddSDKAgentKeyValue(AdditionalMetadata, "GOOS", runtime.GOOS) 112 r.AddSDKAgentKeyValue(AdditionalMetadata, "GOARCH", runtime.GOARCH) 113 if ev := os.Getenv(execEnvVar); len(ev) > 0 { 114 r.AddSDKAgentKey(EnvironmentMetadata, ev) 115 } 116} 117 118func addProductName(builder *smithyhttp.UserAgentBuilder) { 119 builder.AddKeyValue(aws.SDKName, aws.SDKVersion) 120} 121 122// AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one. 123func AddUserAgentKey(key string) func(*middleware.Stack) error { 124 return func(stack *middleware.Stack) error { 125 requestUserAgent, err := getOrAddRequestUserAgent(stack) 126 if err != nil { 127 return err 128 } 129 requestUserAgent.AddUserAgentKey(key) 130 return nil 131 } 132} 133 134// AddUserAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one. 135func AddUserAgentKeyValue(key, value string) func(*middleware.Stack) error { 136 return func(stack *middleware.Stack) error { 137 requestUserAgent, err := getOrAddRequestUserAgent(stack) 138 if err != nil { 139 return err 140 } 141 requestUserAgent.AddUserAgentKeyValue(key, value) 142 return nil 143 } 144} 145 146// AddSDKAgentKey retrieves a requestUserAgent from the provided stack, or initializes one. 147func AddSDKAgentKey(keyType SDKAgentKeyType, key string) func(*middleware.Stack) error { 148 return func(stack *middleware.Stack) error { 149 requestUserAgent, err := getOrAddRequestUserAgent(stack) 150 if err != nil { 151 return err 152 } 153 requestUserAgent.AddSDKAgentKey(keyType, key) 154 return nil 155 } 156} 157 158// AddSDKAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one. 159func AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) func(*middleware.Stack) error { 160 return func(stack *middleware.Stack) error { 161 requestUserAgent, err := getOrAddRequestUserAgent(stack) 162 if err != nil { 163 return err 164 } 165 requestUserAgent.AddSDKAgentKeyValue(keyType, key, value) 166 return nil 167 } 168} 169 170// AddRequestUserAgentMiddleware registers a requestUserAgent middleware on the stack if not present. 171func AddRequestUserAgentMiddleware(stack *middleware.Stack) error { 172 _, err := getOrAddRequestUserAgent(stack) 173 return err 174} 175 176func getOrAddRequestUserAgent(stack *middleware.Stack) (*requestUserAgent, error) { 177 id := (*requestUserAgent)(nil).ID() 178 bm, ok := stack.Build.Get(id) 179 if !ok { 180 bm = newRequestUserAgent() 181 err := stack.Build.Add(bm, middleware.After) 182 if err != nil { 183 return nil, err 184 } 185 } 186 187 requestUserAgent, ok := bm.(*requestUserAgent) 188 if !ok { 189 return nil, fmt.Errorf("%T for %s middleware did not match expected type", bm, id) 190 } 191 192 return requestUserAgent, nil 193} 194 195// AddUserAgentKey adds the component identified by name to the User-Agent string. 196func (u *requestUserAgent) AddUserAgentKey(key string) { 197 u.userAgent.AddKey(key) 198} 199 200// AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string. 201func (u *requestUserAgent) AddUserAgentKeyValue(key, value string) { 202 u.userAgent.AddKeyValue(key, value) 203} 204 205// AddUserAgentKey adds the component identified by name to the User-Agent string. 206func (u *requestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) { 207 u.sdkAgent.AddKey(keyType.string() + "/" + key) 208} 209 210// AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string. 211func (u *requestUserAgent) AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) { 212 u.sdkAgent.AddKeyValue(keyType.string()+"/"+key, value) 213} 214 215// ID the name of the middleware. 216func (u *requestUserAgent) ID() string { 217 return "UserAgent" 218} 219 220// HandleBuild adds or appends the constructed user agent to the request. 221func (u *requestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) ( 222 out middleware.BuildOutput, metadata middleware.Metadata, err error, 223) { 224 switch req := in.Request.(type) { 225 case *smithyhttp.Request: 226 u.addHTTPUserAgent(req) 227 u.addHTTPSDKAgent(req) 228 default: 229 return out, metadata, fmt.Errorf("unknown transport type %T", in) 230 } 231 232 return next.HandleBuild(ctx, in) 233} 234 235func (u *requestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) { 236 const userAgent = "User-Agent" 237 updateHTTPHeader(request, userAgent, u.userAgent.Build()) 238} 239 240func (u *requestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) { 241 const sdkAgent = "X-Amz-User-Agent" 242 updateHTTPHeader(request, sdkAgent, u.sdkAgent.Build()) 243} 244 245func updateHTTPHeader(request *smithyhttp.Request, header string, value string) { 246 var current string 247 if v := request.Header[header]; len(v) > 0 { 248 current = v[0] 249 } 250 if len(current) > 0 { 251 current = value + " " + current 252 } else { 253 current = value 254 } 255 request.Header[header] = append(request.Header[header][:0], current) 256} 257