Compare commits

..

8 Commits

Author SHA1 Message Date
Ralph Slooten
f16289078e Merge branch 'release/v1.19.3' 2024-07-26 22:17:16 +12:00
Ralph Slooten
5580967c78 Release v1.19.3 2024-07-26 22:17:15 +12:00
Ralph Slooten
eeb2c03424 Chore: Update Go dependencies 2024-07-26 22:09:41 +12:00
Ralph Slooten
0127b9a1f2 Merge branch 'feature/stored-xss' into develop 2024-07-26 22:06:14 +12:00
Ralph Slooten
a078c318e8 Fix(Security): Prevent bypass of Contend Security Policy using stored XSS, and sanitize preview HTML data (DOMPurify)
This closes a security hole whereby a bad actor with SMTP access can bypass the CSP headers with a series of specially crafted HTML messages. A special thanks to @bmodotdev for responsibly disclosing the vulnerability and proving information and an initial fix.
2024-07-26 22:02:14 +12:00
Ralph Slooten
9e881ea868 Chore: Display nicer noscript message when JavaScript is disabled 2024-07-24 19:19:26 +12:00
Ralph Slooten
41c957b807 Add security policy 2024-07-23 17:23:56 +12:00
Ralph Slooten
ea0b5f66f7 Merge tag 'v1.19.2' into develop
Release v1.19.2
2024-07-21 16:11:55 +12:00
9 changed files with 169 additions and 38 deletions

View File

@@ -2,6 +2,16 @@
Notable changes to Mailpit will be documented in this file.
## [v1.19.3]
### Chore
- Update Go dependencies
- Display nicer noscript message when JavaScript is disabled
### Fix
- **Security:** Prevent bypass of Contend Security Policy using stored XSS, and sanitize preview HTML data (DOMPurify)
## [v1.19.2]
### Chore

19
SECURITY.md Normal file
View File

@@ -0,0 +1,19 @@
# Reporting security vulnerabilities
Your efforts to responsibly disclose your findings are appreciated.
** **Please do _not_ report security vulnerabilities through public GitHub issues.** **
If you believe you have found a **security vulnerability**, then please report it to security@axllent.org so
your findings can be investigated, and if confirmed, fixed and released in a timely manner.
Your report should include:
- Mailpit version
- A vulnerability description
- Reproduction steps (if applicable)
- Any other details you think are likely to be important
You should receive an initial acknowledgement within 24 hours in most cases, and will kept updated throughout the process.
With your consent, your contributions will be publicly acknowledged.

View File

@@ -205,6 +205,9 @@ func VerifyConfig() error {
cssFontRestriction = "'self'"
}
// The default Content Security Policy is updates on every application page load to replace script-src 'self'
// with a random nonce ID to prevent XSS. This applies to the Mailpit app & API.
// See server.middleWareFunc()
ContentSecurityPolicy = fmt.Sprintf("default-src 'self'; script-src 'self'; style-src %s 'unsafe-inline'; frame-src 'self'; img-src * data: blob:; font-src %s data:; media-src 'self'; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'self';",
cssFontRestriction, cssFontRestriction,
)

