diff --git a/src/element/embeddable.ts b/src/element/embeddable.ts index 0010246fbb..6c73006ad3 100644 --- a/src/element/embeddable.ts +++ b/src/element/embeddable.ts @@ -19,7 +19,7 @@ type EmbeddedLink = | ({ aspectRatio: { w: number; h: number }; warning?: string; - sandbox?: { allowSameOrigin?: boolean }; + sandbox: { allowSameOrigin?: boolean }; } & ( | { type: "video" | "generic"; link: string } | { type: "document"; srcdoc: (theme: Theme) => string } @@ -67,7 +67,18 @@ const ALLOWED_DOMAINS = new Set([ "stackblitz.com", "val.town", "giphy.com", - "dddice.com", +]); + +const ALLOW_SAME_ORIGIN = new Set([ + "youtube.com", + "youtu.be", + "vimeo.com", + "player.vimeo.com", + "figma.com", + "twitter.com", + "x.com", + "*.simplepdf.eu", + "stackblitz.com", ]); const createSrcDoc = (body: string) => { @@ -85,6 +96,10 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => { const originalLink = link; + const allowSameOrigin = ALLOW_SAME_ORIGIN.has( + matchHostname(link, ALLOW_SAME_ORIGIN) || "", + ); + let type: "video" | "generic" = "generic"; let aspectRatio = { w: 560, h: 840 }; const ytLink = link.match(RE_YOUTUBE); @@ -107,8 +122,18 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => { break; } aspectRatio = isPortrait ? { w: 315, h: 560 } : { w: 560, h: 315 }; - embeddedLinkCache.set(originalLink, { link, aspectRatio, type }); - return { link, aspectRatio, type }; + embeddedLinkCache.set(originalLink, { + link, + aspectRatio, + type, + sandbox: { allowSameOrigin }, + }); + return { + link, + aspectRatio, + type, + sandbox: { allowSameOrigin }, + }; } const vimeoLink = link.match(RE_VIMEO); @@ -122,8 +147,13 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => { aspectRatio = { w: 560, h: 315 }; //warning deliberately ommited so it is displayed only once per link //same link next time will be served from cache - embeddedLinkCache.set(originalLink, { link, aspectRatio, type }); - return { link, aspectRatio, type, warning }; + embeddedLinkCache.set(originalLink, { + link, + aspectRatio, + type, + sandbox: { allowSameOrigin }, + }); + return { link, aspectRatio, type, warning, sandbox: { allowSameOrigin } }; } const figmaLink = link.match(RE_FIGMA); @@ -133,16 +163,26 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => { link, )}`; aspectRatio = { w: 550, h: 550 }; - embeddedLinkCache.set(originalLink, { link, aspectRatio, type }); - return { link, aspectRatio, type }; + embeddedLinkCache.set(originalLink, { + link, + aspectRatio, + type, + sandbox: { allowSameOrigin }, + }); + return { link, aspectRatio, type, sandbox: { allowSameOrigin } }; } const valLink = link.match(RE_VALTOWN); if (valLink) { link = valLink[1] === "embed" ? valLink[0] : valLink[0].replace("/v", "/embed"); - embeddedLinkCache.set(originalLink, { link, aspectRatio, type }); - return { link, aspectRatio, type }; + embeddedLinkCache.set(originalLink, { + link, + aspectRatio, + type, + sandbox: { allowSameOrigin }, + }); + return { link, aspectRatio, type, sandbox: { allowSameOrigin } }; } if (RE_TWITTER.test(link)) { @@ -162,7 +202,7 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => { `
`, ), aspectRatio: { w: 480, h: 480 }, - sandbox: { allowSameOrigin: true }, + sandbox: { allowSameOrigin }, }; embeddedLinkCache.set(originalLink, ret); return ret; @@ -185,13 +225,19 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => { `), aspectRatio: { w: 550, h: 720 }, + sandbox: { allowSameOrigin }, }; embeddedLinkCache.set(link, ret); return ret; } - embeddedLinkCache.set(link, { link, aspectRatio, type }); - return { link, aspectRatio, type }; + embeddedLinkCache.set(link, { + link, + aspectRatio, + type, + sandbox: { allowSameOrigin }, + }); + return { link, aspectRatio, type, sandbox: { allowSameOrigin } }; }; export const isEmbeddableOrLabel = ( @@ -266,34 +312,39 @@ export const actionSetEmbeddableAsActiveTool = register({ }, }); -const validateHostname = ( +const matchHostname = ( url: string, /** using a Set assumes it already contains normalized bare domains */ allowedHostnames: Set