1// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
2// resty source code and usage is governed by a MIT style
3// license that can be found in the LICENSE file.
4
5package resty
6
7import (
8	"bytes"
9	"encoding/json"
10	"encoding/xml"
11	"fmt"
12	"io"
13	"log"
14	"mime/multipart"
15	"net/http"
16	"net/textproto"
17	"os"
18	"path/filepath"
19	"reflect"
20	"runtime"
21	"sort"
22	"strings"
23)
24
25//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
26// Package Helper methods
27//___________________________________
28
29// IsStringEmpty method tells whether given string is empty or not
30func IsStringEmpty(str string) bool {
31	return len(strings.TrimSpace(str)) == 0
32}
33
34// DetectContentType method is used to figure out `Request.Body` content type for request header
35func DetectContentType(body interface{}) string {
36	contentType := plainTextType
37	kind := kindOf(body)
38	switch kind {
39	case reflect.Struct, reflect.Map:
40		contentType = jsonContentType
41	case reflect.String:
42		contentType = plainTextType
43	default:
44		if b, ok := body.([]byte); ok {
45			contentType = http.DetectContentType(b)
46		} else if kind == reflect.Slice {
47			contentType = jsonContentType
48		}
49	}
50
51	return contentType
52}
53
54// IsJSONType method is to check JSON content type or not
55func IsJSONType(ct string) bool {
56	return jsonCheck.MatchString(ct)
57}
58
59// IsXMLType method is to check XML content type or not
60func IsXMLType(ct string) bool {
61	return xmlCheck.MatchString(ct)
62}
63
64// Unmarshal content into object from JSON or XML
65// Deprecated: kept for backward compatibility
66func Unmarshal(ct string, b []byte, d interface{}) (err error) {
67	if IsJSONType(ct) {
68		err = json.Unmarshal(b, d)
69	} else if IsXMLType(ct) {
70		err = xml.Unmarshal(b, d)
71	}
72
73	return
74}
75
76// Unmarshalc content into object from JSON or XML
77func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
78	if IsJSONType(ct) {
79		err = c.JSONUnmarshal(b, d)
80	} else if IsXMLType(ct) {
81		err = xml.Unmarshal(b, d)
82	}
83
84	return
85}
86
87//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
88// RequestLog and ResponseLog type
89//___________________________________
90
91// RequestLog struct is used to collected information from resty request
92// instance for debug logging. It sent to request log callback before resty
93// actually logs the information.
94type RequestLog struct {
95	Header http.Header
96	Body   string
97}
98
99// ResponseLog struct is used to collected information from resty response
100// instance for debug logging. It sent to response log callback before resty
101// actually logs the information.
102type ResponseLog struct {
103	Header http.Header
104	Body   string
105}
106
107//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
108// Package Unexported methods
109//___________________________________
110
111// way to disable the HTML escape as opt-in
112func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
113	if !r.jsonEscapeHTML {
114		return noescapeJSONMarshal(d)
115	} else if !c.jsonEscapeHTML {
116		return noescapeJSONMarshal(d)
117	}
118	return c.JSONMarshal(d)
119}
120
121func firstNonEmpty(v ...string) string {
122	for _, s := range v {
123		if !IsStringEmpty(s) {
124			return s
125		}
126	}
127	return ""
128}
129
130func getLogger(w io.Writer) *log.Logger {
131	return log.New(w, "RESTY ", log.LstdFlags)
132}
133
134var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
135
136func escapeQuotes(s string) string {
137	return quoteEscaper.Replace(s)
138}
139
140func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
141	hdr := make(textproto.MIMEHeader)
142	hdr.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
143		escapeQuotes(param), escapeQuotes(fileName)))
144	hdr.Set("Content-Type", contentType)
145	return hdr
146}
147
148func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
149	partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
150	if err != nil {
151		return err
152	}
153
154	_, err = io.Copy(partWriter, mf.Reader)
155	return err
156}
157
158func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
159	// Auto detect actual multipart content type
160	cbuf := make([]byte, 512)
161	size, err := r.Read(cbuf)
162	if err != nil {
163		return err
164	}
165
166	partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
167	if err != nil {
168		return err
169	}
170
171	if _, err = partWriter.Write(cbuf[:size]); err != nil {
172		return err
173	}
174
175	_, err = io.Copy(partWriter, r)
176	return err
177}
178
179func addFile(w *multipart.Writer, fieldName, path string) error {
180	file, err := os.Open(path)
181	if err != nil {
182		return err
183	}
184	defer closeq(file)
185	return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
186}
187
188func addFileReader(w *multipart.Writer, f *File) error {
189	return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
190}
191
192func getPointer(v interface{}) interface{} {
193	vv := valueOf(v)
194	if vv.Kind() == reflect.Ptr {
195		return v
196	}
197	return reflect.New(vv.Type()).Interface()
198}
199
200func isPayloadSupported(m string, allowMethodGet bool) bool {
201	return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
202}
203
204func typeOf(i interface{}) reflect.Type {
205	return indirect(valueOf(i)).Type()
206}
207
208func valueOf(i interface{}) reflect.Value {
209	return reflect.ValueOf(i)
210}
211
212func indirect(v reflect.Value) reflect.Value {
213	return reflect.Indirect(v)
214}
215
216func kindOf(v interface{}) reflect.Kind {
217	return typeOf(v).Kind()
218}
219
220func createDirectory(dir string) (err error) {
221	if _, err = os.Stat(dir); err != nil {
222		if os.IsNotExist(err) {
223			if err = os.MkdirAll(dir, 0755); err != nil {
224				return
225			}
226		}
227	}
228	return
229}
230
231func canJSONMarshal(contentType string, kind reflect.Kind) bool {
232	return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
233}
234
235func functionName(i interface{}) string {
236	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
237}
238
239func acquireBuffer() *bytes.Buffer {
240	return bufPool.Get().(*bytes.Buffer)
241}
242
243func releaseBuffer(buf *bytes.Buffer) {
244	if buf != nil {
245		buf.Reset()
246		bufPool.Put(buf)
247	}
248}
249
250func closeq(v interface{}) {
251	if c, ok := v.(io.Closer); ok {
252		sliently(c.Close())
253	}
254}
255
256func sliently(_ ...interface{}) {}
257
258func composeHeaders(hdrs http.Header) string {
259	var str []string
260	for _, k := range sortHeaderKeys(hdrs) {
261		str = append(str, fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
262	}
263	return strings.Join(str, "\n")
264}
265
266func sortHeaderKeys(hdrs http.Header) []string {
267	var keys []string
268	for key := range hdrs {
269		keys = append(keys, key)
270	}
271	sort.Strings(keys)
272	return keys
273}
274
275func copyHeaders(hdrs http.Header) http.Header {
276	nh := http.Header{}
277	for k, v := range hdrs {
278		nh[k] = v
279	}
280	return nh
281}
282