github.com/decred/politeia@v1.4.0/politeiawww/legacy/pi/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 pi
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"net/http"
    11  	"runtime/debug"
    12  	"time"
    13  
    14  	pdv2 "github.com/decred/politeia/politeiad/api/v2"
    15  	pdclient "github.com/decred/politeia/politeiad/client"
    16  	v1 "github.com/decred/politeia/politeiawww/api/pi/v1"
    17  	"github.com/decred/politeia/util"
    18  	"github.com/pkg/errors"
    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 Pi 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  // handlePDError handles errors from politeiad.
   100  func handlePDError(w http.ResponseWriter, r *http.Request, format string, pde pdclient.RespError) {
   101  	var (
   102  		pluginID   = pde.ErrorReply.PluginID
   103  		errCode    = pde.ErrorReply.ErrorCode
   104  		errContext = pde.ErrorReply.ErrorContext
   105  	)
   106  	e := convertPDErrorCode(errCode)
   107  	switch {
   108  	case pluginID != "":
   109  		// politeiad plugin error. Log it and return a 400.
   110  		m := fmt.Sprintf("%v Plugin error: %v %v",
   111  			util.RemoteAddr(r), pluginID, errCode)
   112  		if errContext != "" {
   113  			m += fmt.Sprintf(": %v", errContext)
   114  		}
   115  		log.Infof(m)
   116  		util.RespondWithJSON(w, http.StatusBadRequest,
   117  			v1.PluginErrorReply{
   118  				PluginID:     pluginID,
   119  				ErrorCode:    errCode,
   120  				ErrorContext: errContext,
   121  			})
   122  		return
   123  
   124  	case e != v1.ErrorCodeInvalid:
   125  		// User error from politeiad that corresponds to a records user
   126  		// error. Log it and return a 400.
   127  		m := fmt.Sprintf("%v Records user error: %v %v",
   128  			util.RemoteAddr(r), e, v1.ErrorCodes[e])
   129  		if errContext != "" {
   130  			m += fmt.Sprintf(": %v", errContext)
   131  		}
   132  		log.Infof(m)
   133  		util.RespondWithJSON(w, http.StatusBadRequest,
   134  			v1.UserErrorReply{
   135  				ErrorCode:    e,
   136  				ErrorContext: errContext,
   137  			})
   138  		return
   139  
   140  	default:
   141  		// politeiad error does not correspond to a user error. Log it
   142  		// and return a 500.
   143  		ts := time.Now().Unix()
   144  		log.Errorf("%v %v %v %v Internal error %v: error code "+
   145  			"from politeiad: %v", util.RemoteAddr(r), r.Method, r.URL,
   146  			r.Proto, ts, errCode)
   147  
   148  		util.RespondWithJSON(w, http.StatusInternalServerError,
   149  			v1.ServerErrorReply{
   150  				ErrorCode: ts,
   151  			})
   152  		return
   153  	}
   154  }
   155  
   156  // convertPDErrorCode converts user errors from politeiad into pi user errors.
   157  func convertPDErrorCode(errCode uint32) v1.ErrorCodeT {
   158  	// Any error statuses that are intentionally omitted means that
   159  	// politeiawww should 500.
   160  	switch pdv2.ErrorCodeT(errCode) {
   161  	case pdv2.ErrorCodeTokenInvalid:
   162  		return v1.ErrorCodeRecordTokenInvalid
   163  	case pdv2.ErrorCodeRecordNotFound:
   164  		return v1.ErrorCodeRecordNotFound
   165  	case pdv2.ErrorCodeRequestPayloadInvalid:
   166  		// Intentionally omitted
   167  	case pdv2.ErrorCodeChallengeInvalid:
   168  		// Intentionally omitted
   169  	case pdv2.ErrorCodeMetadataStreamInvalid:
   170  		// Intentionally omitted
   171  	case pdv2.ErrorCodeMetadataStreamDuplicate:
   172  		// Intentionally omitted
   173  	case pdv2.ErrorCodeFilesEmpty:
   174  		// Intentionally omitted
   175  	case pdv2.ErrorCodeFileNameInvalid:
   176  		// Intentionally omitted
   177  	case pdv2.ErrorCodeFileNameDuplicate:
   178  		// Intentionally omitted
   179  	case pdv2.ErrorCodeFileDigestInvalid:
   180  		// Intentionally omitted
   181  	case pdv2.ErrorCodeFilePayloadInvalid:
   182  		// Intentionally omitted
   183  	case pdv2.ErrorCodeFileMIMETypeInvalid:
   184  		// Intentionally omitted
   185  	case pdv2.ErrorCodeFileMIMETypeUnsupported:
   186  		// Intentionally omitted
   187  	case pdv2.ErrorCodeRecordLocked:
   188  		// Intentionally omitted
   189  	case pdv2.ErrorCodeNoRecordChanges:
   190  		// Intentionally omitted
   191  	case pdv2.ErrorCodeStatusChangeInvalid:
   192  		// Intentionally omitted
   193  	case pdv2.ErrorCodePluginIDInvalid:
   194  		// Intentionally omitted
   195  	case pdv2.ErrorCodePluginCmdInvalid:
   196  		// Intentionally omitted
   197  	}
   198  	return v1.ErrorCodeInvalid
   199  }