Render and sanitize chat messages server-side. (#237)
* Render and sanitize chat messages server-side. Closes #235 * Render content.md server-side and return it in the client config * Remove showdown from web project * Update api spec * Move example user content file
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"mvdan.cc/xurls"
|
||||
)
|
||||
|
||||
//ChatMessage represents a single chat message
|
||||
type ChatMessage struct {
|
||||
@@ -16,6 +26,91 @@ type ChatMessage struct {
|
||||
}
|
||||
|
||||
//Valid checks to ensure the message is valid
|
||||
func (s ChatMessage) Valid() bool {
|
||||
return s.Author != "" && s.Body != "" && s.ID != ""
|
||||
func (m ChatMessage) Valid() bool {
|
||||
return m.Author != "" && m.Body != "" && m.ID != ""
|
||||
}
|
||||
|
||||
// RenderAndSanitizeMessageBody will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
|
||||
// the message into something safe and renderable for clients.
|
||||
func (m *ChatMessage) RenderAndSanitizeMessageBody() {
|
||||
raw := m.Body
|
||||
|
||||
// Set the new, sanitized and rendered message body
|
||||
m.Body = RenderAndSanitize(raw)
|
||||
}
|
||||
|
||||
// RenderAndSanitize will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
|
||||
// the message into something safe and renderable for clients.
|
||||
func RenderAndSanitize(raw string) string {
|
||||
rendered := renderMarkdown(raw)
|
||||
safe := sanitize(rendered)
|
||||
|
||||
// Set the new, sanitized and rendered message body
|
||||
return strings.TrimSpace(safe)
|
||||
}
|
||||
|
||||
func renderMarkdown(raw string) string {
|
||||
markdown := goldmark.New(
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithUnsafe(),
|
||||
),
|
||||
goldmark.WithExtensions(
|
||||
extension.NewLinkify(
|
||||
extension.WithLinkifyAllowedProtocols([][]byte{
|
||||
[]byte("http:"),
|
||||
[]byte("https:"),
|
||||
}),
|
||||
extension.WithLinkifyURLRegexp(
|
||||
xurls.Strict,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
var buf bytes.Buffer
|
||||
if err := markdown.Convert([]byte(trimmed), &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func sanitize(raw string) string {
|
||||
p := bluemonday.StrictPolicy()
|
||||
|
||||
// Require URLs to be parseable by net/url.Parse
|
||||
p.AllowStandardURLs()
|
||||
|
||||
// Allow links
|
||||
p.AllowAttrs("href").OnElements("a")
|
||||
|
||||
// Force all URLs to have "noreferrer" in their rel attribute.
|
||||
p.RequireNoReferrerOnLinks(true)
|
||||
|
||||
// Links will get target="_blank" added to them.
|
||||
p.AddTargetBlankToFullyQualifiedLinks(true)
|
||||
|
||||
// Allow paragraphs
|
||||
p.AllowElements("br")
|
||||
p.AllowElements("p")
|
||||
|
||||
// Allow img tags
|
||||
p.AllowElements("img")
|
||||
p.AllowAttrs("src").OnElements("img")
|
||||
p.AllowAttrs("alt").OnElements("img")
|
||||
p.AllowAttrs("title").OnElements("img")
|
||||
|
||||
// Custom emoji have a class already specified.
|
||||
// We should only allow classes on emoji, not *all* imgs.
|
||||
// But TODO.
|
||||
p.AllowAttrs("class").OnElements("img")
|
||||
|
||||
// Allow bold
|
||||
p.AllowElements("strong")
|
||||
|
||||
// Allow emphasis
|
||||
p.AllowElements("em")
|
||||
|
||||
return p.Sanitize(raw)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user