2
0
Эх сурвалжийг харах

fix: Gist embed allowing unsafe html (#7883)

David Luzar 1 жил өмнө
parent
commit
2879c9d852

+ 5 - 1
src/components/App.tsx

@@ -946,7 +946,11 @@ 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={`${
+                        embedLink?.sandbox?.allowSameOrigin
+                          ? "allow-same-origin"
+                          : ""
+                      } allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads`}
                     />
                     />
                   )}
                   )}
                 </div>
                 </div>

+ 26 - 45
src/element/embeddable.ts

@@ -18,6 +18,7 @@ type EmbeddedLink =
   | ({
   | ({
       aspectRatio: { w: number; h: number };
       aspectRatio: { w: number; h: number };
       warning?: string;
       warning?: string;
+      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 }
@@ -30,20 +31,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.com/;
+const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com/;
 const RE_TWITTER_EMBED =
 const RE_TWITTER_EMBED =
-  /^<blockquote[\s\S]*?\shref=["'](https:\/\/twitter.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;
@@ -143,46 +144,27 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
   }
   }
 
 
   if (RE_TWITTER.test(link)) {
   if (RE_TWITTER.test(link)) {
-    let ret: EmbeddedLink;
-    // assume embed code
-    if (/<blockquote/.test(link)) {
-      const srcDoc = createSrcDoc(link);
-      ret = {
-        type: "document",
-        srcdoc: () => srcDoc,
-        aspectRatio: { w: 480, h: 480 },
-      };
-      // 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>`,
-          ),
-        aspectRatio: { w: 480, h: 480 },
-      };
-    }
+    // the embed srcdoc still supports twitter.com domain only
+    link = link.replace(/\bx.com\b/, "twitter.com");
+
+    const ret: EmbeddedLink = {
+      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>`,
+        ),
+      aspectRatio: { w: 480, h: 480 },
+      sandbox: { allowSameOrigin: true },
+    };
     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: EmbeddedLink;
-    // assume embed code
-    if (/<script>/.test(link)) {
-      const srcDoc = createSrcDoc(link);
-      ret = {
-        type: "document",
-        srcdoc: () => srcDoc,
-        aspectRatio: { w: 550, h: 720 },
-      };
-      // assume regular url
-    } else {
-      ret = {
-        type: "document",
-        srcdoc: () =>
-          createSrcDoc(`
+    const ret: EmbeddedLink = {
+      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; }
@@ -190,9 +172,8 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
             .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>
         `),
         `),
-        aspectRatio: { w: 550, h: 720 },
-      };
-    }
+      aspectRatio: { w: 550, h: 720 },
+    };
     embeddedLinkCache.set(link, ret);
     embeddedLinkCache.set(link, ret);
     return ret;
     return ret;
   }
   }
@@ -310,8 +291,8 @@ export const extractSrc = (htmlString: string): string => {
   }
   }
 
 
   const gistMatch = htmlString.match(RE_GH_GIST_EMBED);
   const gistMatch = htmlString.match(RE_GH_GIST_EMBED);
-  if (gistMatch && gistMatch.length === 2) {
-    return gistMatch[1];
+  if (gistMatch && gistMatch.length === 3) {
+    return `https://gist.github.com/${gistMatch[1]}/${gistMatch[2]}`;
   }
   }
 
 
   if (RE_GIPHY.test(htmlString)) {
   if (RE_GIPHY.test(htmlString)) {