From 6659ad9896318474c7d9d99149d505884819eaa2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:57:06 +0000 Subject: [PATCH 1/5] Initial plan From 346a769ef05c58ed40c4fdc0810aa86d1cf8bd16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:05:34 +0000 Subject: [PATCH 2/5] Implement webview navigation domain validation and 404 handling Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- .../Resources/en.lproj/Localizable.strings | 2 + Sources/App/WebView/WebViewController.swift | 63 ++++++++++++++++++- .../Shared/Resources/Swiftgen/Strings.swift | 6 ++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Sources/App/Resources/en.lproj/Localizable.strings b/Sources/App/Resources/en.lproj/Localizable.strings index 5808aa6e4..f24c37bc5 100644 --- a/Sources/App/Resources/en.lproj/Localizable.strings +++ b/Sources/App/Resources/en.lproj/Localizable.strings @@ -49,6 +49,8 @@ "alerts.open_url_from_notification.title" = "Open URL?"; "alerts.prompt.cancel" = "Cancel"; "alerts.prompt.ok" = "OK"; +"alerts.navigation_error.message" = "This page cannot be displayed because it's outside your Home Assistant server or the page was not found."; +"alerts.navigation_error.title" = "Navigation Error"; "always_open_label" = "Always Open"; "announcement.drop_support.button" = "Continue"; "announcement.drop_support.subtitle" = "After careful consideration, we will be discontinuing support for iOS 12, 13 and 14 in our upcoming updates."; diff --git a/Sources/App/WebView/WebViewController.swift b/Sources/App/WebView/WebViewController.swift index ff696c2c0..8cb24f929 100644 --- a/Sources/App/WebView/WebViewController.swift +++ b/Sources/App/WebView/WebViewController.swift @@ -1280,6 +1280,47 @@ extension WebViewController { } } + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + // Only check domain for user-initiated link clicks + guard navigationAction.navigationType == .linkActivated else { + // Allow all other navigation types (back/forward, reload, programmatic, etc.) + decisionHandler(.allow) + return + } + + guard let targetURL = navigationAction.request.url else { + decisionHandler(.allow) + return + } + + // Allow special schemes like about:blank + if let scheme = targetURL.scheme?.lowercased(), ["about", "file"].contains(scheme) { + decisionHandler(.allow) + return + } + + // Check if the target URL belongs to the active server domain + guard let activeURL = server.info.connection.activeURL() else { + // If there's no active URL, allow navigation (let other error handling deal with it) + decisionHandler(.allow) + return + } + + // Allow navigation within the same domain + if targetURL.baseIsEqual(to: activeURL) { + decisionHandler(.allow) + } else { + // URL is outside the active domain - cancel and show alert + Current.Log.warning("Navigation blocked: URL \(targetURL) is outside active domain \(activeURL)") + decisionHandler(.cancel) + showNavigationErrorAlert() + } + } + func webView( _ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, @@ -1303,8 +1344,13 @@ extension WebViewController { // error response, let's inspect if it's restoring a page or normal navigation if navigationResponse.response.url != initialURL { - // just a normal loading error - decisionHandler(.allow) + // Normal loading error (not initial URL restoration) + // Cancel the navigation, go back if possible, and show alert + decisionHandler(.cancel) + if webView.canGoBack { + webView.goBack() + } + showNavigationErrorAlert() } else { // first: clear that saved url, it's bad initialURL = nil @@ -1321,6 +1367,19 @@ extension WebViewController { } } + private func showNavigationErrorAlert() { + let alert = UIAlertController( + title: L10n.Alerts.NavigationError.title, + message: L10n.Alerts.NavigationError.message, + preferredStyle: .alert + ) + alert.addAction(.init(title: L10n.okLabel, style: .default)) + + DispatchQueue.main.async { [weak self] in + self?.present(alert, animated: true) + } + } + // WKUIDelegate func webView( _ webView: WKWebView, diff --git a/Sources/Shared/Resources/Swiftgen/Strings.swift b/Sources/Shared/Resources/Swiftgen/Strings.swift index b6ace3c1b..01af36ee4 100644 --- a/Sources/Shared/Resources/Swiftgen/Strings.swift +++ b/Sources/Shared/Resources/Swiftgen/Strings.swift @@ -254,6 +254,12 @@ public enum L10n { /// OK public static var ok: String { return L10n.tr("Localizable", "alerts.prompt.ok") } } + public enum NavigationError { + /// This page cannot be displayed because it's outside your Home Assistant server or the page was not found. + public static var message: String { return L10n.tr("Localizable", "alerts.navigation_error.message") } + /// Navigation Error + public static var title: String { return L10n.tr("Localizable", "alerts.navigation_error.title") } + } } public enum Announcement { From 17ba48f542d4d91835a7f7afac4e7249c62331a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:08:20 +0000 Subject: [PATCH 3/5] Prevent duplicate navigation error alerts Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- Sources/App/WebView/WebViewController.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/App/WebView/WebViewController.swift b/Sources/App/WebView/WebViewController.swift index 8cb24f929..0bf17fbc5 100644 --- a/Sources/App/WebView/WebViewController.swift +++ b/Sources/App/WebView/WebViewController.swift @@ -1376,7 +1376,12 @@ extension WebViewController { alert.addAction(.init(title: L10n.okLabel, style: .default)) DispatchQueue.main.async { [weak self] in - self?.present(alert, animated: true) + guard let self else { return } + if presentedViewController != nil { + Current.Log.info("Navigation error alert not shown because another view is already presented") + return + } + present(alert, animated: true) } } From 076b5c2b56d876d1ece87c5c4a46add309cdada5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o?= <5808343+bgoncal@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:43:02 +0100 Subject: [PATCH 4/5] Lint --- Sources/App/WebView/WebViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/App/WebView/WebViewController.swift b/Sources/App/WebView/WebViewController.swift index 0bf17fbc5..12f99a8b9 100644 --- a/Sources/App/WebView/WebViewController.swift +++ b/Sources/App/WebView/WebViewController.swift @@ -1374,7 +1374,7 @@ extension WebViewController { preferredStyle: .alert ) alert.addAction(.init(title: L10n.okLabel, style: .default)) - + DispatchQueue.main.async { [weak self] in guard let self else { return } if presentedViewController != nil { From b00f6d3b0b3ad647fb7169a3898876c39015b905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o?= <5808343+bgoncal@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:04:02 +0100 Subject: [PATCH 5/5] Strings --- Sources/Shared/Resources/Swiftgen/Strings.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Shared/Resources/Swiftgen/Strings.swift b/Sources/Shared/Resources/Swiftgen/Strings.swift index 4b3903f23..62c6cea15 100644 --- a/Sources/Shared/Resources/Swiftgen/Strings.swift +++ b/Sources/Shared/Resources/Swiftgen/Strings.swift @@ -236,6 +236,12 @@ public enum L10n { public static var title: String { return L10n.tr("Localizable", "alerts.deprecations.notification_category.title") } } } + public enum NavigationError { + /// This page cannot be displayed because it's outside your Home Assistant server or the page was not found. + public static var message: String { return L10n.tr("Localizable", "alerts.navigation_error.message") } + /// Navigation Error + public static var title: String { return L10n.tr("Localizable", "alerts.navigation_error.title") } + } public enum OpenUrlFromDeepLink { /// Open URL (%@) from deep link? public static func message(_ p1: Any) -> String { @@ -256,12 +262,6 @@ public enum L10n { /// OK public static var ok: String { return L10n.tr("Localizable", "alerts.prompt.ok") } } - public enum NavigationError { - /// This page cannot be displayed because it's outside your Home Assistant server or the page was not found. - public static var message: String { return L10n.tr("Localizable", "alerts.navigation_error.message") } - /// Navigation Error - public static var title: String { return L10n.tr("Localizable", "alerts.navigation_error.title") } - } } public enum Announcement {