From e28bb7f1985b598038781119861779f860c3cc8b Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:51:34 +1100 Subject: [PATCH 1/2] Don't let invalid tagger regex crash UI --- ui/v2.5/src/components/Tagger/utils.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ui/v2.5/src/components/Tagger/utils.ts b/ui/v2.5/src/components/Tagger/utils.ts index 6bac6cb0428..92f5960406d 100644 --- a/ui/v2.5/src/components/Tagger/utils.ts +++ b/ui/v2.5/src/components/Tagger/utils.ts @@ -83,6 +83,17 @@ export function prepareQueryString( mode: ParseMode, blacklist: string[] ) { + const regexs = blacklist + .map((b) => { + try { + return new RegExp(b, "gi"); + } catch { + // ignore + return null; + } + }) + .filter((r) => r !== null) as RegExp[]; + if ((mode === "auto" && scene.date && scene.studio) || mode === "metadata") { let str = [ scene.date, @@ -92,8 +103,8 @@ export function prepareQueryString( ] .filter((s) => s !== "") .join(" "); - blacklist.forEach((b) => { - str = str.replace(new RegExp(b, "gi"), " "); + regexs.forEach((re) => { + str = str.replace(re, " "); }); return str; } @@ -106,8 +117,9 @@ export function prepareQueryString( } else if (mode === "dir" && paths.length) { s = paths[paths.length - 1]; } - blacklist.forEach((b) => { - s = s.replace(new RegExp(b, "gi"), " "); + + regexs.forEach((re) => { + s = s.replace(re, " "); }); s = parseDate(s); return s.replace(/\./g, " ").replace(/ +/g, " "); From 41554dcf2736caadac57d7dd5abcf3da6aa1f706 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:54:02 +1100 Subject: [PATCH 2/2] Validate blacklist entries and show errors --- .../src/components/Tagger/scenes/Config.tsx | 160 +++++++++++------- ui/v2.5/src/locales/en-GB.json | 3 + 2 files changed, 99 insertions(+), 64 deletions(-) diff --git a/ui/v2.5/src/components/Tagger/scenes/Config.tsx b/ui/v2.5/src/components/Tagger/scenes/Config.tsx index 1ed137fb04a..f15fbd250f5 100644 --- a/ui/v2.5/src/components/Tagger/scenes/Config.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/Config.tsx @@ -1,5 +1,5 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons"; -import React, { useRef, useContext } from "react"; +import React, { useContext, useState } from "react"; import { Badge, Button, @@ -14,41 +14,110 @@ import { Icon } from "src/components/Shared/Icon"; import { ParseMode, TagOperation } from "../constants"; import { TaggerStateContext } from "../context"; -interface IConfigProps { - show: boolean; -} - -const Config: React.FC = ({ show }) => { - const { config, setConfig } = useContext(TaggerStateContext); +const Blacklist: React.FC<{ + list: string[]; + setList: (blacklist: string[]) => void; +}> = ({ list, setList }) => { const intl = useIntl(); - const blacklistRef = useRef(null); - function addBlacklistItem() { - if (!blacklistRef.current) return; + const [currentValue, setCurrentValue] = useState(""); + const [error, setError] = useState(); - const input = blacklistRef.current.value; - if (!input) return; + function addBlacklistItem() { + if (!currentValue) return; // don't add duplicate items - if (!config.blacklist.includes(input)) { - setConfig({ - ...config, - blacklist: [...config.blacklist, input], - }); + if (list.includes(currentValue)) { + setError( + intl.formatMessage({ + id: "component_tagger.config.errors.blacklist_duplicate", + }) + ); + return; + } + + // validate regex + try { + new RegExp(currentValue); + } catch (e) { + setError((e as SyntaxError).message); + return; } - blacklistRef.current.value = ""; + setList([...list, currentValue]); + + setCurrentValue(""); } function removeBlacklistItem(index: number) { - const newBlacklist = [...config.blacklist]; + const newBlacklist = [...list]; newBlacklist.splice(index, 1); - setConfig({ - ...config, - blacklist: newBlacklist, - }); + setList(newBlacklist); } + return ( +
+
+ +
+ + + { + setCurrentValue(e.currentTarget.value); + setError(undefined); + }} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === "Enter") { + addBlacklistItem(); + e.preventDefault(); + } + }} + isInvalid={!!error} + /> + + + + {error} + + +
+ {intl.formatMessage( + { id: "component_tagger.config.blacklist_desc" }, + { chars_require_escape: [\^$.|?*+() } + )} +
+ {list.map((item, index) => ( + + {item.toString()} + + + ))} +
+ ); +}; + +interface IConfigProps { + show: boolean; +} + +const Config: React.FC = ({ show }) => { + const { config, setConfig } = useContext(TaggerStateContext); + const intl = useIntl(); + return ( @@ -198,47 +267,10 @@ const Config: React.FC = ({ show }) => {
-
- -
- - ) => { - if (e.key === "Enter") { - addBlacklistItem(); - e.preventDefault(); - } - }} - /> - - - - -
- {intl.formatMessage( - { id: "component_tagger.config.blacklist_desc" }, - { chars_require_escape: [\^$.|?*+() } - )} -
- {config.blacklist.map((item, index) => ( - - {item.toString()} - - - ))} + setConfig({ ...config, blacklist })} + />
diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 784579c95b8..143632af005 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -173,6 +173,9 @@ "active_instance": "Active stash-box instance:", "blacklist_desc": "Blacklist items are excluded from queries. Note that they are regular expressions and also case-insensitive. Certain characters must be escaped with a backslash: {chars_require_escape}", "blacklist_label": "Blacklist", + "errors": { + "blacklist_duplicate": "Duplicate blacklist item" + }, "mark_organized_desc": "Immediately mark the scene as Organized after the Save button is clicked.", "mark_organized_label": "Mark as Organized on save", "query_mode_auto": "Auto",