Explorar el Código

fix: allow same origin for all necessary domains (#7889)

David Luzar hace 1 año
padre
commit
da2e507298
Se han modificado 1 ficheros con 85 adiciones y 26 borrados
  1. 85 26
      packages/excalidraw/element/embeddable.ts

+ 85 - 26
packages/excalidraw/element/embeddable.ts

@@ -12,8 +12,11 @@ import {
   IframeData,
   IframeData,
 } from "./types";
 } from "./types";
 import { sanitizeHTMLAttribute } from "../data/url";
 import { sanitizeHTMLAttribute } from "../data/url";
+import { MarkRequired } from "../utility-types";
 
 
-const embeddedLinkCache = new Map<string, IframeData>();
+type IframeDataWithSandbox = MarkRequired<IframeData, "sandbox">;
+
+const embeddedLinkCache = new Map<string, IframeDataWithSandbox>();
 
 
 const RE_YOUTUBE =
 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]*$/;
@@ -55,7 +58,18 @@ const ALLOWED_DOMAINS = new Set([
   "stackblitz.com",
   "stackblitz.com",
   "val.town",
   "val.town",
   "giphy.com",
   "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",
 ]);
 ]);
 
 
 export const createSrcDoc = (body: string) => {
 export const createSrcDoc = (body: string) => {
@@ -64,7 +78,7 @@ export const createSrcDoc = (body: string) => {
 
 
 export const getEmbedLink = (
 export const getEmbedLink = (
   link: string | null | undefined,
   link: string | null | undefined,
-): IframeData | null => {
+): IframeDataWithSandbox | null => {
   if (!link) {
   if (!link) {
     return null;
     return null;
   }
   }
@@ -75,6 +89,10 @@ export const getEmbedLink = (
 
 
   const originalLink = link;
   const originalLink = link;
 
 
+  const allowSameOrigin = ALLOW_SAME_ORIGIN.has(
+    matchHostname(link, ALLOW_SAME_ORIGIN) || "",
+  );
+
   let type: "video" | "generic" = "generic";
   let type: "video" | "generic" = "generic";
   let aspectRatio = { w: 560, h: 840 };
   let aspectRatio = { w: 560, h: 840 };
   const ytLink = link.match(RE_YOUTUBE);
   const ytLink = link.match(RE_YOUTUBE);
@@ -101,8 +119,14 @@ export const getEmbedLink = (
       link,
       link,
       intrinsicSize: aspectRatio,
       intrinsicSize: aspectRatio,
       type,
       type,
+      sandbox: { allowSameOrigin },
     });
     });
-    return { link, intrinsicSize: aspectRatio, type };
+    return {
+      link,
+      intrinsicSize: aspectRatio,
+      type,
+      sandbox: { allowSameOrigin },
+    };
   }
   }
 
 
   const vimeoLink = link.match(RE_VIMEO);
   const vimeoLink = link.match(RE_VIMEO);
@@ -120,8 +144,15 @@ export const getEmbedLink = (
       link,
       link,
       intrinsicSize: aspectRatio,
       intrinsicSize: aspectRatio,
       type,
       type,
+      sandbox: { allowSameOrigin },
     });
     });
-    return { link, intrinsicSize: aspectRatio, type, error };
+    return {
+      link,
+      intrinsicSize: aspectRatio,
+      type,
+      error,
+      sandbox: { allowSameOrigin },
+    };
   }
   }
 
 
   const figmaLink = link.match(RE_FIGMA);
   const figmaLink = link.match(RE_FIGMA);
@@ -135,8 +166,14 @@ export const getEmbedLink = (
       link,
       link,
       intrinsicSize: aspectRatio,
       intrinsicSize: aspectRatio,
       type,
       type,
+      sandbox: { allowSameOrigin },
     });
     });
-    return { link, intrinsicSize: aspectRatio, type };
+    return {
+      link,
+      intrinsicSize: aspectRatio,
+      type,
+      sandbox: { allowSameOrigin },
+    };
   }
   }
 
 
   const valLink = link.match(RE_VALTOWN);
   const valLink = link.match(RE_VALTOWN);
@@ -147,8 +184,14 @@ export const getEmbedLink = (
       link,
       link,
       intrinsicSize: aspectRatio,
       intrinsicSize: aspectRatio,
       type,
       type,
+      sandbox: { allowSameOrigin },
     });
     });
