mirror of
https://github.com/golang/go
synced 2024-11-20 09:54:45 -07:00
net/http: provide access to POST-only form values
Fixes #3630. R=rsc CC=bradfitz, dsymonds, golang-dev, rodrigo.moraes https://golang.org/cl/6210067
This commit is contained in:
parent
a9a8d7b544
commit
abb3c0618b
@ -132,6 +132,12 @@ type Request struct {
|
||||
// The HTTP client ignores Form and uses Body instead.
|
||||
Form url.Values
|
||||
|
||||
// PostForm contains the parsed form data from POST or PUT
|
||||
// body parameters.
|
||||
// This field is only available after ParseForm is called.
|
||||
// The HTTP client ignores PostForm and uses Body instead.
|
||||
PostForm url.Values
|
||||
|
||||
// MultipartForm is the parsed multipart form, including file uploads.
|
||||
// This field is only available after ParseMultipartForm is called.
|
||||
// The HTTP client ignores MultipartForm and uses Body instead.
|
||||
@ -588,66 +594,93 @@ func (l *maxBytesReader) Close() error {
|
||||
return l.r.Close()
|
||||
}
|
||||
|
||||
func copyValues(dst, src url.Values) {
|
||||
for k, vs := range src {
|
||||
for _, value := range vs {
|
||||
dst.Add(k, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parsePostForm(r *Request) (vs url.Values, err error) {
|
||||
if r.Body == nil {
|
||||
err = errors.New("missing form body")
|
||||
return
|
||||
}
|
||||
ct := r.Header.Get("Content-Type")
|
||||
ct, _, err = mime.ParseMediaType(ct)
|
||||
switch {
|
||||
case ct == "application/x-www-form-urlencoded":
|
||||
var reader io.Reader = r.Body
|
||||
maxFormSize := int64(1<<63 - 1)
|
||||
if _, ok := r.Body.(*maxBytesReader); !ok {
|
||||
maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
|
||||
reader = io.LimitReader(r.Body, maxFormSize+1)
|
||||
}
|
||||
b, e := ioutil.ReadAll(reader)
|
||||
if e != nil {
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
break
|
||||
}
|
||||
if int64(len(b)) > maxFormSize {
|
||||
err = errors.New("http: POST too large")
|
||||
return
|
||||
}
|
||||
vs, e = url.ParseQuery(string(b))
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
case ct == "multipart/form-data":
|
||||
// handled by ParseMultipartForm (which is calling us, or should be)
|
||||
// TODO(bradfitz): there are too many possible
|
||||
// orders to call too many functions here.
|
||||
// Clean this up and write more tests.
|
||||
// request_test.go contains the start of this,
|
||||
// in TestRequestMultipartCallOrder.
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseForm parses the raw query from the URL.
|
||||
//
|
||||
// For POST or PUT requests, it also parses the request body as a form.
|
||||
// POST and PUT body parameters take precedence over URL query string values.
|
||||
// If the request Body's size has not already been limited by MaxBytesReader,
|
||||
// the size is capped at 10MB.
|
||||
//
|
||||
// ParseMultipartForm calls ParseForm automatically.
|
||||
// It is idempotent.
|
||||
func (r *Request) ParseForm() (err error) {
|
||||
if r.Form != nil {
|
||||
return
|
||||
}
|
||||
if r.URL != nil {
|
||||
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
||||
}
|
||||
if r.Method == "POST" || r.Method == "PUT" {
|
||||
if r.Body == nil {
|
||||
return errors.New("missing form body")
|
||||
if r.PostForm == nil {
|
||||
if r.Method == "POST" || r.Method == "PUT" {
|
||||
r.PostForm, err = parsePostForm(r)
|
||||
}
|
||||
ct := r.Header.Get("Content-Type")
|
||||
ct, _, err = mime.ParseMediaType(ct)
|
||||
switch {
|
||||
case ct == "application/x-www-form-urlencoded":
|
||||
var reader io.Reader = r.Body
|
||||
maxFormSize := int64(1<<63 - 1)
|
||||
if _, ok := r.Body.(*maxBytesReader); !ok {
|
||||
maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
|
||||
reader = io.LimitReader(r.Body, maxFormSize+1)
|
||||
}
|
||||
b, e := ioutil.ReadAll(reader)
|
||||
if e != nil {
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
break
|
||||
}
|
||||
if int64(len(b)) > maxFormSize {
|
||||
return errors.New("http: POST too large")
|
||||
}
|
||||
var newValues url.Values
|
||||
newValues, e = url.ParseQuery(string(b))
|
||||
if r.PostForm == nil {
|
||||
r.PostForm = make(url.Values)
|
||||
}
|
||||
}
|
||||
if r.Form == nil {
|
||||
if len(r.PostForm) > 0 {
|
||||
r.Form = make(url.Values)
|
||||
copyValues(r.Form, r.PostForm)
|
||||
}
|
||||
var newValues url.Values
|
||||
if r.URL != nil {
|
||||
var e error
|
||||
newValues, e = url.ParseQuery(r.URL.RawQuery)
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
if r.Form == nil {
|
||||
r.Form = make(url.Values)
|
||||
}
|
||||
// Copy values into r.Form. TODO: make this smoother.
|
||||
for k, vs := range newValues {
|
||||
for _, value := range vs {
|
||||
r.Form.Add(k, value)
|
||||
}
|
||||
}
|
||||
case ct == "multipart/form-data":
|
||||
// handled by ParseMultipartForm (which is calling us, or should be)
|
||||
// TODO(bradfitz): there are too many possible
|
||||
// orders to call too many functions here.
|
||||
// Clean this up and write more tests.
|
||||
// request_test.go contains the start of this,
|
||||
// in TestRequestMultipartCallOrder.
|
||||
}
|
||||
if newValues == nil {
|
||||
newValues = make(url.Values)
|
||||
}
|
||||
if r.Form == nil {
|
||||
r.Form = newValues
|
||||
} else {
|
||||
copyValues(r.Form, newValues)
|
||||
}
|
||||
}
|
||||
return err
|
||||
@ -693,6 +726,7 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error {
|
||||
}
|
||||
|
||||
// FormValue returns the first value for the named component of the query.
|
||||
// POST and PUT body parameters take precedence over URL query string values.
|
||||
// FormValue calls ParseMultipartForm and ParseForm if necessary.
|
||||
func (r *Request) FormValue(key string) string {
|
||||
if r.Form == nil {
|
||||
@ -704,6 +738,19 @@ func (r *Request) FormValue(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// PostFormValue returns the first value for the named component of the POST
|
||||
// or PUT request body. URL query parameters are ignored.
|
||||
// PostFormValue calls ParseMultipartForm and ParseForm if necessary.
|
||||
func (r *Request) PostFormValue(key string) string {
|
||||
if r.PostForm == nil {
|
||||
r.ParseMultipartForm(defaultMaxMemory)
|
||||
}
|
||||
if vs := r.PostForm[key]; len(vs) > 0 {
|
||||
return vs[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FormFile returns the first file for the provided form key.
|
||||
// FormFile calls ParseMultipartForm and ParseForm if necessary.
|
||||
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
|
||||
|
@ -30,8 +30,8 @@ func TestQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPostQuery(t *testing.T) {
|
||||
req, _ := NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x",
|
||||
strings.NewReader("z=post&both=y"))
|
||||
req, _ := NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&empty=not",
|
||||
strings.NewReader("z=post&both=y&prio=2&empty="))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||||
|
||||
if q := req.FormValue("q"); q != "foo" {
|
||||
@ -40,8 +40,23 @@ func TestPostQuery(t *testing.T) {
|
||||
if z := req.FormValue("z"); z != "post" {
|
||||
t.Errorf(`req.FormValue("z") = %q, want "post"`, z)
|
||||
}
|
||||
if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"x", "y"}) {
|
||||
t.Errorf(`req.FormValue("both") = %q, want ["x", "y"]`, both)
|
||||
if bq, found := req.PostForm["q"]; found {
|
||||
t.Errorf(`req.PostForm["q"] = %q, want no entry in map`, bq)
|
||||
}
|
||||
if bz := req.PostFormValue("z"); bz != "post" {
|
||||
t.Errorf(`req.PostFormValue("z") = %q, want "post"`, bz)
|
||||
}
|
||||
if qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) {
|
||||
t.Errorf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs)
|
||||
}
|
||||
if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) {
|
||||
t.Errorf(`req.Form["both"] = %q, want ["y", "x"]`, both)
|
||||
}
|
||||
if prio := req.FormValue("prio"); prio != "2" {
|
||||
t.Errorf(`req.FormValue("prio") = %q, want "2" (from body)`, prio)
|
||||
}
|
||||
if empty := req.FormValue("empty"); empty != "" {
|
||||
t.Errorf(`req.FormValue("empty") = %q, want "" (from body)`, empty)
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +91,23 @@ func TestParseFormUnknownContentType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFormInitializeOnError(t *testing.T) {
|
||||
nilBody, _ := NewRequest("POST", "http://www.google.com/search?q=foo", nil)
|
||||
tests := []*Request{
|
||||
nilBody,
|
||||
{Method: "GET", URL: nil},
|
||||
}
|
||||
for i, req := range tests {
|
||||
err := req.ParseForm()
|
||||
if req.Form == nil {
|
||||
t.Errorf("%d. Form not initialized, error %v", i, err)
|
||||
}
|
||||
if req.PostForm == nil {
|
||||
t.Errorf("%d. PostForm not initialized, error %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipartReader(t *testing.T) {
|
||||
req := &Request{
|
||||
Method: "POST",
|
||||
|
Loading…
Reference in New Issue
Block a user