import Foundation /// Mirrors internal/protocols/hysteria2 config_write.go + endpoint.go (SOCKS 1080 / HTTP 1081). enum Hy2ConfigBuilder { struct Endpoint { var server: String var auth: String = "" var sni: String = "" var insecure: Bool = false var pinSHA256: String = "" var obfs: String = "" var obfsPassword: String = "" } static func buildYAML(uri: String, socks: String = "127.0.0.1:1080", http: String = "127.0.0.1:1081") throws -> String { let ep = try parseEndpoint(uri) var b = "# generated by Navis — do not edit\n" b += "server: \(yamlQuote(ep.server))\n" if !ep.auth.isEmpty { b += "auth: \(yamlQuote(ep.auth))\n" } if !ep.sni.isEmpty || ep.insecure || !ep.pinSHA256.isEmpty { b += "tls:\n" if !ep.sni.isEmpty { b += " sni: \(yamlQuote(ep.sni))\n" } if ep.insecure { b += " insecure: true\n" } if !ep.pinSHA256.isEmpty { b += " pinSHA256: \(yamlQuote(ep.pinSHA256))\n" } } let obfs = ep.obfs.lowercased() switch obfs { case "salamander": if ep.obfsPassword.isEmpty { throw BuilderError.message("hysteria2: salamander требует пароль (obfs-password)") } b += "obfs:\n type: salamander\n salamander:\n password: \(yamlQuote(ep.obfsPassword))\n" case "gecko": if ep.obfsPassword.isEmpty { throw BuilderError.message("hysteria2: gecko требует пароль (obfs-password)") } b += "obfs:\n type: gecko\n gecko:\n password: \(yamlQuote(ep.obfsPassword))\n" default: break } // Default BBR like desktop when no bandwidth overrides. b += "congestion:\n type: bbr\n bbrProfile: standard\n" b += "socks5:\n listen: \(yamlQuote(socks))\n" b += "http:\n listen: \(yamlQuote(http))\n" return b } static func parseEndpoint(_ raw: String) throws -> Endpoint { var s = raw.trimmingCharacters(in: .whitespacesAndNewlines) if let hash = s.firstIndex(of: "#") { s = String(s[.. String { for k in keys { if let v = q[k], !v.trimmingCharacters(in: .whitespaces).isEmpty { return v.trimmingCharacters(in: .whitespaces) } } return "" } private static func yamlQuote(_ s: String) -> String { if s.isEmpty { return "\"\"" } if s.contains(where: { ":#{}[],&*?|-<>=!%@`'\"".contains($0) || $0.isWhitespace }) { let escaped = s.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") return "\"\(escaped)\"" } return s } }