mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
# Conflicts: # apis/record_auth_with_otp.go # core/base_backup.go # go.mod # go.sum # plugins/jsvm/internal/types/generated/types.d.ts # tools/router/rereadable_read_closer_test.go # ui/dist/assets/AuthMethodsDocs-C2aSYmNv.js # ui/dist/assets/AuthRefreshDocs-BapWJLRK.js # ui/dist/assets/AuthWithOAuth2Docs-Ck_pnbL8.js # ui/dist/assets/AuthWithOtpDocs-D8uWq_C5.js # ui/dist/assets/AuthWithPasswordDocs-BQyMhAeO.js # ui/dist/assets/BatchApiDocs-rvUuWLZE.js # ui/dist/assets/CodeEditor-DrMyw8z_.js # ui/dist/assets/CreateApiDocs-XWzcGuMB.js # ui/dist/assets/DeleteApiDocs-CScE0aw3.js # ui/dist/assets/EmailChangeDocs-CxOtrB4s.js # ui/dist/assets/FieldsQueryParam-BzNUQIb5.js # ui/dist/assets/FilterAutocompleteInput-Dm2G2coC.js # ui/dist/assets/Leaflet-DyumpnOc.js # ui/dist/assets/ListApiDocs-CdOzMZnr.js # ui/dist/assets/PageInstaller-vIXdffAJ.js # ui/dist/assets/PageOAuth2RedirectFailure-ClpyaW_B.js # ui/dist/assets/PageOAuth2RedirectSuccess-BLZml1eK.js # ui/dist/assets/PageRecordConfirmEmailChange-Cm3O2YD1.js # ui/dist/assets/PageRecordConfirmPasswordReset-D0XfuL45.js # ui/dist/assets/PageRecordConfirmVerification-CYyNYH_v.js # ui/dist/assets/PageSuperuserConfirmPasswordReset-BvlQ0_9t.js # ui/dist/assets/PageSuperuserRequestPasswordReset-gUPMclJZ.js # ui/dist/assets/PasswordResetDocs-DMF1vqE0.js # ui/dist/assets/RealtimeApiDocs-CNVij_C2.js # ui/dist/assets/UpdateApiDocs-CwQ3ZhFQ.js # ui/dist/assets/VerificationDocs-DUeWWjrS.js # ui/dist/assets/ViewApiDocs-gocWxbTE.js # ui/dist/assets/index-CWGqJiyp.js # ui/dist/index.html
133 lines
3.1 KiB
Go
133 lines
3.1 KiB
Go
package apis
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tools/hook"
|
|
"github.com/hanzoai/base/tools/router"
|
|
)
|
|
|
|
var ErrRequestEntityTooLarge = router.NewApiError(http.StatusRequestEntityTooLarge, "Request entity too large", nil)
|
|
|
|
const DefaultMaxBodySize int64 = 32 << 20
|
|
|
|
const (
|
|
DefaultBodyLimitMiddlewareId = "baseBodyLimit"
|
|
DefaultBodyLimitMiddlewarePriority = DefaultRateLimitMiddlewarePriority + 10
|
|
)
|
|
|
|
// BodyLimit returns a middleware handler that changes the default request body size limit.
|
|
//
|
|
// If limitBytes <= 0, no limit is applied.
|
|
//
|
|
// Otherwise, if the request body size exceeds the configured limitBytes,
|
|
// it sends 413 error response.
|
|
func BodyLimit(limitBytes int64) *hook.Handler[*core.RequestEvent] {
|
|
return &hook.Handler[*core.RequestEvent]{
|
|
Id: DefaultBodyLimitMiddlewareId,
|
|
Priority: DefaultBodyLimitMiddlewarePriority,
|
|
Func: func(e *core.RequestEvent) error {
|
|
err := applyBodyLimit(e, limitBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return e.Next()
|
|
},
|
|
}
|
|
}
|
|
|
|
func dynamicCollectionBodyLimit(collectionPathParam string) *hook.Handler[*core.RequestEvent] {
|
|
if collectionPathParam == "" {
|
|
collectionPathParam = "collection"
|
|
}
|
|
|
|
return &hook.Handler[*core.RequestEvent]{
|
|
Id: DefaultBodyLimitMiddlewareId,
|
|
Priority: DefaultBodyLimitMiddlewarePriority,
|
|
Func: func(e *core.RequestEvent) error {
|
|
collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue(collectionPathParam))
|
|
if err != nil {
|
|
return e.NotFoundError("Missing or invalid collection context.", err)
|
|
}
|
|
|
|
limitBytes := DefaultMaxBodySize
|
|
if !collection.IsView() {
|
|
for _, f := range collection.Fields {
|
|
if calc, ok := f.(core.MaxBodySizeCalculator); ok {
|
|
limitBytes += calc.CalculateMaxBodySize()
|
|
}
|
|
}
|
|
}
|
|
|
|
err = applyBodyLimit(e, limitBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return e.Next()
|
|
},
|
|
}
|
|
}
|
|
|
|
func applyBodyLimit(e *core.RequestEvent, limitBytes int64) error {
|
|
// no limit
|
|
if limitBytes <= 0 {
|
|
return nil
|
|
}
|
|
|
|
// optimistically check the submitted request content length
|
|
if e.Request.ContentLength > limitBytes {
|
|
return ErrRequestEntityTooLarge
|
|
}
|
|
|
|
// replace the request body
|
|
//
|
|
// note: we don't use sync.Pool since the size of the elements could vary too much
|
|
// and it might not be efficient (see https://github.com/golang/go/issues/23199)
|
|
e.Request.Body = &limitedReader{ReadCloser: e.Request.Body, limit: limitBytes}
|
|
|
|
return nil
|
|
}
|
|
|
|
type limitedReader struct {
|
|
io.ReadCloser
|
|
limit int64
|
|
totalRead int64
|
|
}
|
|
|
|
func (r *limitedReader) Read(b []byte) (int, error) {
|
|
n, err := r.ReadCloser.Read(b)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
r.totalRead += int64(n)
|
|
if r.totalRead > r.limit {
|
|
return n, ErrRequestEntityTooLarge
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
// explicit casts to ensure that the main struct methods will be invoked
|
|
// (extra precautions in case of nested interface wrapping erasure)
|
|
// ---
|
|
|
|
func (r *limitedReader) Reread() {
|
|
rereader, ok := r.ReadCloser.(router.Rereader)
|
|
if ok {
|
|
rereader.Reread()
|
|
}
|
|
}
|
|
|
|
func (r *limitedReader) Close() error {
|
|
closer, ok := r.ReadCloser.(io.Closer)
|
|
if ok {
|
|
return closer.Close()
|
|
}
|
|
return nil
|
|
}
|