|
@@ -276,15 +276,15 @@ func filterAndRenderHTML(buf *strings.Builder, n *html.Node, parsedBaseUrl *url.
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- attrNames, htmlAttributes := sanitizeAttributes(parsedBaseUrl, tag, n.Attr, sanitizerOptions)
|
|
|
|
|
- if !hasRequiredAttributes(tag, attrNames) {
|
|
|
|
|
|
|
+ htmlAttributes, hasAllRequiredAttributes := sanitizeAttributes(parsedBaseUrl, tag, n.Attr, sanitizerOptions)
|
|
|
|
|
+ if !hasAllRequiredAttributes {
|
|
|
// The tag doesn't have every required attributes but we're still interested in its content
|
|
// The tag doesn't have every required attributes but we're still interested in its content
|
|
|
filterAndRenderHTMLChildren(buf, n, parsedBaseUrl, sanitizerOptions)
|
|
filterAndRenderHTMLChildren(buf, n, parsedBaseUrl, sanitizerOptions)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
buf.WriteString("<")
|
|
buf.WriteString("<")
|
|
|
buf.WriteString(n.Data)
|
|
buf.WriteString(n.Data)
|
|
|
- if len(attrNames) > 0 {
|
|
|
|
|
|
|
+ if len(htmlAttributes) > 0 {
|
|
|
buf.WriteString(" " + htmlAttributes)
|
|
buf.WriteString(" " + htmlAttributes)
|
|
|
}
|
|
}
|
|
|
buf.WriteString(">")
|
|
buf.WriteString(">")
|
|
@@ -311,37 +311,30 @@ func filterAndRenderHTMLChildren(buf *strings.Builder, n *html.Node, parsedBaseU
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func getExtraAttributes(tagName string, isYouTubeEmbed bool, sanitizerOptions *SanitizerOptions) ([]string, []string) {
|
|
|
|
|
|
|
+func getExtraAttributes(tagName string, isYouTubeEmbed bool, sanitizerOptions *SanitizerOptions) []string {
|
|
|
switch tagName {
|
|
switch tagName {
|
|
|
case "a":
|
|
case "a":
|
|
|
- attributeNames := []string{"rel", "referrerpolicy"}
|
|
|
|
|
htmlAttributes := []string{`rel="noopener noreferrer"`, `referrerpolicy="no-referrer"`}
|
|
htmlAttributes := []string{`rel="noopener noreferrer"`, `referrerpolicy="no-referrer"`}
|
|
|
if sanitizerOptions.OpenLinksInNewTab {
|
|
if sanitizerOptions.OpenLinksInNewTab {
|
|
|
- attributeNames = append(attributeNames, "target")
|
|
|
|
|
htmlAttributes = append(htmlAttributes, `target="_blank"`)
|
|
htmlAttributes = append(htmlAttributes, `target="_blank"`)
|
|
|
}
|
|
}
|
|
|
- return attributeNames, htmlAttributes
|
|
|
|
|
|
|
+ return htmlAttributes
|
|
|
case "video", "audio":
|
|
case "video", "audio":
|
|
|
- return []string{"controls"}, []string{"controls"}
|
|
|
|
|
|
|
+ return []string{"controls"}
|
|
|
case "iframe":
|
|
case "iframe":
|
|
|
- extraAttrNames := []string{}
|
|
|
|
|
- extraHTMLAttributes := []string{}
|
|
|
|
|
|
|
+ extraHTMLAttributes := []string{`sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"`, `loading="lazy"`}
|
|
|
|
|
|
|
|
// Note: the referrerpolicy seems to be required to avoid YouTube error 153 video player configuration error
|
|
// Note: the referrerpolicy seems to be required to avoid YouTube error 153 video player configuration error
|
|
|
// See https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-player-api-client-identity
|
|
// See https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-player-api-client-identity
|
|
|
if isYouTubeEmbed {
|
|
if isYouTubeEmbed {
|
|
|
- extraAttrNames = append(extraAttrNames, "referrerpolicy")
|
|
|
|
|
extraHTMLAttributes = append(extraHTMLAttributes, `referrerpolicy="strict-origin-when-cross-origin"`)
|
|
extraHTMLAttributes = append(extraHTMLAttributes, `referrerpolicy="strict-origin-when-cross-origin"`)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- extraAttrNames = append(extraAttrNames, "sandbox", "loading")
|
|
|
|
|
- extraHTMLAttributes = append(extraHTMLAttributes, `sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"`, `loading="lazy"`)
|
|
|
|
|
-
|
|
|
|
|
- return extraAttrNames, extraHTMLAttributes
|
|
|
|
|
|
|
+ return extraHTMLAttributes
|
|
|
case "img":
|
|
case "img":
|
|
|
- return []string{"loading"}, []string{`loading="lazy"`}
|
|
|
|
|
|
|
+ return []string{`loading="lazy"`}
|
|
|
default:
|
|
default:
|
|
|
- return nil, nil
|
|
|
|
|
|
|
+ return nil
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -499,18 +492,15 @@ func rewriteIframeURL(link string) string {
|
|
|
return link
|
|
return link
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []html.Attribute, sanitizerOptions *SanitizerOptions) ([]string, string) {
|
|
|
|
|
|
|
+func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []html.Attribute, sanitizerOptions *SanitizerOptions) (string, bool) {
|
|
|
htmlAttrs := make([]string, 0, len(attributes))
|
|
htmlAttrs := make([]string, 0, len(attributes))
|
|
|
attrNames := make([]string, 0, len(attributes))
|
|
attrNames := make([]string, 0, len(attributes))
|
|
|
|
|
|
|
|
var isAnchorLink bool
|
|
var isAnchorLink bool
|
|
|
var isYouTubeEmbed bool
|
|
var isYouTubeEmbed bool
|
|
|
|
|
|
|
|
- allowedAttributes, ok := allowedHTMLTagsAndAttributes[tagName]
|
|
|
|
|
- if !ok {
|
|
|
|
|
- // This should never happen, as the tag was validated in the caller of `sanitizeAttributes`
|
|
|
|
|
- return []string{}, ""
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // We know the element is present, as the tag was validated in the caller of `sanitizeAttributes`
|
|
|
|
|
+ allowedAttributes := allowedHTMLTagsAndAttributes[tagName]
|
|
|
|
|
|
|
|
for _, attribute := range attributes {
|
|
for _, attribute := range attributes {
|
|
|
if !slices.Contains(allowedAttributes, attribute.Key) {
|
|
if !slices.Contains(allowedAttributes, attribute.Key) {
|
|
@@ -596,15 +586,15 @@ func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []htm
|
|
|
htmlAttrs = append(htmlAttrs, attribute.Key+`="`+html.EscapeString(value)+`"`)
|
|
htmlAttrs = append(htmlAttrs, attribute.Key+`="`+html.EscapeString(value)+`"`)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if !hasRequiredAttributes(tagName, attrNames) {
|
|
|
|
|
+ return "", false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if !isAnchorLink {
|
|
if !isAnchorLink {
|
|
|
- extraAttrNames, extraHTMLAttributes := getExtraAttributes(tagName, isYouTubeEmbed, sanitizerOptions)
|
|
|
|
|
- if len(extraAttrNames) > 0 {
|
|
|
|
|
- attrNames = append(attrNames, extraAttrNames...)
|
|
|
|
|
- htmlAttrs = append(htmlAttrs, extraHTMLAttributes...)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ htmlAttrs = append(htmlAttrs, getExtraAttributes(tagName, isYouTubeEmbed, sanitizerOptions)...)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return attrNames, strings.Join(htmlAttrs, " ")
|
|
|
|
|
|
|
+ return strings.Join(htmlAttrs, " "), true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func sanitizeSrcsetAttr(parsedBaseURL *url.URL, value string) string {
|
|
func sanitizeSrcsetAttr(parsedBaseURL *url.URL, value string) string {
|