瀏覽代碼

UTF-8 headers

binwiederhier 2 年之前
父節點
當前提交
cfa8d92af1
共有 4 個文件被更改,包括 33 次插入5 次删除
  1. 1 0
      server/errors.go
  2. 12 1
      server/server.go
  3. 17 4
      server/server_test.go
  4. 3 0
      server/util.go

+ 1 - 0
server/errors.go

@@ -106,6 +106,7 @@ var (
 	errHTTPBadRequestNotAPaidUser                    = &errHTTP{40027, http.StatusBadRequest, "invalid request: not a paid user", "", nil}
 	errHTTPBadRequestBillingRequestInvalid           = &errHTTP{40028, http.StatusBadRequest, "invalid request: not a valid billing request", "", nil}
 	errHTTPBadRequestBillingSubscriptionExists       = &errHTTP{40029, http.StatusBadRequest, "invalid request: billing subscription already exists", "", nil}
+	errHTTPBadRequestInvalidMimeHeader               = &errHTTP{40030, http.StatusBadRequest, "invalid request: invalid MIME encoding of header", "", 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}

+ 12 - 1
server/server.go

@@ -843,10 +843,17 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
 func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email string, unifiedpush bool, err *errHTTP) {
 	cache = readBoolParam(r, true, "x-cache", "cache")
 	firebase = readBoolParam(r, true, "x-firebase", "firebase")
-	m.Title = readParam(r, "x-title", "title", "t")
 	m.Click = readParam(r, "x-click", "click")
 	icon := readParam(r, "x-icon", "icon")
 	filename := readParam(r, "x-filename", "filename", "file", "f")
+	title := readParam(r, "x-title", "title", "t")
+	if title != "" {
+		title, err := mimeDecoder.DecodeHeader(title)
+		if err != nil {
+			return false, false, "", false, errHTTPBadRequestInvalidMimeHeader.Wrap("invalid X-Title header: %s", err.Error())
+		}
+		m.Title = title
+	}
 	attach := readParam(r, "x-attach", "attach", "a")
 	if attach != "" || filename != "" {
 		m.Attachment = &attachment{}
@@ -884,6 +891,10 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
 	}
 	messageStr := strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
 	if messageStr != "" {
+		messageStr, err := mimeDecoder.DecodeHeader(messageStr)
+		if err != nil {
+			return false, false, "", false, errHTTPBadRequestInvalidMimeHeader.Wrap("invalid X-Message header: %s", err.Error())
+		}
 		m.Message = messageStr
 	}
 	var e error

+ 17 - 4
server/server_test.go

@@ -21,8 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/assert"
-
 	"github.com/stretchr/testify/require"
 	"heckel.io/ntfy/log"
 	"heckel.io/ntfy/util"
@@ -2106,8 +2104,8 @@ func TestServer_PublishWhileUpdatingStatsWithLotsOfMessages(t *testing.T) {
 	start = time.Now()
 	response := request(t, s, "PUT", "/mytopic", "some body", nil)
 	m := toMessage(t, response.Body.String())
-	assert.Equal(t, "some body", m.Message)
-	assert.True(t, time.Since(start) < 100*time.Millisecond)
+	require.Equal(t, "some body", m.Message)
+	require.True(t, time.Since(start) < 100*time.Millisecond)
 	log.Info("Done: Publishing message; took %s", time.Since(start).Round(time.Millisecond))
 
 	// Wait for all goroutines
@@ -2469,6 +2467,21 @@ func TestServer_MessageCountPersistence(t *testing.T) {
 	require.Equal(t, int64(1234), s.messages)
 }
 
+func TestServer_PublishWithUTF8MimeHeader(t *testing.T) {
+	s := newTestServer(t, newTestConfig(t))
+
+	response := request(t, s, "POST", "/mytopic", "some attachment", map[string]string{
+		"X-Filename": "some attachment.txt",
+		"X-Message":  "=?UTF-8?B?8J+HqfCfh6o=?=",
+		"X-Title":    "=?UTF-8?B?bnRmeSDlvojmo5I=?=, no really I mean it! =?UTF-8?Q?This is q=C3=BC=C3=B6ted-print=C3=A4ble.?=",
+	})
+	require.Equal(t, 200, response.Code)
+	m := toMessage(t, response.Body.String())
+	require.Equal(t, "🇩🇪", m.Message)
+	require.Equal(t, "ntfy 很棒, no really I mean it! This is qüöted-printäble.", m.Title)
+	require.Equal(t, "some attachment.txt", m.Attachment.Name)
+}
+
 func newTestConfig(t *testing.T) *Config {
 	conf := NewConfig()
 	conf.BaseURL = "http://127.0.0.1:12345"

+ 3 - 0
server/util.go

@@ -5,11 +5,14 @@ import (
 	"fmt"
 	"heckel.io/ntfy/util"
 	"io"
+	"mime"
 	"net/http"
 	"net/netip"
 	"strings"
 )
 
+var mimeDecoder mime.WordDecoder
+
 func readBoolParam(r *http.Request, defaultValue bool, names ...string) bool {
 	value := strings.ToLower(readParam(r, names...))
 	if value == "" {