Review fixes

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-01 19:44:23 +04:00
parent e5b155a584
commit 821f66cc20
15 changed files with 255 additions and 39 deletions

View File

@@ -100,7 +100,7 @@ func renderNode(buf *bytes.Buffer, n Node) error {
return fmt.Errorf("cannot render image node: %w", err)
}
buf.WriteString("<img")
writeAttr(buf, "src", attrs.Src)
writeAttr(buf, "src", safeImageSrc(attrs.Src))
if attrs.Alt != nil {
writeAttr(buf, "alt", *attrs.Alt)
}
@@ -274,21 +274,36 @@ const linkRelBlankTargetDefault = "noopener noreferrer"
// linkRelToEmit returns the rel attribute value for a link mark, or empty when
// the attribute should be omitted. When target opens a new browsing context
// (_blank) and the document provides no rel, browsers would grant the opened
// page access to window.opener unless noopener is set.
// (_blank), noopener is always injected to prevent the opened page from
// accessing window.opener, even when the document supplies a custom rel.
func linkRelToEmit(attrs LinkAttrs) string {
blanksTarget := attrs.Target != nil &&
strings.EqualFold(strings.TrimSpace(*attrs.Target), "_blank")
if attrs.Rel != nil {
if s := strings.TrimSpace(*attrs.Rel); s != "" {
if blanksTarget {
return ensureNoopener(s)
}
return s
}
}
if attrs.Target == nil {
return ""
if blanksTarget {
return linkRelBlankTargetDefault
}
if !strings.EqualFold(strings.TrimSpace(*attrs.Target), "_blank") {
return ""
return ""
}
// ensureNoopener returns rel unchanged when it already contains the noopener
// token (case-insensitive check). Otherwise it appends " noopener".
func ensureNoopener(rel string) string {
for tok := range strings.FieldsSeq(rel) {
if strings.EqualFold(tok, "noopener") {
return rel
}
}
return linkRelBlankTargetDefault
return rel + " noopener"
}
func writeAttr(buf *bytes.Buffer, name, value string) {
@@ -334,3 +349,36 @@ func safeLinkHref(href string) string {
}
return href
}
// safeImageSrc returns a value safe to use in an HTML img src attribute.
// Only http, https, and data schemes are permitted; everything else
// (javascript:, vbscript:, etc.) is replaced with an empty string so the
// image simply does not render.
func safeImageSrc(src string) string {
src = strings.TrimSpace(src)
if src == "" {
return ""
}
if strings.HasPrefix(src, "/") {
if len(src) > 1 && (src[1] == '/' || src[1] == '\\') {
return ""
}
return src
}
u, err := url.Parse(src)
if err != nil {
return ""
}
if u.Scheme != "" {
switch strings.ToLower(u.Scheme) {
case "http", "https", "data":
return src
default:
return ""
}
}
if u.Host != "" {
return ""
}
return src
}