2023-05-26 21:21:16 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
import 'dart:convert';
|
2022-11-08 16:18:48 +00:00
|
|
|
import 'dart:io';
|
2022-08-26 08:11:35 +00:00
|
|
|
|
2022-11-08 16:18:48 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-06-12 21:12:53 +00:00
|
|
|
import 'package:socks5_proxy/socks.dart';
|
2024-06-23 00:30:24 +00:00
|
|
|
import 'package:tor_ffi_plugin/socks_socket.dart';
|
2024-06-04 15:16:57 +00:00
|
|
|
|
2024-05-23 00:37:06 +00:00
|
|
|
import '../widgets/desktop/primary_button.dart';
|
|
|
|
import '../widgets/desktop/secondary_button.dart';
|
|
|
|
import '../widgets/stack_dialog.dart';
|
2024-06-04 15:16:57 +00:00
|
|
|
import 'format.dart';
|
|
|
|
import 'logger.dart';
|
2022-08-26 08:11:35 +00:00
|
|
|
|
2022-11-08 16:18:48 +00:00
|
|
|
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,
|
2024-06-12 21:12:53 +00:00
|
|
|
bool allowBadX509Certificate, {
|
|
|
|
required ({
|
|
|
|
InternetAddress host,
|
|
|
|
int port,
|
|
|
|
})? proxyInfo,
|
|
|
|
}) async {
|
2024-07-31 20:12:47 +00:00
|
|
|
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);
|
2024-06-12 21:12:53 +00:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:12:47 +00:00
|
|
|
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);
|
2024-07-31 20:18:15 +00:00
|
|
|
|
|
|
|
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);
|
2024-07-31 20:12:47 +00:00
|
|
|
} 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,
|
|
|
|
),
|
|
|
|
]);
|
2022-11-08 16:18:48 +00:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:12:47 +00:00
|
|
|
httpClient.badCertificateCallback = (cert, url, port) {
|
|
|
|
if (allowBadX509Certificate) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-11-08 16:18:48 +00:00
|
|
|
|
2024-07-31 20:12:47 +00:00
|
|
|
if (badCertResponse == null) {
|
|
|
|
badCertResponse =
|
|
|
|
MoneroNodeConnectionResponse(cert, url, port, false);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
2022-11-08 16:18:48 +00:00
|
|
|
|
2024-06-23 00:30:24 +00:00
|
|
|
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();
|
2024-08-08 19:55:53 +00:00
|
|
|
// print("HTTP Response: $result");
|
2024-07-31 20:18:15 +00:00
|
|
|
|
|
|
|
final success =
|
|
|
|
result.contains('"result":') && !result.contains('"error"');
|
|
|
|
|
|
|
|
return MoneroNodeConnectionResponse(null, null, null, success);
|
2024-07-31 20:12:47 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
if (badCertResponse != null) {
|
|
|
|
return badCertResponse!;
|
|
|
|
} else {
|
|
|
|
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
|
2024-07-30 23:53:43 +00:00
|
|
|
return MoneroNodeConnectionResponse(null, null, null, false);
|
|
|
|
}
|
2024-07-31 20:12:47 +00:00
|
|
|
} finally {
|
|
|
|
httpClient.close(force: true);
|
2022-11-08 16:18:48 +00:00
|
|
|
}
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-08 16:18:48 +00:00
|
|
|
|
|
|
|
Future<bool> showBadX509CertificateDialog(
|
|
|
|
X509Certificate cert,
|
|
|
|
String url,
|
|
|
|
int port,
|
|
|
|
BuildContext context,
|
|
|
|
) async {
|
2022-11-08 17:41:12 +00:00
|
|
|
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()}";
|
|
|
|
}
|
|
|
|
|
2022-11-08 16:18:48 +00:00
|
|
|
final result = await showDialog<bool>(
|
|
|
|
context: context,
|
|
|
|
barrierDismissible: false,
|
|
|
|
builder: (context) {
|
|
|
|
return StackDialog(
|
|
|
|
title: "Untrusted X509Certificate",
|
2022-11-08 17:41:12 +00:00
|
|
|
message: "SHA1:\n$sha1",
|
2022-11-08 16:18:48 +00:00
|
|
|
leftButton: SecondaryButton(
|
|
|
|
label: "Cancel",
|
|
|
|
onPressed: () {
|
|
|
|
Navigator.of(context).pop(false);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
rightButton: PrimaryButton(
|
|
|
|
label: "Trust",
|
|
|
|
onPressed: () {
|
|
|
|
Navigator.of(context).pop(true);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
return result ?? false;
|
|
|
|
}
|