user_agent.go (7452B)
1 package middleware 2 3 import ( 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 15 var languageVersion = strings.TrimPrefix(runtime.Version(), "go") 16 17 // SDKAgentKeyType is the metadata type to add to the SDK agent string 18 type 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. 22 const ( 23 _ SDKAgentKeyType = iota 24 APIMetadata 25 OperatingSystemMetadata 26 LanguageMetadata 27 EnvironmentMetadata 28 FeatureMetadata 29 ConfigMetadata 30 FrameworkMetadata 31 AdditionalMetadata 32 ApplicationIdentifier 33 ) 34 35 func (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 60 const execEnvVar = `AWS_EXECUTION_ENV` 61 62 var validChars = map[rune]bool{ 63 '!': true, '#': true, '$': true, '%': true, '&': true, '\'': true, '*': true, '+': true, 64 '-': true, '.': true, '^': true, '_': true, '`': true, '|': true, '~': true, 65 } 66 67 // RequestUserAgent is a build middleware that set the User-Agent for the request. 68 type RequestUserAgent struct { 69 sdkAgent, userAgent *smithyhttp.UserAgentBuilder 70 } 71 72 // NewRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the 73 // request. 74 // 75 // User-Agent example: 76 // 77 // aws-sdk-go-v2/1.2.3 78 // 79 // X-Amz-User-Agent example: 80 // 81 // aws-sdk-go-v2/1.2.3 md/GOOS/linux md/GOARCH/amd64 lang/go/1.15 82 func NewRequestUserAgent() *RequestUserAgent { 83 userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder() 84 addProductName(userAgent) 85 addProductName(sdkAgent) 86 87 r := &RequestUserAgent{ 88 sdkAgent: sdkAgent, 89 userAgent: userAgent, 90 } 91 92 addSDKMetadata(r) 93 94 return r 95 } 96 97 func addSDKMetadata(r *RequestUserAgent) { 98 r.AddSDKAgentKey(OperatingSystemMetadata, getNormalizedOSName()) 99 r.AddSDKAgentKeyValue(LanguageMetadata, "go", languageVersion) 100 r.AddSDKAgentKeyValue(AdditionalMetadata, "GOOS", runtime.GOOS) 101 r.AddSDKAgentKeyValue(AdditionalMetadata, "GOARCH", runtime.GOARCH) 102 if ev := os.Getenv(execEnvVar); len(ev) > 0 { 103 r.AddSDKAgentKey(EnvironmentMetadata, ev) 104 } 105 } 106 107 func addProductName(builder *smithyhttp.UserAgentBuilder) { 108 builder.AddKeyValue(aws.SDKName, aws.SDKVersion) 109 } 110 111 // AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one. 112 func AddUserAgentKey(key string) func(*middleware.Stack) error { 113 return func(stack *middleware.Stack) error { 114 requestUserAgent, err := getOrAddRequestUserAgent(stack) 115 if err != nil { 116 return err 117 } 118 requestUserAgent.AddUserAgentKey(key) 119 return nil 120 } 121 } 122 123 // AddUserAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one. 124 func AddUserAgentKeyValue(key, value string) func(*middleware.Stack) error { 125 return func(stack *middleware.Stack) error { 126 requestUserAgent, err := getOrAddRequestUserAgent(stack) 127 if err != nil { 128 return err 129 } 130 requestUserAgent.AddUserAgentKeyValue(key, value) 131 return nil 132 } 133 } 134 135 // AddSDKAgentKey retrieves a requestUserAgent from the provided stack, or initializes one. 136 func AddSDKAgentKey(keyType SDKAgentKeyType, key string) func(*middleware.Stack) error { 137 return func(stack *middleware.Stack) error { 138 requestUserAgent, err := getOrAddRequestUserAgent(stack) 139 if err != nil { 140 return err 141 } 142 requestUserAgent.AddSDKAgentKey(keyType, key) 143 return nil 144 } 145 } 146 147 // AddSDKAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one. 148 func AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) func(*middleware.Stack) error { 149 return func(stack *middleware.Stack) error { 150 requestUserAgent, err := getOrAddRequestUserAgent(stack) 151 if err != nil { 152 return err 153 } 154 requestUserAgent.AddSDKAgentKeyValue(keyType, key, value) 155 return nil 156 } 157 } 158 159 // AddRequestUserAgentMiddleware registers a requestUserAgent middleware on the stack if not present. 160 func AddRequestUserAgentMiddleware(stack *middleware.Stack) error { 161 _, err := getOrAddRequestUserAgent(stack) 162 return err 163 } 164 165 func getOrAddRequestUserAgent(stack *middleware.Stack) (*RequestUserAgent, error) { 166 id := (*RequestUserAgent)(nil).ID() 167 bm, ok := stack.Build.Get(id) 168 if !ok { 169 bm = NewRequestUserAgent() 170 err := stack.Build.Add(bm, middleware.After) 171 if err != nil { 172 return nil, err 173 } 174 } 175 176 requestUserAgent, ok := bm.(*RequestUserAgent) 177 if !ok { 178 return nil, fmt.Errorf("%T for %s middleware did not match expected type", bm, id) 179 } 180 181 return requestUserAgent, nil 182 } 183 184 // AddUserAgentKey adds the component identified by name to the User-Agent string. 185 func (u *RequestUserAgent) AddUserAgentKey(key string) { 186 u.userAgent.AddKey(strings.Map(rules, key)) 187 } 188 189 // AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string. 190 func (u *RequestUserAgent) AddUserAgentKeyValue(key, value string) { 191 u.userAgent.AddKeyValue(strings.Map(rules, key), strings.Map(rules, value)) 192 } 193 194 // AddSDKAgentKey adds the component identified by name to the User-Agent string. 195 func (u *RequestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) { 196 // TODO: should target sdkAgent 197 u.userAgent.AddKey(keyType.string() + "/" + strings.Map(rules, key)) 198 } 199 200 // AddSDKAgentKeyValue adds the key identified by the given name and value to the User-Agent string. 201 func (u *RequestUserAgent) AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) { 202 // TODO: should target sdkAgent 203 u.userAgent.AddKeyValue(keyType.string(), strings.Map(rules, key)+"#"+strings.Map(rules, value)) 204 } 205 206 // ID the name of the middleware. 207 func (u *RequestUserAgent) ID() string { 208 return "UserAgent" 209 } 210 211 // HandleBuild adds or appends the constructed user agent to the request. 212 func (u *RequestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) ( 213 out middleware.BuildOutput, metadata middleware.Metadata, err error, 214 ) { 215 switch req := in.Request.(type) { 216 case *smithyhttp.Request: 217 u.addHTTPUserAgent(req) 218 // TODO: To be re-enabled 219 // u.addHTTPSDKAgent(req) 220 default: 221 return out, metadata, fmt.Errorf("unknown transport type %T", in) 222 } 223 224 return next.HandleBuild(ctx, in) 225 } 226 227 func (u *RequestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) { 228 const userAgent = "User-Agent" 229 updateHTTPHeader(request, userAgent, u.userAgent.Build()) 230 } 231 232 func (u *RequestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) { 233 const sdkAgent = "X-Amz-User-Agent" 234 updateHTTPHeader(request, sdkAgent, u.sdkAgent.Build()) 235 } 236 237 func updateHTTPHeader(request *smithyhttp.Request, header string, value string) { 238 var current string 239 if v := request.Header[header]; len(v) > 0 { 240 current = v[0] 241 } 242 if len(current) > 0 { 243 current = value + " " + current 244 } else { 245 current = value 246 } 247 request.Header[header] = append(request.Header[header][:0], current) 248 } 249 250 func rules(r rune) rune { 251 switch { 252 case r >= '0' && r <= '9': 253 return r 254 case r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z': 255 return r 256 case validChars[r]: 257 return r 258 default: 259 return '-' 260 } 261 }