SHA256
Introduce real SwiftUI auth/dashboard/task/note/shopping screens, a bounded origin-bound URLSession transport and Keychain-backed native sessions. Archive the rejected WebKit renderer rather than embedding it for unfinished modules. Sixteen portable URL/contract/transport tests and Swift format/parsing pass on Linux; Apple SDK application and UI validation remains mandatory. This is an incomplete native slice, not full PWA parity or a release.
58 lines
2.3 KiB
Swift
58 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
struct ServerAddress: Equatable, Sendable {
|
|
let url: URL
|
|
|
|
init(_ input: String) throws {
|
|
let value = input.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !value.contains("\\"), !value.contains(where: { $0.isWhitespace }),
|
|
var parts = URLComponents(string: value),
|
|
parts.scheme?.lowercased() == "https",
|
|
let host = parts.host, !host.isEmpty,
|
|
parts.user == nil, parts.password == nil,
|
|
parts.query == nil, parts.fragment == nil,
|
|
parts.percentEncodedPath.isEmpty || parts.percentEncodedPath == "/",
|
|
parts.port == nil || (1...65535).contains(parts.port!)
|
|
else {
|
|
throw ConnectionError.invalidAddress
|
|
}
|
|
parts.scheme = "https"
|
|
parts.host = host.lowercased()
|
|
parts.path = ""
|
|
if parts.port == 443 { parts.port = nil }
|
|
guard let url = parts.url else { throw ConnectionError.invalidAddress }
|
|
self.url = url
|
|
}
|
|
|
|
func owns(_ candidate: URL) -> Bool {
|
|
guard let parts = URLComponents(url: candidate, resolvingAgainstBaseURL: false),
|
|
let own = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
|
else { return false }
|
|
return parts.scheme?.lowercased() == "https" && parts.user == nil && parts.password == nil
|
|
&& parts.host?.lowercased() == own.host?.lowercased()
|
|
&& (parts.port ?? 443) == (own.port ?? 443)
|
|
}
|
|
|
|
func ownsDownload(_ candidate: URL) -> Bool {
|
|
if owns(candidate) { return true }
|
|
guard candidate.scheme == "blob",
|
|
let inner = URL(string: String(candidate.absoluteString.dropFirst(5)))
|
|
else { return false }
|
|
return owns(inner)
|
|
}
|
|
}
|
|
|
|
enum ConnectionError: LocalizedError {
|
|
case invalidAddress, incompatible, unavailable
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .invalidAddress:
|
|
return "Use a root HTTPS URL with a valid host and port. Remove credentials, subpaths, query and fragment."
|
|
case .incompatible:
|
|
return "This server does not expose compatible Yuvomi metadata."
|
|
case .unavailable:
|
|
return "Cannot verify the server. Check its certificate, VPN, network and reverse proxy."
|
|
}
|
|
}
|
|
}
|