import Foundation /// Macht aus dem, was jemand eintippt, eine Adresse, die antwortet. /// /// Weder Jellyfin noch Flaro haben ein Verzeichnis wie plex.tv — die Adresse /// kommt vom Nutzer. Und die tippt niemand vollständig: „192.168.1.50" ohne /// Schema, ohne Port, oder „medien.example.com" ohne „https://". Dieser Typ /// rät nicht **eine** Ergänzung, sondern nennt die plausiblen in der /// Reihenfolge, in der sie es wahrscheinlich sind; der Aufrufer probiert sie /// durch und nimmt die erste, die sich ausweist. /// /// Die Reihenfolge ist der ganze Inhalt dieser Datei, und sie ist nicht /// beliebig: eine IP-Adresse im eigenen Netz spricht fast nie HTTPS, ein /// Domänenname fast immer. /// /// **In der neutralen Schicht, weil beide Server dasselbe Problem haben.** /// Nur der Standardport unterscheidet sich, und der ist ein Wert, kein /// Gedanke. Diese Überlegungen zweimal hinzuschreiben hiesse, dass die eine /// Fassung beim nächsten Sonderfall nachgezogen wird und die andere nicht. public enum ServerAddress { public static func candidates(for input: String, defaultPort: Int) -> [URL] { var text = input.trimmingCharacters(in: .whitespacesAndNewlines) while text.hasSuffix("/") { text.removeLast() } guard !text.isEmpty else { return [] } // Wer ein Schema hinschreibt, meint es auch so. Daran wird nichts // ergänzt — sonst landet eine Anfrage unverschlüsselt, obwohl jemand // ausdrücklich „https" getippt hat. if text.contains("://") { return URL(string: text).map { [$0] } ?? [] } // IPv6 in eckigen Klammern: hier wird nicht geraten, weil die // Doppelpunkte sonst als Port gelesen würden. if text.hasPrefix("[") { return ["http://\(text)", "https://\(text)"].compactMap(URL.init(string:)) } let authority = String(text.split(separator: "/", maxSplits: 1).first ?? "") let host = String(authority.split(separator: ":").first ?? "") let hasExplicitPort = authority.contains(":") && Int(authority.split(separator: ":").last ?? "") != nil if hasExplicitPort { // Port genannt, Schema nicht: erst unverschlüsselt, weil ein // eigener Port fast immer ein Server ohne Zertifikat ist. return ["http://\(text)", "https://\(text)"].compactMap(URL.init(string:)) } // Eine reine Zahlenadresse, ein Bonjour-Name oder ein Wort ohne Punkt // — alles Dinge, die es nur im eigenen Netz gibt. let isLocalLooking = host.allSatisfy { $0.isNumber || $0 == "." } || host.hasSuffix(".local") || !host.contains(".") let mitPort = withDefaultPort(text, port: defaultPort) let forms = isLocalLooking ? ["http://\(mitPort)", "http://\(text)", "https://\(text)"] : ["https://\(text)", "http://\(text)", "http://\(mitPort)"] return forms.compactMap(URL.init(string:)) } /// Hängt den Standardport an — vor einen etwaigen Pfad, nicht dahinter. private static func withDefaultPort(_ text: String, port: Int) -> String { guard let slash = text.firstIndex(of: "/") else { return "\(text):\(port)" } return "\(text[text.startIndex..