diff --git a/server/errors.go b/server/errors.go index 6051a3f5..3b754383 100644 --- a/server/errors.go +++ b/server/errors.go @@ -42,7 +42,7 @@ var ( errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""} errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"} - errHTTPEntityTooLargeAttachmentTooLarge = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", ""} + errHTTPEntityTooLargeAttachmentTooLarge = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"} diff --git a/server/server_test.go b/server/server_test.go index ff1a9000..c0b41c03 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1024,9 +1024,9 @@ func TestServer_PublishAttachmentTooLargeContentLength(t *testing.T) { "Content-Length": "20000000", }) err := toHTTPError(t, response.Body.String()) - require.Equal(t, 400, response.Code) - require.Equal(t, 400, err.HTTPCode) - require.Equal(t, 40012, err.Code) + require.Equal(t, 413, response.Code) + require.Equal(t, 413, err.HTTPCode) + require.Equal(t, 41301, err.Code) } func TestServer_PublishAttachmentTooLargeBodyAttachmentFileSizeLimit(t *testing.T) { @@ -1036,9 +1036,9 @@ func TestServer_PublishAttachmentTooLargeBodyAttachmentFileSizeLimit(t *testing. s := newTestServer(t, c) response := request(t, s, "PUT", "/mytopic", content, nil) err := toHTTPError(t, response.Body.String()) - require.Equal(t, 400, response.Code) - require.Equal(t, 400, err.HTTPCode) - require.Equal(t, 40012, err.Code) + require.Equal(t, 413, response.Code) + require.Equal(t, 413, err.HTTPCode) + require.Equal(t, 41301, err.Code) } func TestServer_PublishAttachmentExpiryBeforeDelivery(t *testing.T) { @@ -1068,9 +1068,9 @@ func TestServer_PublishAttachmentTooLargeBodyVisitorAttachmentTotalSizeLimit(t * content := util.RandomString(5001) // 5000+5001 > , see below response = request(t, s, "PUT", "/mytopic", content, nil) err := toHTTPError(t, response.Body.String()) - require.Equal(t, 400, response.Code) - require.Equal(t, 400, err.HTTPCode) - require.Equal(t, 40012, err.Code) + require.Equal(t, 413, response.Code) + require.Equal(t, 413, err.HTTPCode) + require.Equal(t, 41301, err.Code) } func TestServer_PublishAttachmentAndPrune(t *testing.T) { @@ -1144,8 +1144,32 @@ func TestServer_PublishAttachmentBandwidthLimitUploadOnly(t *testing.T) { // And a failed one response := request(t, s, "PUT", "/mytopic", content, nil) err := toHTTPError(t, response.Body.String()) - require.Equal(t, 400, response.Code) - require.Equal(t, 40012, err.Code) + require.Equal(t, 413, response.Code) + require.Equal(t, 41301, err.Code) +} + +func TestServer_PublishAttachmentUserStats(t *testing.T) { + content := util.RandomString(4999) // > 4096 + + c := newTestConfig(t) + c.AttachmentFileSizeLimit = 5000 + c.VisitorAttachmentTotalSizeLimit = 6000 + s := newTestServer(t, c) + + // Upload one attachment + response := request(t, s, "PUT", "/mytopic", content, nil) + msg := toMessage(t, response.Body.String()) + require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/") + + // User stats + response = request(t, s, "GET", "/user/stats", "", nil) + require.Equal(t, 200, response.Code) + var stats visitorStats + require.Nil(t, json.NewDecoder(strings.NewReader(response.Body.String())).Decode(&stats)) + require.Equal(t, int64(5000), stats.AttachmentFileSizeLimit) + require.Equal(t, int64(6000), stats.VisitorAttachmentBytesTotal) + require.Equal(t, int64(4999), stats.VisitorAttachmentBytesUsed) + require.Equal(t, int64(1001), stats.VisitorAttachmentBytesRemaining) } func newTestConfig(t *testing.T) *Config { diff --git a/util/limit.go b/util/limit.go index 36d8f66f..8df768ad 100644 --- a/util/limit.go +++ b/util/limit.go @@ -15,10 +15,6 @@ var ErrLimitReached = errors.New("limit reached") type Limiter interface { // Allow adds n to the limiters internal value, or returns ErrLimitReached if the limit has been reached Allow(n int64) error - - // Remaining returns the remaining count until the limit is reached; may return -1 if the implementation - // does not support this operation. - Remaining() int64 } // FixedLimiter is a helper that allows adding values up to a well-defined limit. Once the limit is reached @@ -48,13 +44,6 @@ func (l *FixedLimiter) Allow(n int64) error { return nil } -// Remaining returns the remaining count until the limit is reached -func (l *FixedLimiter) Remaining() int64 { - l.mu.Lock() - defer l.mu.Unlock() - return l.limit - l.value -} - // RateLimiter is a Limiter that wraps a rate.Limiter, allowing a floating time-based limit. type RateLimiter struct { limiter *rate.Limiter @@ -85,11 +74,6 @@ func (l *RateLimiter) Allow(n int64) error { return nil } -// Remaining is not implemented for RateLimiter. It always returns -1. -func (l *RateLimiter) Remaining() int64 { - return -1 -} - // LimitWriter implements an io.Writer that will pass through all Write calls to the underlying // writer w until any of the limiter's limit is reached, at which point a Write will return ErrLimitReached. // Each limiter's value is increased with every write. diff --git a/web/src/app/Api.js b/web/src/app/Api.js index 73a6dde9..e35b3d10 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -51,6 +51,13 @@ class Api { console.log(`[Api] Publishing message to ${url}`); const send = new Promise(function (resolve, reject) { xhr.open("PUT", url); + if (body.type) { + xhr.overrideMimeType(body.type); + } + for (const [key, value] of Object.entries(headers)) { + xhr.setRequestHeader(key, value); + } + xhr.upload.addEventListener("progress", onProgress); xhr.addEventListener('readystatechange', (ev) => { if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) { console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response); @@ -70,13 +77,6 @@ class Api { reject(errorText ?? "An error occurred"); } }) - xhr.upload.addEventListener("progress", onProgress); - if (body.type) { - xhr.overrideMimeType(body.type); - } - for (const [key, value] of Object.entries(headers)) { - xhr.setRequestHeader(key, value); - } xhr.send(body); }); send.abort = () => {