Procházet zdrojové kódy

fix: avoid YouTube error 153 video player configuration error

Frédéric Guillot před 5 měsíci
rodič
revize
7e2dd3afe6

+ 3 - 1
internal/reader/rewrite/content_rewrite_functions.go

@@ -286,7 +286,9 @@ func getYoutubVideoIDFromURL(entryURL string) string {
 }
 
 func buildVideoPlayerIframe(absoluteVideoURL string) string {
-	return `<iframe width="650" height="350" frameborder="0" src="` + absoluteVideoURL + `" allowfullscreen></iframe>`
+	// 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
+	return `<iframe width="650" height="350" frameborder="0" src="` + absoluteVideoURL + `" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe>`
 }
 
 func addVideoPlayerIframe(absoluteVideoURL, entryContent string) string {

+ 34 - 34
internal/reader/rewrite/content_rewrite_test.go

@@ -72,7 +72,7 @@ func TestRewriteYoutubeVideoLink(t *testing.T) {
 	controlEntry := &model.Entry{
 		URL:     "https://www.youtube.com/watch?v=1234",
 		Title:   `A title`,
-		Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1234" allowfullscreen></iframe><br>Video Description`,
+		Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1234" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video Description`,
 	}
 	testEntry := &model.Entry{
 		URL:     "https://www.youtube.com/watch?v=1234",
@@ -92,7 +92,7 @@ func TestRewriteYoutubeShortLink(t *testing.T) {
 	controlEntry := &model.Entry{
 		URL:     "https://www.youtube.com/shorts/1LUWKWZkPjo",
 		Title:   `A title`,
-		Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1LUWKWZkPjo" allowfullscreen></iframe><br>Video Description`,
+		Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1LUWKWZkPjo" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video Description`,
 	}
 	testEntry := &model.Entry{
 		URL:     "https://www.youtube.com/shorts/1LUWKWZkPjo",
@@ -140,7 +140,7 @@ func TestRewriteYoutubeLinkAndCustomEmbedURL(t *testing.T) {
 	controlEntry := &model.Entry{
 		URL:     "https://www.youtube.com/watch?v=1234",
 		Title:   `A title`,
-		Content: `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/1234" allowfullscreen></iframe><br>Video Description`,
+		Content: `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/1234" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video Description`,
 	}
 	testEntry := &model.Entry{
 		URL:     "https://www.youtube.com/watch?v=1234",
@@ -159,7 +159,7 @@ func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
 	controlEntry := &model.Entry{
 		URL:     "https://www.youtube.com/watch?v=1234",
 		Title:   `A title`,
-		Content: `<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/1234" allowfullscreen></iframe><br>Video Description`,
+		Content: `<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/1234" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video Description`,
 	}
 	testEntry := &model.Entry{
 		URL:     "https://www.youtube.com/watch?v=1234",
@@ -179,7 +179,7 @@ func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
 	controlEntry := &model.Entry{
 		URL:     "https://www.youtube.com/shorts/1LUWKWZkPjo",
 		Title:   `A title`,
-		Content: `<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/1LUWKWZkPjo" allowfullscreen></iframe><br>Video Description`,
+		Content: `<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/1LUWKWZkPjo" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video Description`,
 	}
 	testEntry := &model.Entry{
 		URL:     "https://www.youtube.com/shorts/1LUWKWZkPjo",
@@ -199,16 +199,16 @@ func TestAddYoutubeVideoFromId(t *testing.T) {
 
 	scenarios := map[string]string{
 		// Test with single YouTube ID
-		`Some content with youtube ID <script type="text/javascript" data-reactid="6">window.__APOLLO_STATE__ = {youtube_id: "9uASADiYe_8"}</script>`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/9uASADiYe_8" allowfullscreen></iframe><br>Some content with youtube ID <script type="text/javascript" data-reactid="6">window.__APOLLO_STATE__ = {youtube_id: "9uASADiYe_8"}</script>`,
+		`Some content with youtube ID <script type="text/javascript" data-reactid="6">window.__APOLLO_STATE__ = {youtube_id: "9uASADiYe_8"}</script>`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/9uASADiYe_8" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Some content with youtube ID <script type="text/javascript" data-reactid="6">window.__APOLLO_STATE__ = {youtube_id: "9uASADiYe_8"}</script>`,
 
 		// Test with multiple YouTube IDs
-		`Content with youtube_id: "dQw4w9WgXcQ" and youtube_id: "jNQXAC9IVRw"`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br><iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/jNQXAC9IVRw" allowfullscreen></iframe><br>Content with youtube_id: "dQw4w9WgXcQ" and youtube_id: "jNQXAC9IVRw"`,
+		`Content with youtube_id: "dQw4w9WgXcQ" and youtube_id: "jNQXAC9IVRw"`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br><iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/jNQXAC9IVRw" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Content with youtube_id: "dQw4w9WgXcQ" and youtube_id: "jNQXAC9IVRw"`,
 
 		// Test with YouTube ID using equals sign
-		`Some content with youtube_id = "dQw4w9WgXcQ"`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Some content with youtube_id = "dQw4w9WgXcQ"`,
+		`Some content with youtube_id = "dQw4w9WgXcQ"`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Some content with youtube_id = "dQw4w9WgXcQ"`,
 
 		// Test with spaces around delimiters
-		`Some content with youtube_id : "dQw4w9WgXcQ"`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Some content with youtube_id : "dQw4w9WgXcQ"`,
+		`Some content with youtube_id : "dQw4w9WgXcQ"`: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Some content with youtube_id : "dQw4w9WgXcQ"`,
 
 		// Test with YouTube ID without quotes (regex requires quotes)
 		`Some content with youtube_id: dQw4w9WgXcQ and more`: `Some content with youtube_id: dQw4w9WgXcQ and more`,
@@ -245,7 +245,7 @@ func TestAddYoutubeVideoFromIdWithCustomEmbedURL(t *testing.T) {
 	}
 
 	input := `Some content with youtube_id: "dQw4w9WgXcQ"`
-	expected := `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Some content with youtube_id: "dQw4w9WgXcQ"`
+	expected := `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Some content with youtube_id: "dQw4w9WgXcQ"`
 
 	actual := addYoutubeVideoFromId(input)
 	if actual != expected {
@@ -260,35 +260,35 @@ func TestAddInvidiousVideo(t *testing.T) {
 		// Test with various Invidious instances
 		"https://invidious.io/watch?v=dQw4w9WgXcQ": {
 			"Some video content",
-			`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Some video content`,
+			`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Some video content`,
 		},
 		"https://yewtu.be/watch?v=jNQXAC9IVRw": {
 			"Another video description",
-			`<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/jNQXAC9IVRw" allowfullscreen></iframe><br>Another video description`,
+			`<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/jNQXAC9IVRw" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Another video description`,
 		},
 		"http://invidious.snopyta.org/watch?v=dQw4w9WgXcQ": {
 			"HTTP instance test",
-			`<iframe width="650" height="350" frameborder="0" src="https://invidious.snopyta.org/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>HTTP instance test`,
+			`<iframe width="650" height="350" frameborder="0" src="https://invidious.snopyta.org/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>HTTP instance test`,
 		},
 		"https://youtube.com/watch?v=dQw4w9WgXcQ": {
 			"YouTube URL (also matches regex)",
-			`<iframe width="650" height="350" frameborder="0" src="https://youtube.com/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>YouTube URL (also matches regex)`,
+			`<iframe width="650" height="350" frameborder="0" src="https://youtube.com/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>YouTube URL (also matches regex)`,
 		},
 		"https://example.org/watch?v=dQw4w9WgXcQ": {
 			"Any domain with watch pattern",
-			`<iframe width="650" height="350" frameborder="0" src="https://example.org/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Any domain with watch pattern`,
+			`<iframe width="650" height="350" frameborder="0" src="https://example.org/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Any domain with watch pattern`,
 		},
 
 		// Test with query parameters
 		"https://invidious.io/watch?v=dQw4w9WgXcQ&t=30s": {
 			"Video with timestamp",
-			`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ?t=30s" allowfullscreen></iframe><br>Video with timestamp`,
+			`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ?t=30s" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video with timestamp`,
 		},
 
 		// Test with more complex query parameters
 		"https://invidious.io/watch?v=dQw4w9WgXcQ&t=30s&autoplay=1": {
 			"Video with multiple parameters",
-			`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ?autoplay=1&t=30s" allowfullscreen></iframe><br>Video with multiple parameters`,
+			`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ?autoplay=1&t=30s" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>Video with multiple parameters`,
 		},
 
 		// Test with non-matching URLs (should return content unchanged)
@@ -308,7 +308,7 @@ func TestAddInvidiousVideo(t *testing.T) {
 		// Test with empty content
 		"https://empty.invidious.io/watch?v=dQw4w9WgXcQ": {
 			"",
-			`<iframe width="650" height="350" frameborder="0" src="https://empty.invidious.io/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>`,
+			`<iframe width="650" height="350" frameborder="0" src="https://empty.invidious.io/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>`,
 		},
 	}
 
@@ -1257,14 +1257,14 @@ func TestStripImageQueryParams(t *testing.T) {
 			<p>Article content with images having query parameters:</p>
 			<img src="https://example.org/images/image1.jpg?width=200&height=113&q=80&blur=90" alt="Image with params">
 			<img src="https://example.org/images/image2.jpg?width=800&height=600&q=85" alt="Another image with params">
-			
+
 			<p>More images with various query parameters:</p>
 			<img src="https://example.org/image123.jpg?blur=50&size=small&format=webp" alt="Complex query params">
 			<img src="https://example.org/image123.jpg?size=large&quality=95&cache=123" alt="Different params">
-			
+
 			<p>Image without query parameters:</p>
 			<img src="https://example.org/single-image.jpg" alt="Clean image">
-			
+
 			<p>Images with various other params:</p>
 			<img src="https://example.org/normal1.jpg?width=300&format=jpg" alt="Normal 1">
 			<img src="https://example.org/normal1.jpg?width=600&quality=high" alt="Normal 2">
@@ -1278,14 +1278,14 @@ func TestStripImageQueryParams(t *testing.T) {
 			<p>Article content with images having query parameters:</p>
 			<img src="https://example.org/images/image1.jpg" alt="Image with params"/>
 			<img src="https://example.org/images/image2.jpg?width=800&amp;height=600&amp;q=85" alt="Another image with params"/>
-			
+
 			<p>More images with various query parameters:</p>
 			<img src="https://example.org/image123.jpg" alt="Complex query params"/>
 			<img src="https://example.org/image123.jpg?size=large&amp;quality=95&amp;cache=123" alt="Different params"/>
-			
+
 			<p>Image without query parameters:</p>
 			<img src="https://example.org/single-image.jpg" alt="Clean image"/>
-			
+
 			<p>Images with various other params:</p>
 			<img src="https://example.org/normal1.jpg?width=300&amp;format=jpg" alt="Normal 1"/>
 			<img src="https://example.org/normal1.jpg?width=600&amp;quality=high" alt="Normal 2"/>
@@ -1327,17 +1327,17 @@ func TestStripImageQueryParamsEdgeCases(t *testing.T) {
 		Title: `Edge Cases`,
 		Content: `
 		<p>Edge cases for image query parameter stripping:</p>
-		
+
 		<!-- Various query parameters -->
 		<img src="https://example.org/image1.jpg?blur=80&width=300" alt="Multiple params">
-		
+
 		<!-- Complex query parameters -->
 		<img src="https://example.org/image2.jpg?BLUR=60&format=webp&cache=123" alt="Complex params">
 		<img src="https://example.org/image3.jpg?quality=high&version=2" alt="Other params">
-		
+
 		<!-- Query params in middle of string -->
 		<img src="https://example.org/image4.jpg?size=large&blur=30&format=webp&quality=90" alt="Middle params">
-		
+
 		<!-- Image without query params -->
 		<img src="https://example.org/clean.jpg" alt="Clean image">
 		`,
@@ -1347,17 +1347,17 @@ func TestStripImageQueryParamsEdgeCases(t *testing.T) {
 		URL:   "https://example.org/article",
 		Title: `Edge Cases`,
 		Content: `<p>Edge cases for image query parameter stripping:</p>
-		
+
 		<!-- Various query parameters -->
 		<img src="https://example.org/image1.jpg" alt="Multiple params"/>
-		
+
 		<!-- Complex query parameters -->
 		<img src="https://example.org/image2.jpg?BLUR=60&amp;format=webp&amp;cache=123" alt="Complex params"/>
 		<img src="https://example.org/image3.jpg?quality=high&amp;version=2" alt="Other params"/>
-		
+
 		<!-- Query params in middle of string -->
 		<img src="https://example.org/image4.jpg" alt="Middle params"/>
-		
+
 		<!-- Image without query params -->
 		<img src="https://example.org/clean.jpg" alt="Clean image"/>
 		`,
@@ -1375,7 +1375,7 @@ func TestStripImageQueryParamsSimple(t *testing.T) {
 		Title: `Simple Test`,
 		Content: `
 		<p>Testing query parameter stripping:</p>
-		
+
 		<!-- Images with various query parameters -->
 		<img src="https://example.org/test1.jpg?blur=0&width=300" alt="With blur zero">
 		<img src="https://example.org/test2.jpg?blur=50&width=300&format=webp" alt="With blur fifty">
@@ -1388,7 +1388,7 @@ func TestStripImageQueryParamsSimple(t *testing.T) {
 		URL:   "https://example.org/article",
 		Title: `Simple Test`,
 		Content: `<p>Testing query parameter stripping:</p>
-		
+
 		<!-- Images with various query parameters -->
 		<img src="https://example.org/test1.jpg?blur=0&amp;width=300" alt="With blur zero"/>
 		<img src="https://example.org/test2.jpg" alt="With blur fifty"/>

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

@@ -45,7 +45,7 @@ var (
 		"h5":         {"id"},
 		"h6":         {"id"},
 		"hr":         {},
-		"iframe":     {"width", "height", "frameborder", "src", "allowfullscreen"},
+		"iframe":     {"width", "height", "frameborder", "src", "allowfullscreen", "referrerpolicy"},
 		"img":        {"alt", "title", "src", "srcset", "sizes", "width", "height", "fetchpriority", "decoding"},
 		"ins":        {},
 		"kbd":        {},

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

@@ -457,6 +457,18 @@ func TestIFrameWithChildElements(t *testing.T) {
 	}
 }
 
+func TestIFrameWithReferrerPolicy(t *testing.T) {
+	config.Opts = config.NewConfigOptions()
+
+	input := `<iframe src="https://www.youtube.com/embed/test123" referrerpolicy="strict-origin-when-cross-origin"></iframe>`
+	expected := `<iframe src="https://www.youtube-nocookie.com/embed/test123" referrerpolicy="strict-origin-when-cross-origin" 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 TestLinkWithTarget(t *testing.T) {
 	input := `<p>This link is <a href="http://example.org/index.html">an anchor</a></p>`
 	expected := `<p>This link is <a href="http://example.org/index.html" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">an anchor</a></p>`