requestbody: Return HTTP 413 (fix #4558)

This commit is contained in:
Matthew Holt 2022-03-11 12:34:55 -07:00
parent b82e22b459
commit 3d616e8c6d
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
4 changed files with 25 additions and 8 deletions

View file

@ -82,6 +82,9 @@ func (e HandlerError) Error() string {
return strings.TrimSpace(s) return strings.TrimSpace(s)
} }
// Unwrap returns the underlying error value. See the `errors` package for info.
func (e HandlerError) Unwrap() error { return e.Err }
// randString returns a string of n random characters. // randString returns a string of n random characters.
// It is not even remotely secure OR a proper distribution. // It is not even remotely secure OR a proper distribution.
// But it's good enough for some things. It excludes certain // But it's good enough for some things. It excludes certain

View file

@ -162,12 +162,8 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
// read the request body into a buffer (can't pool because we // read the request body into a buffer (can't pool because we
// don't know its lifetime and would have to make a copy anyway) // don't know its lifetime and would have to make a copy anyway)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err := io.Copy(buf, req.Body) _, _ = io.Copy(buf, req.Body) // can't handle error, so just ignore it
if err != nil { req.Body = io.NopCloser(buf) // replace real body with buffered data
return "", true
}
// replace real body with buffered data
req.Body = io.NopCloser(buf)
return buf.String(), true return buf.String(), true
// original request, before any internal changes // original request, before any internal changes

View file

@ -15,6 +15,7 @@
package requestbody package requestbody
import ( import (
"io"
"net/http" "net/http"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -28,6 +29,7 @@ func init() {
// RequestBody is a middleware for manipulating the request body. // RequestBody is a middleware for manipulating the request body.
type RequestBody struct { type RequestBody struct {
// The maximum number of bytes to allow reading from the body by a later handler. // The maximum number of bytes to allow reading from the body by a later handler.
// If more bytes are read, an error with HTTP status 413 is returned.
MaxSize int64 `json:"max_size,omitempty"` MaxSize int64 `json:"max_size,omitempty"`
} }
@ -44,10 +46,24 @@ func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next cad
return next.ServeHTTP(w, r) return next.ServeHTTP(w, r)
} }
if rb.MaxSize > 0 { if rb.MaxSize > 0 {
r.Body = http.MaxBytesReader(w, r.Body, rb.MaxSize) r.Body = errorWrapper{http.MaxBytesReader(w, r.Body, rb.MaxSize)}
} }
return next.ServeHTTP(w, r) return next.ServeHTTP(w, r)
} }
// errorWrapper wraps errors that are returned from Read()
// so that they can be associated with a proper status code.
type errorWrapper struct {
io.ReadCloser
}
func (ew errorWrapper) Read(p []byte) (n int, err error) {
n, err = ew.ReadCloser.Read(p)
if err != nil && err.Error() == "http: request body too large" {
err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err)
}
return
}
// Interface guard // Interface guard
var _ caddyhttp.MiddlewareHandler = (*RequestBody)(nil) var _ caddyhttp.MiddlewareHandler = (*RequestBody)(nil)

View file

@ -17,6 +17,7 @@ package caddyhttp
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -600,7 +601,8 @@ func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter
// If err is a HandlerError, the returned values will // If err is a HandlerError, the returned values will
// have richer information. // have richer information.
func errLogValues(err error) (status int, msg string, fields []zapcore.Field) { func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
if handlerErr, ok := err.(HandlerError); ok { var handlerErr HandlerError
if errors.As(err, &handlerErr) {
status = handlerErr.StatusCode status = handlerErr.StatusCode
if handlerErr.Err == nil { if handlerErr.Err == nil {
msg = err.Error() msg = err.Error()