From 91a81159b455c0f837b7a7035c15f4d4da23dfdc Mon Sep 17 00:00:00 2001
From: Jonathan de Jong <jonathandejong02@gmail.com>
Date: Fri, 18 Aug 2023 14:51:38 +0200
Subject: [PATCH] Make UP return 410 after topic has expired

---
 server/errors.go | 1 +
 server/server.go | 9 ++++++++-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/server/errors.go b/server/errors.go
index 27ba3df0..e05f4a8f 100644
--- a/server/errors.go
+++ b/server/errors.go
@@ -125,6 +125,7 @@ var (
 	errHTTPConflictSubscriptionExists                = &errHTTP{40903, http.StatusConflict, "conflict: topic subscription already exists", "", nil}
 	errHTTPConflictPhoneNumberExists                 = &errHTTP{40904, http.StatusConflict, "conflict: phone number already exists", "", nil}
 	errHTTPGonePhoneVerificationExpired              = &errHTTP{41001, http.StatusGone, "phone number verification expired or does not exist", "", nil}
+	errHTTPGoneSubscriptionExpired                   = &errHTTP{41002, http.StatusGone, "subscription expired", "", nil}
 	errHTTPEntityTooLargeAttachment                  = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations", nil}
 	errHTTPEntityTooLargeMatrixRequest               = &errHTTP{41302, http.StatusRequestEntityTooLarge, "Matrix request is larger than the max allowed length", "", nil}
 	errHTTPEntityTooLargeJSONBody                    = &errHTTP{41303, http.StatusRequestEntityTooLarge, "JSON body too large", "", nil}
diff --git a/server/server.go b/server/server.go
index 0ab36524..c830490c 100644
--- a/server/server.go
+++ b/server/server.go
@@ -742,7 +742,14 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
 	if e != nil {
 		return nil, e.With(t)
 	}
-	if unifiedpush && s.config.VisitorSubscriberRateLimiting && t.RateVisitor() == nil {
+	if unifiedpush && s.config.VisitorSubscriberRateLimiting && t.Stale() {
+		// This subscription is verifiably dead, we should notify the server.
+		//
+		// This not cause the below condition (where ratevisitor is nil) to be excluded,
+		// since this will check `time.Since(t.lastAccess) > topicExpungeAfter` if ratevisitor is nil,
+		// and return on that.
+		return nil, errHTTPGoneSubscriptionExpired.With(t)
+	} else if unifiedpush && s.config.VisitorSubscriberRateLimiting && t.RateVisitor() == nil {
 		// UnifiedPush clients must subscribe before publishing to allow proper subscriber-based rate limiting (see
 		// Rate-Topics header). The 5xx response is because some app servers (in particular Mastodon) will remove
 		// the subscription as invalid if any 400-499 code (except 429/408) is returned.