26
"encoding/asn1"
27
"encoding/base64"
28
"encoding/pem"
29
+ "errors"
30
"fmt"
31
"io"
32
"net"
232
if req.Body == nil {
233
return "", true
234
}
234
- // normally net/http will close the body for us, but since we
235
- // are replacing it with a fake one, we have to ensure we close
236
- // the real body ourselves when we're done
237
- defer req.Body.Close()
238
- // read the request body into a buffer (can't pool because we
239
- // don't know its lifetime and would have to make a copy anyway)
240
- buf := new(bytes.Buffer)
241
- _, _ = io.Copy(buf, req.Body) // can't handle error, so just ignore it
242
- req.Body = io.NopCloser(buf) // replace real body with buffered data
243
- return buf.String(), true
235
+ body, err := readRequestBodyForPlaceholder(req)
236
+ if err != nil {
237
+ return err, true
238
+ }
239
+ return string(body), true
240
241
case "http.request.body_base64":
242
if req.Body == nil {
243
return "", true
244
}
249
- // normally net/http will close the body for us, but since we
250
- // are replacing it with a fake one, we have to ensure we close
251
- // the real body ourselves when we're done
252
- defer req.Body.Close()
253
- // read the request body into a buffer (can't pool because we
254
- // don't know its lifetime and would have to make a copy anyway)
255
- buf := new(bytes.Buffer)
256
- _, _ = io.Copy(buf, req.Body) // can't handle error, so just ignore it
257
- req.Body = io.NopCloser(buf) // replace real body with buffered data
258
- return base64.StdEncoding.EncodeToString(buf.Bytes()), true
245
+ body, err := readRequestBodyForPlaceholder(req)
246
+ if err != nil {
247
+ return err, true
248
+ }
249
+ return base64.StdEncoding.EncodeToString(body), true
250
251
// original request, before any internal changes
252
case "http.request.orig_method":
407
repl.Map(httpVars)
408
}
409
410
+// RequestBodyLimitError is returned by the {http.request.body} and
411
+// {http.request.body_base64} placeholders when reading the request body
412
+// exceeded a request_body max_size limit. Placeholder consumers such as
413
+// templates and the vars matchers recognize exactly this condition and no
414
+// other error, so unrelated errors keep their existing behavior.
415
+type RequestBodyLimitError struct {
416
+ Err error // the underlying reason, e.g. *http.MaxBytesError
417
+}
418
+
419
+// Error returns the reason the request body could not be read in full.
420
+func (e RequestBodyLimitError) Error() string {
421
+ return fmt.Sprintf("request body: %v", e.Err)
422
+}
423
+
424
+// StatusCode returns 413 to match the request_body handler's response to an
425
+// oversized body.
426
+func (e RequestBodyLimitError) StatusCode() int {
427
+ return http.StatusRequestEntityTooLarge
428
+}
429
+
430
+// Unwrap returns the underlying error value. See the `errors` package for info.
431
+func (e RequestBodyLimitError) Unwrap() error { return e.Err }
432
+
433
+// readRequestBodyForPlaceholder reads the request body into a buffer for the
434
+// {http.request.body} and {http.request.body_base64} placeholders. Usually the
435
+// body is copied verbatim and the buffered replacement is installed on req. If
436
+// the read fails because the request_body max_size limit was exceeded, the
437
+// request_body handler wraps the http.MaxBytesError into a HandlerError
438
+// carrying status 413; here that error is converted to a RequestBodyLimitError,
439
+// which the placeholder consumers recognize, instead of silently returning the
440
+// truncated prefix as though it were the complete body. Only a max_size-driven
441
+// handler error is converted; any other read error is still ignored, as it was
442
+// before, because the request is probably not going to be served normally
443
+// anyway and we want to keep the placeholder's behavior unchanged in that case.
444
+func readRequestBodyForPlaceholder(req *http.Request) ([]byte, error) {
445
+ // normally net/http will close the body for us, but since we are replacing
446
+ // it with a fake one, we have to ensure we close the real body ourselves
447
+ defer req.Body.Close()
448
+
449
+ // read the request body into a buffer (can't pool because we don't know its
450
+ // lifetime and would have to make a copy anyway)
451
+ buf := new(bytes.Buffer)
452
+ _, err := io.Copy(buf, req.Body)
453
+ if err != nil {
454
+ var handlerErr HandlerError
455
+ var maxBytes *http.MaxBytesError
456
+ if errors.As(err, &handlerErr) && errors.As(handlerErr.Err, &maxBytes) {
457
+ // return the dedicated body-limit marker carrying only the
458
+ // MaxBytesError; the generated ID and stack trace of the handler
459
+ // error are intentionally dropped so a placeholder stringified
460
+ // into a response body (e.g. respond {http.request.body}) cannot
461
+ // leak the call stack to the client. consumers recognize this
462
+ // marker and no other error, so unrelated HandlerError values
463
+ // keep their existing behavior.
464
+ return nil, RequestBodyLimitError{Err: maxBytes}
465
+ }
466
+ }
467
+
468
+ // replace the real body with the buffered data
469
+ req.Body = io.NopCloser(buf)
470
+ return buf.Bytes(), nil
471
+}
472
+
473
func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
474
if req == nil || req.TLS == nil {
475
return nil, false