mirror of
https://github.com/aykhans/sarin.git
synced 2026-10-17 14:53:52 +00:00
feat: limit response body sizes
fasthttp allocates the body from the declared Content-Length before reading it, so a server could exhaust memory by declaring a size it never sends. The new max response body config bounds every response, defaults to 10MiB and accepts sizes like 10MiB or 2MB. Scripts get the same as a maxBodySize option on http().
This commit is contained in:
17 files changed
+452
-147
No files matched your search
@@ -21,6 +21,7 @@ type HTTPRequest struct {
|
||||
Timeout time.Duration // zero means the doer's default timeout
|
||||
Insecure bool
|
||||
MaxRedirects int
|
||||
MaxBodySize int64 // zero means no limit
|
||||
}
|
||||
|
||||
// HTTPResponse is the response returned to a script by the http.* functions.
|
||||
@@ -66,6 +67,9 @@ const httpDefaultMethod = http.MethodGet
|
||||
// httpMaxRedirectsLimit is the highest maxRedirects a script may ask for.
|
||||
const httpMaxRedirectsLimit = 100
|
||||
|
||||
// httpMaxBodySizeLimit is the highest maxBodySize a script may ask for, 1 TiB.
|
||||
const httpMaxBodySizeLimit int64 = 1 << 40
|
||||
|
||||
// Option keys accepted by the http function.
|
||||
const (
|
||||
httpOptionMethod = "method"
|
||||
@@ -76,6 +80,7 @@ const (
|
||||
httpOptionTimeout = "timeout"
|
||||
httpOptionInsecure = "insecure"
|
||||
httpOptionMaxRedirects = "maxRedirects"
|
||||
httpOptionMaxBodySize = "maxBodySize"
|
||||
)
|
||||
|
||||
// httpBridge connects http to the doer. Calls only work while transform runs.
|
||||
@@ -118,6 +123,22 @@ func parseHTTPMaxRedirects(count float64) (int, error) {
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// parseHTTPMaxBodySize checks that the body limit is a non-negative whole number.
|
||||
// It can return the following errors:
|
||||
// - types.ScriptHTTPOptionError
|
||||
func parseHTTPMaxBodySize(size float64) (int64, error) {
|
||||
if math.IsNaN(size) || size < 0 || size > float64(httpMaxBodySizeLimit) || size != math.Trunc(size) {
|
||||
return 0, types.NewScriptHTTPOptionError(
|
||||
httpOptionMaxBodySize,
|
||||
types.NewScriptTypeError(
|
||||
"a whole number of bytes between 0 and "+strconv.FormatInt(httpMaxBodySizeLimit, 10),
|
||||
strconv.FormatFloat(size, 'g', -1, 64),
|
||||
),
|
||||
)
|
||||
}
|
||||
return int64(size), nil
|
||||
}
|
||||
|
||||
// parseHTTPTimeout parses a Go duration string such as "500ms" or "2s".
|
||||
// It can return the following errors:
|
||||
// - types.ScriptHTTPOptionError
|
||||
|
||||
@@ -117,6 +117,16 @@ func (e *JsEngine) parseHTTPOptions(opts *goja.Object, req *HTTPRequest) error {
|
||||
if req.MaxRedirects, err = parseHTTPMaxRedirects(count); err != nil {
|
||||
return err
|
||||
}
|
||||
case httpOptionMaxBodySize:
|
||||
size, ok := numberValue(value)
|
||||
if !ok {
|
||||
return types.NewScriptHTTPOptionError(name, types.NewScriptTypeError("number", jsTypeName(value)))
|
||||
}
|
||||
|
||||
var err error
|
||||
if req.MaxBodySize, err = parseHTTPMaxBodySize(size); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return types.NewScriptHTTPOptionError(name, types.ErrScriptHTTPUnknownOption)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,13 @@ func (e *LuaEngine) parseHTTPOptions(opts *lua.LTable, req *HTTPRequest) error {
|
||||
return
|
||||
}
|
||||
req.MaxRedirects, err = parseHTTPMaxRedirects(float64(count))
|
||||
case httpOptionMaxBodySize:
|
||||
size, ok := v.(lua.LNumber)
|
||||
if !ok {
|
||||
err = types.NewScriptHTTPOptionError(name, types.NewScriptTypeError("number", v.Type().String()))
|
||||
return
|
||||
}
|
||||
req.MaxBodySize, err = parseHTTPMaxBodySize(float64(size))
|
||||
default:
|
||||
err = types.NewScriptHTTPOptionError(name, types.ErrScriptHTTPUnknownOption)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user