Skip to content

Commit b9c176d

Browse files
committed
Tests
1 parent a04f2f9 commit b9c176d

File tree

4 files changed

+81
-70
lines changed

4 files changed

+81
-70
lines changed

server/errors.go

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ var (
119119
errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil}
120120
errHTTPBadRequestTemplatedMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message or title is too large after replacing template", "", nil}
121121
errHTTPBadRequestTemplatedMessageNotJSON = &errHTTP{40042, http.StatusBadRequest, "invalid request: message body must be JSON if templating is enabled", "", nil}
122+
errHTTPBadRequestTemplateInvalid = &errHTTP{40043, http.StatusBadRequest, "invalid request: could not parse template", "", nil}
123+
errHTTPBadRequestTemplateExecutionFailed = &errHTTP{40044, http.StatusBadRequest, "invalid request: template execution failed", "", nil}
122124
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
123125
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
124126
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}

server/server.go

+12-15
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ const (
135135
unifiedPushTopicPrefix = "up" // Temporarily, we rate limit all "up*" topics based on the subscriber
136136
unifiedPushTopicLength = 14 // Length of UnifiedPush topics, including the "up" part
137137
messagesHistoryMax = 10 // Number of message count values to keep in memory
138+
templateMaxExecutionTime = 100 * time.Millisecond
138139
)
139140

140141
// WebSocket constants
@@ -1102,34 +1103,30 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedR
11021103
return errHTTPEntityTooLargeJSONBody
11031104
}
11041105
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
1105-
m.Message = replaceTemplate(m.Message, peekedBody)
1106-
m.Title = replaceTemplate(m.Title, peekedBody)
1106+
if m.Message, err = replaceTemplate(m.Message, peekedBody); err != nil {
1107+
return err
1108+
}
1109+
if m.Title, err = replaceTemplate(m.Title, peekedBody); err != nil {
1110+
return err
1111+
}
11071112
if len(m.Message) > s.config.MessageSizeLimit {
11081113
return errHTTPBadRequestTemplatedMessageTooLarge
11091114
}
11101115
return nil
11111116
}
11121117

1113-
func replaceTemplate(tpl string, source string) string {
1114-
rendered, err := replaceTemplateInternal(tpl, source)
1115-
if err != nil {
1116-
return "<invalid template>"
1117-
}
1118-
return rendered
1119-
}
1120-
1121-
func replaceTemplateInternal(tpl string, source string) (string, error) {
1118+
func replaceTemplate(tpl string, source string) (string, error) {
11221119
var data any
11231120
if err := json.Unmarshal([]byte(source), &data); err != nil {
1124-
return "", err
1121+
return "", errHTTPBadRequestTemplatedMessageNotJSON
11251122
}
11261123
t, err := template.New("").Parse(tpl)
11271124
if err != nil {
1128-
return "", err
1125+
return "", errHTTPBadRequestTemplateInvalid
11291126
}
11301127
var buf bytes.Buffer
1131-
if err := t.Execute(&buf, data); err != nil {
1132-
return "", err
1128+
if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
1129+
return "", errHTTPBadRequestTemplateExecutionFailed
11331130
}
11341131
return buf.String(), nil
11351132
}

server/server_test.go

+33-55
Large diffs are not rendered by default.

util/timeout_writer.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package util
2+
3+
import (
4+
"errors"
5+
"io"
6+
"time"
7+
)
8+
9+
// ErrWriteTimeout is returned when a write timed out
10+
var ErrWriteTimeout = errors.New("write operation failed due to timeout since creation")
11+
12+
// TimeoutWriter wraps an io.Writer that will time out after the given timeout
13+
type TimeoutWriter struct {
14+
writer io.Writer
15+
timeout time.Duration
16+
start time.Time
17+
}
18+
19+
// NewTimeoutWriter creates a new TimeoutWriter
20+
func NewTimeoutWriter(w io.Writer, timeout time.Duration) *TimeoutWriter {
21+
return &TimeoutWriter{
22+
writer: w,
23+
timeout: timeout,
24+
start: time.Now(),
25+
}
26+
}
27+
28+
// Write implements the io.Writer interface, failing if called after the timeout period from creation.
29+
func (tw *TimeoutWriter) Write(p []byte) (n int, err error) {
30+
if time.Since(tw.start) > tw.timeout {
31+
return 0, errors.New("write operation failed due to timeout since creation")
32+
}
33+
return tw.writer.Write(p)
34+
}

0 commit comments

Comments
 (0)