diff --git a/docs/releases.md b/docs/releases.md index b8ff0ca4..44b42243 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1798,4 +1798,8 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release ## Not released yet -Nothing. \ No newline at end of file +### ntfy server v2.20.x (UNRELEASED) + +**Bug fixes + maintenance:** + +* Reject invalid e-mail addresses (e.g. multiple comma-separated recipients) with HTTP 400 diff --git a/server/errors.go b/server/errors.go index 7a120e5b..77caf239 100644 --- a/server/errors.go +++ b/server/errors.go @@ -142,6 +142,7 @@ var ( errHTTPBadRequestTemplateFileNotFound = &errHTTP{40047, http.StatusBadRequest, "invalid request: template file not found", "https://ntfy.sh/docs/publish/#message-templating", nil} errHTTPBadRequestTemplateFileInvalid = &errHTTP{40048, http.StatusBadRequest, "invalid request: template file invalid", "https://ntfy.sh/docs/publish/#message-templating", nil} errHTTPBadRequestSequenceIDInvalid = &errHTTP{40049, http.StatusBadRequest, "invalid request: sequence ID invalid", "https://ntfy.sh/docs/publish/#updating-deleting-notifications", nil} + errHTTPBadRequestEmailAddressInvalid = &errHTTP{40050, http.StatusBadRequest, "invalid request: invalid e-mail address", "https://ntfy.sh/docs/publish/#e-mail-notifications", nil} errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil} errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil} diff --git a/server/server.go b/server/server.go index 075d3079..71f39357 100644 --- a/server/server.go +++ b/server/server.go @@ -122,6 +122,7 @@ var ( fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`) urlRegex = regexp.MustCompile(`^https?://`) phoneNumberRegex = regexp.MustCompile(`^\+\d{1,100}$`) + emailAddressRegex = regexp.MustCompile(`^[^\s,;]+@[^\s,;]+$`) //go:embed site webFs embed.FS @@ -1163,6 +1164,9 @@ func (s *Server) parsePublishParams(r *http.Request, m *model.Message) (cache bo m.Icon = icon } email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e") + if email != "" && !emailAddressRegex.MatchString(email) { + return false, false, "", "", "", false, "", errHTTPBadRequestEmailAddressInvalid + } if s.smtpSender == nil && email != "" { return false, false, "", "", "", false, "", errHTTPBadRequestEmailDisabled } diff --git a/server/server_test.go b/server/server_test.go index 71743638..cb20cbda 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1543,6 +1543,30 @@ func TestServer_PublishEmailNoMailer_Fail(t *testing.T) { }) } +func TestServer_PublishEmailAddressInvalid(t *testing.T) { + forEachBackend(t, func(t *testing.T, databaseURL string) { + s := newTestServer(t, newTestConfig(t, databaseURL)) + s.smtpSender = &testMailer{} + addresses := []string{ + "test@example.com, other@example.com", + "invalidaddress", + "@nope", + "nope@", + } + for _, email := range addresses { + response := request(t, s, "PUT", "/mytopic", "fail", map[string]string{ + "E-Mail": email, + }) + require.Equal(t, 400, response.Code, "expected 400 for email: %s", email) + } + // Valid address should succeed + response := request(t, s, "PUT", "/mytopic", "success", map[string]string{ + "E-Mail": "test@example.com", + }) + require.Equal(t, 200, response.Code) + }) +} + func TestServer_PublishAndExpungeTopicAfter16Hours(t *testing.T) { forEachBackend(t, func(t *testing.T, databaseURL string) { t.Parallel()