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("%s>", 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
}