From afe3d1e2612a3a9f64e264f33bd21f99ad038061 Mon Sep 17 00:00:00 2001 From: Ilyas Hallak Date: Sun, 19 Oct 2025 19:37:35 +0200 Subject: [PATCH] feat: Add endpoint normalization with validation rules - Default to https if no scheme provided - Only accept http and https schemes - Add trailing slash to path automatically - Remove query parameters and fragments - Update endpoint field with normalized value after save --- .../UI/Settings/SettingsServerViewModel.swift | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/readeck/UI/Settings/SettingsServerViewModel.swift b/readeck/UI/Settings/SettingsServerViewModel.swift index 30abf33..468757a 100644 --- a/readeck/UI/Settings/SettingsServerViewModel.swift +++ b/readeck/UI/Settings/SettingsServerViewModel.swift @@ -62,8 +62,15 @@ class SettingsServerViewModel { isLoading = true defer { isLoading = false } do { - let user = try await loginUseCase.execute(endpoint: endpoint, username: username.trimmingCharacters(in: .whitespacesAndNewlines), password: password) - try await saveServerSettingsUseCase.execute(endpoint: endpoint, username: username, password: password, token: user.token) + // Normalize endpoint before saving + let normalizedEndpoint = normalizeEndpoint(endpoint) + + let user = try await loginUseCase.execute(endpoint: normalizedEndpoint, username: username.trimmingCharacters(in: .whitespacesAndNewlines), password: password) + try await saveServerSettingsUseCase.execute(endpoint: normalizedEndpoint, username: username, password: password, token: user.token) + + // Update local endpoint with normalized version + endpoint = normalizedEndpoint + isLoggedIn = true successMessage = "Server settings saved and successfully logged in." try await SettingsRepository().saveHasFinishedSetup(true) @@ -73,6 +80,55 @@ class SettingsServerViewModel { isLoggedIn = false } } + + // MARK: - Endpoint Normalization + + private func normalizeEndpoint(_ endpoint: String) -> String { + var normalized = endpoint.trimmingCharacters(in: .whitespacesAndNewlines) + + // Remove query parameters + if let queryIndex = normalized.firstIndex(of: "?") { + normalized = String(normalized[.. String { + var urlComponents = components + + // Ensure scheme is http or https, default to https + if urlComponents.scheme == nil { + urlComponents.scheme = "https" + } else if urlComponents.scheme != "http" && urlComponents.scheme != "https" { + urlComponents.scheme = "https" + } + + // Add trailing slash to path if not present + if urlComponents.path.isEmpty || !urlComponents.path.hasSuffix("/") { + if urlComponents.path.isEmpty { + urlComponents.path = "/" + } else { + urlComponents.path += "/" + } + } + + // Remove query parameters (already done above, but double check) + urlComponents.query = nil + urlComponents.fragment = nil + + return urlComponents.string ?? components.string ?? "" + } @MainActor func logout() async {