1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package client
16
17import (
18	"bytes"
19	"fmt"
20	"io"
21	"log"
22	"mime/multipart"
23	"net/http"
24	"net/textproto"
25	"net/url"
26	"os"
27	"path"
28	"path/filepath"
29	"strings"
30	"time"
31
32	"github.com/go-openapi/strfmt"
33
34	"github.com/go-openapi/runtime"
35)
36
37// NewRequest creates a new swagger http client request
38func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) {
39	return &request{
40		pathPattern: pathPattern,
41		method:      method,
42		writer:      writer,
43		header:      make(http.Header),
44		query:       make(url.Values),
45		timeout:     DefaultTimeout,
46		getBody:     getRequestBuffer,
47	}, nil
48}
49
50// Request represents a swagger client request.
51//
52// This Request struct converts to a HTTP request.
53// There might be others that convert to other transports.
54// There is no error checking here, it is assumed to be used after a spec has been validated.
55// so impossible combinations should not arise (hopefully).
56//
57// The main purpose of this struct is to hide the machinery of adding params to a transport request.
58// The generated code only implements what is necessary to turn a param into a valid value for these methods.
59type request struct {
60	pathPattern string
61	method      string
62	writer      runtime.ClientRequestWriter
63
64	pathParams map[string]string
65	header     http.Header
66	query      url.Values
67	formFields url.Values
68	fileFields map[string][]runtime.NamedReadCloser
69	payload    interface{}
70	timeout    time.Duration
71	buf        *bytes.Buffer
72
73	getBody func(r *request) []byte
74}
75
76var (
77	// ensure interface compliance
78	_ runtime.ClientRequest = new(request)
79)
80
81func (r *request) isMultipart(mediaType string) bool {
82	if len(r.fileFields) > 0 {
83		return true
84	}
85
86	return runtime.MultipartFormMime == mediaType
87}
88
89// BuildHTTP creates a new http request based on the data from the params
90func (r *request) BuildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) {
91	return r.buildHTTP(mediaType, basePath, producers, registry, nil)
92}
93func escapeQuotes(s string) string {
94	return strings.NewReplacer("\\", "\\\\", `"`, "\\\"").Replace(s)
95}
96func (r *request) buildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry, auth runtime.ClientAuthInfoWriter) (*http.Request, error) {
97	// build the data
98	if err := r.writer.WriteToRequest(r, registry); err != nil {
99		return nil, err
100	}
101
102	// Our body must be an io.Reader.
103	// When we create the http.Request, if we pass it a
104	// bytes.Buffer then it will wrap it in an io.ReadCloser
105	// and set the content length automatically.
106	var body io.Reader
107	var pr *io.PipeReader
108	var pw *io.PipeWriter
109
110	r.buf = bytes.NewBuffer(nil)
111	if r.payload != nil || len(r.formFields) > 0 || len(r.fileFields) > 0 {
112		body = r.buf
113		if r.isMultipart(mediaType) {
114			pr, pw = io.Pipe()
115			body = pr
116		}
117	}
118
119	// check if this is a form type request
120	if len(r.formFields) > 0 || len(r.fileFields) > 0 {
121		if !r.isMultipart(mediaType) {
122			r.header.Set(runtime.HeaderContentType, mediaType)
123			formString := r.formFields.Encode()
124			r.buf.WriteString(formString)
125			goto DoneChoosingBodySource
126		}
127
128		mp := multipart.NewWriter(pw)
129		r.header.Set(runtime.HeaderContentType, mangleContentType(mediaType, mp.Boundary()))
130
131		go func() {
132			defer func() {
133				mp.Close()
134				pw.Close()
135			}()
136
137			for fn, v := range r.formFields {
138				for _, vi := range v {
139					if err := mp.WriteField(fn, vi); err != nil {
140						pw.CloseWithError(err)
141						log.Println(err)
142					}
143				}
144			}
145
146			defer func() {
147				for _, ff := range r.fileFields {
148					for _, ffi := range ff {
149						ffi.Close()
150					}
151				}
152			}()
153			for fn, f := range r.fileFields {
154				for _, fi := range f {
155					buf := bytes.NewBuffer([]byte{})
156
157					// Need to read the data so that we can detect the content type
158					_, err := io.Copy(buf, fi)
159					if err != nil {
160						_ = pw.CloseWithError(err)
161						log.Println(err)
162					}
163					fileBytes := buf.Bytes()
164					fileContentType := http.DetectContentType(fileBytes)
165
166					newFi := runtime.NamedReader(fi.Name(), buf)
167
168					// Create the MIME headers for the new part
169					h := make(textproto.MIMEHeader)
170					h.Set("Content-Disposition",
171						fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
172							escapeQuotes(fn), escapeQuotes(filepath.Base(fi.Name()))))
173					h.Set("Content-Type", fileContentType)
174
175					wrtr, err := mp.CreatePart(h)
176					if err != nil {
177						pw.CloseWithError(err)
178						log.Println(err)
179					} else if _, err := io.Copy(wrtr, newFi); err != nil {
180						pw.CloseWithError(err)
181						log.Println(err)
182					}
183				}
184			}
185		}()
186
187		goto DoneChoosingBodySource
188	}
189
190	// if there is payload, use the producer to write the payload, and then
191	// set the header to the content-type appropriate for the payload produced
192	if r.payload != nil {
193		// TODO: infer most appropriate content type based on the producer used,
194		// and the `consumers` section of the spec/operation
195		r.header.Set(runtime.HeaderContentType, mediaType)
196		if rdr, ok := r.payload.(io.ReadCloser); ok {
197			body = rdr
198			goto DoneChoosingBodySource
199		}
200
201		if rdr, ok := r.payload.(io.Reader); ok {
202			body = rdr
203			goto DoneChoosingBodySource
204		}
205
206		producer := producers[mediaType]
207		if err := producer.Produce(r.buf, r.payload); err != nil {
208			return nil, err
209		}
210	}
211
212DoneChoosingBodySource:
213
214	if runtime.CanHaveBody(r.method) && body == nil && r.header.Get(runtime.HeaderContentType) == "" {
215		r.header.Set(runtime.HeaderContentType, mediaType)
216	}
217
218	if auth != nil {
219		// If we're not using r.buf as our http.Request's body,
220		// either the payload is an io.Reader or io.ReadCloser,
221		// or we're doing a multipart form/file.
222		//
223		// In those cases, if the AuthenticateRequest call asks for the body,
224		// we must read it into a buffer and provide that, then use that buffer
225		// as the body of our http.Request.
226		//
227		// This is done in-line with the GetBody() request rather than ahead
228		// of time, because there's no way to know if the AuthenticateRequest
229		// will even ask for the body of the request.
230		//
231		// If for some reason the copy fails, there's no way to return that
232		// error to the GetBody() call, so return it afterwards.
233		//
234		// An error from the copy action is prioritized over any error
235		// from the AuthenticateRequest call, because the mis-read
236		// body may have interfered with the auth.
237		//
238		var copyErr error
239		if buf, ok := body.(*bytes.Buffer); body != nil && (!ok || buf != r.buf) {
240			var copied bool
241			r.getBody = func(r *request) []byte {
242				if copied {
243					return getRequestBuffer(r)
244				}
245
246				defer func() {
247					copied = true
248				}()
249
250				if _, copyErr = io.Copy(r.buf, body); copyErr != nil {
251					return nil
252				}
253
254				if closer, ok := body.(io.ReadCloser); ok {
255					if copyErr = closer.Close(); copyErr != nil {
256						return nil
257					}
258				}
259
260				body = r.buf
261				return getRequestBuffer(r)
262			}
263		}
264
265		authErr := auth.AuthenticateRequest(r, registry)
266
267		if copyErr != nil {
268			return nil, fmt.Errorf("error retrieving the response body: %v", copyErr)
269		}
270
271		if authErr != nil {
272			return nil, authErr
273		}
274	}
275
276	// create http request
277	var reinstateSlash bool
278	if r.pathPattern != "" && r.pathPattern != "/" && r.pathPattern[len(r.pathPattern)-1] == '/' {
279		reinstateSlash = true
280	}
281	urlPath := path.Join(basePath, r.pathPattern)
282	for k, v := range r.pathParams {
283		urlPath = strings.Replace(urlPath, "{"+k+"}", url.PathEscape(v), -1)
284	}
285	if reinstateSlash {
286		urlPath = urlPath + "/"
287	}
288
289	req, err := http.NewRequest(r.method, urlPath, body)
290	if err != nil {
291		return nil, err
292	}
293
294	req.URL.RawQuery = r.query.Encode()
295	req.Header = r.header
296
297	return req, nil
298}
299
300func mangleContentType(mediaType, boundary string) string {
301	if strings.ToLower(mediaType) == runtime.URLencodedFormMime {
302		return fmt.Sprintf("%s; boundary=%s", mediaType, boundary)
303	}
304	return "multipart/form-data; boundary=" + boundary
305}
306
307func (r *request) GetMethod() string {
308	return r.method
309}
310
311func (r *request) GetPath() string {
312	path := r.pathPattern
313	for k, v := range r.pathParams {
314		path = strings.Replace(path, "{"+k+"}", v, -1)
315	}
316	return path
317}
318
319func (r *request) GetBody() []byte {
320	return r.getBody(r)
321}
322
323func getRequestBuffer(r *request) []byte {
324	if r.buf == nil {
325		return nil
326	}
327	return r.buf.Bytes()
328}
329
330// SetHeaderParam adds a header param to the request
331// when there is only 1 value provided for the varargs, it will set it.
332// when there are several values provided for the varargs it will add it (no overriding)
333func (r *request) SetHeaderParam(name string, values ...string) error {
334	if r.header == nil {
335		r.header = make(http.Header)
336	}
337	r.header[http.CanonicalHeaderKey(name)] = values
338	return nil
339}
340
341// GetHeaderParams returns the all headers currently set for the request
342func (r *request) GetHeaderParams() http.Header {
343	return r.header
344}
345
346// SetQueryParam adds a query param to the request
347// when there is only 1 value provided for the varargs, it will set it.
348// when there are several values provided for the varargs it will add it (no overriding)
349func (r *request) SetQueryParam(name string, values ...string) error {
350	if r.query == nil {
351		r.query = make(url.Values)
352	}
353	r.query[name] = values
354	return nil
355}
356
357// GetQueryParams returns a copy of all query params currently set for the request
358func (r *request) GetQueryParams() url.Values {
359	var result = make(url.Values)
360	for key, value := range r.query {
361		result[key] = append([]string{}, value...)
362	}
363	return result
364}
365
366// SetFormParam adds a forn param to the request
367// when there is only 1 value provided for the varargs, it will set it.
368// when there are several values provided for the varargs it will add it (no overriding)
369func (r *request) SetFormParam(name string, values ...string) error {
370	if r.formFields == nil {
371		r.formFields = make(url.Values)
372	}
373	r.formFields[name] = values
374	return nil
375}
376
377// SetPathParam adds a path param to the request
378func (r *request) SetPathParam(name string, value string) error {
379	if r.pathParams == nil {
380		r.pathParams = make(map[string]string)
381	}
382
383	r.pathParams[name] = value
384	return nil
385}
386
387// SetFileParam adds a file param to the request
388func (r *request) SetFileParam(name string, files ...runtime.NamedReadCloser) error {
389	for _, file := range files {
390		if actualFile, ok := file.(*os.File); ok {
391			fi, err := os.Stat(actualFile.Name())
392			if err != nil {
393				return err
394			}
395			if fi.IsDir() {
396				return fmt.Errorf("%q is a directory, only files are supported", file.Name())
397			}
398		}
399	}
400
401	if r.fileFields == nil {
402		r.fileFields = make(map[string][]runtime.NamedReadCloser)
403	}
404	if r.formFields == nil {
405		r.formFields = make(url.Values)
406	}
407
408	r.fileFields[name] = files
409	return nil
410}
411
412func (r *request) GetFileParam() map[string][]runtime.NamedReadCloser {
413	return r.fileFields
414}
415
416// SetBodyParam sets a body parameter on the request.
417// This does not yet serialze the object, this happens as late as possible.
418func (r *request) SetBodyParam(payload interface{}) error {
419	r.payload = payload
420	return nil
421}
422
423func (r *request) GetBodyParam() interface{} {
424	return r.payload
425}
426
427// SetTimeout sets the timeout for a request
428func (r *request) SetTimeout(timeout time.Duration) error {
429	r.timeout = timeout
430	return nil
431}
432