-    return { link, intrinsicSize: aspectRatio, type };
+    return {
+      link,
+      intrinsicSize: aspectRatio,
+      type,
+      sandbox: { allowSameOrigin },
+    };
   }
   }
 
 
   if (RE_TWITTER.test(link)) {
   if (RE_TWITTER.test(link)) {
@@ -161,14 +204,14 @@ export const getEmbedLink = (
       `https://twitter.com/x/status/${postId}`,
       `https://twitter.com/x/status/${postId}`,
     );
     );
 
 
-    const ret: IframeData = {
+    const ret: IframeDataWithSandbox = {
       type: "document",
       type: "document",
       srcdoc: (theme: string) =>
       srcdoc: (theme: string) =>
         createSrcDoc(
         createSrcDoc(
           `<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>`,
           `<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 },
     };
     };
     embeddedLinkCache.set(originalLink, ret);
     embeddedLinkCache.set(originalLink, ret);
     return ret;
     return ret;
@@ -179,7 +222,7 @@ export const getEmbedLink = (
     const safeURL = sanitizeHTMLAttribute(
     const safeURL = sanitizeHTMLAttribute(
       `https://gist.github.com/${user}/${gistId}`,
       `https://gist.github.com/${user}/${gistId}`,
     );
     );
-    const ret: IframeData = {
+    const ret: IframeDataWithSandbox = {
       type: "document",
       type: "document",
       srcdoc: () =>
       srcdoc: () =>
         createSrcDoc(`
         createSrcDoc(`
@@ -191,13 +234,24 @@ export const getEmbedLink = (
           </style>
           </style>
         `),
         `),
       intrinsicSize: { w: 550, h: 720 },
       intrinsicSize: { w: 550, h: 720 },
+      sandbox: { allowSameOrigin },
     };
     };
     embeddedLinkCache.set(link, ret);
     embeddedLinkCache.set(link, ret);
     return ret;
     return ret;
   }
   }
 
 
-  embeddedLinkCache.set(link, { link, intrinsicSize: aspectRatio, type });
-  return { link, intrinsicSize: aspectRatio, type };
+  embeddedLinkCache.set(link, {
+    link,
+    intrinsicSize: aspectRatio,
+    type,
+    sandbox: { allowSameOrigin },
+  });
+  return {
+    link,
+    intrinsicSize: aspectRatio,
+    type,
+    sandbox: { allowSameOrigin },
+  };
 };
 };
 
 
 export const createPlaceholderEmbeddableLabel = (
 export const createPlaceholderEmbeddableLabel = (
@@ -265,34 +319,39 @@ export const actionSetEmbeddableAsActiveTool = register({
   },
   },
 });
 });
 
 
-const validateHostname = (
+const matchHostname = (
   url: string,
   url: string,
   /** using a Set assumes it already contains normalized bare domains */
   /** using a Set assumes it already contains normalized bare domains */
   allowedHostnames: Set<string> | string,
   allowedHostnames: Set<string> | string,
-): boolean => {
+): string | null => {
   try {
   try {
     const { hostname } = new URL(url);
     const { hostname } = new URL(url);
 
 
     const bareDomain = hostname.replace(/^www\./, "");
     const bareDomain = hostname.replace(/^www\./, "");
-    const bareDomainWithFirstSubdomainWildcarded = bareDomain.replace(
-      /^([^.]+)/,
-      "*",
-    );
 
 
     if (allowedHostnames instanceof Set) {
     if (allowedHostnames instanceof Set) {
-      return (
-        ALLOWED_DOMAINS.has(bareDomain) ||
-        ALLOWED_DOMAINS.has(bareDomainWithFirstSubdomainWildcarded)
+      if (ALLOWED_DOMAINS.has(bareDomain)) {
+        return bareDomain;
+      }
+
+      const bareDomainWithFirstSubdomainWildcarded = bareDomain.replace(
+        /^([^.]+)/,
+        "*",
       );
       );
+      if (ALLOWED_DOMAINS.has(bareDomainWithFirstSubdomainWildcarded)) {
+        return bareDomainWithFirstSubdomainWildcarded;
+      }
+      return null;
     }
     }
 
 
-    if (bareDomain === allowedHostnames.replace(/^www\./, "")) {
-      return true;
+    const bareAllowedHostname = allowedHostnames.replace(/^www\./, "");
+    if (bareDomain === bareAllowedHostname) {
+      return bareAllowedHostname;
     }
     }
   } catch (error) {
   } catch (error) {
     // ignore
     // ignore
   }
   }
-  return false;
+  return null;
 };
 };
 
 
 export const maybeParseEmbedSrc = (str: string): string => {
 export const maybeParseEmbedSrc = (str: string): string => {
@@ -342,7 +401,7 @@ export const embeddableURLValidator = (
           if (url.match(domain)) {
           if (url.match(domain)) {
             return true;
             return true;
           }
           }
-        } else if (validateHostname(url, domain)) {
+        } else if (matchHostname(url, domain)) {
           return true;
           return true;
         }
         }
       }
       }
@@ -350,5 +409,5 @@ export const embeddableURLValidator = (
     }
     }
   }
   }
 
 
-  return validateHostname(url, ALLOWED_DOMAINS);
+  return !!matchHostname(url, ALLOWED_DOMAINS);
 };
 };