]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: provide access to POST-only form values
authorPatrick Mylund Nielsen <patrick@patrickmn.com>
Tue, 26 Jun 2012 00:41:46 +0000 (20:41 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 26 Jun 2012 00:41:46 +0000 (20:41 -0400)
Fixes #3630.

R=rsc
CC=bradfitz, dsymonds, golang-dev, rodrigo.moraes
https://golang.org/cl/6210067

src/pkg/net/http/request.go
src/pkg/net/http/request_test.go

index a206b483a42c68389885dfaa54f10ae82348354f..61557ff83020849c15fec40cb74c4cfe8cbfc342 100644 (file)
@@ -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.PostForm == nil {
+               if r.Method == "POST" || r.Method == "PUT" {
+                       r.PostForm, err = parsePostForm(r)
+               }
+               if r.PostForm == nil {
+                       r.PostForm = make(url.Values)
+               }
        }
-       if r.Method == "POST" || r.Method == "PUT" {
-               if r.Body == nil {
-                       return errors.New("missing form body")
+       if r.Form == nil {
+               if len(r.PostForm) > 0 {
+                       r.Form = make(url.Values)
+                       copyValues(r.Form, r.PostForm)
                }
-               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))
+               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) {
index 6e00b9bfd394ed2e875b0967b2ea6abc12315175..db7419b26fe3ffd5a169aa7f1fd229df481250e2 100644 (file)
@@ -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",