// Client for forwarding requests to k_server (:8780). // Mirrors the k_proxy → k_server leg in k_proxy_app.py. import 'dart:io'; import 'dart:typed_data'; const String kServerHost = '127.0.0.1'; // k_server address (same device or Qubes forward) const int kServerPort = 8780; class KServerResponse { final int statusCode; final HttpHeaders headers; final Uint8List body; KServerResponse({ required this.statusCode, required this.headers, required this.body, }); } class KServerClient { HttpClient? _client; HttpClient _getClient() { // TLS: k_server uses self-signed cert from generate_phase2_certs.py. // In dev, accept any cert; in prod, pin the CA cert. _client ??= HttpClient() ..badCertificateCallback = (cert, host, port) { // TODO: replace with CA pinning once certs are bundled. return true; }; return _client!; } Future forward({ required String method, required String path, required HttpHeaders headers, required Uint8List body, }) async { final client = _getClient(); final uri = Uri( scheme: 'https', host: kServerHost, port: kServerPort, path: path, ); final req = await client.openUrl(method, uri); // Forward relevant headers headers.forEach((name, values) { if (_shouldForwardHeader(name)) { for (final v in values) req.headers.add(name, v); } }); if (body.isNotEmpty) { req.headers.contentLength = body.length; req.add(body); } final res = await req.close(); final resBody = await res.fold>([], (a, b) => a..addAll(b)); return KServerResponse( statusCode: res.statusCode, headers: res.headers, body: Uint8List.fromList(resBody), ); } bool _shouldForwardHeader(String name) { // 'authorization' is intentionally stripped: it carries the k_phone session // token which is meaningless to k_server. k_server authenticates via the // X-Proxy-Token header added by the Kotlin layer, not by bearer tokens. const skip = {'host', 'connection', 'transfer-encoding', 'authorization'}; return !skip.contains(name.toLowerCase()); } void close() => _client?.close(); }