2018-09-28 06:10:41 +00:00
|
|
|
package request
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
2019-04-30 22:02:28 +00:00
|
|
|
|
|
|
|
"git.xdrm.io/go/aicra/internal/multipart"
|
2018-05-29 13:42:41 +00:00
|
|
|
)
|
|
|
|
|
2019-04-30 22:02:28 +00:00
|
|
|
// DataSet represents all data that can be caught:
|
|
|
|
// - URI (guessed from the URI by removing the controller path)
|
|
|
|
// - GET (default url data)
|
|
|
|
// - POST (from json, form-data, url-encoded)
|
|
|
|
type DataSet struct {
|
|
|
|
|
|
|
|
// ordered values from the URI
|
|
|
|
// catches all after the controller path
|
|
|
|
//
|
|
|
|
// points to DataSet.Data
|
|
|
|
URI []*Parameter
|
|
|
|
|
|
|
|
// uri parameters following the QUERY format
|
|
|
|
//
|
|
|
|
// points to DataSet.Data
|
|
|
|
Get map[string]*Parameter
|
|
|
|
|
|
|
|
// form data depending on the Content-Type:
|
|
|
|
// 'application/json' => key-value pair is parsed as json into the map
|
|
|
|
// 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters
|
|
|
|
// 'multipart/form-data' => parse form-data format
|
|
|
|
//
|
|
|
|
// points to DataSet.Data
|
|
|
|
Form map[string]*Parameter
|
|
|
|
|
|
|
|
// contains URL+GET+FORM data with prefixes:
|
|
|
|
// - FORM: no prefix
|
|
|
|
// - URL: 'URL#' followed by the index in Uri
|
|
|
|
// - GET: 'GET@' followed by the key in GET
|
|
|
|
Set map[string]*Parameter
|
|
|
|
}
|
|
|
|
|
2018-09-13 08:21:35 +00:00
|
|
|
// NewDataset creates an empty request dataset
|
2018-06-01 08:51:51 +00:00
|
|
|
func NewDataset() *DataSet {
|
|
|
|
return &DataSet{
|
2018-07-08 23:34:21 +00:00
|
|
|
URI: make([]*Parameter, 0),
|
2018-06-01 08:51:51 +00:00
|
|
|
Get: make(map[string]*Parameter),
|
|
|
|
Form: make(map[string]*Parameter),
|
|
|
|
Set: make(map[string]*Parameter),
|
2018-05-29 13:42:41 +00:00
|
|
|
}
|
2018-06-01 08:51:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build builds a 'DataSet' from an http request
|
|
|
|
func (i *DataSet) Build(req *http.Request) {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
2018-06-01 08:51:51 +00:00
|
|
|
/* (1) GET (query) data */
|
2018-05-29 13:42:41 +00:00
|
|
|
i.fetchGet(req)
|
|
|
|
|
2018-06-01 08:51:51 +00:00
|
|
|
/* (2) We are done if GET method */
|
2018-05-29 13:42:41 +00:00
|
|
|
if req.Method == "GET" {
|
2018-06-01 08:51:51 +00:00
|
|
|
return
|
2018-05-29 13:42:41 +00:00
|
|
|
}
|
|
|
|
|
2018-06-01 08:51:51 +00:00
|
|
|
/* (3) POST (body) data */
|
2018-05-29 13:42:41 +00:00
|
|
|
i.fetchForm(req)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-07-08 23:34:21 +00:00
|
|
|
// SetURI stores URL data and fills 'Set'
|
2018-05-29 13:42:41 +00:00
|
|
|
// with creating pointers inside 'Url'
|
2018-07-08 23:34:21 +00:00
|
|
|
func (i *DataSet) SetURI(data []string) {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
for index, value := range data {
|
|
|
|
|
|
|
|
// create set index
|
|
|
|
setindex := fmt.Sprintf("URL#%d", index)
|
|
|
|
|
|
|
|
// store value in 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
i.Set[setindex] = &Parameter{
|
2018-05-29 13:42:41 +00:00
|
|
|
Parsed: false,
|
|
|
|
Value: value,
|
|
|
|
}
|
|
|
|
|
|
|
|
// create link in 'Url'
|
2018-07-08 23:34:21 +00:00
|
|
|
i.URI = append(i.URI, i.Set[setindex])
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchGet stores data from the QUERY (in url parameters)
|
2018-06-01 08:51:51 +00:00
|
|
|
func (i *DataSet) fetchGet(req *http.Request) {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
for name, value := range req.URL.Query() {
|
|
|
|
|
2018-09-28 06:54:04 +00:00
|
|
|
// prevent invalid names
|
|
|
|
if !validName(name) {
|
|
|
|
log.Printf("invalid variable name: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-05-29 13:42:41 +00:00
|
|
|
// prevent injections
|
2018-06-01 08:51:51 +00:00
|
|
|
if nameInjection(name) {
|
2018-05-29 13:42:41 +00:00
|
|
|
log.Printf("get.injection: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// create set index
|
|
|
|
setindex := fmt.Sprintf("GET@%s", name)
|
|
|
|
|
|
|
|
// store value in 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
i.Set[setindex] = &Parameter{
|
2018-05-29 13:42:41 +00:00
|
|
|
Parsed: false,
|
|
|
|
Value: value,
|
|
|
|
}
|
|
|
|
|
|
|
|
// create link in 'Get'
|
|
|
|
i.Get[name] = i.Set[setindex]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchForm stores FORM data
|
|
|
|
//
|
|
|
|
// - parse 'form-data' if not supported (not POST requests)
|
|
|
|
// - parse 'x-www-form-urlencoded'
|
|
|
|
// - parse 'application/json'
|
2018-06-01 08:51:51 +00:00
|
|
|
func (i *DataSet) fetchForm(req *http.Request) {
|
|
|
|
|
2018-05-29 13:42:41 +00:00
|
|
|
contentType := req.Header.Get("Content-Type")
|
|
|
|
|
|
|
|
// parse json
|
|
|
|
if strings.HasPrefix(contentType, "application/json") {
|
2018-07-08 23:34:21 +00:00
|
|
|
i.parseJSON(req)
|
2018-05-29 13:42:41 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse urlencoded
|
|
|
|
if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
2018-06-01 08:51:51 +00:00
|
|
|
i.parseUrlencoded(req)
|
2018-05-29 13:42:41 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse multipart
|
|
|
|
if strings.HasPrefix(contentType, "multipart/form-data; boundary=") {
|
2018-06-01 08:51:51 +00:00
|
|
|
i.parseMultipart(req)
|
2018-05-29 13:42:41 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// if unknown type store nothing
|
|
|
|
}
|
|
|
|
|
2018-07-08 23:34:21 +00:00
|
|
|
// parseJSON parses JSON from the request body inside 'Form'
|
2018-05-29 13:42:41 +00:00
|
|
|
// and 'Set'
|
2018-07-08 23:34:21 +00:00
|
|
|
func (i *DataSet) parseJSON(req *http.Request) {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
parsed := make(map[string]interface{}, 0)
|
|
|
|
|
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
|
|
|
|
|
|
// if parse error: do nothing
|
|
|
|
if err := decoder.Decode(&parsed); err != nil {
|
2018-09-26 05:35:53 +00:00
|
|
|
log.Printf("json.parse() %s\n", err)
|
2018-05-29 13:42:41 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// else store values 'parsed' values
|
|
|
|
for name, value := range parsed {
|
|
|
|
|
2018-09-28 06:54:04 +00:00
|
|
|
// prevent invalid names
|
|
|
|
if !validName(name) {
|
|
|
|
log.Printf("invalid variable name: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-05-29 13:42:41 +00:00
|
|
|
// prevent injections
|
2018-06-01 08:51:51 +00:00
|
|
|
if nameInjection(name) {
|
2018-05-29 13:42:41 +00:00
|
|
|
log.Printf("post.injection: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// store value in 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
i.Set[name] = &Parameter{
|
2018-05-29 13:42:41 +00:00
|
|
|
Parsed: true,
|
|
|
|
Value: value,
|
|
|
|
}
|
|
|
|
|
|
|
|
// create link in 'Form'
|
|
|
|
i.Form[name] = i.Set[name]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-06-01 08:51:51 +00:00
|
|
|
// parseUrlencoded parses urlencoded from the request body inside 'Form'
|
2018-05-29 13:42:41 +00:00
|
|
|
// and 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
func (i *DataSet) parseUrlencoded(req *http.Request) {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
// use http.Request interface
|
2018-09-26 05:35:53 +00:00
|
|
|
if err := req.ParseForm(); err != nil {
|
|
|
|
log.Printf("urlencoded.parse() %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
for name, value := range req.PostForm {
|
|
|
|
|
2018-09-28 06:54:04 +00:00
|
|
|
// prevent invalid names
|
|
|
|
if !validName(name) {
|
|
|
|
log.Printf("invalid variable name: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-05-29 13:42:41 +00:00
|
|
|
// prevent injections
|
2018-06-01 08:51:51 +00:00
|
|
|
if nameInjection(name) {
|
2018-05-29 13:42:41 +00:00
|
|
|
log.Printf("post.injection: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// store value in 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
i.Set[name] = &Parameter{
|
2018-05-29 13:42:41 +00:00
|
|
|
Parsed: false,
|
|
|
|
Value: value,
|
|
|
|
}
|
|
|
|
|
|
|
|
// create link in 'Form'
|
|
|
|
i.Form[name] = i.Set[name]
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-06-01 08:51:51 +00:00
|
|
|
// parseMultipart parses multi-part from the request body inside 'Form'
|
2018-05-29 13:42:41 +00:00
|
|
|
// and 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
func (i *DataSet) parseMultipart(req *http.Request) {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
/* (1) Create reader */
|
2018-09-25 19:22:25 +00:00
|
|
|
boundary := req.Header.Get("Content-Type")[len("multipart/form-data; boundary="):]
|
|
|
|
mpr, err := multipart.NewReader(req.Body, boundary)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
/* (2) Parse multipart */
|
2018-09-26 05:35:53 +00:00
|
|
|
if err = mpr.Parse(); err != nil {
|
|
|
|
log.Printf("multipart.parse() %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
2018-05-29 13:42:41 +00:00
|
|
|
|
|
|
|
/* (3) Store data into 'Form' and 'Set */
|
2018-09-25 19:22:25 +00:00
|
|
|
for name, data := range mpr.Data {
|
2018-05-29 13:42:41 +00:00
|
|
|
|
2018-09-28 06:54:04 +00:00
|
|
|
// prevent invalid names
|
|
|
|
if !validName(name) {
|
|
|
|
log.Printf("invalid variable name: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-05-29 13:42:41 +00:00
|
|
|
// prevent injections
|
2018-06-01 08:51:51 +00:00
|
|
|
if nameInjection(name) {
|
2018-05-29 13:42:41 +00:00
|
|
|
log.Printf("post.injection: '%s'\n", name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// store value in 'Set'
|
2018-06-01 08:51:51 +00:00
|
|
|
i.Set[name] = &Parameter{
|
2018-05-29 13:42:41 +00:00
|
|
|
Parsed: false,
|
2018-09-25 19:22:25 +00:00
|
|
|
File: len(data.GetHeader("filename")) > 0,
|
|
|
|
Value: string(data.Data),
|
2018-05-29 13:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// create link in 'Form'
|
|
|
|
i.Form[name] = i.Set[name]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
2019-04-30 22:02:28 +00:00
|
|
|
|
|
|
|
// nameInjection returns whether there is
|
|
|
|
// a parameter name injection:
|
|
|
|
// - inferred GET parameters
|
|
|
|
// - inferred URL parameters
|
|
|
|
func nameInjection(pName string) bool {
|
|
|
|
return strings.HasPrefix(pName, "GET@") || strings.HasPrefix(pName, "URL#")
|
|
|
|
}
|
|
|
|
|
|
|
|
// validName returns whether a parameter name (without the GET@ or URL# prefix) is valid
|
|
|
|
// if fails if the name begins/ends with underscores
|
|
|
|
func validName(pName string) bool {
|
|
|
|
return strings.Trim(pName, "_") == pName
|
|
|
|
}
|