1package autorest 2 3// Copyright 2017 Microsoft Corporation 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "encoding/xml" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "mime/multipart" 26 "net/http" 27 "net/url" 28 "strings" 29) 30 31const ( 32 mimeTypeJSON = "application/json" 33 mimeTypeOctetStream = "application/octet-stream" 34 mimeTypeFormPost = "application/x-www-form-urlencoded" 35 36 headerAuthorization = "Authorization" 37 headerAuxAuthorization = "x-ms-authorization-auxiliary" 38 headerContentType = "Content-Type" 39 headerUserAgent = "User-Agent" 40) 41 42// used as a key type in context.WithValue() 43type ctxPrepareDecorators struct{} 44 45// WithPrepareDecorators adds the specified PrepareDecorators to the provided context. 46// If no PrepareDecorators are provided the context is unchanged. 47func WithPrepareDecorators(ctx context.Context, prepareDecorator []PrepareDecorator) context.Context { 48 if len(prepareDecorator) == 0 { 49 return ctx 50 } 51 return context.WithValue(ctx, ctxPrepareDecorators{}, prepareDecorator) 52} 53 54// GetPrepareDecorators returns the PrepareDecorators in the provided context or the provided default PrepareDecorators. 55func GetPrepareDecorators(ctx context.Context, defaultPrepareDecorators ...PrepareDecorator) []PrepareDecorator { 56 inCtx := ctx.Value(ctxPrepareDecorators{}) 57 if pd, ok := inCtx.([]PrepareDecorator); ok { 58 return pd 59 } 60 return defaultPrepareDecorators 61} 62 63// Preparer is the interface that wraps the Prepare method. 64// 65// Prepare accepts and possibly modifies an http.Request (e.g., adding Headers). Implementations 66// must ensure to not share or hold per-invocation state since Preparers may be shared and re-used. 67type Preparer interface { 68 Prepare(*http.Request) (*http.Request, error) 69} 70 71// PreparerFunc is a method that implements the Preparer interface. 72type PreparerFunc func(*http.Request) (*http.Request, error) 73 74// Prepare implements the Preparer interface on PreparerFunc. 75func (pf PreparerFunc) Prepare(r *http.Request) (*http.Request, error) { 76 return pf(r) 77} 78 79// PrepareDecorator takes and possibly decorates, by wrapping, a Preparer. Decorators may affect the 80// http.Request and pass it along or, first, pass the http.Request along then affect the result. 81type PrepareDecorator func(Preparer) Preparer 82 83// CreatePreparer creates, decorates, and returns a Preparer. 84// Without decorators, the returned Preparer returns the passed http.Request unmodified. 85// Preparers are safe to share and re-use. 86func CreatePreparer(decorators ...PrepareDecorator) Preparer { 87 return DecoratePreparer( 88 Preparer(PreparerFunc(func(r *http.Request) (*http.Request, error) { return r, nil })), 89 decorators...) 90} 91 92// DecoratePreparer accepts a Preparer and a, possibly empty, set of PrepareDecorators, which it 93// applies to the Preparer. Decorators are applied in the order received, but their affect upon the 94// request depends on whether they are a pre-decorator (change the http.Request and then pass it 95// along) or a post-decorator (pass the http.Request along and alter it on return). 96func DecoratePreparer(p Preparer, decorators ...PrepareDecorator) Preparer { 97 for _, decorate := range decorators { 98 p = decorate(p) 99 } 100 return p 101} 102 103// Prepare accepts an http.Request and a, possibly empty, set of PrepareDecorators. 104// It creates a Preparer from the decorators which it then applies to the passed http.Request. 105func Prepare(r *http.Request, decorators ...PrepareDecorator) (*http.Request, error) { 106 if r == nil { 107 return nil, NewError("autorest", "Prepare", "Invoked without an http.Request") 108 } 109 return CreatePreparer(decorators...).Prepare(r) 110} 111 112// WithNothing returns a "do nothing" PrepareDecorator that makes no changes to the passed 113// http.Request. 114func WithNothing() PrepareDecorator { 115 return func(p Preparer) Preparer { 116 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 117 return p.Prepare(r) 118 }) 119 } 120} 121 122// WithHeader returns a PrepareDecorator that sets the specified HTTP header of the http.Request to 123// the passed value. It canonicalizes the passed header name (via http.CanonicalHeaderKey) before 124// adding the header. 125func WithHeader(header string, value string) PrepareDecorator { 126 return func(p Preparer) Preparer { 127 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 128 r, err := p.Prepare(r) 129 if err == nil { 130 setHeader(r, http.CanonicalHeaderKey(header), value) 131 } 132 return r, err 133 }) 134 } 135} 136 137// WithHeaders returns a PrepareDecorator that sets the specified HTTP headers of the http.Request to 138// the passed value. It canonicalizes the passed headers name (via http.CanonicalHeaderKey) before 139// adding them. 140func WithHeaders(headers map[string]interface{}) PrepareDecorator { 141 h := ensureValueStrings(headers) 142 return func(p Preparer) Preparer { 143 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 144 r, err := p.Prepare(r) 145 if err == nil { 146 if r.Header == nil { 147 r.Header = make(http.Header) 148 } 149 150 for name, value := range h { 151 r.Header.Set(http.CanonicalHeaderKey(name), value) 152 } 153 } 154 return r, err 155 }) 156 } 157} 158 159// WithBearerAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose 160// value is "Bearer " followed by the supplied token. 161func WithBearerAuthorization(token string) PrepareDecorator { 162 return WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", token)) 163} 164 165// AsContentType returns a PrepareDecorator that adds an HTTP Content-Type header whose value 166// is the passed contentType. 167func AsContentType(contentType string) PrepareDecorator { 168 return WithHeader(headerContentType, contentType) 169} 170 171// WithUserAgent returns a PrepareDecorator that adds an HTTP User-Agent header whose value is the 172// passed string. 173func WithUserAgent(ua string) PrepareDecorator { 174 return WithHeader(headerUserAgent, ua) 175} 176 177// AsFormURLEncoded returns a PrepareDecorator that adds an HTTP Content-Type header whose value is 178// "application/x-www-form-urlencoded". 179func AsFormURLEncoded() PrepareDecorator { 180 return AsContentType(mimeTypeFormPost) 181} 182 183// AsJSON returns a PrepareDecorator that adds an HTTP Content-Type header whose value is 184// "application/json". 185func AsJSON() PrepareDecorator { 186 return AsContentType(mimeTypeJSON) 187} 188 189// AsOctetStream returns a PrepareDecorator that adds the "application/octet-stream" Content-Type header. 190func AsOctetStream() PrepareDecorator { 191 return AsContentType(mimeTypeOctetStream) 192} 193 194// WithMethod returns a PrepareDecorator that sets the HTTP method of the passed request. The 195// decorator does not validate that the passed method string is a known HTTP method. 196func WithMethod(method string) PrepareDecorator { 197 return func(p Preparer) Preparer { 198 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 199 r.Method = method 200 return p.Prepare(r) 201 }) 202 } 203} 204 205// AsDelete returns a PrepareDecorator that sets the HTTP method to DELETE. 206func AsDelete() PrepareDecorator { return WithMethod("DELETE") } 207 208// AsGet returns a PrepareDecorator that sets the HTTP method to GET. 209func AsGet() PrepareDecorator { return WithMethod("GET") } 210 211// AsHead returns a PrepareDecorator that sets the HTTP method to HEAD. 212func AsHead() PrepareDecorator { return WithMethod("HEAD") } 213 214// AsMerge returns a PrepareDecorator that sets the HTTP method to MERGE. 215func AsMerge() PrepareDecorator { return WithMethod("MERGE") } 216 217// AsOptions returns a PrepareDecorator that sets the HTTP method to OPTIONS. 218func AsOptions() PrepareDecorator { return WithMethod("OPTIONS") } 219 220// AsPatch returns a PrepareDecorator that sets the HTTP method to PATCH. 221func AsPatch() PrepareDecorator { return WithMethod("PATCH") } 222 223// AsPost returns a PrepareDecorator that sets the HTTP method to POST. 224func AsPost() PrepareDecorator { return WithMethod("POST") } 225 226// AsPut returns a PrepareDecorator that sets the HTTP method to PUT. 227func AsPut() PrepareDecorator { return WithMethod("PUT") } 228 229// WithBaseURL returns a PrepareDecorator that populates the http.Request with a url.URL constructed 230// from the supplied baseUrl. Query parameters will be encoded as required. 231func WithBaseURL(baseURL string) PrepareDecorator { 232 return func(p Preparer) Preparer { 233 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 234 r, err := p.Prepare(r) 235 if err == nil { 236 var u *url.URL 237 if u, err = url.Parse(baseURL); err != nil { 238 return r, err 239 } 240 if u.Scheme == "" { 241 return r, fmt.Errorf("autorest: No scheme detected in URL %s", baseURL) 242 } 243 if u.RawQuery != "" { 244 q, err := url.ParseQuery(u.RawQuery) 245 if err != nil { 246 return r, err 247 } 248 u.RawQuery = q.Encode() 249 } 250 r.URL = u 251 } 252 return r, err 253 }) 254 } 255} 256 257// WithBytes returns a PrepareDecorator that takes a list of bytes 258// which passes the bytes directly to the body 259func WithBytes(input *[]byte) PrepareDecorator { 260 return func(p Preparer) Preparer { 261 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 262 r, err := p.Prepare(r) 263 if err == nil { 264 if input == nil { 265 return r, fmt.Errorf("Input Bytes was nil") 266 } 267 268 r.ContentLength = int64(len(*input)) 269 r.Body = ioutil.NopCloser(bytes.NewReader(*input)) 270 } 271 return r, err 272 }) 273 } 274} 275 276// WithCustomBaseURL returns a PrepareDecorator that replaces brace-enclosed keys within the 277// request base URL (i.e., http.Request.URL) with the corresponding values from the passed map. 278func WithCustomBaseURL(baseURL string, urlParameters map[string]interface{}) PrepareDecorator { 279 parameters := ensureValueStrings(urlParameters) 280 for key, value := range parameters { 281 baseURL = strings.Replace(baseURL, "{"+key+"}", value, -1) 282 } 283 return WithBaseURL(baseURL) 284} 285 286// WithFormData returns a PrepareDecoratore that "URL encodes" (e.g., bar=baz&foo=quux) into the 287// http.Request body. 288func WithFormData(v url.Values) PrepareDecorator { 289 return func(p Preparer) Preparer { 290 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 291 r, err := p.Prepare(r) 292 if err == nil { 293 s := v.Encode() 294 295 setHeader(r, http.CanonicalHeaderKey(headerContentType), mimeTypeFormPost) 296 r.ContentLength = int64(len(s)) 297 r.Body = ioutil.NopCloser(strings.NewReader(s)) 298 } 299 return r, err 300 }) 301 } 302} 303 304// WithMultiPartFormData returns a PrepareDecoratore that "URL encodes" (e.g., bar=baz&foo=quux) form parameters 305// into the http.Request body. 306func WithMultiPartFormData(formDataParameters map[string]interface{}) PrepareDecorator { 307 return func(p Preparer) Preparer { 308 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 309 r, err := p.Prepare(r) 310 if err == nil { 311 var body bytes.Buffer 312 writer := multipart.NewWriter(&body) 313 for key, value := range formDataParameters { 314 if rc, ok := value.(io.ReadCloser); ok { 315 var fd io.Writer 316 if fd, err = writer.CreateFormFile(key, key); err != nil { 317 return r, err 318 } 319 if _, err = io.Copy(fd, rc); err != nil { 320 return r, err 321 } 322 } else { 323 if err = writer.WriteField(key, ensureValueString(value)); err != nil { 324 return r, err 325 } 326 } 327 } 328 if err = writer.Close(); err != nil { 329 return r, err 330 } 331 setHeader(r, http.CanonicalHeaderKey(headerContentType), writer.FormDataContentType()) 332 r.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes())) 333 r.ContentLength = int64(body.Len()) 334 return r, err 335 } 336 return r, err 337 }) 338 } 339} 340 341// WithFile returns a PrepareDecorator that sends file in request body. 342func WithFile(f io.ReadCloser) PrepareDecorator { 343 return func(p Preparer) Preparer { 344 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 345 r, err := p.Prepare(r) 346 if err == nil { 347 b, err := ioutil.ReadAll(f) 348 if err != nil { 349 return r, err 350 } 351 r.Body = ioutil.NopCloser(bytes.NewReader(b)) 352 r.ContentLength = int64(len(b)) 353 } 354 return r, err 355 }) 356 } 357} 358 359// WithBool returns a PrepareDecorator that encodes the passed bool into the body of the request 360// and sets the Content-Length header. 361func WithBool(v bool) PrepareDecorator { 362 return WithString(fmt.Sprintf("%v", v)) 363} 364 365// WithFloat32 returns a PrepareDecorator that encodes the passed float32 into the body of the 366// request and sets the Content-Length header. 367func WithFloat32(v float32) PrepareDecorator { 368 return WithString(fmt.Sprintf("%v", v)) 369} 370 371// WithFloat64 returns a PrepareDecorator that encodes the passed float64 into the body of the 372// request and sets the Content-Length header. 373func WithFloat64(v float64) PrepareDecorator { 374 return WithString(fmt.Sprintf("%v", v)) 375} 376 377// WithInt32 returns a PrepareDecorator that encodes the passed int32 into the body of the request 378// and sets the Content-Length header. 379func WithInt32(v int32) PrepareDecorator { 380 return WithString(fmt.Sprintf("%v", v)) 381} 382 383// WithInt64 returns a PrepareDecorator that encodes the passed int64 into the body of the request 384// and sets the Content-Length header. 385func WithInt64(v int64) PrepareDecorator { 386 return WithString(fmt.Sprintf("%v", v)) 387} 388 389// WithString returns a PrepareDecorator that encodes the passed string into the body of the request 390// and sets the Content-Length header. 391func WithString(v string) PrepareDecorator { 392 return func(p Preparer) Preparer { 393 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 394 r, err := p.Prepare(r) 395 if err == nil { 396 r.ContentLength = int64(len(v)) 397 r.Body = ioutil.NopCloser(strings.NewReader(v)) 398 } 399 return r, err 400 }) 401 } 402} 403 404// WithJSON returns a PrepareDecorator that encodes the data passed as JSON into the body of the 405// request and sets the Content-Length header. 406func WithJSON(v interface{}) PrepareDecorator { 407 return func(p Preparer) Preparer { 408 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 409 r, err := p.Prepare(r) 410 if err == nil { 411 b, err := json.Marshal(v) 412 if err == nil { 413 r.ContentLength = int64(len(b)) 414 r.Body = ioutil.NopCloser(bytes.NewReader(b)) 415 } 416 } 417 return r, err 418 }) 419 } 420} 421 422// WithXML returns a PrepareDecorator that encodes the data passed as XML into the body of the 423// request and sets the Content-Length header. 424func WithXML(v interface{}) PrepareDecorator { 425 return func(p Preparer) Preparer { 426 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 427 r, err := p.Prepare(r) 428 if err == nil { 429 b, err := xml.Marshal(v) 430 if err == nil { 431 // we have to tack on an XML header 432 withHeader := xml.Header + string(b) 433 bytesWithHeader := []byte(withHeader) 434 435 r.ContentLength = int64(len(bytesWithHeader)) 436 setHeader(r, headerContentLength, fmt.Sprintf("%d", len(bytesWithHeader))) 437 r.Body = ioutil.NopCloser(bytes.NewReader(bytesWithHeader)) 438 } 439 } 440 return r, err 441 }) 442 } 443} 444 445// WithPath returns a PrepareDecorator that adds the supplied path to the request URL. If the path 446// is absolute (that is, it begins with a "/"), it replaces the existing path. 447func WithPath(path string) PrepareDecorator { 448 return func(p Preparer) Preparer { 449 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 450 r, err := p.Prepare(r) 451 if err == nil { 452 if r.URL == nil { 453 return r, NewError("autorest", "WithPath", "Invoked with a nil URL") 454 } 455 if r.URL, err = parseURL(r.URL, path); err != nil { 456 return r, err 457 } 458 } 459 return r, err 460 }) 461 } 462} 463 464// WithEscapedPathParameters returns a PrepareDecorator that replaces brace-enclosed keys within the 465// request path (i.e., http.Request.URL.Path) with the corresponding values from the passed map. The 466// values will be escaped (aka URL encoded) before insertion into the path. 467func WithEscapedPathParameters(path string, pathParameters map[string]interface{}) PrepareDecorator { 468 parameters := escapeValueStrings(ensureValueStrings(pathParameters)) 469 return func(p Preparer) Preparer { 470 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 471 r, err := p.Prepare(r) 472 if err == nil { 473 if r.URL == nil { 474 return r, NewError("autorest", "WithEscapedPathParameters", "Invoked with a nil URL") 475 } 476 for key, value := range parameters { 477 path = strings.Replace(path, "{"+key+"}", value, -1) 478 } 479 if r.URL, err = parseURL(r.URL, path); err != nil { 480 return r, err 481 } 482 } 483 return r, err 484 }) 485 } 486} 487 488// WithPathParameters returns a PrepareDecorator that replaces brace-enclosed keys within the 489// request path (i.e., http.Request.URL.Path) with the corresponding values from the passed map. 490func WithPathParameters(path string, pathParameters map[string]interface{}) PrepareDecorator { 491 parameters := ensureValueStrings(pathParameters) 492 return func(p Preparer) Preparer { 493 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 494 r, err := p.Prepare(r) 495 if err == nil { 496 if r.URL == nil { 497 return r, NewError("autorest", "WithPathParameters", "Invoked with a nil URL") 498 } 499 for key, value := range parameters { 500 path = strings.Replace(path, "{"+key+"}", value, -1) 501 } 502 503 if r.URL, err = parseURL(r.URL, path); err != nil { 504 return r, err 505 } 506 } 507 return r, err 508 }) 509 } 510} 511 512func parseURL(u *url.URL, path string) (*url.URL, error) { 513 p := strings.TrimRight(u.String(), "/") 514 if !strings.HasPrefix(path, "/") { 515 path = "/" + path 516 } 517 return url.Parse(p + path) 518} 519 520// WithQueryParameters returns a PrepareDecorators that encodes and applies the query parameters 521// given in the supplied map (i.e., key=value). 522func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorator { 523 parameters := MapToValues(queryParameters) 524 return func(p Preparer) Preparer { 525 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 526 r, err := p.Prepare(r) 527 if err == nil { 528 if r.URL == nil { 529 return r, NewError("autorest", "WithQueryParameters", "Invoked with a nil URL") 530 } 531 v := r.URL.Query() 532 for key, value := range parameters { 533 for i := range value { 534 d, err := url.QueryUnescape(value[i]) 535 if err != nil { 536 return r, err 537 } 538 value[i] = d 539 } 540 v[key] = value 541 } 542 r.URL.RawQuery = v.Encode() 543 } 544 return r, err 545 }) 546 } 547} 548