Browse Source

feat(sanitizer): add support for fetchpriority and decoding attributes in img tags

Frédéric Guillot 10 months ago
parent
commit
21d22d7f0b
2 changed files with 117 additions and 1 deletions
  1. 23 1
      internal/reader/sanitizer/sanitizer.go
  2. 94 0
      internal/reader/sanitizer/sanitizer_test.go

+ 23 - 1
internal/reader/sanitizer/sanitizer.go

@@ -46,7 +46,7 @@ var (
 		"h6":         {"id"},
 		"hr":         {},
 		"iframe":     {"width", "height", "frameborder", "src", "allowfullscreen"},
-		"img":        {"alt", "title", "src", "srcset", "sizes", "width", "height"},
+		"img":        {"alt", "title", "src", "srcset", "sizes", "width", "height", "fetchpriority", "decoding"},
 		"ins":        {},
 		"kbd":        {},
 		"li":         {"id"},
@@ -234,6 +234,18 @@ func sanitizeAttributes(baseURL, tagName string, attributes []html.Attribute, sa
 			continue
 		}
 
+		if tagName == "img" && attribute.Key == "fetchpriority" {
+			if !isValidFetchPriorityValue(value) {
+				continue
+			}
+		}
+
+		if tagName == "img" && attribute.Key == "decoding" {
+			if !isValidDecodingValue(value) {
+				continue
+			}
+		}
+
 		if (tagName == "img" || tagName == "source") && attribute.Key == "srcset" {
 			value = sanitizeSrcsetAttr(baseURL, value)
 		}
@@ -540,3 +552,13 @@ func getIntegerAttributeValue(name string, attributes []html.Attribute) int {
 	}
 	return 0
 }
+
+func isValidFetchPriorityValue(value string) bool {
+	allowedValues := []string{"high", "low", "auto"}
+	return slices.Contains(allowedValues, value)
+}
+
+func isValidDecodingValue(value string) bool {
+	allowedValues := []string{"sync", "async", "auto"}
+	return slices.Contains(allowedValues, value)
+}

+ 94 - 0
internal/reader/sanitizer/sanitizer_test.go

@@ -139,6 +139,100 @@ func TestImgWithSrcsetAndNoSrcAttribute(t *testing.T) {
 	}
 }
 
+func TestImgWithFetchPriorityAttribute(t *testing.T) {
+	cases := []struct {
+		input    string
+		expected string
+	}{
+		{
+			`<img src="https://example.org/image.png" fetchpriority="high">`,
+			`<img src="https://example.org/image.png" fetchpriority="high" loading="lazy">`,
+		},
+		{
+			`<img src="https://example.org/image.png" fetchpriority="low">`,
+			`<img src="https://example.org/image.png" fetchpriority="low" loading="lazy">`,
+		},
+		{
+			`<img src="https://example.org/image.png" fetchpriority="auto">`,
+			`<img src="https://example.org/image.png" fetchpriority="auto" loading="lazy">`,
+		},
+	}
+
+	for _, tc := range cases {
+		output := SanitizeHTMLWithDefaultOptions("http://example.org/", tc.input)
+		if output != tc.expected {
+			t.Errorf(`Wrong output for input %q: expected %q, got %q`, tc.input, tc.expected, output)
+		}
+	}
+}
+
+func TestImgWithInvalidFetchPriorityAttribute(t *testing.T) {
+	input := `<img src="https://example.org/image.png" fetchpriority="invalid">`
+	expected := `<img src="https://example.org/image.png" loading="lazy">`
+	output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
+
+	if output != expected {
+		t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
+	}
+}
+
+func TestNonImgWithFetchPriorityAttribute(t *testing.T) {
+	input := `<p fetchpriority="high">Text</p>`
+	expected := `<p>Text</p>`
+	output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
+
+	if output != expected {
+		t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
+	}
+}
+
+func TestImgWithDecodingAttribute(t *testing.T) {
+	cases := []struct {
+		input    string
+		expected string
+	}{
+		{
+			`<img src="https://example.org/image.png" decoding="sync">`,
+			`<img src="https://example.org/image.png" decoding="sync" loading="lazy">`,
+		},
+		{
+			`<img src="https://example.org/image.png" decoding="async">`,
+			`<img src="https://example.org/image.png" decoding="async" loading="lazy">`,
+		},
+		{
+			`<img src="https://example.org/image.png" decoding="auto">`,
+			`<img src="https://example.org/image.png" decoding="auto" loading="lazy">`,
+		},
+	}
+
+	for _, tc := range cases {
+		output := SanitizeHTMLWithDefaultOptions("http://example.org/", tc.input)
+		if output != tc.expected {
+			t.Errorf(`Wrong output for input %q: expected %q, got %q`, tc.input, tc.expected, output)
+		}
+	}
+}
+
+func TestImgWithInvalidDecodingAttribute(t *testing.T) {
+	input := `<img src="https://example.org/image.png" decoding="invalid">`
+	expected := `<img src="https://example.org/image.png" loading="lazy">`
+	output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
+
+	if output != expected {
+		t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
+	}
+}
+
+func TestNonImgWithDecodingAttribute(t *testing.T) {
+	input := `<p decoding="async">Text</p>`
+	expected := `<p>Text</p>`
+	output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
+
+	if output != expected {
+		t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
+	}
+}
+
 func TestSourceWithSrcsetAndMedia(t *testing.T) {
 	input := `<picture><source media="(min-width: 800px)" srcset="elva-800w.jpg"></picture>`
 	expected := `<picture><source media="(min-width: 800px)" srcset="http://example.org/elva-800w.jpg"></picture>`