From e4b6c3fd4ddae5352969ff4e3178bf413806148e Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:58:26 -0700 Subject: [PATCH] Disallow special characters in view URL (#26280) * Disallow special characters in view URL --- src/panels/lovelace/editor/types.ts | 1 + .../view-editor/hui-dialog-edit-view.ts | 6 ++++++ .../editor/view-editor/hui-view-editor.ts | 21 ++++++++++++++++++- src/translations/en.json | 2 ++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index 7731cb730a..30878a3540 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -25,6 +25,7 @@ export interface GUIModeChangedEvent { export interface ViewEditEvent extends Event { detail: { config: LovelaceViewConfig; + valid?: boolean; }; } diff --git a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts index 801691a552..77eedca8a5 100644 --- a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts @@ -73,6 +73,8 @@ export class HuiDialogEditView extends LitElement { @state() private _dirty = false; + @state() private _valid = true; + @state() private _yamlMode = false; @query("ha-yaml-editor") private _editor?: HaYamlEditor; @@ -308,6 +310,7 @@ export class HuiDialogEditView extends LitElement { ?disabled=${!this._config || this._saving || !this._dirty || + !this._valid || convertToSection || convertNotSupported} @click=${this._save} @@ -579,6 +582,9 @@ export class HuiDialogEditView extends LitElement { ev.detail.config && !deepEqual(this._config, ev.detail.config) ) { + if (ev.detail.valid !== undefined) { + this._valid = ev.detail.valid; + } this._config = ev.detail.config; this._dirty = true; } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index c67204e344..76d0307417 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -23,10 +23,13 @@ declare global { interface HASSDomEvents { "view-config-changed": { config: LovelaceViewConfig; + valid?: boolean; }; } } +const VALID_PATH_REGEX = /^[a-zA-Z0-9_-]+$/; + @customElement("hui-view-editor") export class HuiViewEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -35,6 +38,8 @@ export class HuiViewEditor extends LitElement { @state() private _config!: LovelaceViewConfig; + @state() private _error: Record | undefined; + private _suggestedPath = false; private _schema = memoizeOne( @@ -144,6 +149,8 @@ export class HuiViewEditor extends LitElement { .schema=${schema} .computeLabel=${this._computeLabel} .computeHelper=${this._computeHelper} + .computeError=${this._computeError} + .error=${this._error} @value-changed=${this._valueChanged} > `; @@ -168,9 +175,20 @@ export class HuiViewEditor extends LitElement { config.path = slugify(config.title || "", "-"); } - fireEvent(this, "view-config-changed", { config }); + let valid = true; + this._error = undefined; + if (config.path && !VALID_PATH_REGEX.test(config.path)) { + valid = false; + this._error = { path: "error_invalid_path" }; + } + + fireEvent(this, "view-config-changed", { valid, config }); } + private _computeError = (error: string) => + this.hass.localize(`ui.panel.lovelace.editor.edit_view.${error}` as any) || + error; + private _computeLabel = ( schema: SchemaUnion> ) => { @@ -197,6 +215,7 @@ export class HuiViewEditor extends LitElement { schema: SchemaUnion> ) => { switch (schema.name) { + case "path": case "subview": case "dense_section_placement": case "top_margin": diff --git a/src/translations/en.json b/src/translations/en.json index c19498c7f1..a6de4a8e72 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7028,10 +7028,12 @@ "top_margin": "Add additional space above", "top_margin_helper": "Helps reveal more of the background", "subview_helper": "Subviews don't appear in tabs and have a back button.", + "path_helper": "This value will become part of the URL path to open this view.", "edit_ui": "Edit in visual editor", "edit_yaml": "Edit in YAML", "saving_failed": "Saving failed", "error_same_url": "You cannot save a view with the same URL as a different existing view.", + "error_invalid_path": "URL contains invalid/reserved characters. Please enter a simple string only for the path of this view.", "move_to_dashboard": "Move to dashboard" }, "edit_view_header": {