src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }