plugins/viewhtml: add opt-in query param to load remote resources

Loading remote resources by default has privacy implications.
This commit is contained in:
Simon Ser 2020-02-25 16:13:10 +01:00
parent a8a3c82579
commit b3f98de1da
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
4 changed files with 33 additions and 3 deletions

View file

@ -9,6 +9,7 @@ import (
"strings" "strings"
"git.sr.ht/~emersion/koushin" "git.sr.ht/~emersion/koushin"
koushinbase "git.sr.ht/~emersion/koushin/plugins/base"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -20,6 +21,17 @@ var (
func init() { func init() {
p := koushin.GoPlugin{Name: "viewhtml"} 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 { p.GET("/proxy", func(ctx *koushin.Context) error {
if !proxyEnabled { if !proxyEnabled {
return echo.NewHTTPError(http.StatusForbidden, "proxy disabled") return echo.NewHTTPError(http.StatusForbidden, "proxy disabled")

View file

@ -71,7 +71,9 @@ var allowedStyles = map[string]bool{
} }
type sanitizer struct { type sanitizer struct {
msg *koushinbase.IMAPMessage msg *koushinbase.IMAPMessage
allowRemoteResources bool
hasRemoteResources bool
} }
func (san *sanitizer) sanitizeImageURL(src string) string { func (san *sanitizer) sanitizeImageURL(src string) string {
@ -94,7 +96,9 @@ func (san *sanitizer) sanitizeImageURL(src string) string {
return part.URL(true).String() return part.URL(true).String()
case "https": case "https":
if !proxyEnabled { san.hasRemoteResources = true
if !proxyEnabled || !san.allowRemoteResources {
return "about:blank" return "about:blank"
} }

View file

@ -25,6 +25,8 @@ var tpl = template.Must(template.New("view-html.html").Parse(tplSrc))
type viewer struct{} type viewer struct{}
func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { 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() mimeType, _, err := part.Header.ContentType()
if err != nil { if err != nil {
return nil, err 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) 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) body, err = san.sanitizeHTML(body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to sanitize HTML part: %v", err) return nil, fmt.Errorf("failed to sanitize HTML part: %v", err)
} }
ctx.Set("viewhtml.hasRemoteResources", san.hasRemoteResources)
var buf bytes.Buffer var buf bytes.Buffer
err = tpl.Execute(&buf, string(body)) err = tpl.Execute(&buf, string(body))
if err != nil { if err != nil {

View file

@ -99,6 +99,13 @@
{{end}} {{end}}
</ul> </ul>
{{if and .Extra.HasRemoteResources (not .Extra.RemoteResourcesAllowed)}}
<p class="alert alert-info">
This message contains remote content.
<a href="?part={{.PartPath}}&allow-remote-resources=1" class="alert-link">Load</a>
</p>
{{end}}
{{if .View}} {{if .View}}
{{.View}} {{.View}}
{{else}} {{else}}