1// Copyright (c) 2015-2021 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/xml" 10 "fmt" 11 "io" 12 "log" 13 "mime/multipart" 14 "net/http" 15 "net/textproto" 16 "os" 17 "path/filepath" 18 "reflect" 19 "runtime" 20 "sort" 21 "strings" 22) 23 24//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 25// Logger interface 26//_______________________________________________________________________ 27 28// Logger interface is to abstract the logging from Resty. Gives control to 29// the Resty users, choice of the logger. 30type Logger interface { 31 Errorf(format string, v ...interface{}) 32 Warnf(format string, v ...interface{}) 33 Debugf(format string, v ...interface{}) 34} 35 36func createLogger() *logger { 37 l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)} 38 return l 39} 40 41var _ Logger = (*logger)(nil) 42 43type logger struct { 44 l *log.Logger 45} 46 47func (l *logger) Errorf(format string, v ...interface{}) { 48 l.output("ERROR RESTY "+format, v...) 49} 50 51func (l *logger) Warnf(format string, v ...interface{}) { 52 l.output("WARN RESTY "+format, v...) 53} 54 55func (l *logger) Debugf(format string, v ...interface{}) { 56 l.output("DEBUG RESTY "+format, v...) 57} 58 59func (l *logger) output(format string, v ...interface{}) { 60 if len(v) == 0 { 61 l.l.Print(format) 62 return 63 } 64 l.l.Printf(format, v...) 65} 66 67//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 68// Package Helper methods 69//_______________________________________________________________________ 70 71// IsStringEmpty method tells whether given string is empty or not 72func IsStringEmpty(str string) bool { 73 return len(strings.TrimSpace(str)) == 0 74} 75 76// DetectContentType method is used to figure out `Request.Body` content type for request header 77func DetectContentType(body interface{}) string { 78 contentType := plainTextType 79 kind := kindOf(body) 80 switch kind { 81 case reflect.Struct, reflect.Map: 82 contentType = jsonContentType 83 case reflect.String: 84 contentType = plainTextType 85 default: 86 if b, ok := body.([]byte); ok { 87 contentType = http.DetectContentType(b) 88 } else if kind == reflect.Slice { 89 contentType = jsonContentType 90 } 91 } 92 93 return contentType 94} 95 96// IsJSONType method is to check JSON content type or not 97func IsJSONType(ct string) bool { 98 return jsonCheck.MatchString(ct) 99} 100 101// IsXMLType method is to check XML content type or not 102func IsXMLType(ct string) bool { 103 return xmlCheck.MatchString(ct) 104} 105 106// Unmarshalc content into object from JSON or XML 107func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) { 108 if IsJSONType(ct) { 109 err = c.JSONUnmarshal(b, d) 110 } else if IsXMLType(ct) { 111 err = xml.Unmarshal(b, d) 112 } 113 114 return 115} 116 117//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 118// RequestLog and ResponseLog type 119//_______________________________________________________________________ 120 121// RequestLog struct is used to collected information from resty request 122// instance for debug logging. It sent to request log callback before resty 123// actually logs the information. 124type RequestLog struct { 125 Header http.Header 126 Body string 127} 128 129// ResponseLog struct is used to collected information from resty response 130// instance for debug logging. It sent to response log callback before resty 131// actually logs the information. 132type ResponseLog struct { 133 Header http.Header 134 Body string 135} 136 137//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 138// Package Unexported methods 139//_______________________________________________________________________ 140 141// way to disable the HTML escape as opt-in 142func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) { 143 if !r.jsonEscapeHTML { 144 return noescapeJSONMarshal(d) 145 } else if !c.jsonEscapeHTML { 146 return noescapeJSONMarshal(d) 147 } 148 return c.JSONMarshal(d) 149} 150 151func firstNonEmpty(v ...string) string { 152 for _, s := range v { 153 if !IsStringEmpty(s) { 154 return s 155 } 156 } 157 return "" 158} 159 160var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") 161 162func escapeQuotes(s string) string { 163 return quoteEscaper.Replace(s) 164} 165 166func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader { 167 hdr := make(textproto.MIMEHeader) 168 169 var contentDispositionValue string 170 if IsStringEmpty(fileName) { 171 contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param) 172 } else { 173 contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 174 param, escapeQuotes(fileName)) 175 } 176 hdr.Set("Content-Disposition", contentDispositionValue) 177 178 if !IsStringEmpty(contentType) { 179 hdr.Set(hdrContentTypeKey, contentType) 180 } 181 return hdr 182} 183 184func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error { 185 partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType)) 186 if err != nil { 187 return err 188 } 189 190 _, err = io.Copy(partWriter, mf.Reader) 191 return err 192} 193 194func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error { 195 // Auto detect actual multipart content type 196 cbuf := make([]byte, 512) 197 size, err := r.Read(cbuf) 198 if err != nil { 199 return err 200 } 201 202 partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf))) 203 if err != nil { 204 return err 205 } 206 207 if _, err = partWriter.Write(cbuf[:size]); err != nil { 208 return err 209 } 210 211 _, err = io.Copy(partWriter, r) 212 return err 213} 214 215func addFile(w *multipart.Writer, fieldName, path string) error { 216 file, err := os.Open(path) 217 if err != nil { 218 return err 219 } 220 defer closeq(file) 221 return writeMultipartFormFile(w, fieldName, filepath.Base(path), file) 222} 223 224func addFileReader(w *multipart.Writer, f *File) error { 225 return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader) 226} 227 228func getPointer(v interface{}) interface{} { 229 vv := valueOf(v) 230 if vv.Kind() == reflect.Ptr { 231 return v 232 } 233 return reflect.New(vv.Type()).Interface() 234} 235 236func isPayloadSupported(m string, allowMethodGet bool) bool { 237 return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet)) 238} 239 240func typeOf(i interface{}) reflect.Type { 241 return indirect(valueOf(i)).Type() 242} 243 244func valueOf(i interface{}) reflect.Value { 245 return reflect.ValueOf(i) 246} 247 248func indirect(v reflect.Value) reflect.Value { 249 return reflect.Indirect(v) 250} 251 252func kindOf(v interface{}) reflect.Kind { 253 return typeOf(v).Kind() 254} 255 256func createDirectory(dir string) (err error) { 257 if _, err = os.Stat(dir); err != nil { 258 if os.IsNotExist(err) { 259 if err = os.MkdirAll(dir, 0755); err != nil { 260 return 261 } 262 } 263 } 264 return 265} 266 267func canJSONMarshal(contentType string, kind reflect.Kind) bool { 268 return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) 269} 270 271func functionName(i interface{}) string { 272 return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() 273} 274 275func acquireBuffer() *bytes.Buffer { 276 return bufPool.Get().(*bytes.Buffer) 277} 278 279func releaseBuffer(buf *bytes.Buffer) { 280 if buf != nil { 281 buf.Reset() 282 bufPool.Put(buf) 283 } 284} 285 286func closeq(v interface{}) { 287 if c, ok := v.(io.Closer); ok { 288 silently(c.Close()) 289 } 290} 291 292func silently(_ ...interface{}) {} 293 294func composeHeaders(c *Client, r *Request, hdrs http.Header) string { 295 str := make([]string, 0, len(hdrs)) 296 for _, k := range sortHeaderKeys(hdrs) { 297 var v string 298 if k == "Cookie" { 299 cv := strings.TrimSpace(strings.Join(hdrs[k], ", ")) 300 if c.GetClient().Jar != nil { 301 for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) { 302 if cv != "" { 303 cv = cv + "; " + c.String() 304 } else { 305 cv = c.String() 306 } 307 } 308 } 309 v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv)) 310 } else { 311 v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", "))) 312 } 313 if v != "" { 314 str = append(str, "\t"+v) 315 } 316 } 317 return strings.Join(str, "\n") 318} 319 320func sortHeaderKeys(hdrs http.Header) []string { 321 keys := make([]string, 0, len(hdrs)) 322 for key := range hdrs { 323 keys = append(keys, key) 324 } 325 sort.Strings(keys) 326 return keys 327} 328 329func copyHeaders(hdrs http.Header) http.Header { 330 nh := http.Header{} 331 for k, v := range hdrs { 332 nh[k] = v 333 } 334 return nh 335} 336 337type noRetryErr struct { 338 err error 339} 340 341func (e *noRetryErr) Error() string { 342 return e.err.Error() 343} 344 345func wrapNoRetryErr(err error) error { 346 if err != nil { 347 err = &noRetryErr{err: err} 348 } 349 return err 350} 351 352func unwrapNoRetryErr(err error) error { 353 if e, ok := err.(*noRetryErr); ok { 354 err = e.err 355 } 356 return err 357} 358