diff --git a/src/assets/stylesheets/app.css b/src/assets/stylesheets/app.css index ec082b1..ee7c025 100644 --- a/src/assets/stylesheets/app.css +++ b/src/assets/stylesheets/app.css @@ -360,6 +360,27 @@ select.form-control:not([multiple]):not([size]) { margin-bottom: 0.5rem; } +.content .video-wrapper { + position: relative; + display: block; + width: 100%; + overflow: hidden; +} + +.content .video-wrapper::before { + display: block; + padding-top: 56.25%; /* 16x9 aspect ratio */ + content: ""; +} + +.content .video-wrapper iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + .content pre { overflow-x: auto; color: inherit; diff --git a/src/content/sanitizer/sanitizer.go b/src/content/sanitizer/sanitizer.go index 3285e18..c486be8 100644 --- a/src/content/sanitizer/sanitizer.go +++ b/src/content/sanitizer/sanitizer.go @@ -58,19 +58,31 @@ func Sanitize(baseURL, input string) string { attrNames, htmlAttributes := sanitizeAttributes(baseURL, tagName, token.Attr) if hasRequiredAttributes(tagName, attrNames) { + wrap := isVideoIframe(token) + if wrap { + buffer.WriteString(`
`) + } + if len(attrNames) > 0 { buffer.WriteString("<" + tagName + " " + htmlAttributes + ">") } else { buffer.WriteString("<" + tagName + ">") } - tagStack = append(tagStack, tagName) + if wrap { + buffer.WriteString("
") + } else { + tagStack = append(tagStack, tagName) + } } } else if isBlockedTag(tagName) { blacklistedTagDepth++ } case html.EndTagToken: tagName := token.Data + if tagName == "iframe" { + continue + } if isValidTag(tagName) && inList(tagName, tagStack) { buffer.WriteString(fmt.Sprintf("", tagName)) } else if isBlockedTag(tagName) { @@ -417,3 +429,22 @@ func isValidDataAttribute(value string) bool { } return false } + +func isVideoIframe(token html.Token) bool { + videoWhitelist := map[string]bool{ + "player.bilibili.com": true, + "player.vimeo.com": true, + "www.dailymotion.com": true, + "www.youtube-nocookie.com": true, + "www.youtube.com": true, + } + if token.Data == "iframe" { + for _, attr := range token.Attr { + if attr.Key == "src" { + domain := htmlutil.URLDomain(attr.Val) + return videoWhitelist[domain] + } + } + } + return false +} diff --git a/src/content/sanitizer/sanitizer_test.go b/src/content/sanitizer/sanitizer_test.go index 05c6989..eaa5f70 100644 --- a/src/content/sanitizer/sanitizer_test.go +++ b/src/content/sanitizer/sanitizer_test.go @@ -175,7 +175,7 @@ func TestInvalidIFrame(t *testing.T) { func TestIFrameWithChildElements(t *testing.T) { input := `` - expected := `` + expected := `
` output := Sanitize("http://example.com/", input) if expected != output { @@ -255,7 +255,7 @@ func TestEspaceAttributes(t *testing.T) { func TestReplaceIframeURL(t *testing.T) { input := `` - expected := `` + expected := `
` output := Sanitize("http://example.org/", input) if expected != output { @@ -292,3 +292,13 @@ func TestReplaceStyle(t *testing.T) { t.Errorf(`Wrong output: "%s" != "%s"`, expected, output) } } + +func TestWrapYoutubeIFrames(t *testing.T) { + input := `` + expected := `
` + output := Sanitize("http://example.org/", input) + + if expected != output { + t.Errorf("Wrong output:\nwant: %v\nhave: %v", expected, output) + } +} diff --git a/src/server/routes.go b/src/server/routes.go index 321995e..a55a8b3 100644 --- a/src/server/routes.go +++ b/src/server/routes.go @@ -417,7 +417,7 @@ func (s *Server) handlePageCrawl(c *router.Context) { if content := silo.VideoIFrame(url); content != "" { c.JSON(http.StatusOK, map[string]string{ - "content": content, + "content": sanitizer.Sanitize(url, content), }) return }