Disallow special characters in view URL (#26280)

* Disallow special characters in view URL
This commit is contained in:
karwosts 2025-07-24 11:58:26 -07:00 committed by GitHub
parent 43f1d9be44
commit e4b6c3fd4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 29 additions and 1 deletions

View File

@ -25,6 +25,7 @@ export interface GUIModeChangedEvent {
export interface ViewEditEvent extends Event { export interface ViewEditEvent extends Event {
detail: { detail: {
config: LovelaceViewConfig; config: LovelaceViewConfig;
valid?: boolean;
}; };
} }

View File

@ -73,6 +73,8 @@ export class HuiDialogEditView extends LitElement {
@state() private _dirty = false; @state() private _dirty = false;
@state() private _valid = true;
@state() private _yamlMode = false; @state() private _yamlMode = false;
@query("ha-yaml-editor") private _editor?: HaYamlEditor; @query("ha-yaml-editor") private _editor?: HaYamlEditor;
@ -308,6 +310,7 @@ export class HuiDialogEditView extends LitElement {
?disabled=${!this._config || ?disabled=${!this._config ||
this._saving || this._saving ||
!this._dirty || !this._dirty ||
!this._valid ||
convertToSection || convertToSection ||
convertNotSupported} convertNotSupported}
@click=${this._save} @click=${this._save}
@ -579,6 +582,9 @@ export class HuiDialogEditView extends LitElement {
ev.detail.config && ev.detail.config &&
!deepEqual(this._config, 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._config = ev.detail.config;
this._dirty = true; this._dirty = true;
} }

View File

@ -23,10 +23,13 @@ declare global {
interface HASSDomEvents { interface HASSDomEvents {
"view-config-changed": { "view-config-changed": {
config: LovelaceViewConfig; config: LovelaceViewConfig;
valid?: boolean;
}; };
} }
} }
const VALID_PATH_REGEX = /^[a-zA-Z0-9_-]+$/;
@customElement("hui-view-editor") @customElement("hui-view-editor")
export class HuiViewEditor extends LitElement { export class HuiViewEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -35,6 +38,8 @@ export class HuiViewEditor extends LitElement {
@state() private _config!: LovelaceViewConfig; @state() private _config!: LovelaceViewConfig;
@state() private _error: Record<string, string> | undefined;
private _suggestedPath = false; private _suggestedPath = false;
private _schema = memoizeOne( private _schema = memoizeOne(
@ -144,6 +149,8 @@ export class HuiViewEditor extends LitElement {
.schema=${schema} .schema=${schema}
.computeLabel=${this._computeLabel} .computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper} .computeHelper=${this._computeHelper}
.computeError=${this._computeError}
.error=${this._error}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-form> ></ha-form>
`; `;
@ -168,9 +175,20 @@ export class HuiViewEditor extends LitElement {
config.path = slugify(config.title || "", "-"); 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 = ( private _computeLabel = (
schema: SchemaUnion<ReturnType<typeof this._schema>> schema: SchemaUnion<ReturnType<typeof this._schema>>
) => { ) => {
@ -197,6 +215,7 @@ export class HuiViewEditor extends LitElement {
schema: SchemaUnion<ReturnType<typeof this._schema>> schema: SchemaUnion<ReturnType<typeof this._schema>>
) => { ) => {
switch (schema.name) { switch (schema.name) {
case "path":
case "subview": case "subview":
case "dense_section_placement": case "dense_section_placement":
case "top_margin": case "top_margin":

View File

@ -7028,10 +7028,12 @@
"top_margin": "Add additional space above", "top_margin": "Add additional space above",
"top_margin_helper": "Helps reveal more of the background", "top_margin_helper": "Helps reveal more of the background",
"subview_helper": "Subviews don't appear in tabs and have a back button.", "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_ui": "Edit in visual editor",
"edit_yaml": "Edit in YAML", "edit_yaml": "Edit in YAML",
"saving_failed": "Saving failed", "saving_failed": "Saving failed",
"error_same_url": "You cannot save a view with the same URL as a different existing view.", "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" "move_to_dashboard": "Move to dashboard"
}, },
"edit_view_header": { "edit_view_header": {