mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-14 11:47:33 +00:00
WIP
This commit is contained in:
parent
a75fb08ef1
commit
d88dbbc90f
4 changed files with 200 additions and 11 deletions
3
go.mod
3
go.mod
|
@ -27,6 +27,7 @@ require github.com/pkg/errors v0.9.1 // indirect
|
|||
|
||||
require (
|
||||
firebase.google.com/go/v4 v4.10.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.23
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/stripe/stripe-go/v74 v74.14.0
|
||||
)
|
||||
|
@ -39,6 +40,7 @@ require (
|
|||
cloud.google.com/go/longrunning v0.4.1 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
@ -50,6 +52,7 @@ require (
|
|||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -22,6 +22,8 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak
|
|||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
|
||||
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
@ -85,6 +87,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9
|
|||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
|
||||
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
@ -95,6 +99,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp
|
|||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY=
|
||||
github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
|
||||
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8 h1:0uFGkScHef2Xd8g74BMHU1jFcnKEm0PzrPn4CluQ9FI=
|
||||
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8/go.mod h1:T0THb4kP9D3NNqlvCwIG4GyUioTAzEhB4RNVzig/43E=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
@ -231,37 +233,66 @@ func readMailBody(body io.Reader, header mail.Header) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.ToLower(contentType) == "text/plain" {
|
||||
return readPlainTextMailBody(body, header.Get("Content-Transfer-Encoding"))
|
||||
} else if strings.HasPrefix(strings.ToLower(contentType), "multipart/") {
|
||||
return readMultipartMailBody(body, params, 0)
|
||||
canonicalContentType := strings.ToLower(contentType)
|
||||
if canonicalContentType == "text/plain" || canonicalContentType == "text/html" {
|
||||
return readTextMailBody(body, canonicalContentType, header.Get("Content-Transfer-Encoding"))
|
||||
} else if strings.HasPrefix(canonicalContentType, "multipart/") {
|
||||
return readMultipartMailBody(body, params)
|
||||
}
|
||||
return "", errUnsupportedContentType
|
||||
}
|
||||
|
||||
func readMultipartMailBody(body io.Reader, params map[string]string, depth int) (string, error) {
|
||||
func readMultipartMailBody(body io.Reader, params map[string]string) (string, error) {
|
||||
parts := make(map[string]string)
|
||||
if err := readMultipartMailBodyParts(body, params, 0, parts); err != nil && err != io.EOF {
|
||||
return "", err
|
||||
} else if s, ok := parts["text/plain"]; ok {
|
||||
return s, nil
|
||||
} else if s, ok := parts["text/html"]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return "", io.EOF
|
||||
}
|
||||
|
||||
func readMultipartMailBodyParts(body io.Reader, params map[string]string, depth int, parts map[string]string) error {
|
||||
if depth >= maxMultipartDepth {
|
||||
return "", errMultipartNestedTooDeep
|
||||
return errMultipartNestedTooDeep
|
||||
}
|
||||
mr := multipart.NewReader(body, params["boundary"])
|
||||
for {
|
||||
part, err := mr.NextPart()
|
||||
if err != nil { // may be io.EOF
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
partContentType, partParams, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
if strings.ToLower(partContentType) == "text/plain" {
|
||||
return readPlainTextMailBody(part, part.Header.Get("Content-Transfer-Encoding"))
|
||||
canonicalPartContentType := strings.ToLower(partContentType)
|
||||
if canonicalPartContentType == "text/plain" || canonicalPartContentType == "text/html" {
|
||||
s, err := readTextMailBody(part, canonicalPartContentType, part.Header.Get("Content-Transfer-Encoding"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parts[canonicalPartContentType] = s
|
||||
} else if strings.HasPrefix(strings.ToLower(partContentType), "multipart/") {
|
||||
return readMultipartMailBody(part, partParams, depth+1)
|
||||
if err := readMultipartMailBodyParts(part, partParams, depth+1, parts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Continue with next part
|
||||
}
|
||||
}
|
||||
|
||||
func readTextMailBody(reader io.Reader, contentType, transferEncoding string) (string, error) {
|
||||
if contentType == "text/plain" {
|
||||
return readPlainTextMailBody(reader, transferEncoding)
|
||||
} else if contentType == "text/html" {
|
||||
return readHTMLMailBody(reader, transferEncoding)
|
||||
}
|
||||
return "", fmt.Errorf("unsupported content type: %s", contentType)
|
||||
}
|
||||
|
||||
func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, error) {
|
||||
if strings.ToLower(transferEncoding) == "base64" {
|
||||
reader = base64.NewDecoder(base64.StdEncoding, reader)
|
||||
|
@ -272,3 +303,27 @@ func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, e
|
|||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func readHTMLMailBody(reader io.Reader, transferEncoding string) (string, error) {
|
||||
body, err := readPlainTextMailBody(reader, transferEncoding)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stripped := bluemonday.
|
||||
StrictPolicy().
|
||||
AddSpaceWhenStrippingTag(true).
|
||||
Sanitize(body)
|
||||
return removeExtraEmptyLines(stripped), nil
|
||||
}
|
||||
|
||||
func removeExtraEmptyLines(str string) string {
|
||||
// Replace lines that contain only spaces with empty lines
|
||||
re := regexp.MustCompile(`(?m)^\s+$`)
|
||||
str = re.ReplaceAllString(str, "")
|
||||
|
||||
// Remove more than 2 consecutive empty lines
|
||||
re = regexp.MustCompile(`\n{3,}`)
|
||||
str = re.ReplaceAllString(str, "\n\n")
|
||||
|
||||
return str
|
||||
}
|
||||
|
|
|
@ -492,6 +492,131 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
|
|||
writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: multipart message nested too deep")
|
||||
}
|
||||
|
||||
func TestSmtpBackend_HTMLEmail(t *testing.T) {
|
||||
email := `EHLO example.com
|
||||
MAIL FROM: test@mydomain.me
|
||||
RCPT TO: ntfy-mytopic@ntfy.sh
|
||||
DATA
|
||||
Message-Id: <51610934ss4.mmailer@fritz.box>
|
||||
From: <email@email.com>
|
||||
To: <email@email.com>,
|
||||
<ntfy-subjectatntfy@ntfy.sh>
|
||||
Date: Thu, 30 Mar 2023 02:56:53 +0000
|
||||
Subject: A HTML email
|
||||
Mime-Version: 1.0
|
||||
Content-Type: text/html;
|
||||
charset="utf-8"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<=21DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Alerttitle</title>
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html;charset=3Dutf-8"/>
|
||||
</head>
|
||||
<body style=3D"color: =23000000; background-color: =23f0eee6;">
|
||||
<table width=3D"100%" align=3D"center" style=3D"border:solid 2px =23eeeeee=
|
||||
; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td>
|
||||
<table style=3D"border-collapse: collapse;">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td style=3D"background: =23FFFFFF;">
|
||||
<table style=3D"color: =23FFFFFF; background-color: =23006EC0; border-coll=
|
||||
apse: collapse;">
|
||||
<tr>
|
||||
<td style=3D"width: 1000px; text-align: center; font-size: 18pt; font-fami=
|
||||
ly: Arial, Helvetica, sans-serif; padding: 10px;">
|
||||
|
||||
|
||||
headertext of table
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td style=3D"padding: 10px 20px; background: =23FFFFFF;">
|
||||
<table style=3D"border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style=3D"width: 940px; font-size: 13pt; font-family: Arial, Helvetica,=
|
||||
sans-serif; text-align: left;">
|
||||
" Very important information about a change in your
|
||||
home automation setup
|
||||
|
||||
Now the light is on
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td style=3D"padding: 10px 20px; background: =23FFFFFF;">
|
||||
<table>
|
||||
<tr>
|
||||
<td style=3D"width: 960px; font-size: 10pt; font-family: Arial, Helvetica,=
|
||||
sans-serif; text-align: left;">
|
||||
<hr />
|
||||
If you don't want to recieve this message anymore, stop the push
|
||||
services in your <a href=3D"https:fritzbox" target=3D"_=
|
||||
blank">FRITZ=21Box</a>=2E<br />
|
||||
Here you can see the active push services: "System > Push Service"=2E
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table style=3D"color: =23FFFFFF; background-color: =23006EC0;">
|
||||
<tr>
|
||||
<td style=3D"width: 1000px; font-size: 10pt; font-family: Arial, Helvetica=
|
||||
, sans-serif; text-align: center; padding: 10px;">
|
||||
This mail has ben sent by your <a style=3D"color: =23FFFFFF;" href=3D"https:=
|
||||
//fritzbox" target=3D"_blank">FRITZ=21Box</a=
|
||||
> automatically=2E
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
.
|
||||
`
|
||||
|
||||
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/mytopic", r.URL.Path)
|
||||
require.Equal(t, "A HTML email", r.Header.Get("Title"))
|
||||
require.Equal(t, "what's up", readAll(t, r.Body))
|
||||
})
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
|
||||
}
|
||||
|
||||
func TestSmtpBackend_PlaintextWithToken(t *testing.T) {
|
||||
email := `EHLO example.com
|
||||
MAIL FROM: phil@example.com
|
||||
|
|
Loading…
Reference in a new issue