diff --git a/internal/text/markdown.go b/internal/text/markdown.go index e4b633e75..8486d6935 100644 --- a/internal/text/markdown.go +++ b/internal/text/markdown.go @@ -24,6 +24,7 @@ import ( "strings" "code.superseriousbusiness.org/gotosocial/internal/gtsmodel" + "code.superseriousbusiness.org/gotosocial/internal/id" "code.superseriousbusiness.org/gotosocial/internal/log" "code.superseriousbusiness.org/gotosocial/internal/regexes" "codeberg.org/gruf/go-byteutil" @@ -118,6 +119,18 @@ func (f *Formatter) fromMarkdown( } } + // Inject a footnote ID prefix to avoid + // footnote ID clashes. StatusID isn't + // always set (eg., when parsing instance + // description markdown), so take a random + // ULID if it's not. + var footnoteIDPrefix string + if statusID != "" { + footnoteIDPrefix = statusID + "-" + } else { + footnoteIDPrefix = id.NewULID() + "-" + } + // Instantiate goldmark parser for // markdown, using custom renderer // to add hashtag/mention links. @@ -141,7 +154,9 @@ func (f *Formatter) fromMarkdown( extension.NewLinkify( extension.WithLinkifyURLRegexp(regexes.URLLike), ), - extension.Footnote, + extension.NewFootnote( + extension.WithFootnoteIDPrefix(footnoteIDPrefix), + ), extension.Strikethrough, ), ) diff --git a/internal/text/markdown_test.go b/internal/text/markdown_test.go index 347ca2928..14578398d 100644 --- a/internal/text/markdown_test.go +++ b/internal/text/markdown_test.go @@ -61,7 +61,9 @@ const ( mdCodeBlockWithNewlines = "some code coming up\n\n```\n\n\n\n```\nthat was some code" mdCodeBlockWithNewlinesExpected = "
some code coming up
\n\n\nthat was some code
" mdWithFootnote = "fox mulder,fbi.[^1]\n\n[^1]: federated bureau of investigation" - mdWithFootnoteExpected = "fox mulder,fbi.1
federated bureau of investigation ↩︎
fox mulder,fbi.1
federated bureau of investigation ↩︎
hello this is a cheeky relative link: click it!
" mdWithBlockQuote = "get ready, there's a block quote coming:\n\n>line1\n>line2\n>\n>line3\n\n" mdWithBlockQuoteExpected = "get ready, there's a block quote coming:
" mdHashtagAndCodeBlock = "#Hashtag\n\n```\n#Hashtag\n```" @@ -156,6 +158,11 @@ func (suite *MarkdownTestSuite) TestParseWithFootnote() { suite.Equal(mdWithFootnoteExpected, formatted.HTML) } +func (suite *MarkdownTestSuite) TestParseWithAttemptedRelative() { + formatted := suite.FromMarkdown(mdWithAttemptedRelative) + suite.Equal(mdWithAttemptedRelativeExpected, formatted.HTML) +} + func (suite *MarkdownTestSuite) TestParseWithBlockquote() { formatted := suite.FromMarkdown(mdWithBlockQuote) suite.Equal(mdWithBlockQuoteExpected, formatted.HTML) diff --git a/internal/text/sanitize.go b/internal/text/sanitize.go index 29e1df1d8..87f039f31 100644 --- a/internal/text/sanitize.go +++ b/internal/text/sanitize.go @@ -124,17 +124,28 @@ var regular *bluemonday.Policy = func() *bluemonday.Policy { */ // Permit hyperlinks. - p.AllowAttrs("class", "href", "rel").OnElements("a") + p.AllowAttrs("class", "rel").OnElements("a") + + // Permit footnote roles on anchor elements. + p.AllowAttrs("role").Matching(regexp.MustCompile("^doc-noteref$")).OnElements("a") + p.AllowAttrs("role").Matching(regexp.MustCompile("^doc-backlink$")).OnElements("a") // URLs must be parseable by net/url.Parse(). p.RequireParseableURLs(true) - // Most common URL schemes only. + // Relative URLs are OK as we + // need fragments for footnotes. + p.AllowRelativeURLs(true) + + // However *only* allow common schemes, and also + // relative URLs beginning with "#", ie., fragments. + // We don't want URL's like "../../peepee.html". p.AllowURLSchemes("mailto", "http", "https") + p.AllowAttrs("href").Matching(regexp.MustCompile("^(?:#|mailto|https://|http://).+$")).OnElements("a") // Force rel="noreferrer". // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/noreferrer - p.RequireNoReferrerOnLinks(true) + p.RequireNoReferrerOnFullyQualifiedLinks(true) // Add rel="nofollow" on all fully qualified (not relative) links. // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel#nofollowline1
line2line3