github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/server/http_uri_handler.go (about) 1 package server 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "strconv" 10 "strings" 11 12 "github.com/ari-anchor/sei-tendermint/libs/log" 13 rpctypes "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/types" 14 ) 15 16 // uriReqID is a placeholder ID used for GET requests, which do not receive a 17 // JSON-RPC request ID from the caller. 18 const uriReqID = -1 19 20 // convert from a function name to the http handler 21 func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWriter, *http.Request) { 22 return func(w http.ResponseWriter, req *http.Request) { 23 ctx := rpctypes.WithCallInfo(req.Context(), &rpctypes.CallInfo{ 24 HTTPRequest: req, 25 }) 26 args, err := parseURLParams(rpcFunc.args, req) 27 if err != nil { 28 w.Header().Set("Content-Type", "text/plain") 29 w.WriteHeader(http.StatusBadRequest) 30 fmt.Fprintln(w, err.Error()) 31 return 32 } 33 jreq := rpctypes.NewRequest(uriReqID) 34 result, err := rpcFunc.Call(ctx, args) 35 if err == nil { 36 writeHTTPResponse(w, logger, jreq.MakeResponse(result)) 37 } else { 38 writeHTTPResponse(w, logger, jreq.MakeError(err)) 39 } 40 } 41 } 42 43 func parseURLParams(args []argInfo, req *http.Request) ([]byte, error) { 44 if err := req.ParseForm(); err != nil { 45 return nil, fmt.Errorf("invalid HTTP request: %w", err) 46 } 47 getArg := func(name string) (string, bool) { 48 if req.Form.Has(name) { 49 return req.Form.Get(name), true 50 } 51 return "", false 52 } 53 54 params := make(map[string]interface{}) 55 for _, arg := range args { 56 v, ok := getArg(arg.name) 57 if !ok { 58 continue 59 } 60 if z, err := decodeInteger(v); err == nil { 61 params[arg.name] = z 62 } else if b, err := strconv.ParseBool(v); err == nil { 63 params[arg.name] = b 64 } else if lc := strings.ToLower(v); strings.HasPrefix(lc, "0x") { 65 dec, err := hex.DecodeString(lc[2:]) 66 if err != nil { 67 return nil, fmt.Errorf("invalid hex string: %w", err) 68 } else if len(dec) == 0 { 69 return nil, errors.New("invalid empty hex string") 70 } 71 if arg.isBinary { 72 params[arg.name] = dec 73 } else { 74 params[arg.name] = string(dec) 75 } 76 } else if isQuotedString(v) { 77 var dec string 78 if err := json.Unmarshal([]byte(v), &dec); err != nil { 79 return nil, fmt.Errorf("invalid quoted string: %w", err) 80 } 81 if arg.isBinary { 82 params[arg.name] = []byte(dec) 83 } else { 84 params[arg.name] = dec 85 } 86 } else { 87 params[arg.name] = v 88 } 89 } 90 return json.Marshal(params) 91 } 92 93 // isQuotedString reports whether s is enclosed in double quotes. 94 func isQuotedString(s string) bool { 95 return len(s) >= 2 && strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) 96 } 97 98 // decodeInteger decodes s into an int64. If s is "double quoted" the quotes 99 // are removed; otherwise s must be a base-10 digit string. 100 func decodeInteger(s string) (int64, error) { 101 if isQuotedString(s) { 102 s = s[1 : len(s)-1] 103 } 104 return strconv.ParseInt(s, 10, 64) 105 }