Просмотр исходного кода

refactor(sanitizer): use a map for iframe allow list

Frédéric Guillot 1 год назад
Родитель
Сommit
ac44507af2

+ 29 - 2
internal/config/config_test.go

@@ -2169,12 +2169,33 @@ func TestYouTubeApiKey(t *testing.T) {
 	}
 }
 
+func TestDefaultYouTubeEmbedUrl(t *testing.T) {
+	os.Clearenv()
+
+	opts, err := NewParser().ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := "https://www.youtube-nocookie.com/embed/"
+	result := opts.YouTubeEmbedUrlOverride()
+
+	if result != expected {
+		t.Fatalf(`Unexpected default value, got %v instead of %v`, result, expected)
+	}
+
+	expected = "www.youtube-nocookie.com"
+	result = opts.YouTubeEmbedDomain()
+	if result != expected {
+		t.Fatalf(`Unexpected YouTube embed domain, got %v instead of %v`, result, expected)
+	}
+}
+
 func TestYouTubeEmbedUrlOverride(t *testing.T) {
 	os.Clearenv()
 	os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
 
-	parser := NewParser()
-	opts, err := parser.ParseEnvironmentVariables()
+	opts, err := NewParser().ParseEnvironmentVariables()
 	if err != nil {
 		t.Fatalf(`Parsing failure: %v`, err)
 	}
@@ -2185,6 +2206,12 @@ func TestYouTubeEmbedUrlOverride(t *testing.T) {
 	if result != expected {
 		t.Fatalf(`Unexpected YOUTUBE_EMBED_URL_OVERRIDE value, got %v instead of %v`, result, expected)
 	}
+
+	expected = "invidious.custom"
+	result = opts.YouTubeEmbedDomain()
+	if result != expected {
+		t.Fatalf(`Unexpected YouTube embed domain, got %v instead of %v`, result, expected)
+	}
 }
 
 func TestParseConfigDumpOutput(t *testing.T) {

+ 10 - 1
internal/config/options.go

@@ -155,6 +155,7 @@ type Options struct {
 	filterEntryMaxAgeDays              int
 	youTubeApiKey                      string
 	youTubeEmbedUrlOverride            string
+	youTubeEmbedDomain                 string
 	oauth2UserCreationAllowed          bool
 	oauth2ClientID                     string
 	oauth2ClientSecret                 string
@@ -521,11 +522,19 @@ func (o *Options) YouTubeApiKey() string {
 	return o.youTubeApiKey
 }
 
-// YouTubeEmbedUrlOverride returns YouTube URL which will be used for embeds
+// YouTubeEmbedUrlOverride returns the YouTube embed URL override if defined.
 func (o *Options) YouTubeEmbedUrlOverride() string {
 	return o.youTubeEmbedUrlOverride
 }
 
+// YouTubeEmbedDomain returns the domain used for YouTube embeds.
+func (o *Options) YouTubeEmbedDomain() string {
+	if o.youTubeEmbedDomain != "" {
+		return o.youTubeEmbedDomain
+	}
+	return "www.youtube-nocookie.com"
+}
+
 // FetchNebulaWatchTime returns true if the Nebula video duration
 // should be fetched and used as a reading time.
 func (o *Options) FetchNebulaWatchTime() bool {

+ 7 - 0
internal/config/parser.go

@@ -298,6 +298,13 @@ func (p *Parser) parseLines(lines []string) (err error) {
 	if port != "" {
 		p.opts.listenAddr = ":" + port
 	}
+
+	youtubeEmbedURL, err := url.Parse(p.opts.youTubeEmbedUrlOverride)
+	if err != nil {
+		return fmt.Errorf("config: invalid YOUTUBE_EMBED_URL_OVERRIDE value: %w", err)
+	}
+	p.opts.youTubeEmbedDomain = youtubeEmbedURL.Hostname()
+
 	return nil
 }
 

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

@@ -110,6 +110,21 @@ var (
 		"munderover":     {},
 		"semantics":      {},
 	}
+
+	iframeAllowList = map[string]struct{}{
+		"bandcamp.com":         {},
+		"cdn.embedly.com":      {},
+		"dailymotion.com":      {},
+		"open.spotify.com":     {},
+		"player.bilibili.com":  {},
+		"player.twitch.tv":     {},
+		"player.vimeo.com":     {},
+		"soundcloud.com":       {},
+		"vk.com":               {},
+		"w.soundcloud.com":     {},
+		"youtube-nocookie.com": {},
+		"youtube.com":          {},
+	}
 )
 
 type SanitizerOptions struct {
@@ -266,7 +281,7 @@ func sanitizeAttributes(parsedBaseUrl *url.URL, baseURL, tagName string, attribu
 		if isExternalResourceAttribute(attribute.Key) {
 			switch {
 			case tagName == "iframe":
-				if !isValidIframeSource(parsedBaseUrl, baseURL, attribute.Val) {
+				if !isValidIframeSource(attribute.Val) {
 					continue
 				}
 				value = rewriteIframeURL(attribute.Val)
@@ -448,39 +463,22 @@ func isBlockedResource(src string) bool {
 	})
 }
 
-func isValidIframeSource(parsedBaseUrl *url.URL, baseURL, src string) bool {
-	whitelist := []string{
-		"bandcamp.com",
-		"cdn.embedly.com",
-		"player.bilibili.com",
-		"player.twitch.tv",
-		"player.vimeo.com",
-		"soundcloud.com",
-		"vk.com",
-		"w.soundcloud.com",
-		"dailymotion.com",
-		"youtube-nocookie.com",
-		"youtube.com",
-		"open.spotify.com",
-	}
-	domain := urllib.Domain(src)
+func isValidIframeSource(iframeSourceURL string) bool {
+	iframeSourceDomain := strings.TrimPrefix(urllib.Domain(iframeSourceURL), "www.")
 
-	baseDomain := baseURL
-	if parsedBaseUrl != nil {
-		baseDomain = parsedBaseUrl.Hostname()
+	if _, ok := iframeAllowList[iframeSourceDomain]; ok {
+		return true
 	}
 
-	// allow iframe from same origin
-	if baseDomain == domain {
+	if ytDomain := config.Opts.YouTubeEmbedDomain(); ytDomain != "" && iframeSourceDomain == strings.TrimPrefix(ytDomain, "www.") {
 		return true
 	}
 
-	// allow iframe from custom invidious instance
-	if config.Opts.InvidiousInstance() == domain {
+	if invidiousInstance := config.Opts.InvidiousInstance(); invidiousInstance != "" && iframeSourceDomain == strings.TrimPrefix(invidiousInstance, "www.") {
 		return true
 	}
 
-	return slices.Contains(whitelist, strings.TrimPrefix(domain, "www."))
+	return false
 }
 
 func rewriteIframeURL(link string) string {

+ 48 - 10
internal/reader/sanitizer/sanitizer_test.go

@@ -13,12 +13,6 @@ import (
 	"miniflux.app/v2/internal/config"
 )
 
-func TestMain(m *testing.M) {
-	config.Opts = config.NewOptions()
-	exitCode := m.Run()
-	os.Exit(exitCode)
-}
-
 func BenchmarkSanitize(b *testing.B) {
 	var testCases = map[string][]string{
 		"miniflux_github.html":    {"https://github.com/miniflux/v2", ""},
@@ -352,6 +346,8 @@ func TestInvalidNestedTag(t *testing.T) {
 }
 
 func TestInvalidIFrame(t *testing.T) {
+	config.Opts = config.NewOptions()
+
 	input := `<iframe src="http://example.org/"></iframe>`
 	expected := ``
 	output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
@@ -361,7 +357,51 @@ func TestInvalidIFrame(t *testing.T) {
 	}
 }
 
+func TestSameDomainIFrame(t *testing.T) {
+	config.Opts = config.NewOptions()
+
+	input := `<iframe src="http://example.com/test"></iframe>`
+	expected := ``
+	output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
+
+	if expected != output {
+		t.Errorf(`Wrong output: %q != %q`, expected, output)
+	}
+}
+
+func TestInvidiousIFrame(t *testing.T) {
+	config.Opts = config.NewOptions()
+
+	input := `<iframe src="https://yewtu.be/watch?v=video_id"></iframe>`
+	expected := `<iframe src="https://yewtu.be/watch?v=video_id" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
+	output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
+
+	if expected != output {
+		t.Errorf(`Wrong output: %q != %q`, expected, output)
+	}
+}
+
+func TestCustomYoutubeEmbedURL(t *testing.T) {
+	os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://www.invidious.custom/embed/")
+
+	defer os.Clearenv()
+	var err error
+	if config.Opts, err = config.NewParser().ParseEnvironmentVariables(); err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	input := `<iframe src="https://www.invidious.custom/embed/1234"></iframe>`
+	expected := `<iframe src="https://www.invidious.custom/embed/1234" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
+	output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
+
+	if expected != output {
+		t.Errorf(`Wrong output: %q != %q`, expected, output)
+	}
+}
+
 func TestIFrameWithChildElements(t *testing.T) {
+	config.Opts = config.NewOptions()
+
 	input := `<iframe src="https://www.youtube.com/"><p>test</p></iframe>`
 	expected := `<iframe src="https://www.youtube.com/" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
 	output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
@@ -750,13 +790,11 @@ func TestReplaceProtocolRelativeYoutubeURL(t *testing.T) {
 }
 
 func TestReplaceYoutubeURLWithCustomURL(t *testing.T) {
-	os.Clearenv()
+	defer os.Clearenv()
 	os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
 
 	var err error
-	parser := config.NewParser()
-	config.Opts, err = parser.ParseEnvironmentVariables()
-
+	config.Opts, err = config.NewParser().ParseEnvironmentVariables()
 	if err != nil {
 		t.Fatalf(`Parsing failure: %v`, err)
 	}