diff --git a/pkg/app/context.go b/pkg/app/context.go index 1d925b7a5..8c7041aa5 100644 --- a/pkg/app/context.go +++ b/pkg/app/context.go @@ -705,6 +705,28 @@ func (ctx *RequestContext) multipartFormValueArray(key string) ([]string, bool) return nil, false } +func (ctx *RequestContext) multipartFormMap(key string) (map[string]string, bool) { + mf, err := ctx.MultipartForm() + if err == nil && mf.Value != nil { + dict := make(map[string]string) + ex := false + for k, v := range mf.Value { + if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { + if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { + ex = true + dict[k[i+1:][:j]] = v[0] + } + } + } + if ex { + return dict, true + } else { + return nil, false + } + } + return nil, false +} + func (ctx *RequestContext) RequestBodyStream() io.Reader { return ctx.Request.BodyStream() } @@ -1377,13 +1399,20 @@ func (ctx *RequestContext) PostForm(key string) string { return value } -// PostFormArray returns the specified key from a POST urlencoded form or multipart form +// PostFormArray returns an array of 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 { values, _ := ctx.GetPostFormArray(key) return values } +// PostFormMap returns a map of the specified key from a POST urlencoded form or multipart form +// when it exists, otherwise it returns an empty map `map[string]string{}`. +func (ctx *RequestContext) PostFormMap(key string) map[string]string { + valueMap, _ := ctx.GetPostFormMap(key) + return valueMap +} + // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns the specified defaultValue string. // @@ -1417,9 +1446,9 @@ func (ctx *RequestContext) GetPostForm(key string) (string, bool) { // // For example, during a PATCH request to update the item's tags: // -// 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 +// tag=tag1 tag=tag2 tag=tag3 --> (["tag1", "tag2", "tag3"], true) := GetPostFormArray("tag") // set tag to ["tag1", "tag2", "tag3"] +// tag= --> (nil, true) := GetPostFormArray("tag") // set tags to nil +// --> (nil, false) := GetPostFormArray("tag") // do nothing with tags func (ctx *RequestContext) GetPostFormArray(key string) ([]string, bool) { vs := ctx.PostArgs().PeekAll(key) values := make([]string, len(vs)) @@ -1433,6 +1462,32 @@ func (ctx *RequestContext) GetPostFormArray(key string) ([]string, bool) { } } +// GetPostFormMap is like PostFormMap(key). It returns a map of the specified key from a POST urlencoded +// form or multipart form when it exists `(map[string]string, true)` (even when the value is an empty string), +// otherwise it returns `(map[string]string(nil), false)`. +// +// For example, during a PATCH request to update the map of the field: +// +// field[name]=Tom field[age]=22 field[id]=123456 --> (map[string]string{"name": "Tom", "age": "22", "id": "123456"}, true) := GetPostFormMap("field") // set fields to map[string]string{"name": "Tom", "age": "22", "id": "123456"} +// field= --> (nil, true) := GetPostFormMap("field") // set fields to nil +// --> (nil, false) := GetPostFormMap("field") // do nothing with fields +func (ctx *RequestContext) GetPostFormMap(key string) (map[string]string, bool) { + dict := make(map[string]string) + ctx.PostArgs().VisitAll(func(kb, vb []byte) { + k, v := string(kb), string(vb) + if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { + if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { + dict[k[i+1:][:j]] = v + } + } + }) + if len(dict) == 0 { + return ctx.multipartFormMap(key) + } else { + return dict, true + } +} + // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. func bodyAllowedForStatus(status int) bool { switch { diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go index b2aef633f..e31a13cfa 100644 --- a/pkg/app/context_test.go +++ b/pkg/app/context_test.go @@ -401,6 +401,38 @@ tag=red&tag=green&tag=blue assert.DeepEqual(t, []string{"red", "green", "blue"}, ctx.PostFormArray("tag")) } +func TestPostFormMap(t *testing.T) { + 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="object[name]" + +liteyuki +------WebKitFormBoundaryJwfATyF8tmxSJnLg +Content-Disposition: form-data; name="object[id]" + +200216 +------WebKitFormBoundaryJwfATyF8tmxSJnLg +Content-Disposition: form-data; name="object[age]" + +18 +------WebKitFormBoundaryJwfATyF8tmxSJnLg-- +`) + assert.DeepEqual(t, map[string]string{"name": "liteyuki", "id": "200216", "age": "18"}, ctx.PostFormMap("object")) + + ctx = makeCtxByReqString(t, `POST /upload HTTP/1.1 +Host: localhost:10000 +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 +Content-Length: 54 + +object[name]=liteyuki&object[id]=200216&object[age]=18 +`) + assert.DeepEqual(t, map[string]string{"name": "liteyuki", "id": "200216", "age": "18"}, ctx.PostFormMap("object")) +} + func TestDefaultPostForm(t *testing.T) { ctx := makeCtxByReqString(t, `POST /upload HTTP/1.1 Host: localhost:10000 @@ -976,6 +1008,14 @@ func TestGetPostFormArray(t *testing.T) { assert.DeepEqual(t, []string{"2", "3"}, v) } +func TestGetPostFormMap(t *testing.T) { + c := NewContext(0) + c.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm)) + c.Request.SetBodyString("attr[name]=lts&attr[id]=114514&attr[age]=24&key=1919810") + v, _ := c.GetPostFormMap("attr") + assert.DeepEqual(t, map[string]string{"name": "lts", "id": "114514", "age": "24"}, v) +} + func TestRemoteAddr(t *testing.T) { c := NewContext(0) c.Request.SetRequestURI("http://aaa.com?a=1&b=")