stack_wallet/lib/utilities/test_monero_node_connection.dart
2024-08-08 14:55:53 -05:00

212 lines
5.5 KiB
Dart

/*
* 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<MoneroNodeConnectionResponse> 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<bool> 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<bool>(
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;
}