github.com/letsencrypt/boulder@v0.20251208.0/web/send_error.go (about)

     1  package web
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	blog "github.com/letsencrypt/boulder/log"
    10  	"github.com/letsencrypt/boulder/probs"
    11  )
    12  
    13  // SendError does a few things that we want for each error response:
    14  //   - Adds both the external and the internal error to a RequestEvent.
    15  //   - If the ProblemDetails provided is a ServerInternalProblem, audit logs the
    16  //     internal error.
    17  //   - Prefixes the Type field of the ProblemDetails with the RFC8555 namespace.
    18  //   - Sends an HTTP response containing the error and an error code to the user.
    19  //
    20  // The internal error (ierr) may be nil if no information beyond the
    21  // ProblemDetails is needed for internal debugging.
    22  func SendError(
    23  	log blog.Logger,
    24  	response http.ResponseWriter,
    25  	logEvent *RequestEvent,
    26  	prob *probs.ProblemDetails,
    27  	ierr error,
    28  ) {
    29  	// Write the JSON problem response
    30  	response.Header().Set("Content-Type", "application/problem+json")
    31  	if prob.HTTPStatus != 0 {
    32  		response.WriteHeader(prob.HTTPStatus)
    33  	} else {
    34  		// All problems should have an HTTPStatus set, because all of the functions
    35  		// in the probs package which construct a problem set one. A problem details
    36  		// object getting to this point without a status set is an error.
    37  		response.WriteHeader(http.StatusInternalServerError)
    38  	}
    39  
    40  	// Suppress logging of the "Your account is temporarily prevented from
    41  	// requesting certificates" error.
    42  	var primaryDetail = prob.Detail
    43  	if prob.Type == probs.PausedProblem {
    44  		primaryDetail = "account/ident pair is paused"
    45  	}
    46  
    47  	// Record details to the log event
    48  	logEvent.Error = fmt.Sprintf("%d :: %s :: %s", prob.HTTPStatus, prob.Type, primaryDetail)
    49  	if len(prob.SubProblems) > 0 {
    50  		subDetails := make([]string, len(prob.SubProblems))
    51  		for i, sub := range prob.SubProblems {
    52  			subDetails[i] = fmt.Sprintf("\"%s :: %s :: %s\"", sub.Identifier.Value, sub.Type, sub.Detail)
    53  		}
    54  		logEvent.Error += fmt.Sprintf(" [%s]", strings.Join(subDetails, ", "))
    55  	}
    56  	if ierr != nil {
    57  		logEvent.AddError("%s", ierr)
    58  	}
    59  
    60  	// Set the proper namespace for the problem and any sub-problems.
    61  	prob.Type = probs.ProblemType(probs.ErrorNS) + prob.Type
    62  	for i := range prob.SubProblems {
    63  		prob.SubProblems[i].Type = probs.ProblemType(probs.ErrorNS) + prob.SubProblems[i].Type
    64  	}
    65  
    66  	problemDoc, err := json.MarshalIndent(prob, "", "  ")
    67  	if err != nil {
    68  		log.AuditErrf("Could not marshal error message: %s - %+v", err, prob)
    69  		problemDoc = []byte("{\"detail\": \"Problem marshalling error message.\"}")
    70  	}
    71  
    72  	response.Write(problemDoc)
    73  }