186 lines
5.1 KiB
Go
186 lines
5.1 KiB
Go
// 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:])
|
|
}
|