github.com/decred/politeia@v1.4.0/politeiawww/legacy/records/error.go (about)

     1  // Copyright (c) 2020-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 records
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"runtime/debug"
    13  	"time"
    14  
    15  	pdv2 "github.com/decred/politeia/politeiad/api/v2"
    16  	pdclient "github.com/decred/politeia/politeiad/client"
    17  	v1 "github.com/decred/politeia/politeiawww/api/records/v1"
    18  	"github.com/decred/politeia/util"
    19  )
    20  
    21  func respondWithError(w http.ResponseWriter, r *http.Request, format string, err error) {
    22  	// Check if the client dropped the connection
    23  	if err := r.Context().Err(); err == context.Canceled {
    24  		log.Infof("%v %v %v %v client aborted connection",
    25  			util.RemoteAddr(r), r.Method, r.URL, r.Proto)
    26  
    27  		// Client dropped the connection. There is no need to
    28  		// respond further.
    29  		return
    30  	}
    31  
    32  	// Check for expected error types
    33  	var (
    34  		ue  v1.UserErrorReply
    35  		pe  v1.PluginErrorReply
    36  		pde pdclient.RespError
    37  	)
    38  	switch {
    39  	case errors.As(err, &ue):
    40  		// Records user error
    41  		m := fmt.Sprintf("%v Records user error: %v %v",
    42  			util.RemoteAddr(r), ue.ErrorCode, v1.ErrorCodes[ue.ErrorCode])
    43  		if ue.ErrorContext != "" {
    44  			m += fmt.Sprintf(": %v", ue.ErrorContext)
    45  		}
    46  		log.Infof(m)
    47  		util.RespondWithJSON(w, http.StatusBadRequest,
    48  			v1.UserErrorReply{
    49  				ErrorCode:    ue.ErrorCode,
    50  				ErrorContext: ue.ErrorContext,
    51  			})
    52  		return
    53  
    54  	case errors.As(err, &pe):
    55  		// politeiawww plugin error
    56  		m := fmt.Sprintf("%v Plugin error: %v %v",
    57  			util.RemoteAddr(r), pe.PluginID, pe.ErrorCode)
    58  		if pe.ErrorContext != "" {
    59  			m += fmt.Sprintf(": %v", pe.ErrorContext)
    60  		}
    61  		log.Infof(m)
    62  		util.RespondWithJSON(w, http.StatusBadRequest,
    63  			v1.PluginErrorReply{
    64  				PluginID:     pe.PluginID,
    65  				ErrorCode:    pe.ErrorCode,
    66  				ErrorContext: pe.ErrorContext,
    67  			})
    68  		return
    69  
    70  	case errors.As(err, &pde):
    71  		// Politeiad error
    72  		handlePDError(w, r, format, pde)
    73  
    74  	default:
    75  		// Internal server error. Log it and return a 500.
    76  		t := time.Now().Unix()
    77  		e := fmt.Sprintf(format, err)
    78  		log.Errorf("%v %v %v %v Internal error %v: %v",
    79  			util.RemoteAddr(r), r.Method, r.URL, r.Proto, t, e)
    80  
    81  		// If this is a pkg/errors error then we can pull the
    82  		// stack trace out of the error, otherwise, we use the
    83  		// stack trace for this function.
    84  		stack, ok := util.StackTrace(err)
    85  		if !ok {
    86  			stack = string(debug.Stack())
    87  		}
    88  
    89  		log.Errorf("Stacktrace (NOT A REAL CRASH): %v", stack)
    90  
    91  		util.RespondWithJSON(w, http.StatusInternalServerError,
    92  			v1.ServerErrorReply{
    93  				ErrorCode: t,
    94  			})
    95  		return
    96  	}
    97  }
    98  
    99  func handlePDError(w http.ResponseWriter, r *http.Request, format string, pde pdclient.RespError) {
   100  	var (
   101  		pluginID   = pde.ErrorReply.PluginID
   102  		errCode    = pde.ErrorReply.ErrorCode
   103  		errContext = pde.ErrorReply.ErrorContext
   104  	)
   105  	e := convertPDErrorCode(errCode)
   106  	switch {
   107  	case pluginID != "":
   108  		// politeiad plugin error. Log it and return a 400.
   109  		m := fmt.Sprintf("%v Plugin error: %v %v",
   110  			util.RemoteAddr(r), pluginID, errCode)
   111  		if errContext != "" {
   112  			m += fmt.Sprintf(": %v", errContext)
   113  		}
   114  		log.Infof(m)
   115  		util.RespondWithJSON(w, http.StatusBadRequest,
   116  			v1.PluginErrorReply{
   117  				PluginID:     pluginID,
   118  				ErrorCode:    errCode,
   119  				ErrorContext: errContext,
   120  			})
   121  		return
   122  
   123  	case e != v1.ErrorCodeInvalid:
   124  		// User error from politeiad that corresponds to a records user
   125  		// error. Log it and return a 400.
   126  		m := fmt.Sprintf("%v Records user error: %v %v",
   127  			util.RemoteAddr(r), e, v1.ErrorCodes[e])
   128  		if errContext != "" {
   129  			m += fmt.Sprintf(": %v", errContext)
   130  		}
   131  		log.Infof(m)
   132  		util.RespondWithJSON(w, http.StatusBadRequest,
   133  			v1.UserErrorReply{
   134  				ErrorCode:    e,
   135  				ErrorContext: errContext,
   136  			})
   137  		return
   138  
   139  	default:
   140  		// politeiad error does not correspond to a user error. Log it
   141  		// and return a 500.
   142  		ts := time.Now().Unix()
   143  		log.Errorf("%v %v %v %v Internal error %v: error code "+
   144  			"from politeiad: %v", util.RemoteAddr(r), r.Method, r.URL,
   145  			r.Proto, ts, errCode)
   146  
   147  		util.RespondWithJSON(w, http.StatusInternalServerError,
   148  			v1.ServerErrorReply{
   149  				ErrorCode: ts,
   150  			})
   151  		return
   152  	}
   153  }
   154  
   155  func convertPDErrorCode(errCode uint32) v1.ErrorCodeT {
   156  	// Any error statuses that are intentionally omitted means that
   157  	// politeiawww should 500.
   158  	switch pdv2.ErrorCodeT(errCode) {
   159  	case pdv2.ErrorCodeRequestPayloadInvalid:
   160  		// Intentionally omitted
   161  	case pdv2.ErrorCodeChallengeInvalid:
   162  		// Intentionally omitted
   163  	case pdv2.ErrorCodeMetadataStreamInvalid:
   164  		// Intentionally omitted
   165  	case pdv2.ErrorCodeMetadataStreamDuplicate:
   166  		// Intentionally omitted
   167  	case pdv2.ErrorCodeFilesEmpty:
   168  		return v1.ErrorCodeFilesEmpty
   169  	case pdv2.ErrorCodeFileNameInvalid:
   170  		return v1.ErrorCodeFileNameInvalid
   171  	case pdv2.ErrorCodeFileNameDuplicate:
   172  		return v1.ErrorCodeFileNameDuplicate
   173  	case pdv2.ErrorCodeFileDigestInvalid:
   174  		return v1.ErrorCodeFileDigestInvalid
   175  	case pdv2.ErrorCodeFilePayloadInvalid:
   176  		return v1.ErrorCodeFilePayloadInvalid
   177  	case pdv2.ErrorCodeFileMIMETypeInvalid:
   178  		return v1.ErrorCodeFileMIMETypeInvalid
   179  	case pdv2.ErrorCodeFileMIMETypeUnsupported:
   180  		return v1.ErrorCodeFileMIMETypeUnsupported
   181  	case pdv2.ErrorCodeTokenInvalid:
   182  		return v1.ErrorCodeRecordTokenInvalid
   183  	case pdv2.ErrorCodeRecordNotFound:
   184  		return v1.ErrorCodeRecordNotFound
   185  	case pdv2.ErrorCodeRecordLocked:
   186  		return v1.ErrorCodeRecordLocked
   187  	case pdv2.ErrorCodeNoRecordChanges:
   188  		return v1.ErrorCodeNoRecordChanges
   189  	case pdv2.ErrorCodeStatusChangeInvalid:
   190  		return v1.ErrorCodeStatusChangeInvalid
   191  	case pdv2.ErrorCodePluginIDInvalid:
   192  		// Intentionally omitted
   193  	case pdv2.ErrorCodePluginCmdInvalid:
   194  		// Intentionally omitted
   195  	}
   196  	return v1.ErrorCodeInvalid
   197  }