Merge pull request #89 from anatolym/add-link-shortening
add link shortening #79
This commit is contained in:
+42
-2
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: `<a href="http://blah.com">username</a>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: `<a href="http://blah.com">username</a>`},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user