Files
hash-wallet/cw_core/lib/utils/proxy_wrapper.dart
T
cyanandGitHub 2dc95b2bf0 feat: use better embedded Tor (#2453)
* feat: use better embedded Tor

* update docs [skip ci]

* fix: update torch properly add minimum os version for iOS

* fix: use of invalid thread api on iOS

* fix: disable use of lzma and zstd system libraries

* fix: update torch_dart to work on linux
2025-09-05 23:07:26 +03:00

446 lines
11 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_logger/abstract.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/tor/abstract.dart';
import 'package:http/http.dart';
import 'package:socks5_proxy/socks_client.dart';
import 'package:http/io_client.dart' as ioc;
class ProxyWrapper {
static final ProxyWrapper _proxyWrapper = ProxyWrapper._internal();
static ProxyLogger? logger;
factory ProxyWrapper() {
return _proxyWrapper;
}
ProxyWrapper._internal();
Future<ProxySocket> getSocksSocket(bool sslEnabled, String host, int port, {Duration? connectionTimeout}) async {
logger?.log(
uri: Uri(
scheme: sslEnabled ? "https" : "http",
host: host,
port: port,
),
method: RequestMethod.newProxySocket,
body: Uint8List(0),
response: null,
network: requestNetwork(),
error: null
);
return ProxySocket.connect(sslEnabled, ProxyAddress(host: host, port: port), connectionTimeout: connectionTimeout);
}
RequestNetwork requestNetwork() {
return CakeTor.instance!.started ? RequestNetwork.tor : RequestNetwork.clearnet;
}
ioc.IOClient getHttpIOClient({int? portOverride, bool internal = false}) {
if (!internal) {
logger?.log(
uri: null,
method: RequestMethod.newHttpIOClient,
body: Uint8List(0),
response: null,
network: requestNetwork(),
error: null,
);
}
// ignore: deprecated_member_use_from_same_package
final httpClient = ProxyWrapper().getHttpClient(portOverride: portOverride, internal: true);
return ioc.IOClient(httpClient);
}
int getPort() => CakeTor.instance!.port;
@Deprecated('Use ProxyWrapper().get/post/put methods instead, and provide proper clearnet and onion uri.')
HttpClient getHttpClient({int? portOverride, bool internal = false}) {
if (!internal) {
logger?.log(
uri: null,
method: RequestMethod.newProxySocket,
body: Uint8List(0),
response: null,
network: requestNetwork(),
error: null
);
}
if (CakeTor.instance!.started) {
// Assign connection factory.
final client = HttpClient();
SocksTCPClient.assignToHttpClient(client, [
ProxySettings(
InternetAddress.loopbackIPv4,
CakeTor.instance!.port,
password: null,
),
]);
return client;
} else {
return HttpClient();
}
}
Future<Response> _make({
required RequestMethod method,
required ioc.IOClient client,
required Uri uri,
required Map<String, String>? headers,
String? body,
}) async {
Object? error;
Response? resp;
try {
switch (method) {
case RequestMethod.get:
resp = await client.get(
uri,
headers: headers,
);
break;
case RequestMethod.delete:
resp = await client.delete(
uri,
headers: headers,
body: body,
);
break;
case RequestMethod.post:
resp = await client.post(
uri,
headers: headers,
body: body,
);
break;
case RequestMethod.put:
resp = await client.put(
uri,
headers: headers,
body: body,
);
break;
case RequestMethod.newHttpClient:
case RequestMethod.newHttpIOClient:
case RequestMethod.newProxySocket:
throw UnimplementedError();
}
return resp;
} catch (e) {
error = e;
rethrow;
} finally {
logger?.log(
uri: uri,
method: RequestMethod.get,
body: utf8.encode(body ?? ''),
response: resp,
network: requestNetwork(),
error: error?.toString(),
);
}
}
Future<Response> get({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
}) async {
ioc.IOClient? torClient;
bool torEnabled = CakeTor.instance!.started;
if (CakeTor.instance!.started) {
torEnabled = true;
} else {
torEnabled = false;
}
// if tor is enabled, try to connect to the onion url first:
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
} catch (_) {
rethrow;
}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.get,
client: torClient,
uri: onionUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.get,
client: torClient,
uri: clearnetUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.get,
client: ioc.IOClient(),
uri: clearnetUri,
headers: headers,
);
},
);
} catch (_) {
// we weren't able to get a response:
rethrow;
}
}
throw Exception("Unable to connect to server");
}
Future<Response> post({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
String? body,
bool allowMitmMoneroBypassSSLCheck = false,
}) async {
HttpClient? torHttpClient;
HttpClient cleatnetHttpClient = HttpClient();
if (allowMitmMoneroBypassSSLCheck) {
cleatnetHttpClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
}
ioc.IOClient clearnetClient = ioc.IOClient(cleatnetHttpClient);
bool torEnabled = CakeTor.instance!.started;
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torHttpClient = await getHttpClient(portOverride: portOverride);
} catch (_) {
rethrow;
}
if (allowMitmMoneroBypassSSLCheck) {
torHttpClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.post,
client: ioc.IOClient(torHttpClient),
uri: onionUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.post,
client: ioc.IOClient(torHttpClient),
uri: clearnetUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.post,
client: clearnetClient,
uri: clearnetUri,
headers: headers,
body: body,
);
},
);
} catch (_) {
rethrow;
}
}
throw Exception("Unable to connect to server");
}
Future<Response> put({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
String? body,
}) async {
ioc.IOClient? torClient;
bool torEnabled = CakeTor.instance!.started;
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
} catch (_) {}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.put,
client: torClient!,
uri: onionUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.put,
client: torClient!,
uri: clearnetUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.put,
client: ioc.IOClient(),
uri: clearnetUri,
headers: headers,
body: body,
);
},
);
} catch (_) {
// we weren't able to get a response:
rethrow;
}
}
throw Exception("Unable to connect to server");
}
Future<Response> delete({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
}) async {
ioc.IOClient? torClient;
bool torEnabled = CakeTor.instance!.started;
if (CakeTor.instance!.started) {
torEnabled = true;
} else {
torEnabled = false;
}
// if tor is enabled, try to connect to the onion url first:
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
} catch (_) {
rethrow;
}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.delete,
client: torClient,
uri: onionUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.delete,
client: torClient,
uri: clearnetUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.delete,
client: ioc.IOClient(),
uri: clearnetUri,
headers: headers,
);
},
);
} catch (_) {
// we weren't able to get a response:
rethrow;
}
}
throw Exception("Unable to connect to server");
}
}
class CakeTor {
static CakeTorInstance? instance;
}