Skip to content

Commit 4892e04

Browse files
committed
fix: avoid YouTube error 153 for embed iframes
Add the attribute `referrerpolicy="strict-origin-when-cross-origin"` to YouTube iframes. Superseded PR #3862
1 parent 579b1ef commit 4892e04

File tree

4 files changed

+96
-38
lines changed

4 files changed

+96
-38
lines changed

internal/reader/rewrite/content_rewrite_functions.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,7 @@ func getYoutubVideoIDFromURL(entryURL string) string {
286286
}
287287

288288
func buildVideoPlayerIframe(absoluteVideoURL string) string {
289-
// Note: the referrerpolicy seems to be required to avoid YouTube error 153 video player configuration error
290-
// See https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-player-api-client-identity
291-
return `<iframe width="650" height="350" frameborder="0" src="` + absoluteVideoURL + `" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe>`
289+
return `<iframe width="650" height="350" frameborder="0" src="` + absoluteVideoURL + `" allowfullscreen></iframe>`
292290
}
293291

294292
func addVideoPlayerIframe(absoluteVideoURL, entryContent string) string {

internal/reader/rewrite/content_rewrite_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TestRewriteYoutubeVideoLink(t *testing.T) {
7272
controlEntry := &model.Entry{
7373
URL: "https://www.youtube.com/watch?v=1234",
7474
Title: `A title`,
75-
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`,
75+
Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1234" allowfullscreen></iframe><br>Video Description`,
7676
}
7777
testEntry := &model.Entry{
7878
URL: "https://www.youtube.com/watch?v=1234",
@@ -92,7 +92,7 @@ func TestRewriteYoutubeShortLink(t *testing.T) {
9292
controlEntry := &model.Entry{
9393
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
9494
Title: `A title`,
95-
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`,
95+
Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1LUWKWZkPjo" allowfullscreen></iframe><br>Video Description`,
9696
}
9797
testEntry := &model.Entry{
9898
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
@@ -140,7 +140,7 @@ func TestRewriteYoutubeLinkAndCustomEmbedURL(t *testing.T) {
140140
controlEntry := &model.Entry{
141141
URL: "https://www.youtube.com/watch?v=1234",
142142
Title: `A title`,
143-
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`,
143+
Content: `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/1234" allowfullscreen></iframe><br>Video Description`,
144144
}
145145
testEntry := &model.Entry{
146146
URL: "https://www.youtube.com/watch?v=1234",
@@ -159,7 +159,7 @@ func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
159159
controlEntry := &model.Entry{
160160
URL: "https://www.youtube.com/watch?v=1234",
161161
Title: `A title`,
162-
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`,
162+
Content: `<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/1234" allowfullscreen></iframe><br>Video Description`,
163163
}
164164
testEntry := &model.Entry{
165165
URL: "https://www.youtube.com/watch?v=1234",
@@ -179,7 +179,7 @@ func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
179179
controlEntry := &model.Entry{
180180
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
181181
Title: `A title`,
182-
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`,
182+
Content: `<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/1LUWKWZkPjo" allowfullscreen></iframe><br>Video Description`,
183183
}
184184
testEntry := &model.Entry{
185185
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
@@ -199,16 +199,16 @@ func TestAddYoutubeVideoFromId(t *testing.T) {
199199

200200
scenarios := map[string]string{
201201
// Test with single YouTube ID
202-
`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>`,
202+
`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>`,
203203

204204
// Test with multiple YouTube IDs
205-
`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"`,
205+
`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"`,
206206

207207
// Test with YouTube ID using equals sign
208-
`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"`,
208+
`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"`,
209209

210210
// Test with spaces around delimiters
211-
`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"`,
211+
`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"`,
212212

213213
// Test with YouTube ID without quotes (regex requires quotes)
214214
`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) {
245245
}
246246

247247
input := `Some content with youtube_id: "dQw4w9WgXcQ"`
248-
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"`
248+
expected := `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Some content with youtube_id: "dQw4w9WgXcQ"`
249249

250250
actual := addYoutubeVideoFromId(input)
251251
if actual != expected {
@@ -260,35 +260,35 @@ func TestAddInvidiousVideo(t *testing.T) {
260260
// Test with various Invidious instances
261261
"https://invidious.io/watch?v=dQw4w9WgXcQ": {
262262
"Some video content",
263-
`<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`,
263+
`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Some video content`,
264264
},
265265
"https://yewtu.be/watch?v=jNQXAC9IVRw": {
266266
"Another video description",
267-
`<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`,
267+
`<iframe width="650" height="350" frameborder="0" src="https://yewtu.be/embed/jNQXAC9IVRw" allowfullscreen></iframe><br>Another video description`,
268268
},
269269
"http://invidious.snopyta.org/watch?v=dQw4w9WgXcQ": {
270270
"HTTP instance test",
271-
`<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`,
271+
`<iframe width="650" height="350" frameborder="0" src="https://invidious.snopyta.org/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>HTTP instance test`,
272272
},
273273
"https://youtube.com/watch?v=dQw4w9WgXcQ": {
274274
"YouTube URL (also matches regex)",
275-
`<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)`,
275+
`<iframe width="650" height="350" frameborder="0" src="https://youtube.com/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>YouTube URL (also matches regex)`,
276276
},
277277
"https://example.org/watch?v=dQw4w9WgXcQ": {
278278
"Any domain with watch pattern",
279-
`<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`,
279+
`<iframe width="650" height="350" frameborder="0" src="https://example.org/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>Any domain with watch pattern`,
280280
},
281281

282282
// Test with query parameters
283283
"https://invidious.io/watch?v=dQw4w9WgXcQ&t=30s": {
284284
"Video with timestamp",
285-
`<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`,
285+
`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ?t=30s" allowfullscreen></iframe><br>Video with timestamp`,
286286
},
287287

288288
// Test with more complex query parameters
289289
"https://invidious.io/watch?v=dQw4w9WgXcQ&t=30s&autoplay=1": {
290290
"Video with multiple parameters",
291-
`<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`,
291+
`<iframe width="650" height="350" frameborder="0" src="https://invidious.io/embed/dQw4w9WgXcQ?autoplay=1&t=30s" allowfullscreen></iframe><br>Video with multiple parameters`,
292292
},
293293

294294
// Test with non-matching URLs (should return content unchanged)
@@ -308,7 +308,7 @@ func TestAddInvidiousVideo(t *testing.T) {
308308
// Test with empty content
309309
"https://empty.invidious.io/watch?v=dQw4w9WgXcQ": {
310310
"",
311-
`<iframe width="650" height="350" frameborder="0" src="https://empty.invidious.io/embed/dQw4w9WgXcQ" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe><br>`,
311+
`<iframe width="650" height="350" frameborder="0" src="https://empty.invidious.io/embed/dQw4w9WgXcQ" allowfullscreen></iframe><br>`,
312312
},
313313
}
314314

internal/reader/sanitizer/sanitizer.go

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var (
4545
"h5": {"id"},
4646
"h6": {"id"},
4747
"hr": {},
48-
"iframe": {"width", "height", "frameborder", "src", "allowfullscreen", "referrerpolicy"},
48+
"iframe": {"width", "height", "frameborder", "src", "allowfullscreen"},
4949
"img": {"alt", "title", "src", "srcset", "sizes", "width", "height", "fetchpriority", "decoding"},
5050
"ins": {},
5151
"kbd": {},
@@ -309,6 +309,7 @@ func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []htm
309309
attrNames := make([]string, 0, len(attributes))
310310
var err error
311311
var isAnchorLink bool
312+
var isYouTubeEmbed bool
312313

313314
for _, attribute := range attributes {
314315
if !isValidAttribute(tagName, attribute.Key) {
@@ -355,10 +356,16 @@ func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []htm
355356
if isExternalResourceAttribute(attribute.Key) {
356357
switch {
357358
case tagName == "iframe":
358-
if !isValidIframeSource(attribute.Val) {
359+
iframeSourceDomain, trustedIframeDomain := findAllowedIframeSourceDomain(attribute.Val)
360+
if !trustedIframeDomain {
359361
continue
360362
}
363+
361364
value = rewriteIframeURL(attribute.Val)
365+
366+
if iframeSourceDomain == "youtube.com" || iframeSourceDomain == "youtube-nocookie.com" {
367+
isYouTubeEmbed = true
368+
}
362369
case tagName == "img" && attribute.Key == "src" && isValidDataAttribute(attribute.Val):
363370
value = attribute.Val
364371
case tagName == "a" && attribute.Key == "href" && strings.HasPrefix(attribute.Val, "#"):
@@ -387,7 +394,7 @@ func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []htm
387394
}
388395

389396
if !isAnchorLink {
390-
extraAttrNames, extraHTMLAttributes := getExtraAttributes(tagName, sanitizerOptions)
397+
extraAttrNames, extraHTMLAttributes := getExtraAttributes(tagName, isYouTubeEmbed, sanitizerOptions)
391398
if len(extraAttrNames) > 0 {
392399
attrNames = append(attrNames, extraAttrNames...)
393400
htmlAttrs = append(htmlAttrs, extraHTMLAttributes...)
@@ -397,7 +404,7 @@ func sanitizeAttributes(parsedBaseUrl *url.URL, tagName string, attributes []htm
397404
return attrNames, strings.Join(htmlAttrs, " ")
398405
}
399406

400-
func getExtraAttributes(tagName string, sanitizerOptions *SanitizerOptions) ([]string, []string) {
407+
func getExtraAttributes(tagName string, isYouTubeEmbed bool, sanitizerOptions *SanitizerOptions) ([]string, []string) {
401408
switch tagName {
402409
case "a":
403410
attributeNames := []string{"rel", "referrerpolicy"}
@@ -410,7 +417,20 @@ func getExtraAttributes(tagName string, sanitizerOptions *SanitizerOptions) ([]s
410417
case "video", "audio":
411418
return []string{"controls"}, []string{"controls"}
412419
case "iframe":
413-
return []string{"sandbox", "loading"}, []string{`sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"`, `loading="lazy"`}
420+
extraAttrNames := []string{}
421+
extraHTMLAttributes := []string{}
422+
423+
// Note: the referrerpolicy seems to be required to avoid YouTube error 153 video player configuration error
424+
// See https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-player-api-client-identity
425+
if isYouTubeEmbed {
426+
extraAttrNames = append(extraAttrNames, "referrerpolicy")
427+
extraHTMLAttributes = append(extraHTMLAttributes, `referrerpolicy="strict-origin-when-cross-origin"`)
428+
}
429+
430+
extraAttrNames = append(extraAttrNames, "sandbox", "loading")
431+
extraHTMLAttributes = append(extraHTMLAttributes, `sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"`, `loading="lazy"`)
432+
433+
return extraAttrNames, extraHTMLAttributes
414434
case "img":
415435
return []string{"loading"}, []string{`loading="lazy"`}
416436
default:
@@ -496,22 +516,22 @@ func isBlockedResource(absoluteURL string) bool {
496516
return false
497517
}
498518

499-
func isValidIframeSource(iframeSourceURL string) bool {
519+
func findAllowedIframeSourceDomain(iframeSourceURL string) (string, bool) {
500520
iframeSourceDomain := urllib.DomainWithoutWWW(iframeSourceURL)
501521

502522
if _, ok := iframeAllowList[iframeSourceDomain]; ok {
503-
return true
523+
return iframeSourceDomain, true
504524
}
505525

506526
if ytDomain := config.Opts.YouTubeEmbedDomain(); ytDomain != "" && iframeSourceDomain == strings.TrimPrefix(ytDomain, "www.") {
507-
return true
527+
return iframeSourceDomain, true
508528
}
509529

510530
if invidiousInstance := config.Opts.InvidiousInstance(); invidiousInstance != "" && iframeSourceDomain == strings.TrimPrefix(invidiousInstance, "www.") {
511-
return true
531+
return iframeSourceDomain, true
512532
}
513533

514-
return false
534+
return "", false
515535
}
516536

517537
func rewriteIframeURL(link string) string {

0 commit comments

Comments
 (0)