From 75220ddbb6d9c82ab34c6c514c1083f2d802a27d Mon Sep 17 00:00:00 2001 From: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Date: Sat, 23 May 2026 23:30:20 +0530 Subject: [PATCH 1/2] fix(net/ghttp): report invalid json body errors --- net/ghttp/ghttp_request_param.go | 14 ++++- net/ghttp/ghttp_request_param_form.go | 3 ++ net/ghttp/ghttp_request_param_query.go | 3 ++ net/ghttp/ghttp_request_param_request.go | 3 ++ ...ghttp_z_unit_feature_request_param_test.go | 51 +++++++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 4ff6475df30..5cc9c5b910f 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -236,8 +236,18 @@ func (r *Request) parseBody() { if body := r.GetBody(); len(body) > 0 { // Trim space/new line characters. body = bytes.TrimSpace(body) + if len(body) == 0 { + return + } + contentType := r.Header.Get("Content-Type") + jsonContentType := r.Method != http.MethodGet && gstr.ContainsI(contentType, contentTypeJson) // JSON format checks. - if body[0] == '{' && body[len(body)-1] == '}' { + if jsonContentType { + if err := json.UnmarshalUseNumber(body, &r.bodyMap); err != nil { + r.SetError(gerror.WrapCode(gcode.CodeInvalidParameter, err, "Parse JSON body failed")) + return + } + } else if body[0] == '{' { _ = json.UnmarshalUseNumber(body, &r.bodyMap) } // XML format checks. @@ -248,7 +258,7 @@ func (r *Request) parseBody() { r.bodyMap, _ = gxml.DecodeWithoutRoot(body) } // Default parameters decoding. - if contentType := r.Header.Get("Content-Type"); (contentType == "" || !gstr.Contains(contentType, "multipart/")) && r.bodyMap == nil { + if (contentType == "" || !gstr.Contains(contentType, "multipart/")) && r.bodyMap == nil { r.bodyMap, _ = gstr.Parse(r.GetBodyString()) } } diff --git a/net/ghttp/ghttp_request_param_form.go b/net/ghttp/ghttp_request_param_form.go index 68deb0a6736..0c154ac09ce 100644 --- a/net/ghttp/ghttp_request_param_form.go +++ b/net/ghttp/ghttp_request_param_form.go @@ -98,6 +98,9 @@ func (r *Request) GetFormStruct(pointer any, mapping ...map[string]string) error func (r *Request) doGetFormStruct(pointer any, mapping ...map[string]string) (data map[string]any, err error) { r.parseForm() + if err = r.GetError(); err != nil { + return nil, err + } data = r.formMap if data == nil { data = map[string]any{} diff --git a/net/ghttp/ghttp_request_param_query.go b/net/ghttp/ghttp_request_param_query.go index 2ad5ca5f59f..8da16734a4e 100644 --- a/net/ghttp/ghttp_request_param_query.go +++ b/net/ghttp/ghttp_request_param_query.go @@ -142,6 +142,9 @@ func (r *Request) GetQueryStruct(pointer any, mapping ...map[string]string) erro func (r *Request) doGetQueryStruct(pointer any, mapping ...map[string]string) (data map[string]any, err error) { r.parseQuery() data = r.GetQueryMap() + if err = r.GetError(); err != nil { + return nil, err + } if data == nil { data = map[string]any{} } diff --git a/net/ghttp/ghttp_request_param_request.go b/net/ghttp/ghttp_request_param_request.go index 31e3b83d706..e45021d0676 100644 --- a/net/ghttp/ghttp_request_param_request.go +++ b/net/ghttp/ghttp_request_param_request.go @@ -174,6 +174,9 @@ func (r *Request) GetRequestStruct(pointer any, mapping ...map[string]string) er func (r *Request) doGetRequestStruct(pointer any, mapping ...map[string]string) (data map[string]any, err error) { data = r.GetRequestMap() + if err = r.GetError(); err != nil { + return nil, err + } if data == nil { data = map[string]any{} } diff --git a/net/ghttp/ghttp_z_unit_feature_request_param_test.go b/net/ghttp/ghttp_z_unit_feature_request_param_test.go index 9495b6e1932..b8ffe9f3f95 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_param_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_param_test.go @@ -16,6 +16,7 @@ import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/guid" ) @@ -94,6 +95,26 @@ func (c *cUserTagDefault) User(ctx context.Context, req *UserTagDefaultReq) (res return } +type UserInvalidJsonReq struct { + g.Meta `path:"/user-invalid-json" method:"post" summary:"user invalid json api"` + Name string `v:"required"` +} + +type UserInvalidJsonRes struct { + g.Meta `mime:"application/json"` + Name string +} + +var ( + UserInvalidJson = cUserInvalidJson{} +) + +type cUserInvalidJson struct{} + +func (c *cUserInvalidJson) User(ctx context.Context, req *UserInvalidJsonReq) (res *UserInvalidJsonRes, err error) { + return &UserInvalidJsonRes{Name: req.Name}, nil +} + func Test_ParamsTagDefault(t *testing.T) { s := g.Server(guid.S()) s.Group("/", func(group *ghttp.RouterGroup) { @@ -150,6 +171,36 @@ func Test_ParamsTagDefault(t *testing.T) { }) } +func Test_ParamsInvalidJsonReportsParseError(t *testing.T) { + s := g.Server(guid.S()) + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Bind(UserInvalidJson) + }) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + for i := 0; i < 100 && s.GetListenedPort() == 0; i++ { + time.Sleep(10 * time.Millisecond) + } + if s.GetListenedPort() == 0 { + t.Fatal("server did not start listening") + } + + gtest.C(t, func(t *gtest.T) { + prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()) + client := g.Client() + client.SetPrefix(prefix) + + for _, body := range []string{`{"name":}`, `[{"name":"john"}]`, `name=john`} { + resp := client.ContentJson().PostContent(ctx, "/user-invalid-json", body) + t.Assert(gstr.Contains(resp, "Parse JSON body failed"), true) + t.Assert(gstr.Contains(resp, `"Name"`), false) + } + }) +} + func Benchmark_ParamTagIn(b *testing.B) { b.StopTimer() From 3e21639e379f2caa1fc14bbfa59a52c8732fef0f Mon Sep 17 00:00:00 2001 From: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Date: Sun, 24 May 2026 06:07:23 +0530 Subject: [PATCH 2/2] test(net/ghttp): tighten invalid json assertions --- net/ghttp/ghttp_request_param.go | 8 +++--- ...ghttp_z_unit_feature_request_param_test.go | 26 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 5cc9c5b910f..adb2f173681 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -240,14 +240,16 @@ func (r *Request) parseBody() { return } contentType := r.Header.Get("Content-Type") - jsonContentType := r.Method != http.MethodGet && gstr.ContainsI(contentType, contentTypeJson) + jsonContentType := gstr.ContainsI(contentType, contentTypeJson) + // Preserve GET query/form body compatibility while validating JSON-shaped GET bodies. + strictJsonContentType := jsonContentType && (r.Method != http.MethodGet || body[0] == '{' || body[0] == '[') // JSON format checks. - if jsonContentType { + if strictJsonContentType { if err := json.UnmarshalUseNumber(body, &r.bodyMap); err != nil { r.SetError(gerror.WrapCode(gcode.CodeInvalidParameter, err, "Parse JSON body failed")) return } - } else if body[0] == '{' { + } else if body[0] == '{' && body[len(body)-1] == '}' { _ = json.UnmarshalUseNumber(body, &r.bodyMap) } // XML format checks. diff --git a/net/ghttp/ghttp_z_unit_feature_request_param_test.go b/net/ghttp/ghttp_z_unit_feature_request_param_test.go index b8ffe9f3f95..76c1b058669 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_param_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_param_test.go @@ -8,15 +8,16 @@ package ghttp_test import ( "context" + "encoding/json" "fmt" "strconv" "testing" "time" + "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/test/gtest" - "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/guid" ) @@ -96,8 +97,8 @@ func (c *cUserTagDefault) User(ctx context.Context, req *UserTagDefaultReq) (res } type UserInvalidJsonReq struct { - g.Meta `path:"/user-invalid-json" method:"post" summary:"user invalid json api"` - Name string `v:"required"` + g.Meta `path:"/user-invalid-json" method:"post,get" summary:"user invalid json api"` + Name string } type UserInvalidJsonRes struct { @@ -190,13 +191,26 @@ func Test_ParamsInvalidJsonReportsParseError(t *testing.T) { gtest.C(t, func(t *gtest.T) { prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()) + reqCtx := context.Background() client := g.Client() client.SetPrefix(prefix) + assertInvalidParameter := func(resp string) { + var payload struct { + Code int `json:"code"` + Data *UserInvalidJsonRes `json:"data"` + } + err := json.Unmarshal([]byte(resp), &payload) + t.AssertNil(err) + t.Assert(payload.Code, gcode.CodeInvalidParameter.Code()) + t.Assert(payload.Data, nil) + } + for _, body := range []string{`{"name":}`, `[{"name":"john"}]`, `name=john`} { - resp := client.ContentJson().PostContent(ctx, "/user-invalid-json", body) - t.Assert(gstr.Contains(resp, "Parse JSON body failed"), true) - t.Assert(gstr.Contains(resp, `"Name"`), false) + assertInvalidParameter(client.ContentJson().PostContent(reqCtx, "/user-invalid-json", body)) + } + for _, body := range []string{`{"name":}`, `[{"name":"john"}]`} { + assertInvalidParameter(client.ContentJson().GetContent(reqCtx, "/user-invalid-json", body)) } }) }