github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/cloudflare/cfssl/api/api.go (about) 1 // Package api implements an HTTP-based API and server for CFSSL. 2 package api 3 4 import ( 5 "encoding/json" 6 "github.com/hellobchain/newcryptosm/http" 7 "io/ioutil" 8 9 "github.com/hellobchain/third_party/cloudflare/cfssl/errors" 10 "github.com/hellobchain/third_party/cloudflare/cfssl/log" 11 ) 12 13 // Handler is an interface providing a generic mechanism for handling HTTP requests. 14 type Handler interface { 15 Handle(w http.ResponseWriter, r *http.Request) error 16 } 17 18 // HTTPHandler is a wrapper that encapsulates Handler interface as http.Handler. 19 // HTTPHandler also enforces that the Handler only responds to requests with registered HTTP methods. 20 type HTTPHandler struct { 21 Handler // CFSSL handler 22 Methods []string // The associated HTTP methods 23 } 24 25 // HandlerFunc is similar to the http.HandlerFunc type; it serves as 26 // an adapter allowing the use of ordinary functions as Handlers. If 27 // f is a function with the appropriate signature, HandlerFunc(f) is a 28 // Handler object that calls f. 29 type HandlerFunc func(http.ResponseWriter, *http.Request) error 30 31 // Handle calls f(w, r) 32 func (f HandlerFunc) Handle(w http.ResponseWriter, r *http.Request) error { 33 w.Header().Set("Content-Type", "application/json") 34 return f(w, r) 35 } 36 37 // HandleError is the centralised error handling and reporting. 38 func HandleError(w http.ResponseWriter, err error) (code int) { 39 if err == nil { 40 return http.StatusOK 41 } 42 msg := err.Error() 43 httpCode := http.StatusInternalServerError 44 45 // If it is recognized as HttpError emitted from cfssl, 46 // we rewrite the status code accordingly. If it is a 47 // cfssl error, set the http status to StatusBadRequest 48 switch err := err.(type) { 49 case *errors.HTTPError: 50 httpCode = err.StatusCode 51 code = err.StatusCode 52 case *errors.Error: 53 httpCode = http.StatusBadRequest 54 code = err.ErrorCode 55 msg = err.Message 56 } 57 58 response := NewErrorResponse(msg, code) 59 jsonMessage, err := json.Marshal(response) 60 if err != nil { 61 log.Errorf("Failed to marshal JSON: %v", err) 62 } else { 63 msg = string(jsonMessage) 64 } 65 http.Error(w, msg, httpCode) 66 return code 67 } 68 69 // ServeHTTP encapsulates the call to underlying Handler to handle the request 70 // and return the response with proper HTTP status code 71 func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 72 var err error 73 var match bool 74 // Throw 405 when requested with an unsupported verb. 75 for _, m := range h.Methods { 76 if m == r.Method { 77 match = true 78 } 79 } 80 if match { 81 err = h.Handle(w, r) 82 } else { 83 err = errors.NewMethodNotAllowed(r.Method) 84 } 85 status := HandleError(w, err) 86 log.Infof("%s - \"%s %s\" %d", r.RemoteAddr, r.Method, r.URL, status) 87 } 88 89 // readRequestBlob takes a JSON-blob-encoded response body in the form 90 // map[string]string and returns it, the list of keywords presented, 91 // and any error that occurred. 92 func readRequestBlob(r *http.Request) (map[string]string, error) { 93 var blob map[string]string 94 95 body, err := ioutil.ReadAll(r.Body) 96 if err != nil { 97 return nil, err 98 } 99 r.Body.Close() 100 101 err = json.Unmarshal(body, &blob) 102 if err != nil { 103 return nil, err 104 } 105 return blob, nil 106 } 107 108 // ProcessRequestOneOf reads a JSON blob for the request and makes 109 // sure it contains one of a set of keywords. For example, a request 110 // might have the ('foo' && 'bar') keys, OR it might have the 'baz' 111 // key. In either case, we want to accept the request; however, if 112 // none of these sets shows up, the request is a bad request, and it 113 // should be returned. 114 func ProcessRequestOneOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) { 115 blob, err := readRequestBlob(r) 116 if err != nil { 117 return nil, nil, err 118 } 119 120 var matched []string 121 for _, set := range keywordSets { 122 if matchKeywords(blob, set) { 123 if matched != nil { 124 return nil, nil, errors.NewBadRequestString("mismatched parameters") 125 } 126 matched = set 127 } 128 } 129 if matched == nil { 130 return nil, nil, errors.NewBadRequestString("no valid parameter sets found") 131 } 132 return blob, matched, nil 133 } 134 135 // ProcessRequestFirstMatchOf reads a JSON blob for the request and returns 136 // the first match of a set of keywords. For example, a request 137 // might have one of the following combinations: (foo=1, bar=2), (foo=1), and (bar=2) 138 // By giving a specific ordering of those combinations, we could decide how to accept 139 // the request. 140 func ProcessRequestFirstMatchOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) { 141 blob, err := readRequestBlob(r) 142 if err != nil { 143 return nil, nil, err 144 } 145 146 for _, set := range keywordSets { 147 if matchKeywords(blob, set) { 148 return blob, set, nil 149 } 150 } 151 return nil, nil, errors.NewBadRequestString("no valid parameter sets found") 152 } 153 154 func matchKeywords(blob map[string]string, keywords []string) bool { 155 for _, keyword := range keywords { 156 if _, ok := blob[keyword]; !ok { 157 return false 158 } 159 } 160 return true 161 } 162 163 // ResponseMessage implements the standard for response errors and 164 // messages. A message has a code and a string message. 165 type ResponseMessage struct { 166 Code int `json:"code"` 167 Message string `json:"message"` 168 } 169 170 // Response implements the CloudFlare standard for API 171 // responses. 172 type Response struct { 173 Success bool `json:"success"` 174 Result interface{} `json:"result"` 175 Errors []ResponseMessage `json:"errors"` 176 Messages []ResponseMessage `json:"messages"` 177 } 178 179 // NewSuccessResponse is a shortcut for creating new successul API 180 // responses. 181 func NewSuccessResponse(result interface{}) Response { 182 return Response{ 183 Success: true, 184 Result: result, 185 Errors: []ResponseMessage{}, 186 Messages: []ResponseMessage{}, 187 } 188 } 189 190 // NewSuccessResponseWithMessage is a shortcut for creating new successul API 191 // responses that includes a message. 192 func NewSuccessResponseWithMessage(result interface{}, message string, code int) Response { 193 return Response{ 194 Success: true, 195 Result: result, 196 Errors: []ResponseMessage{}, 197 Messages: []ResponseMessage{{code, message}}, 198 } 199 } 200 201 // NewErrorResponse is a shortcut for creating an error response for a 202 // single error. 203 func NewErrorResponse(message string, code int) Response { 204 return Response{ 205 Success: false, 206 Result: nil, 207 Errors: []ResponseMessage{{code, message}}, 208 Messages: []ResponseMessage{}, 209 } 210 } 211 212 // SendResponse builds a response from the result, sets the JSON 213 // header, and writes to the http.ResponseWriter. 214 func SendResponse(w http.ResponseWriter, result interface{}) error { 215 response := NewSuccessResponse(result) 216 w.Header().Set("Content-Type", "application/json") 217 enc := json.NewEncoder(w) 218 err := enc.Encode(response) 219 return err 220 } 221 222 // SendResponseWithMessage builds a response from the result and the 223 // provided message, sets the JSON header, and writes to the 224 // http.ResponseWriter. 225 func SendResponseWithMessage(w http.ResponseWriter, result interface{}, message string, code int) error { 226 response := NewSuccessResponseWithMessage(result, message, code) 227 w.Header().Set("Content-Type", "application/json") 228 enc := json.NewEncoder(w) 229 err := enc.Encode(response) 230 return err 231 }