diff --git a/plugins/viewhtml/plugin.go b/plugins/viewhtml/plugin.go index b34372f..1007d4a 100644 --- a/plugins/viewhtml/plugin.go +++ b/plugins/viewhtml/plugin.go @@ -9,6 +9,7 @@ import ( "strings" "git.sr.ht/~emersion/koushin" + koushinbase "git.sr.ht/~emersion/koushin/plugins/base" "github.com/labstack/echo/v4" ) @@ -20,6 +21,17 @@ var ( func init() { p := koushin.GoPlugin{Name: "viewhtml"} + p.Inject("message.html", func(ctx *koushin.Context, _data koushin.RenderData) error { + data := _data.(*koushinbase.MessageRenderData) + data.Extra["RemoteResourcesAllowed"] = ctx.QueryParam("allow-remote-resources") == "1" + hasRemoteResources := false + if v := ctx.Get("viewhtml.hasRemoteResources"); v != nil { + hasRemoteResources = v.(bool) + } + data.Extra["HasRemoteResources"] = hasRemoteResources + return nil + }) + p.GET("/proxy", func(ctx *koushin.Context) error { if !proxyEnabled { return echo.NewHTTPError(http.StatusForbidden, "proxy disabled") diff --git a/plugins/viewhtml/sanitize.go b/plugins/viewhtml/sanitize.go index a931fe1..ba2aca9 100644 --- a/plugins/viewhtml/sanitize.go +++ b/plugins/viewhtml/sanitize.go @@ -71,7 +71,9 @@ var allowedStyles = map[string]bool{ } type sanitizer struct { - msg *koushinbase.IMAPMessage + msg *koushinbase.IMAPMessage + allowRemoteResources bool + hasRemoteResources bool } func (san *sanitizer) sanitizeImageURL(src string) string { @@ -94,7 +96,9 @@ func (san *sanitizer) sanitizeImageURL(src string) string { return part.URL(true).String() case "https": - if !proxyEnabled { + san.hasRemoteResources = true + + if !proxyEnabled || !san.allowRemoteResources { return "about:blank" } diff --git a/plugins/viewhtml/viewer.go b/plugins/viewhtml/viewer.go index 47f5eea..abc9f2d 100644 --- a/plugins/viewhtml/viewer.go +++ b/plugins/viewhtml/viewer.go @@ -25,6 +25,8 @@ var tpl = template.Must(template.New("view-html.html").Parse(tplSrc)) type viewer struct{} func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { + allowRemoteResources := ctx.QueryParam("allow-remote-resources") == "1" + mimeType, _, err := part.Header.ContentType() if err != nil { return nil, err @@ -38,12 +40,17 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage return nil, fmt.Errorf("failed to read part body: %v", err) } - san := sanitizer{msg} + san := sanitizer{ + msg: msg, + allowRemoteResources: allowRemoteResources, + } body, err = san.sanitizeHTML(body) if err != nil { return nil, fmt.Errorf("failed to sanitize HTML part: %v", err) } + ctx.Set("viewhtml.hasRemoteResources", san.hasRemoteResources) + var buf bytes.Buffer err = tpl.Execute(&buf, string(body)) if err != nil { diff --git a/themes/sourcehut/message.html b/themes/sourcehut/message.html index fb38932..17f5976 100644 --- a/themes/sourcehut/message.html +++ b/themes/sourcehut/message.html @@ -99,6 +99,13 @@ {{end}} + {{if and .Extra.HasRemoteResources (not .Extra.RemoteResourcesAllowed)}} +

+ This message contains remote content. + Load +

+ {{end}} + {{if .View}} {{.View}} {{else}}