diff options
author | Simon Ser <contact@emersion.fr> | 2020-02-25 18:16:26 +0100 |
---|---|---|
committer | Simon Ser <contact@emersion.fr> | 2020-02-25 18:16:26 +0100 |
commit | 7de332c2bb5cddf6591b93b16e536b845ea7ab76 (patch) | |
tree | 464984925b340a72d2f784fe5d4107cf8c8af09e | |
parent | c96903f3f1e0ed365b0ff4d56e4c423b4a7902e5 (diff) | |
download | alps-7de332c2bb5cddf6591b93b16e536b845ea7ab76.tar.gz alps-7de332c2bb5cddf6591b93b16e536b845ea7ab76.zip |
plugins/viewtext: linkify plaintext messages
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | plugins/viewtext/viewer.go | 97 |
3 files changed, 86 insertions, 14 deletions
@@ -21,6 +21,7 @@ require ( github.com/mattn/go-isatty v0.0.12 // indirect github.com/microcosm-cc/bluemonday v1.0.2 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb + gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 // indirect golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect @@ -67,6 +67,8 @@ github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 h1:oYrL81N608MLZhma3ruL8qTM4xcpYECGut8KSxRY59g= +gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 h1:wCWoJcFExDgyYx2m2hpHgwz8W3+FPdfldvIgzqDIhyg= diff --git a/plugins/viewtext/viewer.go b/plugins/viewtext/viewer.go index cca38a8..9a91f08 100644 --- a/plugins/viewtext/viewer.go +++ b/plugins/viewtext/viewer.go @@ -1,21 +1,55 @@ package koushinviewtext import ( - "bytes" + "bufio" "fmt" "html/template" - "io/ioutil" + "net/url" "strings" "git.sr.ht/~emersion/koushin" koushinbase "git.sr.ht/~emersion/koushin/plugins/base" "github.com/emersion/go-message" + "gitlab.com/golang-commonmark/linkify" ) // TODO: dim quotes and "On xxx, xxx wrote:" lines -// TODO: turn URLs into links -const tpl = `<pre>{{.}}</pre>` +const ( + tplStr = `<pre>{{range .}}{{.}}{{end}}</pre>` + linkTplStr = `<a href="{{.Href}}" target="_blank" rel="nofollow noopener">{{.Text}}</a>` +) + +var tpl *template.Template + +func init() { + tpl = template.Must(template.New("view-text.html").Parse(tplStr)) + template.Must(tpl.New("view-text-link.html").Parse(linkTplStr)) +} + +type linkRenderData struct { + Href string + Text string +} + +var allowedSchemes = map[string]bool{ + "http": true, + "https": true, + "mailto": true, + "ftp": true, + "sftp": true, + "ftps": true, + "tel": true, +} + +func executeTemplate(name string, data interface{}) (template.HTML, error) { + var sb strings.Builder + err := tpl.ExecuteTemplate(&sb, name, data) + if err != nil { + return "", err + } + return template.HTML(sb.String()), nil +} type viewer struct{} @@ -28,20 +62,55 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage return nil, koushinbase.ErrViewUnsupported } - body, err := ioutil.ReadAll(part.Body) - if err != nil { - return nil, fmt.Errorf("failed to read part body: %v", err) - } + var tokens []interface{} + scanner := bufio.NewScanner(part.Body) + for scanner.Scan() { + l := scanner.Text() - t := template.Must(template.New("view-text.html").Parse(tpl)) + i := 0 + for _, link := range linkify.Links(l) { + href := l[link.Start:link.End] + if link.Scheme == "" { + href = "https://" + href + } else if !strings.HasPrefix(href, link.Scheme) { + href = link.Scheme + href + } - var buf bytes.Buffer - err = t.Execute(&buf, string(body)) - if err != nil { - return nil, err + u, err := url.Parse(href) + if err != nil { + continue + } + + if !allowedSchemes[u.Scheme] { + continue + } + + // TODO: redirect mailto links to the composer + + if i < link.Start { + tokens = append(tokens, l[i:link.Start]) + } + tok, err := executeTemplate("view-text-link.html", linkRenderData{ + Href: href, + Text: l[link.Start:link.End], + }) + if err != nil { + return nil, err + } + tokens = append(tokens, tok) + i = link.End + } + if i < len(l) { + tokens = append(tokens, l[i:]) + } + + tokens = append(tokens, "\n") + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to read part body: %v", err) } - return template.HTML(buf.String()), nil + return executeTemplate("view-text.html", tokens) } func init() { |