10
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/PuerkitoBio/goquery v1.9.2
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/axllent/semver v0.0.1
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024
github.com/gomarkdown/markdown v0.0.0-20240723152757-afa4a469d4f9
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/jhillyerd/enmime v1.2.0
@@ -28,7 +28,7 @@ require (
golang.org/x/text v0.16.0
golang.org/x/time v0.5.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.30.2
modernc.org/sqlite v1.31.1
)
require (
@@ -44,7 +44,7 @@ require (
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
@@ -58,8 +58,8 @@ require (
golang.org/x/image v0.18.0 // indirect
golang.org/x/sys v0.22.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect
modernc.org/libc v1.55.4 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect

36
go.sum
View File

@@ -23,8 +23,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024 h1:saBP362Qm7zDdDXqv61kI4rzhmLFq3Z1gx34xpl6cWE=
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20240723152757-afa4a469d4f9 h1:TRYrIWJziqvMVn1owO8bmkDJTlMQFYnf74yhD8LXfgU=
github.com/gomarkdown/markdown v0.0.0-20240723152757-afa4a469d4f9/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -63,8 +63,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mhale/smtpd v0.8.3 h1:8j8YNXajksoSLZja3HdwvYVZPuJSqAxFsib3adzRRt8=
@@ -132,8 +132,8 @@ golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -184,8 +184,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -197,16 +197,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
modernc.org/ccgo/v4 v4.20.4 h1:3pPOlMcblnu5CBU3w1BFtepwBnLezGjPYTH8xBeYZM8=
modernc.org/ccgo/v4 v4.20.4/go.mod h1:meYiLeaGpKQmHBw8roW4DXLkDvusG+MD7LJ/kYyAouU=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/gc/v2 v2.4.3 h1:Ik4ZcMbC7aY4ZDPUhzXVXi7GMub9QcXLTfXn3mWpNw8=
modernc.org/gc/v2 v2.4.3/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e h1:WPC4v0rNIFb2PY+nBBEEKyugPPRHPzUgyN3xZPpGK58=
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.4 h1:iWzZ96v1Iut2Sm4lEp0yzFde20M9zpcI0wY3DFcb+8g=
modernc.org/libc v1.55.4/go.mod h1:GPuVtbWvXUo590z/xfVIQcOqnugb1WovSfMJWMe0ZfA=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@@ -215,8 +215,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk=
modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs=
modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

6
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"bootstrap5-tags": "^1.6.1",
"color-hash": "^2.0.2",
"dayjs": "^1.11.10",
"dompurify": "^3.1.6",
"ical.js": "^2.0.1",
"modern-screenshot": "^4.4.30",
"prismjs": "^1.29.0",
@@ -1417,6 +1418,11 @@
"node": ">=8"
}
},
"node_modules/dompurify": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",

View File

@@ -15,6 +15,7 @@
"bootstrap5-tags": "^1.6.1",
"color-hash": "^2.0.2",
"dayjs": "^1.11.10",
"dompurify": "^3.1.6",
"ical.js": "^2.0.1",
"modern-screenshot": "^4.4.30",
"prismjs": "^1.29.0",

View File

@@ -25,6 +25,7 @@ import (
"github.com/axllent/mailpit/server/pop3"
"github.com/axllent/mailpit/server/websockets"
"github.com/gorilla/mux"
"github.com/lithammer/shortuuid/v4"
)
//go:embed ui
@@ -75,11 +76,11 @@ func Listen() {
}
// UI shortcut
r.HandleFunc(config.Webroot+"view/latest", handlers.RedirectToLatestMessage).Methods("GET")
r.HandleFunc(config.Webroot+"view/latest", middleWareFunc(handlers.RedirectToLatestMessage)).Methods("GET")
// frontend testing
r.HandleFunc(config.Webroot+"view/{id}.html", handlers.GetMessageHTML).Methods("GET")
r.HandleFunc(config.Webroot+"view/{id}.txt", handlers.GetMessageText).Methods("GET")
r.HandleFunc(config.Webroot+"view/{id}.html", middleWareFunc(handlers.GetMessageHTML)).Methods("GET")
r.HandleFunc(config.Webroot+"view/{id}.txt", middleWareFunc(handlers.GetMessageText)).Methods("GET")
// web UI via virtual index.html
r.PathPrefix(config.Webroot + "view/").Handler(middleWareFunc(index)).Methods("GET")
@@ -179,7 +180,21 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
// generate a new random nonce on every request
randomNonce := shortuuid.New()
// header used to pass nonce through to function
r.Header.Set("mp-nonce", randomNonce)
// Prevent JavaScript XSS by adding a nonce for script-src
cspHeader := strings.Replace(
config.ContentSecurityPolicy,
"script-src 'self';",
fmt.Sprintf("script-src 'nonce-%s';", randomNonce),
1,
)
w.Header().Set("Content-Security-Policy", cspHeader)
if AccessControlAllowOrigin != "" && strings.HasPrefix(r.RequestURI, config.Webroot+"api/") {
w.Header().Set("Access-Control-Allow-Origin", AccessControlAllowOrigin)
@@ -281,7 +296,7 @@ func swaggerBasePath(w http.ResponseWriter, _ *http.Request) {
}
// Just returns the default HTML template
func index(w http.ResponseWriter, _ *http.Request) {
func index(w http.ResponseWriter, r *http.Request) {
var h = `<!DOCTYPE html>
<html lang="en" class="h-100">
@@ -298,10 +313,12 @@ func index(w http.ResponseWriter, _ *http.Request) {
<body class="h-100">
<div class="container-fluid h-100 d-flex flex-column" id="app" data-webroot="{{ .Webroot }}" data-version="{{ .Version }}">
<noscript>You require JavaScript to use this app.</noscript>
<noscript class="alert alert-warning position-absolute top-50 start-50 translate-middle">
You need a browser with JavaScript support to use Mailpit
</noscript>
</div>
<script src="{{ .Webroot }}dist/app.js?{{ .Version }}"></script>
<script src="{{ .Webroot }}dist/app.js?{{ .Version }}" nonce="{{ .Nonce }}"></script>
</body>
</html>`
@@ -314,9 +331,11 @@ func index(w http.ResponseWriter, _ *http.Request) {
data := struct {
Webroot string
Version string
Nonce string
}{
Webroot: config.Webroot,
Version: config.Version,
Nonce: r.Header.Get("mp-nonce"),
}
buff := new(bytes.Buffer)

View File

@@ -9,6 +9,7 @@ import Tags from 'bootstrap5-tags'
import { Tooltip } from 'bootstrap'
import commonMixins from '../../mixins/CommonMixins'
import { mailbox } from '../../stores/mailbox'
import DOMPurify from 'dompurify'
export default {
props: {
@@ -73,6 +74,57 @@ export default {
return (mailbox.showHTMLCheck && this.message.HTML)
|| mailbox.showLinkCheck
|| (mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin)
},
// remove bad HTML, JavaScript, iframes etc
sanitizedHTML() {
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.hasAttribute('href') && node.getAttribute('href').substring(0, 1) == '#') {
return
}
if ('target' in node) {
node.setAttribute('target', '_blank');
node.setAttribute('rel', 'noopener noreferrer');
}
if (!node.hasAttribute('target') && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) {
node.setAttribute('xlink:show', '_blank');
}
});
const clean = DOMPurify.sanitize(
this.message.HTML,
{
WHOLE_DOCUMENT: true,
SANITIZE_DOM: false,
ADD_TAGS: [
'link',
'meta',
'o:p',
'style',
],
ADD_ATTR: [
'bordercolor',
'charset',
'content',
'hspace',
'http-equiv',
'itemprop',
'itemscope',
'itemtype',
'link',
'vertical-align',
'vlink',
'vspace',
'xml:lang'
],
FORBID_ATTR: ['script'],
}
)
// for debugging
// this.debugDOMPurify(DOMPurify.removed)
return clean
}
},
@@ -133,7 +185,7 @@ export default {
// delay 0.2s until vue has rendered the iframe content
window.setTimeout(() => {
let p = document.getElementById('preview-html')
if (p) {
if (p && typeof p.contentWindow.document.body != 'undefined') {
// make links open in new window
let anchorEls = p.contentWindow.document.body.querySelectorAll('a')
for (var i = 0; i < anchorEls.length; i++) {
@@ -185,9 +237,31 @@ export default {
this.resizeIframe(el)
},
sanitizeHTML(h) {
// remove <base/> tag if set
return h.replace(/<base .*>/mi, '')
// this function is unused but kept here to use for debugging
debugDOMPurify(removed) {
if (!removed.length) {
return
}
const ignoreNodes = ['target', 'base', 'script', 'v:shapes']
let d = removed.filter((r) => {
if (typeof r.attribute != 'undefined' &&
(ignoreNodes.includes(r.attribute.nodeName) || r.attribute.nodeName.startsWith('xmlns:'))
) {
return false
}
// inline comments
if (typeof r.element != 'undefined' && (r.element.nodeType == 8 || r.element.tagName == 'SCRIPT')) {
return false
}
return true
})
if (d.length) {
console.log(d)
}
},
saveTags() {
@@ -292,7 +366,7 @@ export default {
<tr v-if="message.Bcc && message.Bcc.length" class="small">
<th>Bcc</th>
<td class="privacy">
<span v-for="( t, i ) in message.Bcc ">
<span v-for="(t, i) in message.Bcc">
<template v-if="i > 0">,</template>
<span class="text-spaces">{{ t.Name }}</span>
&lt;<a :href="searchURI(t.Address)" class="text-body">
@@ -510,9 +584,8 @@ export default {
<div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel"
aria-labelledby="nav-html-tab" tabindex="0">
<div id="responsive-view" :class="scaleHTMLPreview" :style="responsiveSizes[scaleHTMLPreview]">
<iframe target-blank="" class="tab-pane d-block" id="preview-html"
:srcdoc="sanitizeHTML(message.HTML)" v-on:load="resizeIframe" frameborder="0"
style="width: 100%; height: 100%; background: #fff;">
<iframe target-blank="" class="tab-pane d-block" id="preview-html" :srcdoc="sanitizedHTML"
v-on:load="resizeIframe" frameborder="0" style="width: 100%; height: 100%; background: #fff;">
</iframe>
</div>
<Attachments v-if="allAttachments(message).length" :message="message"