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	"io/ioutil"
22	"log"
23	"mime/multipart"
24	"net/http"
25	"net/url"
26	"os"
27	"path/filepath"
28	"strings"
29	"time"
30
31	"github.com/go-openapi/runtime"
32	"github.com/go-openapi/strfmt"
33)
34
35// NewRequest creates a new swagger http client request
36func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) {
37	return &request{
38		pathPattern: pathPattern,
39		method:      method,
40		writer:      writer,
41		header:      make(http.Header),
42		query:       make(url.Values),
43		timeout:     DefaultTimeout,
44	}, nil
45}
46
47// Request represents a swagger client request.
48//
49// This Request struct converts to a HTTP request.
50// There might be others that convert to other transports.
51// There is no error checking here, it is assumed to be used after a spec has been validated.
52// so impossible combinations should not arise (hopefully).
53//
54// The main purpose of this struct is to hide the machinery of adding params to a transport request.
55// The generated code only implements what is necessary to turn a param into a valid value for these methods.
56type request struct {
57	pathPattern string
58	method      string
59	writer      runtime.ClientRequestWriter
60
61	pathParams map[string]string
62	header     http.Header
63	query      url.Values
64	formFields url.Values
65	fileFields map[string]runtime.NamedReadCloser
66	payload    interface{}
67	timeout    time.Duration
68}
69
70var (
71	// ensure interface compliance
72	_ runtime.ClientRequest = new(request)
73)
74
75// BuildHTTP creates a new http request based on the data from the params
76func (r *request) BuildHTTP(mediaType string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) {
77	// build the data
78	if err := r.writer.WriteToRequest(r, registry); err != nil {
79		return nil, err
80	}
81
82	// create http request
83	path := r.pathPattern
84	for k, v := range r.pathParams {
85		path = strings.Replace(path, "{"+k+"}", v, -1)
86	}
87
88	var body io.ReadCloser
89	var pr *io.PipeReader
90	var pw *io.PipeWriter
91	buf := bytes.NewBuffer(nil)
92	body = ioutil.NopCloser(buf)
93	if r.fileFields != nil {
94		pr, pw = io.Pipe()
95		body = pr
96	}
97	req, err := http.NewRequest(r.method, path, body)
98	if err != nil {
99		return nil, err
100	}
101	req.URL.RawQuery = r.query.Encode()
102	req.Header = r.header
103
104	// check if this is a form type request
105	if len(r.formFields) > 0 || len(r.fileFields) > 0 {
106		// check if this is multipart
107		if len(r.fileFields) > 0 {
108			mp := multipart.NewWriter(pw)
109			req.Header.Set(runtime.HeaderContentType, mp.FormDataContentType())
110
111			go func() {
112				defer func() {
113					mp.Close()
114					pw.Close()
115				}()
116
117				for fn, v := range r.formFields {
118					if len(v) > 0 {
119						if err := mp.WriteField(fn, v[0]); err != nil {
120							pw.CloseWithError(err)
121							log.Println(err)
122						}
123					}
124				}
125
126				for fn, f := range r.fileFields {
127					wrtr, err := mp.CreateFormFile(fn, filepath.Base(f.Name()))
128					if err != nil {
129						pw.CloseWithError(err)
130						log.Println(err)
131					}
132					defer func() {
133						for _, ff := range r.fileFields {
134							ff.Close()
135						}
136
137					}()
138					if _, err := io.Copy(wrtr, f); err != nil {
139						pw.CloseWithError(err)
140						log.Println(err)
141					}
142				}
143
144			}()
145			return req, nil
146		}
147
148		req.Header.Set(runtime.HeaderContentType, mediaType)
149		// write the form values as the body
150		buf.WriteString(r.formFields.Encode())
151		return req, nil
152	}
153
154	// if there is payload, use the producer to write the payload, and then
155	// set the header to the content-type appropriate for the payload produced
156	if r.payload != nil {
157		// TODO: infer most appropriate content type based on the producer used,
158		// and the `consumers` section of the spec/operation
159		req.Header.Set(runtime.HeaderContentType, mediaType)
160		if rdr, ok := r.payload.(io.ReadCloser); ok {
161			req.Body = rdr
162			return req, nil
163		}
164
165		if rdr, ok := r.payload.(io.Reader); ok {
166			req.Body = ioutil.NopCloser(rdr)
167			return req, nil
168		}
169
170		// set the content length of the request or else a chunked transfer is
171		// declared, and this corrupts outgoing JSON payloads. the content's
172		// length must be set prior to the body being written per the spec at
173		// https://golang.org/pkg/net/http
174		//
175		//     If Body is present, Content-Length is <= 0 and TransferEncoding
176		//     hasn't been set to "identity", Write adds
177		//     "Transfer-Encoding: chunked" to the header. Body is closed
178		//     after it is sent.
179		//
180		// to that end a temporary buffer, b, is created to produce the payload
181		// body, and then its size is used to set the request's content length
182		var b bytes.Buffer
183		producer := producers[mediaType]
184		if err := producer.Produce(&b, r.payload); err != nil {
185			return nil, err
186		}
187		req.ContentLength = int64(b.Len())
188		if _, err := buf.Write(b.Bytes()); err != nil {
189			return nil, err
190		}
191	}
192	return req, nil
193}
194
195// SetHeaderParam adds a header param to the request
196// when there is only 1 value provided for the varargs, it will set it.
197// when there are several values provided for the varargs it will add it (no overriding)
198func (r *request) SetHeaderParam(name string, values ...string) error {
199	if r.header == nil {
200		r.header = make(http.Header)
201	}
202	r.header[http.CanonicalHeaderKey(name)] = values
203	return nil
204}
205
206// SetQueryParam adds a query param to the request
207// when there is only 1 value provided for the varargs, it will set it.
208// when there are several values provided for the varargs it will add it (no overriding)
209func (r *request) SetQueryParam(name string, values ...string) error {
210	if r.query == nil {
211		r.query = make(url.Values)
212	}
213	r.query[name] = values
214	return nil
215}
216
217// SetFormParam adds a forn param to the request
218// when there is only 1 value provided for the varargs, it will set it.
219// when there are several values provided for the varargs it will add it (no overriding)
220func (r *request) SetFormParam(name string, values ...string) error {
221	if r.formFields == nil {
222		r.formFields = make(url.Values)
223	}
224	r.formFields[name] = values
225	return nil
226}
227
228// SetPathParam adds a path param to the request
229func (r *request) SetPathParam(name string, value string) error {
230	if r.pathParams == nil {
231		r.pathParams = make(map[string]string)
232	}
233
234	r.pathParams[name] = value
235	return nil
236}
237
238// SetFileParam adds a file param to the request
239func (r *request) SetFileParam(name string, file runtime.NamedReadCloser) error {
240	if actualFile, ok := file.(*os.File); ok {
241		fi, err := os.Stat(actualFile.Name())
242		if err != nil {
243			return err
244		}
245		if fi.IsDir() {
246			return fmt.Errorf("%q is a directory, only files are supported", file.Name())
247		}
248	}
249
250	if r.fileFields == nil {
251		r.fileFields = make(map[string]runtime.NamedReadCloser)
252	}
253	if r.formFields == nil {
254		r.formFields = make(url.Values)
255	}
256
257	r.fileFields[name] = file
258	return nil
259}
260
261// SetBodyParam sets a body parameter on the request.
262// This does not yet serialze the object, this happens as late as possible.
263func (r *request) SetBodyParam(payload interface{}) error {
264	r.payload = payload
265	return nil
266}
267
268// SetTimeout sets the timeout for a request
269func (r *request) SetTimeout(timeout time.Duration) error {
270	r.timeout = timeout
271	return nil
272}
273