From 7fd5f0b29d4d0b9cd34bd9d5c1e3759d48a6916b Mon Sep 17 00:00:00 2001 From: Hunter Kehoe Date: Tue, 19 Mar 2024 21:56:55 -0600 Subject: [PATCH] allow large HTTP body so long as resulting message is small --- server/errors.go | 1 + server/server.go | 21 ++++++++++++++++++--- server/server_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/server/errors.go b/server/errors.go index 072bdc01..05adeb66 100644 --- a/server/errors.go +++ b/server/errors.go @@ -117,6 +117,7 @@ var ( errHTTPBadRequestWebPushSubscriptionInvalid = &errHTTP{40038, http.StatusBadRequest, "invalid request: web push payload malformed", "", nil} errHTTPBadRequestWebPushEndpointUnknown = &errHTTP{40039, http.StatusBadRequest, "invalid request: web push endpoint unknown", "", nil} errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil} + errHTTPBadRequestTemplatedMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message is too large after replacing template", "", 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 8be6ce75..e6b1b88e 100644 --- a/server/server.go +++ b/server/server.go @@ -1044,8 +1044,11 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi // Body must be attachment, because we passed a filename // 5. curl -T file.txt ntfy.sh/mytopic // If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message -// 6. curl -T file.txt ntfy.sh/mytopic -// If file.txt is > message limit, treat it as an attachment +// 6. curl -H "Template: yes" -T file.txt ntfy.sh/mytopic +// If file.txt is < 4096*2 (message limit*2) and a template is used, try parsing under the assumption +// that the message generated by the template will be less than 4096 +// 7. curl -T file.txt ntfy.sh/mytopic +// If file.txt is > message limit or template && file.txt > message limit*2, treat it as an attachment func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, template bool, unifiedpush bool) error { if m.Event == pollRequestEvent { // Case 1 return s.handleBodyDiscard(body) @@ -1057,8 +1060,16 @@ func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body return s.handleBodyAsAttachment(r, v, m, body) // Case 4 } else if !body.LimitReached && utf8.Valid(body.PeekedBytes) { return s.handleBodyAsTextMessage(m, body, template) // Case 5 + } else if template { + templateBody, err := util.Peek(body, s.config.MessageSizeLimit*2) + if err != nil { + return err + } + if !templateBody.LimitReached { + return s.handleBodyAsTextMessage(m, templateBody, template) // Case 6 + } } - return s.handleBodyAsAttachment(r, v, m, body) // Case 6 + return s.handleBodyAsAttachment(r, v, m, body) // Case 7 } func (s *Server) handleBodyDiscard(body *util.PeekedReadCloser) error { @@ -1104,6 +1115,10 @@ func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeekedReadCloser if m.Attachment != nil && m.Attachment.Name != "" && m.Message == "" { m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name) } + // Ensure message is less than message limit after templating + if len(m.Message) > s.config.MessageSizeLimit { + return errHTTPBadRequestTemplatedMessageTooLarge + } return nil } diff --git a/server/server_test.go b/server/server_test.go index 35ea2c54..99f4c4de 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -2754,6 +2754,35 @@ func TestServer_MessageTemplate_FancyGJSON(t *testing.T) { require.Equal(t, `2 Severe Errors`, m.Title) } +func TestServer_MessageTemplate_ExceedMessageSize_TemplatedMessageOK(t *testing.T) { + c := newTestConfig(t) + c.MessageSizeLimit = 25 // 25 < len(HTTP body) < 25*2 && len(m.Message) < 25 + s := newTestServer(t, c) + response := request(t, s, "PUT", "/mytopic", `{"foo":"bar", "nested":{"title":"here"}}`, map[string]string{ + "X-Message": "${foo}", + "X-Title": "${nested.title}", + "X-Template": "1", + }) + + require.Equal(t, 200, response.Code) + m := toMessage(t, response.Body.String()) + require.Equal(t, "bar", m.Message) + require.Equal(t, "here", m.Title) +} + +func TestServer_MessageTemplate_ExceedMessageSize_TemplatedMessageTooLong(t *testing.T) { + c := newTestConfig(t) + c.MessageSizeLimit = 21 // 21 < len(HTTP body) < 21*2 && !len(m.Message) < 21 + s := newTestServer(t, c) + response := request(t, s, "PUT", "/mytopic", `{"foo":"This is a long message"}`, map[string]string{ + "X-Message": "${foo}", + "X-Template": "1", + }) + + require.Equal(t, 400, response.Code) + require.Equal(t, 40041, toHTTPError(t, response.Body.String()).Code) +} + func newTestConfig(t *testing.T) *Config { conf := NewConfig() conf.BaseURL = "http://127.0.0.1:12345"