mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-13 15:46:49 +03:00
Add WithLimit methods for uncompression (#2147)
* Add WithLimit methods for uncompression The current uncompress methods don't enforce a memory limit and are susceptible to things like zip bombs. This pull introduces new methods so retain backwards compatibility. The old methods might be deprecated in the future. * Fix suggestion
This commit is contained in:
@@ -379,6 +379,7 @@ Important points:
|
||||
- [r.FormValue()](https://pkg.go.dev/net/http#Request.FormValue) **➜** [ctx.FormValue()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormValue)
|
||||
- [r.FormFile()](https://pkg.go.dev/net/http#Request.FormFile) **➜** [ctx.FormFile()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormFile)
|
||||
- [r.MultipartForm](https://pkg.go.dev/net/http#Request) **➜** [ctx.MultipartForm()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
|
||||
For untrusted multipart input, use [ctx.MultipartFormWithLimit()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartFormWithLimit) (or a custom [Server.FormValueFunc](https://pkg.go.dev/github.com/valyala/fasthttp#Server)) to enforce a parsing size limit.
|
||||
- [r.RemoteAddr](https://pkg.go.dev/net/http#Request) **➜** [ctx.RemoteAddr()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
|
||||
- [r.RequestURI](https://pkg.go.dev/net/http#Request) **➜** [ctx.RequestURI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RequestURI)
|
||||
- [r.TLS](https://pkg.go.dev/net/http#Request) **➜** [ctx.IsTLS()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.IsTLS)
|
||||
|
||||
@@ -167,12 +167,16 @@ func AppendBrotliBytes(dst, src []byte) []byte {
|
||||
// WriteUnbrotli writes unbrotlied p to w and returns the number of uncompressed
|
||||
// bytes written to w.
|
||||
func WriteUnbrotli(w io.Writer, p []byte) (int, error) {
|
||||
return writeUnbrotli(w, p, 0)
|
||||
}
|
||||
|
||||
func writeUnbrotli(w io.Writer, p []byte, maxBodySize int) (int, error) {
|
||||
r := &byteSliceReader{b: p}
|
||||
zr, err := acquireBrotliReader(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := copyZeroAlloc(w, zr)
|
||||
n, err := copyZeroAllocWithLimit(w, zr, maxBodySize)
|
||||
releaseBrotliReader(zr)
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
|
||||
+10
-2
@@ -212,12 +212,16 @@ func AppendGzipBytes(dst, src []byte) []byte {
|
||||
// WriteGunzip writes ungzipped p to w and returns the number of uncompressed
|
||||
// bytes written to w.
|
||||
func WriteGunzip(w io.Writer, p []byte) (int, error) {
|
||||
return writeGunzip(w, p, 0)
|
||||
}
|
||||
|
||||
func writeGunzip(w io.Writer, p []byte, maxBodySize int) (int, error) {
|
||||
r := &byteSliceReader{b: p}
|
||||
zr, err := acquireGzipReader(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := copyZeroAlloc(w, zr)
|
||||
n, err := copyZeroAllocWithLimit(w, zr, maxBodySize)
|
||||
releaseGzipReader(zr)
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
@@ -321,12 +325,16 @@ func AppendDeflateBytes(dst, src []byte) []byte {
|
||||
// WriteInflate writes inflated p to w and returns the number of uncompressed
|
||||
// bytes written to w.
|
||||
func WriteInflate(w io.Writer, p []byte) (int, error) {
|
||||
return writeInflate(w, p, 0)
|
||||
}
|
||||
|
||||
func writeInflate(w io.Writer, p []byte, maxBodySize int) (int, error) {
|
||||
r := &byteSliceReader{b: p}
|
||||
zr, err := acquireFlateReader(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := copyZeroAlloc(w, zr)
|
||||
n, err := copyZeroAllocWithLimit(w, zr, maxBodySize)
|
||||
releaseFlateReader(zr)
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
|
||||
@@ -492,7 +492,15 @@ var (
|
||||
// 'Content-Encoding: gzip' for reading un-gzipped body.
|
||||
// Use Body for reading gzipped request body.
|
||||
func (req *Request) BodyGunzip() ([]byte, error) {
|
||||
return gunzipData(req.Body())
|
||||
return req.BodyGunzipWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyGunzipWithLimit returns un-gzipped body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (req *Request) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return gunzipData(req.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
// BodyGunzip returns un-gzipped body data.
|
||||
@@ -501,12 +509,20 @@ func (req *Request) BodyGunzip() ([]byte, error) {
|
||||
// 'Content-Encoding: gzip' for reading un-gzipped body.
|
||||
// Use Body for reading gzipped response body.
|
||||
func (resp *Response) BodyGunzip() ([]byte, error) {
|
||||
return gunzipData(resp.Body())
|
||||
return resp.BodyGunzipWithLimit(0)
|
||||
}
|
||||
|
||||
func gunzipData(p []byte) ([]byte, error) {
|
||||
// BodyGunzipWithLimit returns un-gzipped body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (resp *Response) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return gunzipData(resp.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
func gunzipData(p []byte, maxBodySize int) ([]byte, error) {
|
||||
var bb bytebufferpool.ByteBuffer
|
||||
_, err := WriteGunzip(&bb, p)
|
||||
_, err := writeGunzip(&bb, p, maxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -519,7 +535,15 @@ func gunzipData(p []byte) ([]byte, error) {
|
||||
// 'Content-Encoding: br' for reading un-brotlied body.
|
||||
// Use Body for reading brotlied request body.
|
||||
func (req *Request) BodyUnbrotli() ([]byte, error) {
|
||||
return unBrotliData(req.Body())
|
||||
return req.BodyUnbrotliWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyUnbrotliWithLimit returns un-brotlied body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (req *Request) BodyUnbrotliWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return unBrotliData(req.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
// BodyUnbrotli returns un-brotlied body data.
|
||||
@@ -528,12 +552,20 @@ func (req *Request) BodyUnbrotli() ([]byte, error) {
|
||||
// 'Content-Encoding: br' for reading un-brotlied body.
|
||||
// Use Body for reading brotlied response body.
|
||||
func (resp *Response) BodyUnbrotli() ([]byte, error) {
|
||||
return unBrotliData(resp.Body())
|
||||
return resp.BodyUnbrotliWithLimit(0)
|
||||
}
|
||||
|
||||
func unBrotliData(p []byte) ([]byte, error) {
|
||||
// BodyUnbrotliWithLimit returns un-brotlied body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (resp *Response) BodyUnbrotliWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return unBrotliData(resp.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
func unBrotliData(p []byte, maxBodySize int) ([]byte, error) {
|
||||
var bb bytebufferpool.ByteBuffer
|
||||
_, err := WriteUnbrotli(&bb, p)
|
||||
_, err := writeUnbrotli(&bb, p, maxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -546,7 +578,15 @@ func unBrotliData(p []byte) ([]byte, error) {
|
||||
// 'Content-Encoding: deflate' for reading inflated request body.
|
||||
// Use Body for reading deflated request body.
|
||||
func (req *Request) BodyInflate() ([]byte, error) {
|
||||
return inflateData(req.Body())
|
||||
return req.BodyInflateWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyInflateWithLimit returns inflated body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (req *Request) BodyInflateWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return inflateData(req.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
// BodyInflate returns inflated body data.
|
||||
@@ -555,7 +595,15 @@ func (req *Request) BodyInflate() ([]byte, error) {
|
||||
// 'Content-Encoding: deflate' for reading inflated response body.
|
||||
// Use Body for reading deflated response body.
|
||||
func (resp *Response) BodyInflate() ([]byte, error) {
|
||||
return inflateData(resp.Body())
|
||||
return resp.BodyInflateWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyInflateWithLimit returns inflated body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (resp *Response) BodyInflateWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return inflateData(resp.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
func (ctx *RequestCtx) RequestBodyStream() io.Reader {
|
||||
@@ -563,25 +611,41 @@ func (ctx *RequestCtx) RequestBodyStream() io.Reader {
|
||||
}
|
||||
|
||||
func (req *Request) BodyUnzstd() ([]byte, error) {
|
||||
return unzstdData(req.Body())
|
||||
return req.BodyUnzstdWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyUnzstdWithLimit returns un-zstd body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (req *Request) BodyUnzstdWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return unzstdData(req.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
func (resp *Response) BodyUnzstd() ([]byte, error) {
|
||||
return unzstdData(resp.Body())
|
||||
return resp.BodyUnzstdWithLimit(0)
|
||||
}
|
||||
|
||||
func unzstdData(p []byte) ([]byte, error) {
|
||||
// BodyUnzstdWithLimit returns un-zstd body data and limits the size
|
||||
// of uncompressed body data to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (resp *Response) BodyUnzstdWithLimit(maxBodySize int) ([]byte, error) {
|
||||
return unzstdData(resp.Body(), maxBodySize)
|
||||
}
|
||||
|
||||
func unzstdData(p []byte, maxBodySize int) ([]byte, error) {
|
||||
var bb bytebufferpool.ByteBuffer
|
||||
_, err := WriteUnzstd(&bb, p)
|
||||
_, err := writeUnzstd(&bb, p, maxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bb.B, nil
|
||||
}
|
||||
|
||||
func inflateData(p []byte) ([]byte, error) {
|
||||
func inflateData(p []byte, maxBodySize int) ([]byte, error) {
|
||||
var bb bytebufferpool.ByteBuffer
|
||||
_, err := WriteInflate(&bb, p)
|
||||
_, err := writeInflate(&bb, p, maxBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -590,45 +654,63 @@ func inflateData(p []byte) ([]byte, error) {
|
||||
|
||||
var ErrContentEncodingUnsupported = errors.New("unsupported Content-Encoding")
|
||||
|
||||
// BodyUncompressed returns body data and if needed decompress it from gzip, deflate or Brotli.
|
||||
// BodyUncompressed returns body data and if needed decompresses it from gzip,
|
||||
// deflate, brotli or zstd.
|
||||
//
|
||||
// This method may be used if the response header contains
|
||||
// 'Content-Encoding' for reading uncompressed request body.
|
||||
// Use Body for reading the raw request body.
|
||||
func (req *Request) BodyUncompressed() ([]byte, error) {
|
||||
return req.BodyUncompressedWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyUncompressedWithLimit returns body data and if needed decompresses it from gzip,
|
||||
// deflate, brotli or zstd. The size of uncompressed data is limited to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (req *Request) BodyUncompressedWithLimit(maxBodySize int) ([]byte, error) {
|
||||
switch string(req.Header.ContentEncoding()) {
|
||||
case "":
|
||||
return req.Body(), nil
|
||||
case "deflate":
|
||||
return req.BodyInflate()
|
||||
return req.BodyInflateWithLimit(maxBodySize)
|
||||
case "gzip":
|
||||
return req.BodyGunzip()
|
||||
return req.BodyGunzipWithLimit(maxBodySize)
|
||||
case "br":
|
||||
return req.BodyUnbrotli()
|
||||
return req.BodyUnbrotliWithLimit(maxBodySize)
|
||||
case "zstd":
|
||||
return req.BodyUnzstd()
|
||||
return req.BodyUnzstdWithLimit(maxBodySize)
|
||||
default:
|
||||
return nil, ErrContentEncodingUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
// BodyUncompressed returns body data and if needed decompress it from gzip, deflate or Brotli.
|
||||
// BodyUncompressed returns body data and if needed decompresses it from gzip,
|
||||
// deflate, brotli or zstd.
|
||||
//
|
||||
// This method may be used if the response header contains
|
||||
// 'Content-Encoding' for reading uncompressed response body.
|
||||
// Use Body for reading the raw response body.
|
||||
func (resp *Response) BodyUncompressed() ([]byte, error) {
|
||||
return resp.BodyUncompressedWithLimit(0)
|
||||
}
|
||||
|
||||
// BodyUncompressedWithLimit returns body data and if needed decompresses it from gzip,
|
||||
// deflate, brotli or zstd. The size of uncompressed data is limited to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
func (resp *Response) BodyUncompressedWithLimit(maxBodySize int) ([]byte, error) {
|
||||
switch string(resp.Header.ContentEncoding()) {
|
||||
case "":
|
||||
return resp.Body(), nil
|
||||
case "deflate":
|
||||
return resp.BodyInflate()
|
||||
return resp.BodyInflateWithLimit(maxBodySize)
|
||||
case "gzip":
|
||||
return resp.BodyGunzip()
|
||||
return resp.BodyGunzipWithLimit(maxBodySize)
|
||||
case "br":
|
||||
return resp.BodyUnbrotli()
|
||||
return resp.BodyUnbrotliWithLimit(maxBodySize)
|
||||
case "zstd":
|
||||
return resp.BodyUnzstd()
|
||||
return resp.BodyUnzstdWithLimit(maxBodySize)
|
||||
default:
|
||||
return nil, ErrContentEncodingUnsupported
|
||||
}
|
||||
@@ -1009,9 +1091,26 @@ var ErrNoMultipartForm = errors.New("request Content-Type has bad boundary or is
|
||||
// Returns ErrNoMultipartForm if request's Content-Type
|
||||
// isn't 'multipart/form-data'.
|
||||
//
|
||||
// This method is equivalent to MultipartFormWithLimit(0), i.e. no body size
|
||||
// limit is applied during multipart parsing.
|
||||
//
|
||||
// RemoveMultipartFormFiles must be called after returned multipart form
|
||||
// is processed.
|
||||
func (req *Request) MultipartForm() (*multipart.Form, error) {
|
||||
return req.MultipartFormWithLimit(0)
|
||||
}
|
||||
|
||||
// MultipartFormWithLimit returns request's multipart form and limits the
|
||||
// read multipart body size to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
//
|
||||
// Returns ErrNoMultipartForm if request's Content-Type
|
||||
// isn't 'multipart/form-data'.
|
||||
//
|
||||
// RemoveMultipartFormFiles must be called after returned multipart form
|
||||
// is processed.
|
||||
func (req *Request) MultipartFormWithLimit(maxBodySize int) (*multipart.Form, error) {
|
||||
if req.multipartForm != nil {
|
||||
return req.multipartForm, nil
|
||||
}
|
||||
@@ -1026,30 +1125,46 @@ func (req *Request) MultipartForm() (*multipart.Form, error) {
|
||||
|
||||
if req.bodyStream != nil {
|
||||
bodyStream := req.bodyStream
|
||||
var lr *io.LimitedReader
|
||||
if bytes.Equal(ce, strGzip) {
|
||||
// Do not care about memory usage here.
|
||||
if bodyStream, err = gzip.NewReader(bodyStream); err != nil {
|
||||
return nil, fmt.Errorf("cannot gunzip request body: %w", err)
|
||||
}
|
||||
} else if len(ce) > 0 {
|
||||
return nil, fmt.Errorf("unsupported Content-Encoding: %q", ce)
|
||||
}
|
||||
if maxBodySize > 0 {
|
||||
lr = &io.LimitedReader{
|
||||
R: bodyStream,
|
||||
N: int64(maxBodySize) + 1,
|
||||
}
|
||||
bodyStream = lr
|
||||
}
|
||||
|
||||
mr := multipart.NewReader(bodyStream, req.multipartFormBoundary)
|
||||
req.multipartForm, err = mr.ReadForm(8 * 1024)
|
||||
if err != nil {
|
||||
if lr != nil && lr.N <= 0 {
|
||||
return nil, fmt.Errorf("cannot read multipart/form-data body: %w", ErrBodyTooLarge)
|
||||
}
|
||||
return nil, fmt.Errorf("cannot read multipart/form-data body: %w", err)
|
||||
}
|
||||
if lr != nil && lr.N <= 0 {
|
||||
req.RemoveMultipartFormFiles()
|
||||
return nil, fmt.Errorf("cannot read multipart/form-data body: %w", ErrBodyTooLarge)
|
||||
}
|
||||
} else {
|
||||
body := req.bodyBytes()
|
||||
if bytes.Equal(ce, strGzip) {
|
||||
// Do not care about memory usage here.
|
||||
if body, err = AppendGunzipBytes(nil, body); err != nil {
|
||||
if body, err = gunzipData(body, maxBodySize); err != nil {
|
||||
return nil, fmt.Errorf("cannot gunzip request body: %w", err)
|
||||
}
|
||||
} else if len(ce) > 0 {
|
||||
return nil, fmt.Errorf("unsupported Content-Encoding: %q", ce)
|
||||
}
|
||||
if maxBodySize > 0 && len(body) > maxBodySize {
|
||||
return nil, fmt.Errorf("cannot read multipart/form-data body: %w", ErrBodyTooLarge)
|
||||
}
|
||||
|
||||
req.multipartForm, err = readMultipartForm(bytes.NewReader(body), req.multipartFormBoundary, len(body), len(body))
|
||||
if err != nil {
|
||||
@@ -2464,6 +2579,25 @@ func writeChunk(w *bufio.Writer, b []byte) error {
|
||||
// the given limit.
|
||||
var ErrBodyTooLarge = errors.New("body size exceeds the given limit")
|
||||
|
||||
func copyZeroAllocWithLimit(w io.Writer, r io.Reader, maxBodySize int) (int64, error) {
|
||||
if maxBodySize <= 0 {
|
||||
return copyZeroAlloc(w, r)
|
||||
}
|
||||
|
||||
lr := &io.LimitedReader{
|
||||
R: r,
|
||||
N: int64(maxBodySize) + 1,
|
||||
}
|
||||
n, err := copyZeroAlloc(w, lr)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if lr.N <= 0 {
|
||||
return n, ErrBodyTooLarge
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func readBody(r *bufio.Reader, contentLength, maxBodySize int, dst []byte) ([]byte, error) {
|
||||
if maxBodySize > 0 && contentLength > maxBodySize {
|
||||
return dst, ErrBodyTooLarge
|
||||
|
||||
+125
@@ -448,6 +448,131 @@ func TestResponseBodyUncompressed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyDecodeWithLimitTooLarge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
body := bytes.Repeat([]byte("a"), 2*1024)
|
||||
maxBodySize := 1024
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
encoding string
|
||||
encode func([]byte) []byte
|
||||
}{
|
||||
{
|
||||
name: "gzip",
|
||||
encoding: "gzip",
|
||||
encode: func(src []byte) []byte {
|
||||
return AppendGzipBytes(nil, src)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deflate",
|
||||
encoding: "deflate",
|
||||
encode: func(src []byte) []byte {
|
||||
return AppendDeflateBytes(nil, src)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "brotli",
|
||||
encoding: "br",
|
||||
encode: func(src []byte) []byte {
|
||||
return AppendBrotliBytes(nil, src)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zstd",
|
||||
encoding: "zstd",
|
||||
encode: func(src []byte) []byte {
|
||||
return AppendZstdBytes(nil, src)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name+"_request_uncompressed", func(t *testing.T) {
|
||||
var req Request
|
||||
req.Header.SetContentEncoding(testCase.encoding)
|
||||
req.SetBodyRaw(testCase.encode(body))
|
||||
_, err := req.BodyUncompressedWithLimit(maxBodySize)
|
||||
if !errors.Is(err, ErrBodyTooLarge) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run(testCase.name+"_response_uncompressed", func(t *testing.T) {
|
||||
var resp Response
|
||||
resp.Header.SetContentEncoding(testCase.encoding)
|
||||
resp.SetBodyRaw(testCase.encode(body))
|
||||
_, err := resp.BodyUncompressedWithLimit(maxBodySize)
|
||||
if !errors.Is(err, ErrBodyTooLarge) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMultipartFormWithLimitGzip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var formBodyBuffer bytes.Buffer
|
||||
mw := multipart.NewWriter(&formBodyBuffer)
|
||||
if err := mw.WriteField("foo", strings.Repeat("a", 8*1024)); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
boundary := mw.Boundary()
|
||||
if err := mw.Close(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
formBody := formBodyBuffer.Bytes()
|
||||
gzippedBody := AppendGzipBytes(nil, formBody)
|
||||
|
||||
t.Run("buffered_too_large", func(t *testing.T) {
|
||||
var req Request
|
||||
req.Header.SetMultipartFormBoundary(boundary)
|
||||
req.Header.SetContentEncoding("gzip")
|
||||
req.SetBodyRaw(gzippedBody)
|
||||
|
||||
_, err := req.MultipartFormWithLimit(len(formBody) - 1)
|
||||
if !errors.Is(err, ErrBodyTooLarge) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("streamed_too_large", func(t *testing.T) {
|
||||
var req Request
|
||||
req.Header.SetMultipartFormBoundary(boundary)
|
||||
req.Header.SetContentEncoding("gzip")
|
||||
req.SetBodyStream(bytes.NewReader(gzippedBody), len(gzippedBody))
|
||||
|
||||
_, err := req.MultipartFormWithLimit(len(formBody) - 1)
|
||||
if !errors.Is(err, ErrBodyTooLarge) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("buffered_success", func(t *testing.T) {
|
||||
var req Request
|
||||
req.Header.SetMultipartFormBoundary(boundary)
|
||||
req.Header.SetContentEncoding("gzip")
|
||||
req.SetBodyRaw(gzippedBody)
|
||||
|
||||
f, err := req.MultipartFormWithLimit(len(formBody))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer req.RemoveMultipartFormFiles()
|
||||
|
||||
vv := f.Value["foo"]
|
||||
if len(vv) != 1 {
|
||||
t.Fatalf("unexpected values count: %d", len(vv))
|
||||
}
|
||||
if vv[0] != strings.Repeat("a", 8*1024) {
|
||||
t.Fatalf("unexpected value length: %d", len(vv[0]))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestResponseSwapBodySerial(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -210,10 +210,14 @@ type Server struct {
|
||||
// instead.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// FormValueFunc, which is used by RequestCtx.FormValue and support for customizing
|
||||
// the behaviour of the RequestCtx.FormValue function.
|
||||
// FormValueFunc customizes the behavior of RequestCtx.FormValue.
|
||||
//
|
||||
// NetHttpFormValueFunc gives a FormValueFunc func implementation that is consistent with net/http.
|
||||
// For multipart requests, the default FormValue path calls MultipartForm()
|
||||
// without a body size limit. If you need a limit for multipart parsing,
|
||||
// provide a custom FormValueFunc and call MultipartFormWithLimit() there.
|
||||
//
|
||||
// NetHttpFormValueFunc gives a FormValueFunc implementation that is
|
||||
// consistent with net/http.
|
||||
FormValueFunc FormValueFunc
|
||||
|
||||
nextProtos map[string]ServeHandler
|
||||
@@ -1077,6 +1081,9 @@ func (ctx *RequestCtx) PostArgs() *Args {
|
||||
// Returns ErrNoMultipartForm if request's content-type
|
||||
// isn't 'multipart/form-data'.
|
||||
//
|
||||
// This method is equivalent to MultipartFormWithLimit(0), i.e. no body size
|
||||
// limit is applied during multipart parsing.
|
||||
//
|
||||
// All uploaded temporary files are automatically deleted after
|
||||
// returning from RequestHandler. Either move or copy uploaded files
|
||||
// into new place if you want retaining them.
|
||||
@@ -1090,6 +1097,17 @@ func (ctx *RequestCtx) MultipartForm() (*multipart.Form, error) {
|
||||
return ctx.Request.MultipartForm()
|
||||
}
|
||||
|
||||
// MultipartFormWithLimit returns request's multipart form and limits the read
|
||||
// multipart body size to maxBodySize bytes.
|
||||
//
|
||||
// If maxBodySize <= 0, then no limit is applied.
|
||||
//
|
||||
// Call this method before FormValue/FormFile if you need a limit for
|
||||
// multipart parsing.
|
||||
func (ctx *RequestCtx) MultipartFormWithLimit(maxBodySize int) (*multipart.Form, error) {
|
||||
return ctx.Request.MultipartFormWithLimit(maxBodySize)
|
||||
}
|
||||
|
||||
// FormFile returns uploaded file associated with the given multipart form key.
|
||||
//
|
||||
// The file is automatically deleted after returning from RequestHandler,
|
||||
@@ -1098,6 +1116,9 @@ func (ctx *RequestCtx) MultipartForm() (*multipart.Form, error) {
|
||||
// Use SaveMultipartFile function for permanently saving uploaded file.
|
||||
//
|
||||
// The returned file header is valid until your request handler returns.
|
||||
//
|
||||
// For multipart requests with untrusted input, call MultipartFormWithLimit()
|
||||
// before FormFile.
|
||||
func (ctx *RequestCtx) FormFile(key string) (*multipart.FileHeader, error) {
|
||||
mf, err := ctx.MultipartForm()
|
||||
if err != nil {
|
||||
@@ -1182,6 +1203,10 @@ func SaveMultipartFile(fh *multipart.FileHeader, path string) (err error) {
|
||||
// - FormFile for obtaining uploaded files.
|
||||
//
|
||||
// The returned value is valid until your request handler returns.
|
||||
//
|
||||
// For multipart requests with untrusted input, either call
|
||||
// MultipartFormWithLimit() before FormValue or provide a custom
|
||||
// Server.FormValueFunc that uses MultipartFormWithLimit().
|
||||
func (ctx *RequestCtx) FormValue(key string) []byte {
|
||||
if ctx.formValueFunc != nil {
|
||||
return ctx.formValueFunc(ctx, key)
|
||||
@@ -1189,6 +1214,7 @@ func (ctx *RequestCtx) FormValue(key string) []byte {
|
||||
return defaultFormValue(ctx, key)
|
||||
}
|
||||
|
||||
// FormValueFunc customizes how RequestCtx.FormValue resolves a value.
|
||||
type FormValueFunc func(*RequestCtx, string) []byte
|
||||
|
||||
var (
|
||||
|
||||
@@ -139,12 +139,16 @@ func AppendZstdBytes(dst, src []byte) []byte {
|
||||
// WriteUnzstd writes unzstd p to w and returns the number of uncompressed
|
||||
// bytes written to w.
|
||||
func WriteUnzstd(w io.Writer, p []byte) (int, error) {
|
||||
return writeUnzstd(w, p, 0)
|
||||
}
|
||||
|
||||
func writeUnzstd(w io.Writer, p []byte, maxBodySize int) (int, error) {
|
||||
r := &byteSliceReader{b: p}
|
||||
zr, err := acquireZstdReader(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := copyZeroAlloc(w, zr)
|
||||
n, err := copyZeroAllocWithLimit(w, zr, maxBodySize)
|
||||
releaseZstdReader(zr)
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
|
||||
Reference in New Issue
Block a user