diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx index ad82e59f760315c10b1fed096695a0ba98175e2f..3b64300ac9a96d35a1686a6c14af47a4b9cbe54d 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx @@ -6,6 +6,8 @@ import { t, ngettext, msgid } from "ttag"; import _ from "underscore"; import cx from "classnames"; +import { regexpEscape } from "metabase/lib/string"; + export default class MetadataSchemaList extends Component { constructor(props, context) { super(props, context); @@ -22,7 +24,7 @@ export default class MetadataSchemaList extends Component { this.setState({ searchText: event.target.value, searchRegex: event.target.value - ? new RegExp(RegExp.escape(event.target.value), "i") + ? new RegExp(regexpEscape(event.target.value), "i") : null, }); } diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx index cf425503e003ac6be04f57a2c88f6a7a841ea221..0406cf430971dbf1e4720a3611cdfa243df8b164 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx @@ -8,6 +8,8 @@ import { t, ngettext, msgid } from "ttag"; import _ from "underscore"; import cx from "classnames"; +import { regexpEscape } from "metabase/lib/string"; + export default class MetadataTableList extends Component { constructor(props, context) { super(props, context); @@ -30,7 +32,7 @@ export default class MetadataTableList extends Component { this.setState({ searchText: event.target.value, searchRegex: event.target.value - ? new RegExp(RegExp.escape(event.target.value), "i") + ? new RegExp(regexpEscape(event.target.value), "i") : null, }); } diff --git a/frontend/src/metabase/lib/string.js b/frontend/src/metabase/lib/string.js index 8fb1e7a7af791f04675c0a026a5b0a78b015f3d8..d6b6fe78fe3fd03e1830f9fe585626b01613ed38 100644 --- a/frontend/src/metabase/lib/string.js +++ b/frontend/src/metabase/lib/string.js @@ -3,15 +3,17 @@ import _ from "underscore"; // Creates a regex that will find an order dependent, case insensitive substring. All whitespace will be rendered as ".*" in the regex, to create a fuzzy search. export function createMultiwordSearchRegex(input) { if (input) { - return new RegExp( - _.map(input.split(/\s+/), word => { - return RegExp.escape(word); - }).join(".*"), - "i", - ); + return new RegExp(_.map(input.split(/\s+/), regexpEscape).join(".*"), "i"); } } +// prefix special characters with "\" for creating a regex +export function regexpEscape(s) { + const regexpSpecialChars = /[\^\$\\\.\*\+\?\(\)\[\]\{\}\|]/g; + // "$&" in the replacement string is replaced with the matched string + return s.replace(regexpSpecialChars, "\\$&"); +} + export const countLines = str => str.split(/\n/g).length; export function caseInsensitiveSearch(haystack, needle) { diff --git a/frontend/test/metabase/lib/string.unit.spec.js b/frontend/test/metabase/lib/string.unit.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6632e327d42434ef1455ff60ea2605e4e6b3bc98 --- /dev/null +++ b/frontend/test/metabase/lib/string.unit.spec.js @@ -0,0 +1,17 @@ +import { regexpEscape } from "metabase/lib/string"; + +describe("regexpEscape", () => { + const testCases = [ + ["nothing special here", "nothing special here"], + ["somewhat ./\\ special", "somewhat \\./\\\\ special"], + [ + "extra special ^$\\.*+?()[]{}|", + "extra special \\^\\$\\\\\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|", + ], + ]; + for (const [raw, escaped] of testCases) { + it(`should escape "${raw}" to "${escaped}"`, () => { + expect(regexpEscape(raw)).toEqual(escaped); + }); + } +});