mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-03 10:32:05 +00:00
In #8397 I tried to remove all the cases where we needed to keep track of the target type of parameters for JSON encoding, but there is one case still left: When decoding parameters from URL query terms, there is no way to tell whether or not we need base64 encoding without knowing whether the underlying type of the target is string or []byte. To fix this, keep track of parameters that are []byte valued when RPCFunc is compiling its argument map, and use that when parsing URL query terms. Update the tests accordingly.
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
|
)
|
|
|
|
// uriReqID is a placeholder ID used for GET requests, which do not receive a
|
|
// JSON-RPC request ID from the caller.
|
|
const uriReqID = -1
|
|
|
|
// convert from a function name to the http handler
|
|
func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
ctx := rpctypes.WithCallInfo(req.Context(), &rpctypes.CallInfo{
|
|
HTTPRequest: req,
|
|
})
|
|
args, err := parseURLParams(rpcFunc.args, req)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
fmt.Fprintln(w, err.Error())
|
|
return
|
|
}
|
|
jreq := rpctypes.NewRequest(uriReqID)
|
|
result, err := rpcFunc.Call(ctx, args)
|
|
if err == nil {
|
|
writeHTTPResponse(w, logger, jreq.MakeResponse(result))
|
|
} else {
|
|
writeHTTPResponse(w, logger, jreq.MakeError(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseURLParams(args []argInfo, req *http.Request) ([]byte, error) {
|
|
if err := req.ParseForm(); err != nil {
|
|
return nil, fmt.Errorf("invalid HTTP request: %w", err)
|
|
}
|
|
getArg := func(name string) (string, bool) {
|
|
if req.Form.Has(name) {
|
|
return req.Form.Get(name), true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
params := make(map[string]interface{})
|
|
for _, arg := range args {
|
|
v, ok := getArg(arg.name)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if z, err := decodeInteger(v); err == nil {
|
|
params[arg.name] = z
|
|
} else if b, err := strconv.ParseBool(v); err == nil {
|
|
params[arg.name] = b
|
|
} else if lc := strings.ToLower(v); strings.HasPrefix(lc, "0x") {
|
|
dec, err := hex.DecodeString(lc[2:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid hex string: %w", err)
|
|
} else if len(dec) == 0 {
|
|
return nil, errors.New("invalid empty hex string")
|
|
}
|
|
if arg.isBinary {
|
|
params[arg.name] = dec
|
|
} else {
|
|
params[arg.name] = string(dec)
|
|
}
|
|
} else if isQuotedString(v) {
|
|
var dec string
|
|
if err := json.Unmarshal([]byte(v), &dec); err != nil {
|
|
return nil, fmt.Errorf("invalid quoted string: %w", err)
|
|
}
|
|
if arg.isBinary {
|
|
params[arg.name] = []byte(dec)
|
|
} else {
|
|
params[arg.name] = dec
|
|
}
|
|
} else {
|
|
params[arg.name] = v
|
|
}
|
|
}
|
|
return json.Marshal(params)
|
|
}
|
|
|
|
// isQuotedString reports whether s is enclosed in double quotes.
|
|
func isQuotedString(s string) bool {
|
|
return len(s) >= 2 && strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`)
|
|
}
|
|
|
|
// decodeInteger decodes s into an int64. If s is "double quoted" the quotes
|
|
// are removed; otherwise s must be a base-10 digit string.
|
|
func decodeInteger(s string) (int64, error) {
|
|
if isQuotedString(s) {
|
|
s = s[1 : len(s)-1]
|
|
}
|
|
return strconv.ParseInt(s, 10, 64)
|
|
}
|