diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index f925e2c9..79095374 100644
--- a/internal/config/config_test.go
+++ b/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) {
diff --git a/internal/config/options.go b/internal/config/options.go
index 9605dfe3..636592c1 100644
--- a/internal/config/options.go
+++ b/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 {
diff --git a/internal/config/parser.go b/internal/config/parser.go
index 29ef5018..27b55120 100644
--- a/internal/config/parser.go
+++ b/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
}
diff --git a/internal/reader/sanitizer/sanitizer.go b/internal/reader/sanitizer/sanitizer.go
index 6e9f879e..911aee93 100644
--- a/internal/reader/sanitizer/sanitizer.go
+++ b/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()
- }
-
- // allow iframe from same origin
- if baseDomain == domain {
+ if _, ok := iframeAllowList[iframeSourceDomain]; ok {
return true
}
- // allow iframe from custom invidious instance
- if config.Opts.InvidiousInstance() == domain {
+ if ytDomain := config.Opts.YouTubeEmbedDomain(); ytDomain != "" && iframeSourceDomain == strings.TrimPrefix(ytDomain, "www.") {
return true
}
- return slices.Contains(whitelist, strings.TrimPrefix(domain, "www."))
+ if invidiousInstance := config.Opts.InvidiousInstance(); invidiousInstance != "" && iframeSourceDomain == strings.TrimPrefix(invidiousInstance, "www.") {
+ return true
+ }
+
+ return false
}
func rewriteIframeURL(link string) string {
diff --git a/internal/reader/sanitizer/sanitizer_test.go b/internal/reader/sanitizer/sanitizer_test.go
index 9ce692e2..efa53ff1 100644
--- a/internal/reader/sanitizer/sanitizer_test.go
+++ b/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 := ``
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 := ``
+ 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 := ``
+ expected := ``
+ 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 := ``
+ expected := ``
+ 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 := ``
expected := ``
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)
}