Skip to content

Commit

Permalink
fix(net/gclient): panic when containing @file: parameter value in j…
Browse files Browse the repository at this point in the history
…son post request (#3775)
  • Loading branch information
oldme-git authored Sep 25, 2024
1 parent a1ce97e commit 76783fd
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 34 deletions.
84 changes: 50 additions & 34 deletions net/gclient/gclient_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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) {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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, 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
} 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
}
}
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 {
Expand All @@ -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)
}
}
}
Expand Down
82 changes: 82 additions & 0 deletions net/gclient/gclient_z_unit_issue_test.go
Original file line number Diff line number Diff line change
@@ -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, `<doc><name>@file:</name><value>xml</value></doc>`)
})

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)
})
}

0 comments on commit 76783fd

Please sign in to comment.