github.com/decred/politeia@v1.4.0/politeiawww/legacy/error.go (about) 1 // Copyright (c) 2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package legacy 6 7 import ( 8 "context" 9 "fmt" 10 "net/http" 11 "runtime/debug" 12 "strings" 13 "time" 14 15 pd "github.com/decred/politeia/politeiad/api/v1" 16 cms "github.com/decred/politeia/politeiawww/api/cms/v1" 17 www "github.com/decred/politeia/politeiawww/api/www/v1" 18 "github.com/decred/politeia/util" 19 ) 20 21 // RespondWithError returns an HTTP error status to the client. If it's a user 22 // error, it returns a 4xx HTTP status and the specific user error code. If it's 23 // an internal server error, it returns 500 and an error code which is also 24 // outputted to the logs so that it can be correlated later if the user 25 // files a complaint. 26 func RespondWithError(w http.ResponseWriter, r *http.Request, userHttpCode int, format string, args ...interface{}) { 27 // XXX this function needs to get an error in and a format + args 28 // instead of what it is doing now. 29 // So inError error, format string, args ...interface{} 30 // if err == nil -> internal error using format + args 31 // if err != nil -> if defined error -> return defined error + log.Errorf format+args 32 // if err != nil -> if !defined error -> return + log.Errorf format+args 33 34 // Check if the client dropped the connection 35 if err := r.Context().Err(); err == context.Canceled { 36 log.Infof("%v %v %v %v client aborted connection", 37 util.RemoteAddr(r), r.Method, r.URL, r.Proto) 38 39 // Client dropped the connection. There is no need to 40 // respond further. 41 return 42 } 43 44 // Check for www user error 45 if userErr, ok := args[0].(www.UserError); ok { 46 // Error is a www user error. Log it and return a 400. 47 if userHttpCode == 0 { 48 userHttpCode = http.StatusBadRequest 49 } 50 51 if len(userErr.ErrorContext) == 0 { 52 log.Infof("WWW user error: %v %v %v", 53 util.RemoteAddr(r), int64(userErr.ErrorCode), 54 userErrorStatus(userErr.ErrorCode)) 55 } else { 56 log.Infof("WWW user error: %v %v %v: %v", 57 util.RemoteAddr(r), int64(userErr.ErrorCode), 58 userErrorStatus(userErr.ErrorCode), 59 strings.Join(userErr.ErrorContext, ", ")) 60 } 61 62 util.RespondWithJSON(w, userHttpCode, 63 www.UserError{ 64 ErrorCode: userErr.ErrorCode, 65 ErrorContext: userErr.ErrorContext, 66 }) 67 return 68 } 69 70 // Check for politeiad error 71 if pdError, ok := args[0].(pdError); ok { 72 var ( 73 pluginID = pdError.ErrorReply.Plugin 74 errCode = pdError.ErrorReply.ErrorCode 75 errContext = pdError.ErrorReply.ErrorContext 76 ) 77 78 // Check if the politeiad error corresponds to a www user error 79 wwwErrCode := convertWWWErrorStatus(pluginID, errCode) 80 if wwwErrCode == www.ErrorStatusInvalid { 81 // politeiad error does not correspond to a www user error. Log 82 // it and return a 500. 83 t := time.Now().Unix() 84 if pluginID == "" { 85 log.Errorf("%v %v %v %v Internal error %v: error "+ 86 "code from politeiad: %v", util.RemoteAddr(r), r.Method, 87 r.URL, r.Proto, t, errCode) 88 } else { 89 log.Errorf("%v %v %v %v Internal error %v: error "+ 90 "code from politeiad plugin %v: %v", util.RemoteAddr(r), 91 r.Method, r.URL, r.Proto, t, pluginID, errCode) 92 } 93 94 util.RespondWithJSON(w, http.StatusInternalServerError, 95 www.ErrorReply{ 96 ErrorCode: t, 97 }) 98 return 99 } 100 101 // politeiad error does correspond to a www user error. Log it 102 // and return a 400. 103 if len(errContext) == 0 { 104 log.Infof("WWW user error: %v %v %v", 105 util.RemoteAddr(r), int64(wwwErrCode), 106 userErrorStatus(wwwErrCode)) 107 } else { 108 log.Infof("WWW user error: %v %v %v: %v", 109 util.RemoteAddr(r), int64(wwwErrCode), 110 userErrorStatus(wwwErrCode), 111 strings.Join(errContext, ", ")) 112 } 113 114 util.RespondWithJSON(w, http.StatusBadRequest, 115 www.UserError{ 116 ErrorCode: wwwErrCode, 117 ErrorContext: errContext, 118 }) 119 return 120 } 121 122 // Error is a politeiawww server error. Log it and return a 500. 123 t := time.Now().Unix() 124 ec := fmt.Sprintf("%v %v %v %v Internal error %v: ", util.RemoteAddr(r), 125 r.Method, r.URL, r.Proto, t) 126 log.Errorf(ec+format, args...) 127 log.Errorf("Stacktrace (NOT A REAL CRASH): %s", debug.Stack()) 128 129 util.RespondWithJSON(w, http.StatusInternalServerError, 130 www.ErrorReply{ 131 ErrorCode: t, 132 }) 133 } 134 135 // userErrorStatus retrieves the human readable error message for an error 136 // status code. The status code can be from either the pi or cms api. 137 func userErrorStatus(e www.ErrorStatusT) string { 138 s, ok := www.ErrorStatus[e] 139 if ok { 140 return s 141 } 142 s, ok = cms.ErrorStatus[e] 143 if ok { 144 return s 145 } 146 return "" 147 } 148 149 func convertWWWErrorStatusFromPD(e pd.ErrorStatusT) www.ErrorStatusT { 150 switch e { 151 case pd.ErrorStatusInvalidRequestPayload: 152 // Intentionally omitted because this indicates a politeiawww 153 // server error so a ErrorStatusInvalid should be returned. 154 case pd.ErrorStatusInvalidChallenge: 155 // Intentionally omitted because this indicates a politeiawww 156 // server error so a ErrorStatusInvalid should be returned. 157 case pd.ErrorStatusInvalidFilename: 158 return www.ErrorStatusInvalidFilename 159 case pd.ErrorStatusInvalidFileDigest: 160 return www.ErrorStatusInvalidFileDigest 161 case pd.ErrorStatusInvalidBase64: 162 return www.ErrorStatusInvalidBase64 163 case pd.ErrorStatusInvalidMIMEType: 164 return www.ErrorStatusInvalidMIMEType 165 case pd.ErrorStatusUnsupportedMIMEType: 166 return www.ErrorStatusUnsupportedMIMEType 167 case pd.ErrorStatusInvalidRecordStatusTransition: 168 return www.ErrorStatusInvalidPropStatusTransition 169 } 170 return www.ErrorStatusInvalid 171 } 172 173 func convertWWWErrorStatus(pluginID string, errCode int) www.ErrorStatusT { 174 switch pluginID { 175 case "": 176 // politeiad API 177 e := pd.ErrorStatusT(errCode) 178 return convertWWWErrorStatusFromPD(e) 179 } 180 181 // No corresponding www error status found 182 return www.ErrorStatusInvalid 183 }