mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Validate custom locale and javascript strings (#4893)
* Validate locale json string * Validate custom javascript string
This commit is contained in:
@@ -273,9 +273,14 @@ export interface ISettingModal<T> {
|
|||||||
subHeading?: React.ReactNode;
|
subHeading?: React.ReactNode;
|
||||||
value: T | undefined;
|
value: T | undefined;
|
||||||
close: (v?: T) => void;
|
close: (v?: T) => void;
|
||||||
renderField: (value: T | undefined, setValue: (v?: T) => void) => JSX.Element;
|
renderField: (
|
||||||
|
value: T | undefined,
|
||||||
|
setValue: (v?: T) => void,
|
||||||
|
error?: string
|
||||||
|
) => JSX.Element;
|
||||||
modalProps?: ModalProps;
|
modalProps?: ModalProps;
|
||||||
validate?: (v: T) => boolean | undefined;
|
validate?: (v: T) => boolean | undefined;
|
||||||
|
error?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
export const SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
||||||
@@ -289,6 +294,7 @@ export const SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
|||||||
renderField,
|
renderField,
|
||||||
modalProps,
|
modalProps,
|
||||||
validate,
|
validate,
|
||||||
|
error,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -306,7 +312,7 @@ export const SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
|||||||
{headingID ? <FormattedMessage id={headingID} /> : heading}
|
{headingID ? <FormattedMessage id={headingID} /> : heading}
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{renderField(currentValue, setCurrentValue)}
|
{renderField(currentValue, setCurrentValue, error)}
|
||||||
{subHeadingID ? (
|
{subHeadingID ? (
|
||||||
<div className="sub-heading">
|
<div className="sub-heading">
|
||||||
{intl.formatMessage({ id: subHeadingID })}
|
{intl.formatMessage({ id: subHeadingID })}
|
||||||
@@ -341,9 +347,14 @@ interface IModalSetting<T> extends ISetting {
|
|||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
buttonTextID?: string;
|
buttonTextID?: string;
|
||||||
onChange: (v: T) => void;
|
onChange: (v: T) => void;
|
||||||
renderField: (value: T | undefined, setValue: (v?: T) => void) => JSX.Element;
|
renderField: (
|
||||||
|
value: T | undefined,
|
||||||
|
setValue: (v?: T) => void,
|
||||||
|
error?: string
|
||||||
|
) => JSX.Element;
|
||||||
renderValue?: (v: T | undefined) => JSX.Element;
|
renderValue?: (v: T | undefined) => JSX.Element;
|
||||||
modalProps?: ModalProps;
|
modalProps?: ModalProps;
|
||||||
|
validateChange?: (v: T) => void | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
||||||
@@ -364,10 +375,29 @@ export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
|||||||
modalProps,
|
modalProps,
|
||||||
disabled,
|
disabled,
|
||||||
advanced,
|
advanced,
|
||||||
|
validateChange,
|
||||||
} = props;
|
} = props;
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
const { advancedMode } = useSettings();
|
const { advancedMode } = useSettings();
|
||||||
|
|
||||||
|
function onClose(v: T | undefined) {
|
||||||
|
setError(undefined);
|
||||||
|
if (v !== undefined) {
|
||||||
|
if (validateChange) {
|
||||||
|
try {
|
||||||
|
validateChange(v);
|
||||||
|
} catch (e) {
|
||||||
|
setError((e as Error).message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(v);
|
||||||
|
}
|
||||||
|
setShowModal(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (advanced && !advancedMode) return null;
|
if (advanced && !advancedMode) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -380,10 +410,8 @@ export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
|||||||
subHeading={subHeading}
|
subHeading={subHeading}
|
||||||
value={value}
|
value={value}
|
||||||
renderField={renderField}
|
renderField={renderField}
|
||||||
close={(v) => {
|
close={onClose}
|
||||||
if (v !== undefined) onChange(v);
|
error={error}
|
||||||
setShowModal(false);
|
|
||||||
}}
|
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
/>
|
/>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|||||||
@@ -135,6 +135,40 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateLocaleString(v: string) {
|
||||||
|
if (!v) return;
|
||||||
|
try {
|
||||||
|
JSON.parse(v);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
intl.formatMessage(
|
||||||
|
{ id: "errors.invalid_json_string" },
|
||||||
|
{
|
||||||
|
error: (e as SyntaxError).message,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateJavascriptString(v: string) {
|
||||||
|
if (!v) return;
|
||||||
|
try {
|
||||||
|
// creates a function from the string to validate it but does not execute it
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
||||||
|
new Function(v);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
intl.formatMessage(
|
||||||
|
{ id: "errors.invalid_javascript_string" },
|
||||||
|
{
|
||||||
|
error: (e as SyntaxError).message,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (error) return <h1>{error.message}</h1>;
|
if (error) return <h1>{error.message}</h1>;
|
||||||
if (loading) return <LoadingIndicator />;
|
if (loading) return <LoadingIndicator />;
|
||||||
|
|
||||||
@@ -726,16 +760,23 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
subHeadingID="config.ui.custom_javascript.description"
|
subHeadingID="config.ui.custom_javascript.description"
|
||||||
value={iface.javascript ?? undefined}
|
value={iface.javascript ?? undefined}
|
||||||
onChange={(v) => saveInterface({ javascript: v })}
|
onChange={(v) => saveInterface({ javascript: v })}
|
||||||
renderField={(value, setValue) => (
|
validateChange={validateJavascriptString}
|
||||||
<Form.Control
|
renderField={(value, setValue, err) => (
|
||||||
as="textarea"
|
<>
|
||||||
value={value}
|
<Form.Control
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
as="textarea"
|
||||||
setValue(e.currentTarget.value)
|
value={value}
|
||||||
}
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
rows={16}
|
setValue(e.currentTarget.value)
|
||||||
className="text-input code"
|
}
|
||||||
/>
|
rows={16}
|
||||||
|
className="text-input code"
|
||||||
|
isInvalid={!!err}
|
||||||
|
/>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{err}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
renderValue={() => {
|
renderValue={() => {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -756,16 +797,23 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
subHeadingID="config.ui.custom_locales.description"
|
subHeadingID="config.ui.custom_locales.description"
|
||||||
value={iface.customLocales ?? undefined}
|
value={iface.customLocales ?? undefined}
|
||||||
onChange={(v) => saveInterface({ customLocales: v })}
|
onChange={(v) => saveInterface({ customLocales: v })}
|
||||||
renderField={(value, setValue) => (
|
validateChange={validateLocaleString}
|
||||||
<Form.Control
|
renderField={(value, setValue, err) => (
|
||||||
as="textarea"
|
<>
|
||||||
value={value}
|
<Form.Control
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
as="textarea"
|
||||||
setValue(e.currentTarget.value)
|
value={value}
|
||||||
}
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
rows={16}
|
setValue(e.currentTarget.value)
|
||||||
className="text-input code"
|
}
|
||||||
/>
|
rows={16}
|
||||||
|
className="text-input code"
|
||||||
|
isInvalid={!!err}
|
||||||
|
/>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{err}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
renderValue={() => {
|
renderValue={() => {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@@ -1017,6 +1017,8 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"header": "Error",
|
"header": "Error",
|
||||||
"image_index_greater_than_zero": "Image index must be greater than 0",
|
"image_index_greater_than_zero": "Image index must be greater than 0",
|
||||||
|
"invalid_javascript_string": "Invalid javascript code: {error}",
|
||||||
|
"invalid_json_string": "Invalid JSON string: {error}",
|
||||||
"lazy_component_error_help": "If you recently upgraded Stash, please reload the page or clear your browser cache.",
|
"lazy_component_error_help": "If you recently upgraded Stash, please reload the page or clear your browser cache.",
|
||||||
"loading_type": "Error loading {type}",
|
"loading_type": "Error loading {type}",
|
||||||
"something_went_wrong": "Something went wrong."
|
"something_went_wrong": "Something went wrong."
|
||||||
|
|||||||
Reference in New Issue
Block a user