filebrowser/http/quota.go
Jon_K 0a3d1642f7 Add user quota management system
Introduces comprehensive quota management with real-time usage tracking, frontend components, backend API endpoints, and TUS upload integration for storage limit enforcement.
2026-01-03 01:07:26 -05:00

66 lines
2.0 KiB
Go

package fbhttp
import (
"net/http"
"strconv"
fberrors "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/users"
)
// withQuotaCheck wraps a handler function with quota validation middleware.
// It checks if the user's quota would be exceeded by the operation before allowing it.
func withQuotaCheck(fn handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
// Skip quota check if user has no quota limit or quota is not enforced
if d.user.QuotaLimit == 0 || !d.user.EnforceQuota {
return fn(w, r, d)
}
// Get the file size from the request
fileSize, err := getFileSize(r)
if err != nil {
// If we can't determine file size, allow the operation
// (it will be checked during actual write)
return fn(w, r, d)
}
// Check if the operation would exceed quota
exceeded, currentUsage, err := users.CheckQuotaExceeded(d.user, fileSize)
if err != nil {
return http.StatusInternalServerError, err
}
if exceeded {
// Return 413 Payload Too Large with quota information
w.Header().Set("X-Quota-Limit", users.FormatQuotaDisplay(d.user.QuotaLimit))
w.Header().Set("X-Quota-Used", users.FormatQuotaDisplay(currentUsage))
return http.StatusRequestEntityTooLarge, fberrors.ErrQuotaExceeded
}
// Quota check passed, proceed with the operation
return fn(w, r, d)
}
}
// getFileSize extracts the file size from the HTTP request.
// It checks the Upload-Length header (for TUS uploads) first, then Content-Length.
func getFileSize(r *http.Request) (int64, error) {
// Try to get size from Upload-Length header (TUS protocol)
if uploadLength := r.Header.Get("Upload-Length"); uploadLength != "" {
size, err := strconv.ParseInt(uploadLength, 10, 64)
if err == nil && size > 0 {
return size, nil
}
}
// Try to get size from Content-Length header
if r.ContentLength > 0 {
return r.ContentLength, nil
}
// If neither header is available, return 0
// This might happen with chunked transfer encoding
return 0, nil
}