From 39303854b74c997e96e84cb61e687a1a31745b14 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 11 Mar 2025 15:04:49 -0700 Subject: [PATCH] Allow escaping of regex This will let us use regex to test user input values for searches --- libs/common/src/platform/misc/utils.spec.ts | 20 ++++++++++++++++++++ libs/common/src/platform/misc/utils.ts | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/libs/common/src/platform/misc/utils.spec.ts b/libs/common/src/platform/misc/utils.spec.ts index 964a2a19413..43b61037c43 100644 --- a/libs/common/src/platform/misc/utils.spec.ts +++ b/libs/common/src/platform/misc/utils.spec.ts @@ -706,4 +706,24 @@ describe("Utils Service", () => { }); }); }); + + describe("escapeRegex", () => { + it("escapes special characters", () => { + expect(Utils.escapeRegex("^$.*+?()[]{}|\\")).toEqual( + "\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\", + ); + }); + + it("does not modify strings with no special characters", () => { + expect(Utils.escapeRegex("no special characters")).toEqual("no special characters"); + }); + + it("handles empty strings", () => { + expect(Utils.escapeRegex("")).toEqual(""); + }); + + it.each([null, undefined])("handles %s strings", (nullish) => { + expect(Utils.escapeRegex(nullish!)).toEqual(nullish); + }); + }); }); diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index ef65d2130a0..80cbc61b5fe 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -37,6 +37,7 @@ export class Utils { // Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers. static regexpEmojiPresentation = /(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])/g; + static regexSpecialChar = /[\\^$.*+?()[\]{}|]/g; static readonly validHosts: string[] = ["localhost"]; static readonly originalMinimumPasswordLength = 8; static readonly minimumPasswordLength = 12; @@ -588,6 +589,13 @@ export class Utils { return Math.max(0, Math.floor(diffTime / msPerDay)); } + static escapeRegex(s: string): string { + if (this.regexSpecialChar.test(s)) { + return s.replace(this.regexSpecialChar, "\\$&"); + } + return s; + } + private static isAppleMobile(win: Window) { return ( win.navigator.userAgent.match(/iPhone/i) != null ||