Commit abb3c061 authored by Patrick Mylund Nielsen's avatar Patrick Mylund Nielsen Committed by Russ Cox

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
parent a9a8d7b5
......@@ -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) {
......
......@@ -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",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment