diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index 1bfe52536..3ec5f81e3 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -18,9 +18,9 @@ import ( "github.com/pkg/errors" amino "github.com/tendermint/go-amino" - types "github.com/tendermint/tendermint/rpc/lib/types" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" + types "github.com/tendermint/tendermint/rpc/lib/types" ) // RegisterRPCFuncs adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions. @@ -294,7 +294,7 @@ func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]re continue } - v, err, ok := nonJSONToArg(cdc, argType, arg) + v, err, ok := nonJSONStringToArg(cdc, argType, arg) if err != nil { return nil, err } @@ -303,7 +303,7 @@ func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]re continue } - values[i], err = _jsonStringToArg(cdc, argType, arg) + values[i], err = jsonStringToArg(cdc, argType, arg) if err != nil { return nil, err } @@ -312,26 +312,64 @@ func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]re return values, nil } -func _jsonStringToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, error) { - v := reflect.New(ty) - err := cdc.UnmarshalJSON([]byte(arg), v.Interface()) +func jsonStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error) { + rv := reflect.New(rt) + err := cdc.UnmarshalJSON([]byte(arg), rv.Interface()) if err != nil { - return v, err + return rv, err + } + rv = rv.Elem() + return rv, nil +} + +func nonJSONStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error, bool) { + if rt.Kind() == reflect.Ptr { + rv_, err, ok := nonJSONStringToArg(cdc, rt.Elem(), arg) + if err != nil { + return reflect.Value{}, err, false + } else if ok { + rv := reflect.New(rt.Elem()) + rv.Elem().Set(rv_) + return rv, nil, true + } else { + return reflect.Value{}, nil, false + } + } else { + return _nonJSONStringToArg(cdc, rt, arg) } - v = v.Elem() - return v, nil } -func nonJSONToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, error, bool) { +// NOTE: rt.Kind() isn't a pointer. +func _nonJSONStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error, bool) { + isIntString := RE_INT.Match([]byte(arg)) isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) isHexString := strings.HasPrefix(strings.ToLower(arg), "0x") - expectingString := ty.Kind() == reflect.String - expectingByteSlice := ty.Kind() == reflect.Slice && ty.Elem().Kind() == reflect.Uint8 + + var expectingString, expectingByteSlice, expectingInt bool + switch rt.Kind() { + case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64: + expectingInt = true + case reflect.String: + expectingString = true + case reflect.Slice: + expectingByteSlice = rt.Elem().Kind() == reflect.Uint8 + } + + if isIntString && expectingInt { + qarg := `"` + arg + `"` + // jsonStringToArg + rv, err := jsonStringToArg(cdc, rt, qarg) + if err != nil { + return rv, err, false + } else { + return rv, nil, true + } + } if isHexString { if !expectingString && !expectingByteSlice { err := errors.Errorf("Got a hex string arg, but expected '%s'", - ty.Kind().String()) + rt.Kind().String()) return reflect.ValueOf(nil), err, false } @@ -340,7 +378,7 @@ func nonJSONToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, if err != nil { return reflect.ValueOf(nil), err, false } - if ty.Kind() == reflect.String { + if rt.Kind() == reflect.String { return reflect.ValueOf(string(value)), nil, true } return reflect.ValueOf([]byte(value)), nil, true diff --git a/rpc/lib/server/http_params.go b/rpc/lib/server/http_params.go index 565060678..3c948c0ba 100644 --- a/rpc/lib/server/http_params.go +++ b/rpc/lib/server/http_params.go @@ -15,6 +15,7 @@ var ( dotAtom = atom + `(?:\.` + atom + `)*` domain = `[A-Z0-9.-]+\.[A-Z]{2,4}` + RE_INT = regexp.MustCompile(`^-?[0-9]+$`) RE_HEX = regexp.MustCompile(`^(?i)[a-f0-9]+$`) RE_EMAIL = regexp.MustCompile(`^(?i)(` + dotAtom + `)@(` + dotAtom + `)$`) RE_ADDRESS = regexp.MustCompile(`^(?i)[a-z0-9]{25,34}$`) diff --git a/rpc/lib/server/parse_test.go b/rpc/lib/server/parse_test.go index f71316906..7b0aacdbe 100644 --- a/rpc/lib/server/parse_test.go +++ b/rpc/lib/server/parse_test.go @@ -2,6 +2,8 @@ package rpcserver import ( "encoding/json" + "fmt" + "net/http" "strconv" "testing" @@ -134,7 +136,7 @@ func TestParseJSONArray(t *testing.T) { } } -func TestParseRPC(t *testing.T) { +func TestParseJSONRPC(t *testing.T) { assert := assert.New(t) demo := func(height int, name string) {} @@ -172,5 +174,48 @@ func TestParseRPC(t *testing.T) { } } +} + +func TestParseURI(t *testing.T) { + + demo := func(height int, name string) {} + call := NewRPCFunc(demo, "height,name") + cdc := amino.NewCodec() + + cases := []struct { + raw []string + height int64 + name string + fail bool + }{ + // can parse numbers unquoted and strings quoted + {[]string{"7", `"flew"`}, 7, "flew", false}, + {[]string{"22", `"john"`}, 22, "john", false}, + {[]string{"-10", `"bob"`}, -10, "bob", false}, + // can parse numbers quoted, too + {[]string{`"7"`, `"flew"`}, 7, "flew", false}, + {[]string{`"-10"`, `"bob"`}, -10, "bob", false}, + // cant parse strings uquoted + {[]string{`"-10"`, `bob`}, -10, "bob", true}, + } + for idx, tc := range cases { + i := strconv.Itoa(idx) + // data := []byte(tc.raw) + url := fmt.Sprintf( + "test.com/method?height=%v&name=%v", + tc.raw[0], tc.raw[1]) + req, err := http.NewRequest("GET", url, nil) + assert.NoError(t, err) + vals, err := httpParamsToArgs(call, cdc, req) + if tc.fail { + assert.NotNil(t, err, i) + } else { + assert.Nil(t, err, "%s: %+v", i, err) + if assert.Equal(t, 2, len(vals), i) { + assert.Equal(t, tc.height, vals[0].Int(), i) + assert.Equal(t, tc.name, vals[1].String(), i) + } + } + } }