boulder/web/send_error.go

74 lines
2.5 KiB
Go

package web
import (
"encoding/json"
"fmt"
"net/http"
"strings"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/probs"
)
// SendError does a few things that we want for each error response:
// - Adds both the external and the internal error to a RequestEvent.
// - If the ProblemDetails provided is a ServerInternalProblem, audit logs the
// internal error.
// - Prefixes the Type field of the ProblemDetails with the RFC8555 namespace.
// - Sends an HTTP response containing the error and an error code to the user.
//
// The internal error (ierr) may be nil if no information beyond the
// ProblemDetails is needed for internal debugging.
func SendError(
log blog.Logger,
response http.ResponseWriter,
logEvent *RequestEvent,
prob *probs.ProblemDetails,
ierr error,
) {
// Write the JSON problem response
response.Header().Set("Content-Type", "application/problem+json")
if prob.HTTPStatus != 0 {
response.WriteHeader(prob.HTTPStatus)
} else {
// All problems should have an HTTPStatus set, because all of the functions
// in the probs package which construct a problem set one. A problem details
// object getting to this point without a status set is an error.
response.WriteHeader(http.StatusInternalServerError)
}
// Suppress logging of the "Your account is temporarily prevented from
// requesting certificates" error.
var primaryDetail = prob.Detail
if prob.Type == probs.PausedProblem {
primaryDetail = "account/ident pair is paused"
}
// Record details to the log event
logEvent.Error = fmt.Sprintf("%d :: %s :: %s", prob.HTTPStatus, prob.Type, primaryDetail)
if len(prob.SubProblems) > 0 {
subDetails := make([]string, len(prob.SubProblems))
for i, sub := range prob.SubProblems {
subDetails[i] = fmt.Sprintf("\"%s :: %s :: %s\"", sub.Identifier.Value, sub.Type, sub.Detail)
}
logEvent.Error += fmt.Sprintf(" [%s]", strings.Join(subDetails, ", "))
}
if ierr != nil {
logEvent.AddError("%s", ierr)
}
// Set the proper namespace for the problem and any sub-problems.
prob.Type = probs.ProblemType(probs.ErrorNS) + prob.Type
for i := range prob.SubProblems {
prob.SubProblems[i].Type = probs.ProblemType(probs.ErrorNS) + prob.SubProblems[i].Type
}
problemDoc, err := json.MarshalIndent(prob, "", " ")
if err != nil {
log.AuditErrf("Could not marshal error message: %s - %+v", err, prob)
problemDoc = []byte("{\"detail\": \"Problem marshalling error message.\"}")
}
response.Write(problemDoc)
}