From 55c40c4309517ae23fdc72b61650c8fcea8754e4 Mon Sep 17 00:00:00 2001 From: Kaarel Raspel Date: Thu, 13 Dec 2018 22:36:47 +0200 Subject: [PATCH] #208 enable custom Content-Type for SendFile --- gorequest.go | 47 ++++++++++++++++++++++++++++++++++++++++++++--- gorequest_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/gorequest.go b/gorequest.go index 9f3134c..e17c647 100644 --- a/gorequest.go +++ b/gorequest.go @@ -734,10 +734,11 @@ func (s *SuperAgent) SendString(content string) *SuperAgent { type File struct { Filename string Fieldname string + MimeType string Data []byte } -// SendFile function works only with type "multipart". The function accepts one mandatory and up to two optional arguments. The mandatory (first) argument is the file. +// SendFile function works only with type "multipart". The function accepts one mandatory and up to three optional arguments. The mandatory (first) argument is the file. // The function accepts a path to a file as string: // // gorequest.New(). @@ -784,10 +785,20 @@ type File struct { // SendFile(b, "", "my_custom_fieldname"). // filename left blank, will become "example_file.ext" // End() // +// The third optional argument (fourth argument overall) is the mimetype request form-data part. It defaults to "application/octet-stream". +// +// b, _ := ioutil.ReadFile("./example_file.ext") +// gorequest.New(). +// Post("http://example.com"). +// Type("multipart"). +// SendFile(b, "filename", "my_custom_fieldname", "mime_type"). +// End() +// func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { filename := "" fieldname := "file" + fileType := "application/octet-stream" if len(args) >= 1 && len(args[0]) > 0 { filename = strings.TrimSpace(args[0]) @@ -798,6 +809,13 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { if fieldname == "file" || fieldname == "" { fieldname = "file" + strconv.Itoa(len(s.FileData)+1) } + if len(args) >= 3 && len(args[2]) > 0 { + fileType = strings.TrimSpace(args[2]) + } + if fileType == "" { + s.Errors = append(s.Errors, errors.New("The fourth SendFile method argument for MIME type cannot be an empty string")) + return s + } switch v := reflect.ValueOf(file); v.Kind() { case reflect.String: @@ -817,6 +835,7 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { s.FileData = append(s.FileData, File{ Filename: filename, Fieldname: fieldname, + MimeType: fileType, Data: data, }) case reflect.Slice: @@ -827,6 +846,7 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { f := File{ Filename: filename, Fieldname: fieldname, + MimeType: fileType, Data: make([]byte, len(slice)), } for i := range slice { @@ -837,9 +857,12 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { if len(args) == 1 { return s.SendFile(v.Elem().Interface(), args[0]) } - if len(args) >= 2 { + if len(args) == 2 { return s.SendFile(v.Elem().Interface(), args[0], args[1]) } + if len(args) >= 3 { + return s.SendFile(v.Elem().Interface(), args[0], args[1], args[2]) + } return s.SendFile(v.Elem().Interface()) default: if v.Type() == reflect.TypeOf(os.File{}) { @@ -855,6 +878,7 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { s.FileData = append(s.FileData, File{ Filename: filename, Fieldname: fieldname, + MimeType: fileType, Data: data, }) return s @@ -1239,7 +1263,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { // add the files if len(s.FileData) != 0 { for _, file := range s.FileData { - fw, _ := mw.CreateFormFile(file.Fieldname, file.Filename) + fw, _ := CreateFormFile(mw, file.Fieldname, file.Filename, file.MimeType) fw.Write(file.Data) } contentReader = buf @@ -1299,6 +1323,23 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { return req, nil } +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + +// CreateFormFile is a convenience wrapper around CreatePart. It creates +// a new form-data header with the provided field name and file name. +func CreateFormFile(w *multipart.Writer, fieldname, filename string, contenttype string) (io.Writer, error) { + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", + fmt.Sprintf(`form-data; name="%s"; filename="%s"`, + escapeQuotes(fieldname), escapeQuotes(filename))) + h.Set("Content-Type", contenttype) + return w.CreatePart(h) +} + // AsCurlCommand returns a string representing the runnable `curl' command // version of the request. func (s *SuperAgent) AsCurlCommand() (string, error) { diff --git a/gorequest_test.go b/gorequest_test.go index 728083e..9884e55 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -761,6 +761,7 @@ func TestMultipartRequest(t *testing.T) { const case10b_send_file_by_path_pointer = "/send_file_by_path_pointer" const case11_send_file_by_path_without_name = "/send_file_by_path_without_name" const case12_send_file_by_path_without_name_but_with_fieldname = "/send_file_by_path_without_name_but_with_fieldname" + const case121_send_file_by_path_with_name_and_fieldname_and_mimetype = "/send_file_by_path_with_name_and_fieldname_and_mimetype" const case13_send_file_by_content_without_name = "/send_file_by_content_without_name" const case13a_send_file_by_content_without_name_pointer = "/send_file_by_content_without_name_pointer" @@ -768,6 +769,7 @@ func TestMultipartRequest(t *testing.T) { const case15_send_file_by_content_without_name_but_with_fieldname = "/send_file_by_content_without_name_but_with_fieldname" const case16_send_file_by_content_with_name_and_with_fieldname = "/send_file_by_content_with_name_and_with_fieldname" + const case161_send_file_by_content_with_name_and_fieldname_and_mimetype = "/send_file_by_content_with_name_and_fieldname_and_mimetype" const case17_send_file_multiple_by_path_and_content_without_name = "/send_file_multiple_by_path_and_content_without_name" const case18_send_file_multiple_by_path_and_content_with_name = "/send_file_multiple_by_path_and_content_with_name" @@ -998,6 +1000,21 @@ func TestMultipartRequest(t *testing.T) { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["my_fieldname"][0]) + case case161_send_file_by_content_with_name_and_fieldname_and_mimetype, case121_send_file_by_path_with_name_and_fieldname_and_mimetype: + if len(r.MultipartForm.File) != 1 { + t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) + } + if _, ok := r.MultipartForm.File["my_fieldname"]; !ok { + keys := reflect.ValueOf(r.MultipartForm.File).MapKeys() + t.Error("Expected Fieldname:my_fieldname", "| but got", keys) + } + if r.MultipartForm.File["my_fieldname"][0].Filename != "MY_LICENSE" { + t.Error("Expected Filename:MY_LICENSE", "| but got", r.MultipartForm.File["my_fieldname"][0].Filename) + } + if r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"][0] != "application/octet-stream" { + t.Error("Expected Header:Content-Type:application/json", "| but got", r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"]) + } + checkFile(t, r.MultipartForm.File["my_fieldname"][0]) case case17_send_file_multiple_by_path_and_content_without_name: if len(r.MultipartForm.File) != 2 { t.Error("Expected length of files:[] == 2", "| but got", len(r.MultipartForm.File)) @@ -1208,6 +1225,11 @@ func TestMultipartRequest(t *testing.T) { SendFile(fileByPath, "", "my_fieldname"). End() + New().Post(ts.URL+ case121_send_file_by_path_with_name_and_fieldname_and_mimetype). + Type("multipart"). + SendFile(fileByPath, "MY_LICENSE", "my_fieldname", "application/json"). + End() + b, _ := ioutil.ReadFile("./LICENSE") New().Post(ts.URL + case13_send_file_by_content_without_name). Type("multipart"). @@ -1234,6 +1256,11 @@ func TestMultipartRequest(t *testing.T) { SendFile(b, "MY_LICENSE", "my_fieldname"). End() + New().Post(ts.URL+case12_send_file_by_content_with_name_and_fieldname_and_mimetype). + Type("multipart"). + SendFile(b, "MY_LICENSE", "my_fieldname", "application/json"). + End() + New().Post(ts.URL + case17_send_file_multiple_by_path_and_content_without_name). Type("multipart"). SendFile("./LICENSE").