forked from revel/revel
-
Notifications
You must be signed in to change notification settings - Fork 2
/
params.go
185 lines (162 loc) · 5.1 KB
/
params.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright (c) 2012-2017 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package revel
import (
"encoding/json"
"io/ioutil"
"mime/multipart"
"net/url"
"os"
"reflect"
"errors"
)
// Params provides a unified view of the request params.
// Includes:
// - URL query string
// - Form values
// - File uploads
//
// Warning: param maps other than Values may be nil if there were none.
type Params struct {
url.Values // A unified view of all the individual param maps below.
// Set by the router
Fixed url.Values // Fixed parameters from the route, e.g. App.Action("fixed param")
Route url.Values // Parameters extracted from the route, e.g. /customers/{id}
// Set by the ParamsFilter
Query url.Values // Parameters from the query string, e.g. /index?limit=10
Form url.Values // Parameters from the request body.
Files map[string][]*multipart.FileHeader // Files uploaded in a multipart form
tmpFiles []*os.File // Temp files used during the request.
JSON []byte // JSON data from request body
}
// ParseParams parses the `http.Request` params into `revel.Controller.Params`
func ParseParams(params *Params, req *Request) {
params.Query = req.URL.Query()
// Parse the body depending on the content type.
switch req.ContentType {
case "application/x-www-form-urlencoded":
// Typical form.
if err := req.ParseForm(); err != nil {
WARN.Println("Error parsing request body:", err)
} else {
params.Form = req.Form
}
case "multipart/form-data":
// Multipart form.
// TODO: Extract the multipart form param so app can set it.
if err := req.ParseMultipartForm(32 << 20 /* 32 MB */); err != nil {
WARN.Println("Error parsing request body:", err)
} else {
params.Form = req.MultipartForm.Value
params.Files = req.MultipartForm.File
}
case "application/json":
fallthrough
case "text/json":
if req.Body != nil {
if content, err := ioutil.ReadAll(req.Body); err == nil {
// We wont bind it until we determine what we are binding too
params.JSON = content
} else {
ERROR.Println("Failed to ready request body bytes", err)
}
} else {
INFO.Println("Json post received with empty body")
}
}
params.Values = params.calcValues()
}
// Bind looks for the named parameter, converts it to the requested type, and
// writes it into "dest", which must be settable. If the value can not be
// parsed, "dest" is set to the zero value.
func (p *Params) Bind(dest interface{}, name string) {
value := reflect.ValueOf(dest)
if value.Kind() != reflect.Ptr {
panic("revel/params: non-pointer passed to Bind: " + name)
}
value = value.Elem()
if !value.CanSet() {
panic("revel/params: non-settable variable passed to Bind: " + name)
}
// Remove the json from the Params, this will stop the binder from attempting
// to use the json data to populate the destination interface. We do not want
// to do this on a named bind directly against the param, it is ok to happen when
// the action is invoked.
jsonData := p.JSON
p.JSON = nil
value.Set(Bind(p, name, value.Type()))
p.JSON = jsonData
}
// Bind binds the JSON data to the dest.
func (p *Params) BindJSON(dest interface{}) error {
value := reflect.ValueOf(dest)
if value.Kind() != reflect.Ptr {
WARN.Println("BindJSON not a pointer")
return errors.New("BindJSON not a pointer")
}
if err := json.Unmarshal(p.JSON, dest); err != nil {
WARN.Println("W: bindMap: Unable to unmarshal request:", err)
return err
}
return nil
}
// calcValues returns a unified view of the component param maps.
func (p *Params) calcValues() url.Values {
numParams := len(p.Query) + len(p.Fixed) + len(p.Route) + len(p.Form)
// If there were no params, return an empty map.
if numParams == 0 {
return make(url.Values, 0)
}
// If only one of the param sources has anything, return that directly.
switch numParams {
case len(p.Query):
return p.Query
case len(p.Route):
return p.Route
case len(p.Fixed):
return p.Fixed
case len(p.Form):
return p.Form
}
// Copy everything into a param map,
// order of priority is least to most trusted
values := make(url.Values, numParams)
// ?query vars first
for k, v := range p.Query {
values[k] = append(values[k], v...)
}
// form vars overwrite
for k, v := range p.Form {
values[k] = append(values[k], v...)
}
// :/path vars overwrite
for k, v := range p.Route {
values[k] = append(values[k], v...)
}
// fixed vars overwrite
for k, v := range p.Fixed {
values[k] = append(values[k], v...)
}
return values
}
func ParamsFilter(c *Controller, fc []Filter) {
ParseParams(c.Params, c.Request)
// Clean up from the request.
defer func() {
// Delete temp files.
if c.Request.MultipartForm != nil {
err := c.Request.MultipartForm.RemoveAll()
if err != nil {
WARN.Println("Error removing temporary files:", err)
}
}
for _, tmpFile := range c.Params.tmpFiles {
err := os.Remove(tmpFile.Name())
if err != nil {
WARN.Println("Could not remove upload temp file:", err)
}
}
}()
fc[0](c, fc[1:])
}