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