/* * This file is part of Stack Wallet. * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. * Generated by Cypher Stack on 2023-05-26 * */ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:socks5_proxy/socks.dart'; import 'package:tor_ffi_plugin/socks_socket.dart'; import '../widgets/desktop/primary_button.dart'; import '../widgets/desktop/secondary_button.dart'; import '../widgets/stack_dialog.dart'; import 'format.dart'; import 'logger.dart'; class MoneroNodeConnectionResponse { final X509Certificate? cert; final String? url; final int? port; final bool success; MoneroNodeConnectionResponse(this.cert, this.url, this.port, this.success); } Future testMoneroNodeConnection( Uri uri, bool allowBadX509Certificate, { required ({ InternetAddress host, int port, })? proxyInfo, }) async { if (uri.host.endsWith(".onion")) { if (proxyInfo == null) { // If the host ends in .onion, we can't access it without Tor. return MoneroNodeConnectionResponse(null, null, null, false); } SOCKSSocket? socket; try { // An HttpClient cannot be used for onion nodes. // // The SOCKSSocket class from the tor_ffi_plugin package can be used to // connect to .onion addresses. We'll do the same things as above but // with SOCKSSocket instead of httpClient. socket = await SOCKSSocket.create( proxyHost: proxyInfo.host.address, proxyPort: proxyInfo.port, sslEnabled: false, ); await socket.connect(); await socket.connectTo(uri.host, uri.port); final body = jsonEncode({ "jsonrpc": "2.0", "id": "0", "method": "get_info", }); final request = 'POST /json_rpc HTTP/1.1\r\n' 'Host: ${uri.host}\r\n' 'Content-Type: application/json\r\n' 'Content-Length: ${body.length}\r\n' '\r\n' '$body'; socket.write(request); print("Request sent: $request"); final buffer = StringBuffer(); await for (var response in socket.inputStream) { buffer.write(utf8.decode(response)); if (buffer.toString().contains("\r\n\r\n")) { break; } } final result = buffer.toString(); print("Response received: $result"); // Check if the response contains "results" and does not contain "error" final success = result.contains('"result":') && !result.contains('"error"'); return MoneroNodeConnectionResponse(null, null, null, success); } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); return MoneroNodeConnectionResponse(null, null, null, false); } finally { await socket?.close(); } } else { final httpClient = HttpClient(); MoneroNodeConnectionResponse? badCertResponse; try { if (proxyInfo != null) { SocksTCPClient.assignToHttpClient(httpClient, [ ProxySettings( proxyInfo.host, proxyInfo.port, ), ]); } httpClient.badCertificateCallback = (cert, url, port) { if (allowBadX509Certificate) { return true; } if (badCertResponse == null) { badCertResponse = MoneroNodeConnectionResponse(cert, url, port, false); } else { return false; } return false; }; final request = await httpClient.postUrl(uri); final body = utf8.encode( jsonEncode({ "jsonrpc": "2.0", "id": "0", "method": "get_info", }), ); request.headers.add( 'Content-Length', body.length.toString(), preserveHeaderCase: true, ); request.headers.set( 'Content-Type', 'application/json', preserveHeaderCase: true, ); request.add(body); final response = await request.close(); final result = await response.transform(utf8.decoder).join(); print("HTTP Response: $result"); final success = result.contains('"result":') && !result.contains('"error"'); return MoneroNodeConnectionResponse(null, null, null, success); } catch (e, s) { if (badCertResponse != null) { return badCertResponse!; } else { Logging.instance.log("$e\n$s", level: LogLevel.Warning); return MoneroNodeConnectionResponse(null, null, null, false); } } finally { httpClient.close(force: true); } } } Future showBadX509CertificateDialog( X509Certificate cert, String url, int port, BuildContext context, ) async { final chars = Format.uint8listToString(cert.sha1) .toUpperCase() .characters .toList(growable: false); String sha1 = chars.sublist(0, 2).join(); for (int i = 2; i < chars.length; i += 2) { sha1 += ":${chars.sublist(i, i + 2).join()}"; } final result = await showDialog( context: context, barrierDismissible: false, builder: (context) { return StackDialog( title: "Untrusted X509Certificate", message: "SHA1:\n$sha1", leftButton: SecondaryButton( label: "Cancel", onPressed: () { Navigator.of(context).pop(false); }, ), rightButton: PrimaryButton( label: "Trust", onPressed: () { Navigator.of(context).pop(true); }, ), ); }, ); return result ?? false; }