Răsfoiți Sursa

feat: allow installing libs from excal github (#9041)

David Luzar 6 luni în urmă
părinte
comite
f87c2cde09
2 a modificat fișierele cu 139 adăugiri și 12 ștergeri
  1. 105 0
      packages/excalidraw/data/library.test.ts
  2. 34 12
      packages/excalidraw/data/library.ts

+ 105 - 0
packages/excalidraw/data/library.test.ts

@@ -0,0 +1,105 @@
+import { validateLibraryUrl } from "./library";
+
+describe("validateLibraryUrl", () => {
+  it("should validate hostname & pathname", () => {
+    // valid hostnames
+    // -------------------------------------------------------------------------
+    expect(
+      validateLibraryUrl("https://www.excalidraw.com", ["excalidraw.com"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com", ["excalidraw.com"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://library.excalidraw.com", ["excalidraw.com"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://library.excalidraw.com", [
+        "library.excalidraw.com",
+      ]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/", ["excalidraw.com/"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com", ["excalidraw.com/"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/", ["excalidraw.com"]),
+    ).toBe(true);
+
+    // valid pathnames
+    // -------------------------------------------------------------------------
+    expect(
+      validateLibraryUrl("https://excalidraw.com/path", ["excalidraw.com"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/path/", ["excalidraw.com"]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/specific/path", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/specific/path/", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/specific/path", [
+        "excalidraw.com/specific/path/",
+      ]),
+    ).toBe(true);
+    expect(
+      validateLibraryUrl("https://excalidraw.com/specific/path/other", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toBe(true);
+
+    // invalid hostnames
+    // -------------------------------------------------------------------------
+    expect(() =>
+      validateLibraryUrl("https://xexcalidraw.com", ["excalidraw.com"]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://x-excalidraw.com", ["excalidraw.com"]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.comx", ["excalidraw.com"]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.comx", ["excalidraw.com"]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.com.mx", ["excalidraw.com"]),
+    ).toThrow();
+    // protocol must be https
+    expect(() =>
+      validateLibraryUrl("http://excalidraw.com.mx", ["excalidraw.com"]),
+    ).toThrow();
+
+    // invalid pathnames
+    // -------------------------------------------------------------------------
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.com/specific/other/path", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.com/specific/paths", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.com/specific/path-s", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toThrow();
+    expect(() =>
+      validateLibraryUrl("https://excalidraw.com/some/specific/path", [
+        "excalidraw.com/specific/path",
+      ]),
+    ).toThrow();
+  });
+});

+ 34 - 12
packages/excalidraw/data/library.ts

@@ -36,7 +36,18 @@ import { Queue } from "../queue";
 import { hashElementsVersion, hashString } from "../element";
 import { toValidURL } from "./url";
 
-const ALLOWED_LIBRARY_HOSTNAMES = ["excalidraw.com"];
+/**
+ * format: hostname or hostname/pathname
+ *
+ * Both hostname and pathname are matched partially,
+ * hostname from the end, pathname from the start, with subdomain/path
+ * boundaries
+ **/
+const ALLOWED_LIBRARY_URLS = [
+  "excalidraw.com",
+  // when installing from github PRs
+  "raw.githubusercontent.com/excalidraw/excalidraw-libraries",
+];
 
 type LibraryUpdate = {
   /** deleted library items since last onLibraryChange event */
@@ -469,26 +480,37 @@ export const distributeLibraryItemsOnSquareGrid = (
   return resElements;
 };
 
-const validateLibraryUrl = (
+export const validateLibraryUrl = (
   libraryUrl: string,
   /**
-   * If supplied, takes precedence over the default whitelist.
-   * Return `true` if the URL is valid.
+   * @returns `true` if the URL is valid, throws otherwise.
    */
-  validator?: (libraryUrl: string) => boolean,
-): boolean => {
+  validator:
+    | ((libraryUrl: string) => boolean)
+    | string[] = ALLOWED_LIBRARY_URLS,
+): true => {
   if (
-    validator
+    typeof validator === "function"
       ? validator(libraryUrl)
-      : ALLOWED_LIBRARY_HOSTNAMES.includes(
-          new URL(libraryUrl).hostname.split(".").slice(-2).join("."),
-        )
+      : validator.some((allowedUrlDef) => {
+          const allowedUrl = new URL(
+            `https://${allowedUrlDef.replace(/^https?:\/\//, "")}`,
+          );
+
+          const { hostname, pathname } = new URL(libraryUrl);
+
+          return (
+            new RegExp(`(^|\\.)${allowedUrl.hostname}$`).test(hostname) &&
+            new RegExp(
+              `^${allowedUrl.pathname.replace(/\/+$/, "")}(/+|$)`,
+            ).test(pathname)
+          );
+        })
   ) {
     return true;
   }
 
-  console.error(`Invalid or disallowed library URL: "${libraryUrl}"`);
-  throw new Error("Invalid or disallowed library URL");
+  throw new Error(`Invalid or disallowed library URL: "${libraryUrl}"`);
 };
 
 export const parseLibraryTokensFromUrl = () => {