Merge pull request #89 from anatolym/add-link-shortening

add link shortening #79
This commit is contained in:
Umputun
2018-06-21 21:23:07 -05:00
committed by GitHub
2 changed files with 96 additions and 3 deletions
+42 -2
View File
@@ -2,9 +2,12 @@ package store
import (
"html/template"
"net/url"
"regexp"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/microcosm-cc/bluemonday"
)
@@ -61,6 +64,9 @@ const (
HardDelete DeleteMode = 1
)
// Maximum length for URL text shortening.
const shortURLLen = 32
// PrepareUntrusted pre-processes a comment received from untrusted source by clearing all
// autogen fields and reset everything users not supposed to provide
func (c *Comment) PrepareUntrusted() {
@@ -91,13 +97,47 @@ func (c *Comment) SetDeleted(mode DeleteMode) {
}
}
// Sanitize clean dangerous html/js from the comment
// Sanitize clean dangerous html/js from the comment, shorten autolinks.
func (c *Comment) Sanitize() {
p := bluemonday.UGCPolicy()
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
c.Text = p.Sanitize(c.Text)
c.Text = shortenAutoLinks(p.Sanitize(c.Text), shortURLLen)
c.Orig = p.Sanitize(c.Orig)
c.User.ID = template.HTMLEscapeString(c.User.ID)
c.User.Name = template.HTMLEscapeString(c.User.Name)
c.User.Picture = p.Sanitize(c.User.Picture)
}
// Shortens all the automatic links in HTML: auto link has equal "href" and "text" attributes.
func shortenAutoLinks(commentHTML string, max int) string {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(commentHTML))
if err != nil {
return commentHTML
}
doc.Find("a").Each(func(i int, s *goquery.Selection) {
if href, ok := s.Attr("href"); ok {
if href != s.Text() || len(href) < max+3 || max < 3 {
return
}
url, err := url.Parse(href)
if err != nil {
return
}
url.Path, url.RawQuery, url.Fragment = "", "", ""
host := url.String()
if host == "" {
return
}
short := href[:max-3]
if len(short) < len(host) {
short = host
}
s.SetText(short + "...")
}
})
if html, err := doc.Find("body").Html(); err == nil {
return html
}
return commentHTML
}
+54 -1
View File
@@ -16,7 +16,7 @@ func TestComment_Sanitize(t *testing.T) {
{inp: Comment{}, out: Comment{}},
{
inp: Comment{
Text: `blah <a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>` + "\n\t",
Text: `blah <a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS</a>` + "\n\t",
User: User{ID: `<a href="http://blah.com">username</a>`},
},
out: Comment{
@@ -24,6 +24,16 @@ func TestComment_Sanitize(t *testing.T) {
User: User{ID: `&lt;a href=&#34;http://blah.com&#34;&gt;username&lt;/a&gt;`},
},
},
{
inp: Comment{
Text: `blah <a href="https://www.reddit.com/r/golang/comments/8jdo2l/remark42_is_a_selfhosted_lightweight_and_simple/">https://www.reddit.com/r/golang/comments/8jdo2l/remark42_is_a_selfhosted_lightweight_and_simple/</a>` + "\n\t",
User: User{ID: `<a href="http://blah.com">username</a>`},
},
out: Comment{
Text: `blah <a href="https://www.reddit.com/r/golang/comments/8jdo2l/remark42_is_a_selfhosted_lightweight_and_simple/" rel="nofollow">https://www.reddit.com/r/gola...</a>` + "\n\t",
User: User{ID: `&lt;a href=&#34;http://blah.com&#34;&gt;username&lt;/a&gt;`},
},
},
}
for n, tt := range tbl {
@@ -110,3 +120,46 @@ func TestComment_SetDeletedHard(t *testing.T) {
assert.False(t, comment.Pin)
assert.Equal(t, User{Name: "deleted", ID: "deleted", Picture: "", Admin: false, Blocked: false, IP: ""}, comment.User)
}
func TestComment_ShortenAutoLinks(t *testing.T) {
tbl := []struct {
max int
in, out string
}{
{32, "", ""},
{32, "text", "text"},
{32, "<p>asd</p>", "<p>asd</p>"},
{5, `<a href="incorrect-url">incorrect-url</a>`, `<a href="incorrect-url">incorrect-url</a>`},
{32, `<a href="https://blah.com">some text, not href</a>`, `<a href="https://blah.com">some text, not href</a>`},
{
32,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
},
{
31,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=1...</a>`,
},
{
15,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com...</a>`,
},
{
3,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com...</a>`,
},
{
-1,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
`<a href="https://blah.com/a/b/c/d?g=123#anc">https://blah.com/a/b/c/d?g=123#anc</a>`,
},
}
for n, tt := range tbl {
got := shortenAutoLinks(tt.in, tt.max)
assert.Equalf(t, tt.out, got, "check #%d", n)
}
}