This commit is contained in:
binwiederhier 2024-03-22 22:01:41 -04:00
parent a04f2f9c9a
commit b9c176ddba
4 changed files with 81 additions and 70 deletions

View file

@ -119,6 +119,8 @@ var (
errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil} errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil}
errHTTPBadRequestTemplatedMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message or title is too large after replacing template", "", nil} errHTTPBadRequestTemplatedMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message or title is too large after replacing template", "", nil}
errHTTPBadRequestTemplatedMessageNotJSON = &errHTTP{40042, http.StatusBadRequest, "invalid request: message body must be JSON if templating is enabled", "", nil} errHTTPBadRequestTemplatedMessageNotJSON = &errHTTP{40042, http.StatusBadRequest, "invalid request: message body must be JSON if templating is enabled", "", nil}
errHTTPBadRequestTemplateInvalid = &errHTTP{40043, http.StatusBadRequest, "invalid request: could not parse template", "", nil}
errHTTPBadRequestTemplateExecutionFailed = &errHTTP{40044, http.StatusBadRequest, "invalid request: template execution failed", "", nil}
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil} errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", 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} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}

View file

@ -135,6 +135,7 @@ const (
unifiedPushTopicPrefix = "up" // Temporarily, we rate limit all "up*" topics based on the subscriber unifiedPushTopicPrefix = "up" // Temporarily, we rate limit all "up*" topics based on the subscriber
unifiedPushTopicLength = 14 // Length of UnifiedPush topics, including the "up" part unifiedPushTopicLength = 14 // Length of UnifiedPush topics, including the "up" part
messagesHistoryMax = 10 // Number of message count values to keep in memory messagesHistoryMax = 10 // Number of message count values to keep in memory
templateMaxExecutionTime = 100 * time.Millisecond
) )
// WebSocket constants // WebSocket constants
@ -1102,34 +1103,30 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedR
return errHTTPEntityTooLargeJSONBody return errHTTPEntityTooLargeJSONBody
} }
peekedBody := strings.TrimSpace(string(body.PeekedBytes)) peekedBody := strings.TrimSpace(string(body.PeekedBytes))
m.Message = replaceTemplate(m.Message, peekedBody) if m.Message, err = replaceTemplate(m.Message, peekedBody); err != nil {
m.Title = replaceTemplate(m.Title, peekedBody) return err
}
if m.Title, err = replaceTemplate(m.Title, peekedBody); err != nil {
return err
}
if len(m.Message) > s.config.MessageSizeLimit { if len(m.Message) > s.config.MessageSizeLimit {
return errHTTPBadRequestTemplatedMessageTooLarge return errHTTPBadRequestTemplatedMessageTooLarge
} }
return nil return nil
} }
func replaceTemplate(tpl string, source string) string { func replaceTemplate(tpl string, source string) (string, error) {
rendered, err := replaceTemplateInternal(tpl, source)
if err != nil {
return "<invalid template>"
}
return rendered
}
func replaceTemplateInternal(tpl string, source string) (string, error) {
var data any var data any
if err := json.Unmarshal([]byte(source), &data); err != nil { if err := json.Unmarshal([]byte(source), &data); err != nil {
return "", err return "", errHTTPBadRequestTemplatedMessageNotJSON
} }
t, err := template.New("").Parse(tpl) t, err := template.New("").Parse(tpl)
if err != nil { if err != nil {
return "", err return "", errHTTPBadRequestTemplateInvalid
} }
var buf bytes.Buffer var buf bytes.Buffer
if err := t.Execute(&buf, data); err != nil { if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
return "", err return "", errHTTPBadRequestTemplateExecutionFailed
} }
return buf.String(), nil return buf.String(), nil
} }

File diff suppressed because one or more lines are too long

34
util/timeout_writer.go Normal file
View file

@ -0,0 +1,34 @@
package util
import (
"errors"
"io"
"time"
)
// ErrWriteTimeout is returned when a write timed out
var ErrWriteTimeout = errors.New("write operation failed due to timeout since creation")
// TimeoutWriter wraps an io.Writer that will time out after the given timeout
type TimeoutWriter struct {
writer io.Writer
timeout time.Duration
start time.Time
}
// NewTimeoutWriter creates a new TimeoutWriter
func NewTimeoutWriter(w io.Writer, timeout time.Duration) *TimeoutWriter {
return &TimeoutWriter{
writer: w,
timeout: timeout,
start: time.Now(),
}
}
// Write implements the io.Writer interface, failing if called after the timeout period from creation.
func (tw *TimeoutWriter) Write(p []byte) (n int, err error) {
if time.Since(tw.start) > tw.timeout {
return 0, errors.New("write operation failed due to timeout since creation")
}
return tw.writer.Write(p)
}