Преглед изворни кода

Sanitize Nightscout token to prevent WebSocket crash on launch (#688)

* Sanitize Nightscout token to prevent WebSocket crash on launch

Strip whitespace, newlines, and control characters from the token
before storing it and before opening the WebSocket. A stray character
(typically pasted in) produced an invalid percent-encoded query in
Socket.IO's URL builder, which traps on iOS 26 and crashed the app on
startup. Existing saved tokens are sanitized defensively at connect time.

* Sanitize URL entry path, not just token, to prevent WebSocket crash
Jonas Björkert пре 1 месец
родитељ
комит
a0b07475d3

+ 4 - 2
LoopFollow/Controllers/Nightscout/NightscoutSocketManager.swift

@@ -40,8 +40,10 @@ class NightscoutSocketManager {
             return
             return
         }
         }
 
 
-        let url = Storage.shared.url.value
-        let token = Storage.shared.token.value
+        // Sanitize defensively: values saved before this fix may still hold a stray
+        // whitespace/control char that crashes Socket.IO's URL builder on iOS 26.
+        let url = NightscoutUtils.sanitizeConnectionInput(Storage.shared.url.value)
+        let token = NightscoutUtils.sanitizeConnectionInput(Storage.shared.token.value)
 
 
         guard !url.isEmpty else {
         guard !url.isEmpty else {
             disconnect()
             disconnect()

+ 11 - 0
LoopFollow/Helpers/NightscoutUtils.swift

@@ -162,6 +162,17 @@ class NightscoutUtils {
         return request
         return request
     }
     }
 
 
+    // Strip whitespace/newlines/control characters that can sneak in via paste.
+    // Neither a URL nor a token may legally contain them, and a stray one breaks
+    // WebSocket connect (invalid percent-encoded query traps on iOS 26) or makes
+    // URL parsing fall back to a lossy cleanup that mangles the address.
+    static func sanitizeConnectionInput(_ input: String) -> String {
+        input.unicodeScalars
+            .filter { !CharacterSet.whitespacesAndNewlines.contains($0) && !CharacterSet.controlCharacters.contains($0) }
+            .map(String.init)
+            .joined()
+    }
+
     static func constructURL(baseURL: String, token: String?, endpoint: String, parameters: [String: String]) -> URL? {
     static func constructURL(baseURL: String, token: String?, endpoint: String, parameters: [String: String]) -> URL? {
         var components = URLComponents(string: baseURL)
         var components = URLComponents(string: baseURL)
         components?.path = endpoint
         components?.path = endpoint

+ 7 - 1
LoopFollow/Nightscout/NightscoutSettingsViewModel.swift

@@ -27,7 +27,7 @@ class NightscoutSettingsViewModel: ObservableObject {
     @Published var nightscoutToken: String = Storage.shared.token.value {
     @Published var nightscoutToken: String = Storage.shared.token.value {
         willSet {
         willSet {
             if newValue != nightscoutToken {
             if newValue != nightscoutToken {
-                Storage.shared.token.value = newValue
+                Storage.shared.token.value = NightscoutUtils.sanitizeConnectionInput(newValue)
                 triggerCheckStatus()
                 triggerCheckStatus()
             }
             }
         }
         }
@@ -93,6 +93,12 @@ class NightscoutSettingsViewModel: ObservableObject {
     }
     }
 
 
     func processURL(_ value: String) {
     func processURL(_ value: String) {
+        // Strip whitespace/newlines/control chars first. A URL can't legally contain
+        // them, and a stray one (e.g. a trailing newline from a paste) otherwise makes
+        // URLComponents parsing fail and falls through to the lossy fallback below,
+        // mangling a URL-with-embedded-token into an invalid address.
+        let value = NightscoutUtils.sanitizeConnectionInput(value)
+
         var useTokenUrl = false
         var useTokenUrl = false
 
 
         if let urlComponents = URLComponents(string: value), let queryItems = urlComponents.queryItems {
         if let urlComponents = URLComponents(string: value), let queryItems = urlComponents.queryItems {