Skip to content

Commit 3e8722d

Browse files
committed
Fix YouTube embeds in migrated blog markdown
1 parent 3c070c5 commit 3e8722d

2 files changed

Lines changed: 41 additions & 6 deletions

File tree

client/ublogMarkdown.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ function toProxiedImageUrl(url: string): string {
7474
}
7575
}
7676

77-
function safeIframeUrl(url: string): string | null {
77+
type SafeIframe = {
78+
href: string;
79+
isYouTubeEmbed: boolean;
80+
};
81+
82+
function safeIframe(url: string): SafeIframe | null {
7883
const value = (url || '').trim();
7984
if (!value) return null;
8085
try {
@@ -92,7 +97,7 @@ function safeIframeUrl(url: string): string | null {
9297
) && path.startsWith('/embed/');
9398

9499
if (isLocal || isLichessStudyEmbed || isYouTubeEmbed) {
95-
return parsed.href;
100+
return { href: parsed.href, isYouTubeEmbed };
96101
}
97102
return null;
98103
} catch {
@@ -178,17 +183,20 @@ function sanitizeRenderedFragment(html: string): DocumentFragment {
178183
el.setAttribute('loading', 'lazy');
179184
});
180185

181-
fragment.querySelectorAll<HTMLIFrameElement>('iframe[src]').forEach((el) => {
182-
const safe = safeIframeUrl(el.getAttribute('src') || '');
186+
fragment.querySelectorAll<HTMLIFrameElement>('iframe').forEach((el) => {
187+
const safe = safeIframe(el.getAttribute('src') || '');
183188
if (!safe) {
184189
el.remove();
185190
return;
186191
}
187-
el.setAttribute('src', safe);
192+
el.setAttribute('src', safe.href);
188193
sanitizePositiveIntAttr(el, 'width');
189194
sanitizePositiveIntAttr(el, 'height');
190195
el.setAttribute('loading', 'lazy');
191-
el.setAttribute('referrerpolicy', 'no-referrer');
196+
el.setAttribute(
197+
'referrerpolicy',
198+
safe.isYouTubeEmbed ? 'strict-origin-when-cross-origin' : 'no-referrer',
199+
);
192200
if (!el.hasAttribute('allowfullscreen')) {
193201
el.setAttribute('allowfullscreen', '');
194202
}

tests/ublogMarkdown.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { initUblogMarkdown } from "../client/ublogMarkdown";
2+
3+
function renderBlogMarkdown(markdown: string): HTMLElement {
4+
document.body.innerHTML = `
5+
<textarea id="ublog-markdown-source"></textarea>
6+
<div id="ublog-markdown-render"></div>
7+
`;
8+
const source = document.getElementById("ublog-markdown-source") as HTMLTextAreaElement;
9+
source.value = markdown;
10+
initUblogMarkdown();
11+
return document.getElementById("ublog-markdown-render") as HTMLElement;
12+
}
13+
14+
test("keeps youtube iframe embeds playable", () => {
15+
const root = renderBlogMarkdown(
16+
'<iframe width="560" height="315" src="https://www.youtube.com/embed/rz3f5febUAU" frameborder="0" allowfullscreen></iframe>'
17+
);
18+
const iframe = root.querySelector("iframe");
19+
expect(iframe).not.toBeNull();
20+
expect(iframe?.getAttribute("src")).toBe("https://www.youtube.com/embed/rz3f5febUAU");
21+
expect(iframe?.getAttribute("referrerpolicy")).toBe("strict-origin-when-cross-origin");
22+
});
23+
24+
test("drops iframe embeds with unsafe src", () => {
25+
const root = renderBlogMarkdown('<iframe src="javascript:alert(1)"></iframe>');
26+
expect(root.querySelector("iframe")).toBeNull();
27+
});

0 commit comments

Comments
 (0)