Skip to content

Commit 07272c4

Browse files
Dean KarnDean Karn
authored andcommitted
Add new helper functions
1. Add ParseForm(...) & ParseMultiPartForm(...) functions that also add the SEO params to the parsed Form eg. /users/:id, 'id' will be added to the request.Form just like normal query params would be. 2. Modify Decode(...) function to use the new #1 functions when inCludeQueryParams=true
1 parent 0d7e7c6 commit 07272c4

File tree

6 files changed

+196
-8
lines changed

6 files changed

+196
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
##Pure
22
<img align="right" src="https://raw.githubusercontent.com/go-playground/pure/master/logo.png">
3-
![Project status](https://img.shields.io/badge/version-1.0.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-1.1.0-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/pure/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/pure)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/pure/badge.svg?branch=master)](https://coveralls.io/github/go-playground/pure?branch=master)
66
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/pure)](https://goreportcard.com/report/github.com/go-playground/pure)

helpers.go

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,68 @@ func XMLBytes(w http.ResponseWriter, status int, b []byte) (err error) {
190190
return
191191
}
192192

193+
// ParseForm calls the underlying http.Request ParseForm
194+
// but also adds the URL params to the request Form as if
195+
// they were defined as query params i.e. ?id=13&ok=true but
196+
// does not add the params to the http.Request.URL.RawQuery
197+
// for SEO purposes
198+
func ParseForm(r *http.Request) error {
199+
200+
if err := r.ParseForm(); err != nil {
201+
return err
202+
}
203+
204+
if rvi := r.Context().Value(defaultContextIdentifier); rvi != nil {
205+
206+
rv := rvi.(*requestVars)
207+
208+
if !rv.formParsed {
209+
for _, p := range rv.params {
210+
r.Form.Add(p.Key, p.Value)
211+
}
212+
213+
rv.formParsed = true
214+
}
215+
}
216+
217+
return nil
218+
}
219+
220+
// ParseMultipartForm calls the underlying http.Request ParseMultipartForm
221+
// but also adds the URL params to the request Form as if they were defined
222+
// as query params i.e. ?id=13&ok=true but does not add the params to the
223+
// http.Request.URL.RawQuery for SEO purposes
224+
func ParseMultipartForm(r *http.Request, maxMemory int64) error {
225+
226+
if err := r.ParseMultipartForm(maxMemory); err != nil {
227+
return err
228+
}
229+
230+
if rvi := r.Context().Value(defaultContextIdentifier); rvi != nil {
231+
232+
rv := rvi.(*requestVars)
233+
234+
if !rv.formParsed {
235+
for _, p := range rv.params {
236+
r.Form.Add(p.Key, p.Value)
237+
}
238+
239+
rv.formParsed = true
240+
}
241+
}
242+
243+
return nil
244+
}
245+
193246
// Decode takes the request and attempts to discover it's content type via
194247
// the http headers and then decode the request body into the provided struct.
195248
// Example if header was "application/json" would decode using
196249
// json.NewDecoder(io.LimitReader(r.Body, maxMemory)).Decode(v).
250+
//
251+
// NOTE: when includeFormQueryParams=true both query params and SEO query params will be parsed and
252+
// included eg. route /user/:id?test=true both 'id' and 'test' are treated as query params and added
253+
// to the request.Form prior to decoding; in short SEO query params are treated just like normal
254+
// query params.
197255
func Decode(r *http.Request, includeFormQueryParams bool, maxMemory int64, v interface{}) (err error) {
198256

199257
typ := r.Header.Get(ContentType)
@@ -212,20 +270,28 @@ func Decode(r *http.Request, includeFormQueryParams bool, maxMemory int64, v int
212270

213271
case ApplicationForm:
214272

215-
if err = r.ParseForm(); err == nil {
216-
if includeFormQueryParams {
273+
if includeFormQueryParams {
274+
275+
if err = ParseForm(r); err == nil {
217276
err = DefaultDecoder.Decode(v, r.Form)
218-
} else {
277+
}
278+
279+
} else {
280+
if err = r.ParseForm(); err == nil {
219281
err = DefaultDecoder.Decode(v, r.PostForm)
220282
}
221283
}
222284

223285
case MultipartForm:
224286

225-
if err = r.ParseMultipartForm(maxMemory); err == nil {
226-
if includeFormQueryParams {
287+
if includeFormQueryParams {
288+
289+
if err = ParseMultipartForm(r, maxMemory); err == nil {
227290
err = DefaultDecoder.Decode(v, r.Form)
228-
} else {
291+
}
292+
293+
} else {
294+
if err = r.ParseMultipartForm(maxMemory); err == nil {
229295
err = DefaultDecoder.Decode(v, r.MultipartForm.Value)
230296
}
231297
}

helpers_test.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,17 @@ func TestDecode(t *testing.T) {
8888
err := Decode(r, false, 16<<10, test)
8989
Equal(t, err, nil)
9090
})
91+
p.Post("/decode3/:id", func(w http.ResponseWriter, r *http.Request) {
92+
err := Decode(r, true, 16<<10, test)
93+
Equal(t, err, nil)
94+
})
9195

9296
hf := p.Serve()
9397

9498
form := url.Values{}
9599
form.Add("Posted", "value")
96100

97-
r, _ := http.NewRequest(http.MethodPost, "/decode/13?id=13", strings.NewReader(form.Encode()))
101+
r, _ := http.NewRequest(http.MethodPost, "/decode/14?id=13", strings.NewReader(form.Encode()))
98102
r.Header.Set(ContentType, ApplicationForm)
99103
w := httptest.NewRecorder()
100104

@@ -105,6 +109,17 @@ func TestDecode(t *testing.T) {
105109
Equal(t, test.Posted, "value")
106110
Equal(t, test.MultiPartPosted, "")
107111

112+
r, _ = http.NewRequest(http.MethodPost, "/decode/14", strings.NewReader(form.Encode()))
113+
r.Header.Set(ContentType, ApplicationForm)
114+
w = httptest.NewRecorder()
115+
116+
hf.ServeHTTP(w, r)
117+
118+
Equal(t, w.Code, http.StatusOK)
119+
Equal(t, test.ID, 14)
120+
Equal(t, test.Posted, "value")
121+
Equal(t, test.MultiPartPosted, "")
122+
108123
test = new(TestStruct)
109124
r, _ = http.NewRequest(http.MethodPost, "/decode2/13", strings.NewReader(form.Encode()))
110125
r.Header.Set(ContentType, ApplicationForm)
@@ -161,6 +176,28 @@ func TestDecode(t *testing.T) {
161176
Equal(t, test.Posted, "")
162177
Equal(t, test.MultiPartPosted, "value")
163178

179+
body = &bytes.Buffer{}
180+
writer = multipart.NewWriter(body)
181+
182+
err = writer.WriteField("MultiPartPosted", "value")
183+
Equal(t, err, nil)
184+
185+
// Don't forget to close the multipart writer.
186+
// If you don't close it, your request will be missing the terminating boundary.
187+
err = writer.Close()
188+
Equal(t, err, nil)
189+
190+
test = new(TestStruct)
191+
r, _ = http.NewRequest(http.MethodPost, "/decode3/11", body)
192+
r.Header.Set(ContentType, writer.FormDataContentType())
193+
w = httptest.NewRecorder()
194+
195+
hf.ServeHTTP(w, r)
196+
Equal(t, w.Code, http.StatusOK)
197+
Equal(t, test.ID, 11)
198+
Equal(t, test.Posted, "")
199+
Equal(t, test.MultiPartPosted, "value")
200+
164201
jsonBody := `{"ID":13,"Posted":"value","MultiPartPosted":"value"}`
165202
test = new(TestStruct)
166203
r, _ = http.NewRequest(http.MethodPost, "/decode/13", strings.NewReader(jsonBody))
@@ -447,6 +484,46 @@ func TestXML(t *testing.T) {
447484
Equal(t, w.Body.String(), "xml: unsupported type: func()\n")
448485
}
449486

487+
func TestBadParseForm(t *testing.T) {
488+
489+
// successful scenarios tested under TestDecode
490+
491+
p := New()
492+
p.Get("/users/:id", func(w http.ResponseWriter, r *http.Request) {
493+
494+
if err := ParseForm(r); err != nil {
495+
if _, errr := w.Write([]byte(err.Error())); errr != nil {
496+
panic(errr)
497+
}
498+
return
499+
}
500+
})
501+
502+
code, body := request(http.MethodGet, "/users/16?test=%2f%%efg", p)
503+
Equal(t, code, http.StatusOK)
504+
Equal(t, body, "invalid URL escape \"%%e\"")
505+
}
506+
507+
func TestBadParseMultiPartForm(t *testing.T) {
508+
509+
// successful scenarios tested under TestDecode
510+
511+
p := New()
512+
p.Get("/users/:id", func(w http.ResponseWriter, r *http.Request) {
513+
514+
if err := ParseMultipartForm(r, 10<<5); err != nil {
515+
if _, errr := w.Write([]byte(err.Error())); errr != nil {
516+
panic(err)
517+
}
518+
return
519+
}
520+
})
521+
522+
code, body := requestMultiPart(http.MethodGet, "/users/16?test=%2f%%efg", p)
523+
Equal(t, code, http.StatusOK)
524+
Equal(t, body, "invalid URL escape \"%%e\"")
525+
}
526+
450527
type gzipWriter struct {
451528
io.Writer
452529
http.ResponseWriter

pure.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,8 @@ END:
491491

492492
if len(rv.params) > 0 {
493493

494+
rv.formParsed = false
495+
494496
// create requestVars and store on context
495497
r = r.WithContext(context.WithValue(r.Context(), defaultContextIdentifier, rv))
496498
}

pure_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package pure
22

33
import (
4+
"bytes"
5+
"fmt"
6+
"io"
47
"io/ioutil"
8+
"mime/multipart"
59
"net/http"
610
"net/http/httptest"
711
"strconv"
@@ -864,3 +868,41 @@ func request(method, path string, p *Pure) (int, string) {
864868
hf.ServeHTTP(w, r)
865869
return w.Code, w.Body.String()
866870
}
871+
872+
func requestMultiPart(method string, url string, p *Pure) (int, string) {
873+
874+
body := &bytes.Buffer{}
875+
writer := multipart.NewWriter(body)
876+
877+
part, err := writer.CreateFormFile("file", "test.txt")
878+
if err != nil {
879+
fmt.Println("ERR FILE:", err)
880+
}
881+
882+
buff := bytes.NewBufferString("FILE TEST DATA")
883+
_, err = io.Copy(part, buff)
884+
if err != nil {
885+
fmt.Println("ERR COPY:", err)
886+
}
887+
888+
err = writer.WriteField("username", "joeybloggs")
889+
if err != nil {
890+
fmt.Println("ERR:", err)
891+
}
892+
893+
err = writer.Close()
894+
if err != nil {
895+
fmt.Println("ERR:", err)
896+
}
897+
898+
r, _ := http.NewRequest(method, url, body)
899+
r.Header.Set(ContentType, writer.FormDataContentType())
900+
wr := &closeNotifyingRecorder{
901+
httptest.NewRecorder(),
902+
make(chan bool, 1),
903+
}
904+
hf := p.Serve()
905+
hf.ServeHTTP(wr, r)
906+
907+
return wr.Code, wr.Body.String()
908+
}

request_vars.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type requestVars struct {
1616
r *http.Request
1717
params Params
1818
queryParams url.Values
19+
formParsed bool
1920
}
2021

2122
// Params returns the current routes Params

0 commit comments

Comments
 (0)