From 7de332c2bb5cddf6591b93b16e536b845ea7ab76 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 25 Feb 2020 18:16:26 +0100 Subject: [PATCH] plugins/viewtext: linkify plaintext messages --- go.mod | 1 + go.sum | 2 + plugins/viewtext/viewer.go | 99 ++++++++++++++++++++++++++++++++------ 3 files changed, 87 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 2dc7ba1..3035f2e 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 67a10ac..50b1faa 100644 --- a/go.sum +++ b/go.sum @@ -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 = `
{{.}}
` +const ( + tplStr = `
{{range .}}{{.}}{{end}}
` + linkTplStr = `{{.Text}}` +) + +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 { + var tokens []interface{} + scanner := bufio.NewScanner(part.Body) + for scanner.Scan() { + l := scanner.Text() + + 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 + } + + 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) } - t := template.Must(template.New("view-text.html").Parse(tpl)) - - var buf bytes.Buffer - err = t.Execute(&buf, string(body)) - if err != nil { - return nil, err - } - - return template.HTML(buf.String()), nil + return executeTemplate("view-text.html", tokens) } func init() {