From 0641bb91318ae197877d5b38093dba1ea8e23ed0 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 28 May 2018 18:25:17 +0200 Subject: [PATCH] add 'parseHttpParameter()' to parse as JSON (if slice of size 1 : return only first, else return as array) or return raw string if JSON fails --- checker/default/int/main.go | 28 +++++++- checker/default/string/main.go | 10 ++- request_builder.go | 117 ++++++++++++++++++++++++++++++--- router.go | 50 +++++++++++++- 4 files changed, 190 insertions(+), 15 deletions(-) diff --git a/checker/default/int/main.go b/checker/default/int/main.go index 2e811cf..8c1b605 100644 --- a/checker/default/int/main.go +++ b/checker/default/int/main.go @@ -1,12 +1,34 @@ package main +import ( + "reflect" +) + +var validationTable = map[reflect.Kind]interface{}{ + reflect.Float32: nil, + reflect.Float64: nil, + reflect.Int: nil, + reflect.Int8: nil, + reflect.Int16: nil, + reflect.Int32: nil, + reflect.Int64: nil, + reflect.Uint: nil, + reflect.Uint8: nil, + reflect.Uint16: nil, + reflect.Uint32: nil, + reflect.Uint64: nil, +} + func Match(name string) bool { return name == "int" } func Check(value interface{}) bool { - _, intOK := value.(int) - _, uintOK := value.(uint) - return intOK || uintOK + kind := reflect.TypeOf(value).Kind() + + _, isTypeValid := validationTable[kind] + + return isTypeValid + } diff --git a/checker/default/string/main.go b/checker/default/string/main.go index 57796e5..ad28da1 100644 --- a/checker/default/string/main.go +++ b/checker/default/string/main.go @@ -1,11 +1,17 @@ package main +import ( + "reflect" +) + func Match(name string) bool { return name == "string" } func Check(value interface{}) bool { - _, OK := value.(string) - return OK + kind := reflect.TypeOf(value).Kind() + + return kind == reflect.String + } diff --git a/request_builder.go b/request_builder.go index afb138c..d8b569d 100644 --- a/request_builder.go +++ b/request_builder.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "reflect" "strings" "time" ) @@ -15,40 +16,59 @@ func buildRequest(req *http.Request) (*Request, error) { /* (1) Init request */ uri := NormaliseUri(req.URL.Path) + rawpost := FetchFormData(req) + rawget := FetchGetData(req) inst := &Request{ Uri: strings.Split(uri, "/"), - GetData: FetchGetData(req), - FormData: FetchFormData(req), + GetData: make(map[string]interface{}, 0), + FormData: make(map[string]interface{}, 0), UrlData: make([]interface{}, 0), Data: make(map[string]interface{}, 0), } inst.ControllerUri = make([]string, 0, len(inst.Uri)) /* (2) Fill 'Data' with GET data */ - for name, data := range inst.GetData { - // prevent injections + for name, rawdata := range rawget { + + // 1. Parse arguments + data := parseHttpData(rawdata) + + if data == nil { + continue + } + + // 2. prevent injections if isParameterNameInjection(name) { log.Printf("get.name_injection: '%s'\n", name) delete(inst.GetData, name) continue } - // add into data + // 3. add into data + inst.GetData[name] = data inst.Data[fmt.Sprintf("GET@%s", name)] = data } /* (3) Fill 'Data' with POST data */ - for name, data := range inst.FormData { + for name, rawdata := range rawpost { - // prevent injections + // 1. Parse arguments + data := parseHttpData(rawdata) + + if data == nil { + continue + } + + // 2. prevent injections if isParameterNameInjection(name) { log.Printf("post.name_injection: '%s'\n", name) delete(inst.FormData, name) continue } - // add into data + // 3. add into data inst.Data[name] = data + inst.FormData[name] = data } return inst, nil @@ -154,3 +174,84 @@ func FetchFormData(req *http.Request) map[string]interface{} { func isParameterNameInjection(pName string) bool { return strings.HasPrefix(pName, "GET@") || strings.HasPrefix(pName, "URL#") } + +// parseHttpData parses http GET/POST data +// - []string of 1 element : return json of element 0 +// - string : return json if valid, else return raw string +func parseHttpData(data interface{}) interface{} { + dtype := reflect.TypeOf(data) + dvalue := reflect.ValueOf(data) + + switch dtype.Kind() { + + /* (1) []string -> recursive */ + case reflect.Slice: + + // 1. Return nothing if empty + if dvalue.Len() == 0 { + return nil + } + + // 2. only return first element if alone + if dvalue.Len() == 1 { + + element := dvalue.Index(0) + if element.Kind() != reflect.String { + return nil + } + return parseHttpData(element.String()) + + // 3. Return all elements if more than 1 + } else { + + result := make([]interface{}, dvalue.Len()) + + for i, l := 0, dvalue.Len(); i < l; i++ { + element := dvalue.Index(i) + + // ignore non-string + if element.Kind() != reflect.String { + continue + } + + result[i] = parseHttpData(element.String()) + } + return result + + } + + /* (2) string -> parse */ + case reflect.String: + + // build json wrapper + wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String()) + + // try to parse as json + var result interface{} + err := json.Unmarshal([]byte(wrapper), &result) + + // return if success + if err == nil { + + mapval, ok := result.(map[string]interface{}) + if !ok { + return dvalue.String() + } + + wrapped, ok := mapval["wrapped"] + if !ok { + return dvalue.String() + } + + return wrapped + } + + // else return as string + return dvalue.String() + + } + + /* (3) NIL if unknown type */ + return nil + +} diff --git a/router.go b/router.go index c82aa25..0f32db5 100644 --- a/router.go +++ b/router.go @@ -77,11 +77,57 @@ func (s *Server) route(res http.ResponseWriter, req *http.Request) { /* (4) Check arguments ---------------------------------------------------------*/ - for name, data := range s.Params { - fmt.Printf("- %s: %v\n", name, data) + var paramError Err = ErrSuccess + for name, param := range method.Parameters { + fmt.Printf("- %s: %v | '%v'\n", name, *param.Optional, *param.Rename) + + /* (1) Extract value */ + value, isset := request.Data[name] + + /* (2) OPTIONAL ? */ + if !isset { + + // fail if required + if !*param.Optional { + paramError = ErrMissingParam + paramError.BindArgument(name) + break + + // error if default param is nil + } else if param.Default == nil { + paramError = ErrInvalidDefaultParam + paramError.BindArgument(name) + break + + // set default value if optional + } else { + value = *param.Default + } + + } + + /* (3) Check type */ + isValid := s.Checker.Run(param.Type, value) + if isValid != nil { + paramError = ErrInvalidParam + paramError.BindArgument(name) + paramError.BindArgument(param.Type) + paramError.BindArgument(value) + break + } + } fmt.Printf("\n") + // Fail if argument check failed + if paramError.Code != ErrSuccess.Code { + Json, _ := paramError.MarshalJSON() + res.Header().Add("Content-Type", "application/json") + res.Write(Json) + log.Printf("[err] %s\n", paramError.Reason) + return + } + fmt.Printf("OK\nplugin: '%si.so'\n", strings.Join(request.ControllerUri, "/")) return }