diff --git a/api/r0/public_config.go b/api/r0/public_config.go index 66d313f2..d35dbb37 100644 --- a/api/r0/public_config.go +++ b/api/r0/public_config.go @@ -5,6 +5,7 @@ import ( "github.com/turt2live/matrix-media-repo/api" "github.com/turt2live/matrix-media-repo/common/rcontext" + "github.com/turt2live/matrix-media-repo/quota" ) type PublicConfigResponse struct { @@ -12,15 +13,7 @@ type PublicConfigResponse struct { } func PublicConfig(r *http.Request, rctx rcontext.RequestContext, user api.UserInfo) interface{} { - uploadSize := rctx.Config.Uploads.ReportedMaxSizeBytes - if uploadSize == 0 { - uploadSize = rctx.Config.Uploads.MaxSizeBytes - } - - if uploadSize < 0 { - uploadSize = 0 // invokes the omitEmpty - } - + uploadSize := quota.GetUserUploadMaxSizeBytes(rctx, user.UserId) return &PublicConfigResponse{ UploadMaxSize: uploadSize, } diff --git a/api/r0/upload.go b/api/r0/upload.go index 63b0170b..089466c6 100644 --- a/api/r0/upload.go +++ b/api/r0/upload.go @@ -1,12 +1,13 @@ package r0 import ( - "github.com/getsentry/sentry-go" "io" "io/ioutil" "net/http" "path/filepath" + "github.com/getsentry/sentry-go" + "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/api" "github.com/turt2live/matrix-media-repo/common" @@ -35,7 +36,7 @@ func UploadMedia(r *http.Request, rctx rcontext.RequestContext, user api.UserInf contentType = "application/octet-stream" // binary } - if upload_controller.IsRequestTooLarge(r.ContentLength, r.Header.Get("Content-Length"), rctx) { + if upload_controller.IsRequestTooLarge(r.ContentLength, r.Header.Get("Content-Length"), rctx, user.UserId) { io.Copy(ioutil.Discard, r.Body) // Ditch the entire request return api.RequestTooLarge() } diff --git a/common/config/models_domain.go b/common/config/models_domain.go index 32427247..3887638c 100644 --- a/common/config/models_domain.go +++ b/common/config/models_domain.go @@ -17,10 +17,11 @@ type QuotasConfig struct { } type UploadsConfig struct { - MaxSizeBytes int64 `yaml:"maxBytes"` - MinSizeBytes int64 `yaml:"minBytes"` - ReportedMaxSizeBytes int64 `yaml:"reportedMaxBytes"` - Quota QuotasConfig `yaml:"quotas"` + MaxSizeBytes int64 `yaml:"maxBytes"` + UsersMaxSizeBytes []QuotaUserConfig `yaml:"quotas,flow"` + MinSizeBytes int64 `yaml:"minBytes"` + ReportedMaxSizeBytes int64 `yaml:"reportedMaxBytes"` + Quota QuotasConfig `yaml:"quotas"` } type DatastoreConfig struct { diff --git a/config.sample.yaml b/config.sample.yaml index fa49e74f..08bdc8c2 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -214,6 +214,13 @@ uploads: # The maximum individual file size a user can upload. maxBytes: 104857600 # 100MB default, 0 to disable + # The maximum individual file size a user can upload. If a user does not match + # any of the globs in here, the global `maxBytes` value is used. + # The first matching glob is always used. + usersMaxBytes: + - glob: "@*:*" # Affect all users. Use asterisks (*) to match any character. + maxBytes: 104857600 # maxBytes, by default. 0 to disable + # The minimum number of bytes to let people upload. This is recommended to be non-zero to # ensure that the "cost" of running the media repo is worthwhile - small file uploads tend # to waste more CPU and database resources than small files, thus a default of 100 bytes diff --git a/controllers/preview_controller/preview_resource_handler.go b/controllers/preview_controller/preview_resource_handler.go index 86c42b9d..50995ca2 100644 --- a/controllers/preview_controller/preview_resource_handler.go +++ b/controllers/preview_controller/preview_resource_handler.go @@ -2,9 +2,10 @@ package preview_controller import ( "fmt" - "github.com/getsentry/sentry-go" "sync" + "github.com/getsentry/sentry-go" + "github.com/disintegration/imaging" "github.com/sirupsen/logrus" "github.com/turt2live/matrix-media-repo/common" @@ -129,7 +130,7 @@ func urlPreviewWorkFn(request *resource_handler.WorkRequest) (resp *urlPreviewRe } // Store the thumbnail, if there is one - if preview.Image != nil && !upload_controller.IsRequestTooLarge(preview.Image.ContentLength, preview.Image.ContentLengthHeader, ctx) { + if preview.Image != nil && !upload_controller.IsRequestTooLarge(preview.Image.ContentLength, preview.Image.ContentLengthHeader, ctx, info.forUserId) { contentLength := upload_controller.EstimateContentLength(preview.Image.ContentLength, preview.Image.ContentLengthHeader) // UploadMedia will close the read stream for the thumbnail and dedupe the image diff --git a/controllers/upload_controller/upload_controller.go b/controllers/upload_controller/upload_controller.go index 7936faa7..d9ea42b4 100644 --- a/controllers/upload_controller/upload_controller.go +++ b/controllers/upload_controller/upload_controller.go @@ -2,12 +2,13 @@ package upload_controller import ( "fmt" - "github.com/getsentry/sentry-go" "io" "io/ioutil" "strconv" "time" + "github.com/getsentry/sentry-go" + "github.com/patrickmn/go-cache" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -15,6 +16,7 @@ import ( "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/internal_cache" "github.com/turt2live/matrix-media-repo/plugins" + "github.com/turt2live/matrix-media-repo/quota" "github.com/turt2live/matrix-media-repo/storage" "github.com/turt2live/matrix-media-repo/storage/datastore" "github.com/turt2live/matrix-media-repo/types" @@ -32,12 +34,13 @@ type AlreadyUploadedFile struct { ObjectInfo *types.ObjectInfo } -func IsRequestTooLarge(contentLength int64, contentLengthHeader string, ctx rcontext.RequestContext) bool { - if ctx.Config.Uploads.MaxSizeBytes <= 0 { +func IsRequestTooLarge(contentLength int64, contentLengthHeader string, ctx rcontext.RequestContext, userId string) bool { + maxSize := quota.GetUserUploadMaxSizeBytes(ctx, userId) + if maxSize <= 0 { return false } if contentLength >= 0 { - return contentLength > ctx.Config.Uploads.MaxSizeBytes + return contentLength > maxSize } if contentLengthHeader != "" { parsed, err := strconv.ParseInt(contentLengthHeader, 10, 64) @@ -47,7 +50,7 @@ func IsRequestTooLarge(contentLength int64, contentLengthHeader string, ctx rcon return true // Invalid header } - return parsed > ctx.Config.Uploads.MaxSizeBytes + return parsed > maxSize } return false // We can only assume diff --git a/quota/quota.go b/quota/quota.go index 1a8649ff..fd52887e 100644 --- a/quota/quota.go +++ b/quota/quota.go @@ -33,3 +33,18 @@ func IsUserWithinQuota(ctx rcontext.RequestContext, userId string) (bool, error) return true, nil // no rules == no quota } + +func GetUserUploadMaxSizeBytes(ctx rcontext.RequestContext, userId string) int64 { + for _, u := range ctx.Config.Uploads.UsersMaxSizeBytes { + if glob.Glob(u.Glob, userId) { + if u.MaxBytes == 0 { + return ctx.Config.Uploads.MaxSizeBytes + } else if u.MaxBytes < 0 { + return 0 + } else { + return u.MaxBytes + } + } + } + +}