fix: Gist embed allowing unsafe html (#7883)

This commit is contained in:
David Luzar 2024-04-12 12:57:43 +02:00 committed by GitHub
parent f597bd3e01
commit 0ae9b383d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 46 deletions

View file

@ -1212,7 +1212,9 @@ class App extends React.Component<AppProps, AppState> {
title="Excalidraw Embedded Content" title="Excalidraw Embedded Content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen={true} allowFullScreen={true}
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads" sandbox={`${
src?.sandbox?.allowSameOrigin ? "allow-same-origin" : ""
} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads`}
/> />
)} )}
</div> </div>

View file

@ -18,20 +18,20 @@ const RE_YOUTUBE =
/^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/; /^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
const RE_VIMEO = const RE_VIMEO =
/^(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/; /^(?:http(?:s)?:\/\/)?(?:(?:w){3}\.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/; const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
const RE_GH_GIST = /^https:\/\/gist\.github\.com/; const RE_GH_GIST = /^https:\/\/gist\.github\.com/;
const RE_GH_GIST_EMBED = const RE_GH_GIST_EMBED =
/^<script[\s\S]*?\ssrc=["'](https:\/\/gist.github.com\/.*?)\.js["']/i; /https?:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)\.js["']/i;
// not anchored to start to allow <blockquote> twitter embeds // not anchored to start to allow <blockquote> twitter embeds
const RE_TWITTER = /(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:twitter|x).com/; const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com/;
const RE_TWITTER_EMBED = const RE_TWITTER_EMBED =
/^<blockquote[\s\S]*?\shref=["'](https:\/\/(?:twitter|x).com\/[^"']*)/i; /^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:twitter|x)\.com\/[^"']*)/i;
const RE_VALTOWN = const RE_VALTOWN =
/^https:\/\/(?:www\.)?val.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/; /^https:\/\/(?:www\.)?val\.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
const RE_GENERIC_EMBED = const RE_GENERIC_EMBED =
/^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i; /^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
@ -153,46 +153,24 @@ export const getEmbedLink = (
// the embed srcdoc still supports twitter.com domain only // the embed srcdoc still supports twitter.com domain only
link = link.replace(/\bx.com\b/, "twitter.com"); link = link.replace(/\bx.com\b/, "twitter.com");
let ret: IframeData; const ret: IframeData = {
// assume embed code type: "document",
if (/<blockquote/.test(link)) { srcdoc: (theme: string) =>
const srcDoc = createSrcDoc(link); createSrcDoc(
ret = { `<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
type: "document", ),
srcdoc: () => srcDoc, intrinsicSize: { w: 480, h: 480 },
intrinsicSize: { w: 480, h: 480 }, sandbox: { allowSameOrigin: true },
}; };
// assume regular tweet url
} else {
ret = {
type: "document",
srcdoc: (theme: string) =>
createSrcDoc(
`<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
),
intrinsicSize: { w: 480, h: 480 },
};
}
embeddedLinkCache.set(originalLink, ret); embeddedLinkCache.set(originalLink, ret);
return ret; return ret;
} }
if (RE_GH_GIST.test(link)) { if (RE_GH_GIST.test(link)) {
let ret: IframeData; const ret: IframeData = {
// assume embed code type: "document",
if (/<script>/.test(link)) { srcdoc: () =>
const srcDoc = createSrcDoc(link); createSrcDoc(`
ret = {
type: "document",
srcdoc: () => srcDoc,
intrinsicSize: { w: 550, h: 720 },
};
// assume regular url
} else {
ret = {
type: "document",
srcdoc: () =>
createSrcDoc(`
<script src="${link}.js"></script> <script src="${link}.js"></script>
<style type="text/css"> <style type="text/css">
* { margin: 0px; } * { margin: 0px; }
@ -200,9 +178,8 @@ export const getEmbedLink = (
.gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; } .gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
</style> </style>
`), `),
intrinsicSize: { w: 550, h: 720 }, intrinsicSize: { w: 550, h: 720 },
}; };
}
embeddedLinkCache.set(link, ret); embeddedLinkCache.set(link, ret);
return ret; return ret;
} }
@ -313,8 +290,8 @@ export const maybeParseEmbedSrc = (str: string): string => {
} }
const gistMatch = str.match(RE_GH_GIST_EMBED); const gistMatch = str.match(RE_GH_GIST_EMBED);
if (gistMatch && gistMatch.length === 2) { if (gistMatch && gistMatch.length === 3) {
return gistMatch[1]; return `https://gist.github.com/${gistMatch[1]}/${gistMatch[2]}`;
} }
if (RE_GIPHY.test(str)) { if (RE_GIPHY.test(str)) {
@ -325,6 +302,7 @@ export const maybeParseEmbedSrc = (str: string): string => {
if (match && match.length === 2) { if (match && match.length === 2) {
return match[1]; return match[1];
} }
return str; return str;
}; };

View file

@ -111,6 +111,7 @@ export type IframeData =
| { | {
intrinsicSize: { w: number; h: number }; intrinsicSize: { w: number; h: number };
error?: Error; error?: Error;
sandbox?: { allowSameOrigin?: boolean };
} & ( } & (
| { type: "video" | "generic"; link: string } | { type: "video" | "generic"; link: string }
| { type: "document"; srcdoc: (theme: Theme) => string } | { type: "document"; srcdoc: (theme: Theme) => string }