package pages import ( "errors" "fmt" "net/http" "net/url" ) // DefaultMaxFormBytes bounds a urlencoded body. net/http's own ceiling for one // is 10 MiB per request, which is three orders of magnitude more than any form // on this instance sends and enough to be worth refusing on a page anyone can // reach without logging in. const DefaultMaxFormBytes = 1 << 16 // ErrInvalidForm is returned when the body could not be read as a form: it was // malformed, or it exceeded the limit. A service maps this to 400. var ErrInvalidForm = errors.New("pages: the form could not be read") // FormValues reads a urlencoded body, bounded at max (DefaultMaxFormBytes when // max <= 0), and returns the body's values. // // It returns r.PostForm and never r.Form, and that is the whole reason this // three-line function is shared rather than copied. r.Form merges the query // string into the body's values, so a mutation could be driven entirely from a // URL somebody was linked to — and that request is precisely the one the // same-origin guard sees nothing wrong with, because it really did come from // our own page. A form posts its fields in its body; anything in the query // string of such a POST is not that form. // // The difference between the safe version and the hole is one character in a // field name, in a function every service with a form writes for itself, and // nothing at review time makes its absence visible. That is what makes it // belong beside csrf rather than in each service. func FormValues(w http.ResponseWriter, r *http.Request, max int64) (url.Values, error) { if max <= 0 { max = DefaultMaxFormBytes } r.Body = http.MaxBytesReader(w, r.Body, max) if err := r.ParseForm(); err != nil { return nil, fmt.Errorf("%w (%v)", ErrInvalidForm, err) } return r.PostForm, nil }