fix: parse embeddable srcdoc urls strictly (#7884)

This commit is contained in:
David Luzar 2024-04-12 20:51:17 +02:00 committed by GitHub
parent 4689a6b300
commit afcde542f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 10 deletions

View File

@ -1,11 +1,15 @@
import { sanitizeUrl } from "@braintree/sanitize-url"; import { sanitizeUrl } from "@braintree/sanitize-url";
export const sanitizeHTMLAttribute = (html: string) => {
return html.replace(/"/g, """);
};
export const normalizeLink = (link: string) => { export const normalizeLink = (link: string) => {
link = link.trim(); link = link.trim();
if (!link) { if (!link) {
return link; return link;
} }
return sanitizeUrl(link); return sanitizeUrl(sanitizeHTMLAttribute(link));
}; };
export const isLocalLink = (link: string | null) => { export const isLocalLink = (link: string | null) => {

View File

@ -11,6 +11,7 @@ import {
ExcalidrawIframeLikeElement, ExcalidrawIframeLikeElement,
IframeData, IframeData,
} from "./types"; } from "./types";
import { sanitizeHTMLAttribute } from "../data/url";
const embeddedLinkCache = new Map<string, IframeData>(); const embeddedLinkCache = new Map<string, IframeData>();
@ -21,12 +22,13 @@ 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\/([\w_-]+)\/([\w_-]+)/;
const RE_GH_GIST_EMBED = const RE_GH_GIST_EMBED =
/https?:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)\.js["']/i; /^<script[\s\S]*?\ssrc=["'](https:\/\/gist\.github\.com\/.*?)\.js["']/i;
// not anchored to start to allow <blockquote> twitter embeds // not anchored to start to allow <blockquote> twitter embeds
const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com/; const RE_TWITTER =
/(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com\/[^/]+\/status\/(\d+)/;
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;
@ -150,14 +152,20 @@ export const getEmbedLink = (
} }
if (RE_TWITTER.test(link)) { if (RE_TWITTER.test(link)) {
// the embed srcdoc still supports twitter.com domain only const postId = link.match(RE_TWITTER)![1];
link = link.replace(/\bx.com\b/, "twitter.com"); // the embed srcdoc still supports twitter.com domain only.
// Note that we don't attempt to parse the username as it can consist of
// non-latin1 characters, and the username in the url can be set to anything
// without affecting the embed.
const safeURL = sanitizeHTMLAttribute(
`https://twitter.com/x/status/${postId}`,
);
const ret: IframeData = { const ret: IframeData = {
type: "document", type: "document",
srcdoc: (theme: string) => srcdoc: (theme: string) =>
createSrcDoc( 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>`, `<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${safeURL}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
), ),
intrinsicSize: { w: 480, h: 480 }, intrinsicSize: { w: 480, h: 480 },
sandbox: { allowSameOrigin: true }, sandbox: { allowSameOrigin: true },
@ -167,11 +175,15 @@ export const getEmbedLink = (
} }
if (RE_GH_GIST.test(link)) { if (RE_GH_GIST.test(link)) {
const [, user, gistId] = link.match(RE_GH_GIST)!;
const safeURL = sanitizeHTMLAttribute(
`https://gist.github.com/${user}/${gistId}`,
);
const ret: IframeData = { const ret: IframeData = {
type: "document", type: "document",
srcdoc: () => srcdoc: () =>
createSrcDoc(` createSrcDoc(`
<script src="${link}.js"></script> <script src="${safeURL}.js"></script>
<style type="text/css"> <style type="text/css">
* { margin: 0px; } * { margin: 0px; }
table, .gist { height: 100%; } table, .gist { height: 100%; }
@ -290,8 +302,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 === 3) { if (gistMatch && gistMatch.length === 2) {
return `https://gist.github.com/${gistMatch[1]}/${gistMatch[2]}`; return gistMatch[1];
} }
if (RE_GIPHY.test(str)) { if (RE_GIPHY.test(str)) {