diff --git a/net/gclient/gclient_request.go b/net/gclient/gclient_request.go index 53aa744c1..be1c2ad0f 100644 --- a/net/gclient/gclient_request.go +++ b/net/gclient/gclient_request.go @@ -167,7 +167,10 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data .. if !gstr.ContainsI(url, httpProtocolName) { url = httpProtocolName + `://` + url } - var params string + var ( + params string + allowFileUploading = true + ) if len(data) > 0 { switch c.header[httpHeaderContentType] { case httpHeaderContentTypeJson: @@ -181,6 +184,7 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data .. params = string(b) } } + allowFileUploading = false case httpHeaderContentTypeXml: switch data[0].(type) { @@ -193,6 +197,8 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data .. params = string(b) } } + allowFileUploading = false + default: params = httputil.BuildParams(data[0], c.noUrlEncode) } @@ -223,14 +229,18 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data .. return nil, err } } else { - if strings.Contains(params, httpParamFileHolder) { + if allowFileUploading && strings.Contains(params, httpParamFileHolder) { // File uploading request. var ( - buffer = bytes.NewBuffer(nil) - writer = multipart.NewWriter(buffer) + buffer = bytes.NewBuffer(nil) + writer = multipart.NewWriter(buffer) + isFileUploading = false ) for _, item := range strings.Split(params, "&") { array := strings.Split(item, "=") + if len(array) < 2 { + continue + } if len(array[1]) > 6 && strings.Compare(array[1][0:6], httpParamFileHolder) == 0 { path := array[1][6:] if !gfile.Exists(path) { @@ -241,43 +251,50 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data .. formFileName = gfile.Basename(path) formFieldName = array[0] ) + // it sets post content type as `application/octet-stream` if file, err = writer.CreateFormFile(formFieldName, formFileName); err != nil { - err = gerror.Wrapf(err, `CreateFormFile failed with "%s", "%s"`, formFieldName, formFileName) - return nil, err - } else { - var f *os.File - if f, err = gfile.Open(path); err != nil { - return nil, err - } - if _, err = io.Copy(file, f); err != nil { - err = gerror.Wrapf(err, `io.Copy failed from "%s" to form "%s"`, path, formFieldName) - _ = f.Close() - return nil, err - } - _ = f.Close() + return nil, gerror.Wrapf( + err, `CreateFormFile failed with "%s", "%s"`, formFieldName, formFileName, + ) } + var f *os.File + if f, err = gfile.Open(path); err != nil { + return nil, err + } + if _, err = io.Copy(file, f); err != nil { + _ = f.Close() + return nil, gerror.Wrapf( + err, `io.Copy failed from "%s" to form "%s"`, path, formFieldName, + ) + } + if err = f.Close(); err != nil { + return nil, gerror.Wrapf(err, `close file descriptor failed for "%s"`, path) + } + isFileUploading = true } else { var ( fieldName = array[0] fieldValue = array[1] ) if err = writer.WriteField(fieldName, fieldValue); err != nil { - err = gerror.Wrapf(err, `write form field failed with "%s", "%s"`, fieldName, fieldValue) - return nil, err + return nil, gerror.Wrapf( + err, `write form field failed with "%s", "%s"`, fieldName, fieldValue, + ) } } } // Close finishes the multipart message and writes the trailing // boundary end line to the output. if err = writer.Close(); err != nil { - err = gerror.Wrapf(err, `form writer close failed`) - return nil, err + return nil, gerror.Wrapf(err, `form writer close failed`) } if req, err = http.NewRequest(method, url, buffer); err != nil { - err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url) - return nil, err - } else { + return nil, gerror.Wrapf( + err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url, + ) + } + if isFileUploading { req.Header.Set(httpHeaderContentType, writer.FormDataContentType()) } } else { @@ -286,18 +303,17 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data .. if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil { err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url) return nil, err - } else { - if v, ok := c.header[httpHeaderContentType]; ok { - // Custom Content-Type. - req.Header.Set(httpHeaderContentType, v) - } else if len(paramBytes) > 0 { - if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) { - // Auto-detecting and setting the post content format: JSON. - req.Header.Set(httpHeaderContentType, httpHeaderContentTypeJson) - } else if gregex.IsMatchString(httpRegexParamJson, params) { - // If the parameters passed like "name=value", it then uses form type. - req.Header.Set(httpHeaderContentType, httpHeaderContentTypeForm) - } + } + if v, ok := c.header[httpHeaderContentType]; ok { + // Custom Content-Type. + req.Header.Set(httpHeaderContentType, v) + } else if len(paramBytes) > 0 { + if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) { + // Auto-detecting and setting the post content format: JSON. + req.Header.Set(httpHeaderContentType, httpHeaderContentTypeJson) + } else if gregex.IsMatchString(httpRegexParamJson, params) { + // If the parameters passed like "name=value", it then uses form type. + req.Header.Set(httpHeaderContentType, httpHeaderContentTypeForm) } } } diff --git a/net/gclient/gclient_z_unit_issue_test.go b/net/gclient/gclient_z_unit_issue_test.go new file mode 100644 index 000000000..e3203f3e1 --- /dev/null +++ b/net/gclient/gclient_z_unit_issue_test.go @@ -0,0 +1,82 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gclient_test + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/guid" +) + +func Test_Issue3748(t *testing.T) { + s := g.Server(guid.S()) + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write( + r.GetBody(), + ) + }) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + clientHost := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()) + time.Sleep(100 * time.Millisecond) + + gtest.C(t, func(t *gtest.T) { + client := gclient.New() + client.SetHeader("Content-Type", "application/json") + data := map[string]interface{}{ + "name": "@file:", + "value": "json", + } + client.SetPrefix(clientHost) + content := client.PostContent(ctx, "/", data) + t.Assert(content, `{"name":"@file:","value":"json"}`) + }) + + gtest.C(t, func(t *gtest.T) { + client := gclient.New() + client.SetHeader("Content-Type", "application/xml") + data := map[string]interface{}{ + "name": "@file:", + "value": "xml", + } + client.SetPrefix(clientHost) + content := client.PostContent(ctx, "/", data) + t.Assert(content, `@file:xml`) + }) + + gtest.C(t, func(t *gtest.T) { + client := gclient.New() + client.SetHeader("Content-Type", "application/x-www-form-urlencoded") + data := map[string]interface{}{ + "name": "@file:", + "value": "x-www-form-urlencoded", + } + client.SetPrefix(clientHost) + content := client.PostContent(ctx, "/", data) + t.Assert(strings.Contains(content, `Content-Disposition: form-data; name="value"`), true) + t.Assert(strings.Contains(content, `Content-Disposition: form-data; name="name"`), true) + t.Assert(strings.Contains(content, "\r\n@file:"), true) + t.Assert(strings.Contains(content, "\r\nx-www-form-urlencoded"), true) + }) + + gtest.C(t, func(t *gtest.T) { + client := gclient.New() + data := "@file:" + client.SetPrefix(clientHost) + _, err := client.Post(ctx, "/", data) + t.AssertNil(err) + }) +}