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