From 18cf599caa39b4b5d179da69bf0bebab3ee1ce63 Mon Sep 17 00:00:00 2001 From: snowy Date: Fri, 16 Aug 2024 19:32:16 +0800 Subject: [PATCH 1/3] feat: add method PostFormArray to app.RequestContext --- pkg/app/context.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/app/context.go b/pkg/app/context.go index 369ece736..dea3805e3 100644 --- a/pkg/app/context.go +++ b/pkg/app/context.go @@ -1366,6 +1366,13 @@ func (ctx *RequestContext) PostForm(key string) string { return value } +// PostFormArray returns the specified key from a POST urlencoded form or multipart form +// when it exists, otherwise it returns an empty array `([])`. +func (ctx *RequestContext) PostFormArray(key string) []string { + value, _ := ctx.GetPostForm(key) + return strings.Split(value, ",") +} + // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns the specified defaultValue string. // From c7511637366324dcaa3135fad609bf3e576f283a Mon Sep 17 00:00:00 2001 From: snowy Date: Thu, 22 Aug 2024 13:59:52 +0800 Subject: [PATCH 2/3] feat: add method `PostFormArray` and `multipartFormValueArray` to `app.RequestContext` --- pkg/app/context.go | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/pkg/app/context.go b/pkg/app/context.go index dea3805e3..ac5ce5e45 100644 --- a/pkg/app/context.go +++ b/pkg/app/context.go @@ -694,6 +694,17 @@ func (ctx *RequestContext) multipartFormValue(key string) (string, bool) { return "", false } +func (ctx *RequestContext) multipartFormValueArray(key string) ([]string, bool) { + mf, err := ctx.MultipartForm() + if err == nil && mf.Value != nil { + vv := mf.Value[key] + if len(vv) > 0 { + return vv, true + } + } + return nil, false +} + func (ctx *RequestContext) RequestBodyStream() io.Reader { return ctx.Request.BodyStream() } @@ -1369,8 +1380,8 @@ func (ctx *RequestContext) PostForm(key string) string { // PostFormArray returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns an empty array `([])`. func (ctx *RequestContext) PostFormArray(key string) []string { - value, _ := ctx.GetPostForm(key) - return strings.Split(value, ",") + values, _ := ctx.GetPostFormArray(key) + return values } // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form @@ -1400,6 +1411,32 @@ func (ctx *RequestContext) GetPostForm(key string) (string, bool) { return ctx.multipartFormValue(key) } +// GetPostFormArray is like PostFormArray(key). It returns the specified key from a POST urlencoded +// form or multipart form when it exists `([]string, true)` (even when the value is an empty string), +// otherwise it returns ([]string(nil), false). +// +// For example, during a PATCH request to update the item's tags: +// +// tags=tag1 tag=tag2 tag=tag3 --> (["tag1", "tag2", "tag3"], true) := GetPostFormArray("tags") // set tags to ["tag1", "tag2", "tag3"] +// tags= --> (nil, true) := GetPostFormArray("tags") // set tags to nil +// --> (nil, false) := GetPostFormArray("tags") // do nothing with tags +func (ctx *RequestContext) GetPostFormArray(key string) ([]string, bool) { + //if v, exists := ctx.PostArgs().PeekExists(key); exists { + // return strings.Split(v, ","), exists + //} + vs := ctx.PostArgs().PeekAll(key) + // [][]byte -> []string + values := make([]string, len(vs)) + for i, v := range vs { + values[i] = bytesconv.B2s(v) + } + if len(values) == 0 { + return ctx.multipartFormValueArray(key) + } else { + return values, true + } +} + // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. func bodyAllowedForStatus(status int) bool { switch { From 6262a8ead254916cf6c8674f9e0989854adae70a Mon Sep 17 00:00:00 2001 From: snowy Date: Thu, 22 Aug 2024 15:20:36 +0800 Subject: [PATCH 3/3] fix: type `tags=...` -> `tag=...`, unsafe string convert remove: unused code add: unit test for `PostFormArray` --- pkg/app/context.go | 8 ++------ pkg/app/context_test.go | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/pkg/app/context.go b/pkg/app/context.go index ac5ce5e45..1d925b7a5 100644 --- a/pkg/app/context.go +++ b/pkg/app/context.go @@ -1417,18 +1417,14 @@ func (ctx *RequestContext) GetPostForm(key string) (string, bool) { // // For example, during a PATCH request to update the item's tags: // -// tags=tag1 tag=tag2 tag=tag3 --> (["tag1", "tag2", "tag3"], true) := GetPostFormArray("tags") // set tags to ["tag1", "tag2", "tag3"] +// tag=tag1 tag=tag2 tag=tag3 --> (["tag1", "tag2", "tag3"], true) := GetPostFormArray("tags") // set tags to ["tag1", "tag2", "tag3"] // tags= --> (nil, true) := GetPostFormArray("tags") // set tags to nil // --> (nil, false) := GetPostFormArray("tags") // do nothing with tags func (ctx *RequestContext) GetPostFormArray(key string) ([]string, bool) { - //if v, exists := ctx.PostArgs().PeekExists(key); exists { - // return strings.Split(v, ","), exists - //} vs := ctx.PostArgs().PeekAll(key) - // [][]byte -> []string values := make([]string, len(vs)) for i, v := range vs { - values[i] = bytesconv.B2s(v) + values[i] = string(v) } if len(values) == 0 { return ctx.multipartFormValueArray(key) diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go index 1317a062c..28e54ce6e 100644 --- a/pkg/app/context_test.go +++ b/pkg/app/context_test.go @@ -367,6 +367,40 @@ hello=world`) } } +func TestPostFormArray(t *testing.T) { + t.Parallel() + + ctx := makeCtxByReqString(t, `POST /upload HTTP/1.1 +Host: localhost:10000 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg +Content-Length: 521 + +------WebKitFormBoundaryJwfATyF8tmxSJnLg +Content-Disposition: form-data; name="tag" + +red +------WebKitFormBoundaryJwfATyF8tmxSJnLg +Content-Disposition: form-data; name="tag" + +green +------WebKitFormBoundaryJwfATyF8tmxSJnLg +Content-Disposition: form-data; name="tag" + +blue +------WebKitFormBoundaryJwfATyF8tmxSJnLg-- +`) + assert.DeepEqual(t, []string{"red", "green", "blue"}, ctx.PostFormArray("tag")) + + ctx = makeCtxByReqString(t, `POST /upload HTTP/1.1 +Host: localhost:10000 +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 +Content-Length: 26 + +tag=red&tag=green&tag=blue +`) + assert.DeepEqual(t, []string{"red", "green", "blue"}, ctx.PostFormArray("tag")) +} + func TestDefaultPostForm(t *testing.T) { ctx := makeCtxByReqString(t, `POST /upload HTTP/1.1 Host: localhost:10000 @@ -930,6 +964,14 @@ func TestGetPostForm(t *testing.T) { assert.DeepEqual(t, true, exists) } +func TestGetPostFormArray(t *testing.T) { + c := NewContext(0) + c.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm)) + c.Request.SetBodyString("a=1&b=2&b=3") + v, _ := c.GetPostFormArray("b") + assert.DeepEqual(t, []string{"2", "3"}, v) +} + func TestRemoteAddr(t *testing.T) { c := NewContext(0) c.Request.SetRequestURI("http://aaa.com?a=1&b=")