From 963df21841ce0117121b064c46456267f6b95cea Mon Sep 17 00:00:00 2001 From: Justin Ohms Date: Thu, 7 Sep 2017 09:09:31 -0700 Subject: [PATCH 1/2] Add optional contenttype to multipart/form-data request Allow the use of specified content types other than "application/octet-stream" in multipart file uploads Servers are sometimes configured to filter multipart content by content type. If a server is configured this way it will only process multipart content with a matching content type. In these cases the default "applciation/octet-stream" will be skipped unless an expected content type is provided in each multipart section. --- gorequest.go | 70 +++++++++++++++++++++++++++++++++++++---------- gorequest_test.go | 24 ++++++++++++++++ 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/gorequest.go b/gorequest.go index f928ca3..f35bf9c 100644 --- a/gorequest.go +++ b/gorequest.go @@ -695,9 +695,10 @@ func (s *SuperAgent) SendString(content string) *SuperAgent { } type File struct { - Filename string - Fieldname string - Data []byte + Filename string + Fieldname string + Data []byte + ContentType string } // 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. @@ -747,10 +748,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 contenttype in the multipart/form-data request. It defaults to "application/octet-stream" +// +// b, _ := ioutil.ReadFile("./example_file.ext") +// gorequest.New(). +// Post("http://example.com"). +// Type("multipart"). +// SendFile(b, "", "my_custom_fieldname", "application/xml"). +// End() +// func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { filename := "" fieldname := "file" + contenttype := "" if len(args) >= 1 && len(args[0]) > 0 { filename = strings.TrimSpace(args[0]) @@ -758,6 +769,10 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { if len(args) >= 2 && len(args[1]) > 0 { fieldname = strings.TrimSpace(args[1]) } + if len(args) >= 3 && len(args[2]) > 0 { + contenttype = strings.TrimSpace(args[2]) + } + if fieldname == "file" || fieldname == "" { fieldname = "file" + strconv.Itoa(len(s.FileData)+1) } @@ -778,9 +793,10 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { return s } s.FileData = append(s.FileData, File{ - Filename: filename, - Fieldname: fieldname, - Data: data, + Filename: filename, + Fieldname: fieldname, + Data: data, + ContentType: contenttype, }) case reflect.Slice: slice := makeSliceOfReflectValue(v) @@ -788,9 +804,10 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { filename = "filename" } f := File{ - Filename: filename, - Fieldname: fieldname, - Data: make([]byte, len(slice)), + Filename: filename, + Fieldname: fieldname, + Data: make([]byte, len(slice)), + ContentType: contenttype, } for i := range slice { f.Data[i] = slice[i].(byte) @@ -800,9 +817,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{}) { @@ -816,9 +836,10 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { return s } s.FileData = append(s.FileData, File{ - Filename: filename, - Fieldname: fieldname, - Data: data, + Filename: filename, + Fieldname: fieldname, + Data: data, + ContentType: contenttype, }) return s } @@ -1150,6 +1171,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { contentType = "application/xml" } } else if s.TargetType == "multipart" { + var ( buf = &bytes.Buffer{} mw = multipart.NewWriter(buf) @@ -1198,8 +1220,20 @@ 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.Write(file.Data) + if len(file.ContentType) > 0 { + h := make(textproto.MIMEHeader) + h.Set("Content-Type", file.ContentType) + var fns string + if len(file.Filename) > 0 { + fns = fmt.Sprintf(`; filename="%s"`, escapeQuotes(file.Filename)) + } + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"%s`, escapeQuotes(file.Fieldname), fns)) + fw, _ := mw.CreatePart(h) + fw.Write(file.Data) + } else { + fw, _ := mw.CreateFormFile(file.Fieldname, file.Filename) + fw.Write(file.Data) + } } contentReader = buf } @@ -1265,3 +1299,9 @@ func (s *SuperAgent) AsCurlCommand() (string, error) { } return cmd.String(), nil } + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} diff --git a/gorequest_test.go b/gorequest_test.go index 0d634be..d8df439 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -698,6 +698,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 case16a_send_file_by_content_with_name_fieldname_and_content_type = "/send_file_by_content_with_name_fieldname_and_content_type" 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" @@ -927,6 +928,24 @@ func TestMultipartRequest(t *testing.T) { if r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"][0] != "application/octet-stream" { 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 case16a_send_file_by_content_with_name_fieldname_and_content_type: + + 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 != "LICENSE" { + t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["my_fieldname"][0].Filename) + } + + if r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"][0] != "application/xml" { + t.Error("Expected Header:Content-Type:application/xml", "| 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 { @@ -1164,6 +1183,11 @@ func TestMultipartRequest(t *testing.T) { SendFile(b, "MY_LICENSE", "my_fieldname"). End() + New().Post(ts.URL+case16a_send_file_by_content_with_name_fieldname_and_content_type). + Type("multipart"). + SendFile(b, "LICENSE", "my_fieldname", "application/xml"). + End() + New().Post(ts.URL + case17_send_file_multiple_by_path_and_content_without_name). Type("multipart"). SendFile("./LICENSE"). From 800225d2901339185caaf2cc326c7433915c3667 Mon Sep 17 00:00:00 2001 From: Justin Ohms Date: Tue, 26 Sep 2017 15:05:47 -0700 Subject: [PATCH 2/2] Do not default file name if content type --- gorequest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorequest.go b/gorequest.go index f35bf9c..4ac8375 100644 --- a/gorequest.go +++ b/gorequest.go @@ -800,7 +800,7 @@ func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { }) case reflect.Slice: slice := makeSliceOfReflectValue(v) - if filename == "" { + if filename == "" && contenttype == "" { //default only if contenttype not specified filename = "filename" } f := File{