refact: tls, native-tls fallback rustls-tls (#13263)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-11-03 23:21:01 +08:00
committed by GitHub
parent 44a28aa5bd
commit 910dcf2036
70 changed files with 1184 additions and 318 deletions

View File

@@ -400,6 +400,8 @@ Future<bool?> loginDialog() async {
String? passwordMsg;
var isInProgress = false;
final RxString curOP = ''.obs;
// Track hover state for the close icon
bool isCloseHovered = false;
final loginOptions = [].obs;
Future.delayed(Duration.zero, () async {
@@ -557,21 +559,27 @@ Future<bool?> loginDialog() async {
Text(
translate('Login'),
).marginOnly(top: MyTheme.dialogPadding),
InkWell(
child: Icon(
Icons.close,
size: 25,
// No need to handle the branch of null.
// Because we can ensure the color is not null when debug.
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.55),
MouseRegion(
onEnter: (_) => setState(() => isCloseHovered = true),
onExit: (_) => setState(() => isCloseHovered = false),
child: InkWell(
child: Icon(
Icons.close,
size: 25,
// No need to handle the branch of null.
// Because we can ensure the color is not null when debug.
color: isCloseHovered
? Colors.white
: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.55),
),
onTap: onDialogCancel,
hoverColor: Colors.red,
borderRadius: BorderRadius.circular(5),
),
onTap: onDialogCancel,
hoverColor: Colors.red,
borderRadius: BorderRadius.circular(5),
).marginOnly(top: 10, right: 15),
],
);

View File

@@ -162,8 +162,11 @@ const String kOptionShowVirtualJoystick = "show-virtual-joystick";
// network options
const String kOptionAllowWebSocket = "allow-websocket";
const String kOptionAllowInsecureTLSFallback = "allow-insecure-tls-fallback";
const String kOptionDisableUdp = "disable-udp";
const String kOptionEnableFlutterHttpOnRust = "enable-flutter-http-on-rust";
// buildin opitons
// builtin options
const String kOptionHideServerSetting = "hide-server-settings";
const String kOptionHideProxySetting = "hide-proxy-settings";
const String kOptionHideWebSocketSetting = "hide-websocket-settings";

View File

@@ -1585,6 +1585,27 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
);
}
Widget switchWidget(IconData icon, String title, String tooltipMessage,
String optionKey) =>
listTile(
icon: icon,
title: title,
showTooltip: true,
tooltipMessage: tooltipMessage,
trailing: Switch(
value: mainGetBoolOptionSync(optionKey),
onChanged: locked || isOptionFixed(optionKey)
? null
: (value) {
mainSetBoolOption(optionKey, value);
setState(() {});
},
),
);
final outgoingOnly = bind.isOutgoingOnly();
final divider = const Divider(height: 1, indent: 16, endIndent: 16);
return _Card(
title: 'Network',
children: [
@@ -1596,33 +1617,65 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
listTile(
icon: Icons.dns_outlined,
title: 'ID/Relay Server',
onTap: () => showServerSettings(gFFI.dialogManager),
onTap: () => showServerSettings(gFFI.dialogManager, setState),
),
if (!hideServer && (!hideProxy || !hideWebSocket))
Divider(height: 1, indent: 16, endIndent: 16),
if (!hideProxy && !hideServer) divider,
if (!hideProxy)
listTile(
icon: Icons.network_ping_outlined,
title: 'Socks5/Http(s) Proxy',
onTap: changeSocks5Proxy,
),
if (!hideProxy && !hideWebSocket)
Divider(height: 1, indent: 16, endIndent: 16),
if (!hideWebSocket && (!hideServer || !hideProxy)) divider,
if (!hideWebSocket)
listTile(
icon: Icons.web_asset_outlined,
title: 'Use WebSocket',
showTooltip: true,
tooltipMessage: 'websocket_tip',
trailing: Switch(
value: mainGetBoolOptionSync(kOptionAllowWebSocket),
onChanged: locked
? null
: (value) {
mainSetBoolOption(kOptionAllowWebSocket, value);
setState(() {});
},
),
switchWidget(
Icons.web_asset_outlined,
'Use WebSocket',
'${translate('websocket_tip')}\n\n${translate('oss-not-support-tip')}',
kOptionAllowWebSocket),
if (!isWeb)
futureBuilder(
future: bind.mainIsUsingPublicServer(),
hasData: (isUsingPublicServer) {
if (isUsingPublicServer) {
return Offstage();
} else {
return Column(
children: [
if (!hideServer || !hideProxy || !hideWebSocket)
divider,
switchWidget(
Icons.no_encryption_outlined,
'Allow insecure TLS fallback',
'allow-insecure-tls-fallback-tip',
kOptionAllowInsecureTLSFallback),
if (!outgoingOnly) divider,
if (!outgoingOnly)
listTile(
icon: Icons.lan_outlined,
title: 'Disable UDP',
showTooltip: true,
tooltipMessage:
'${translate('disable-udp-tip')}\n\n${translate('oss-not-support-tip')}',
trailing: Switch(
value: bind.mainGetOptionSync(
key: kOptionDisableUdp) ==
'Y',
onChanged:
locked || isOptionFixed(kOptionDisableUdp)
? null
: (value) async {
await bind.mainSetOption(
key: kOptionDisableUdp,
value: value ? 'Y' : 'N');
setState(() {});
},
),
),
],
);
}
},
),
],
),
@@ -1742,9 +1795,9 @@ class _DisplayState extends State<_Display> {
}
Widget trackpadSpeed(BuildContext context) {
final initSpeed = (int.tryParse(
bind.mainGetUserDefaultOption(key: kKeyTrackpadSpeed)) ??
kDefaultTrackpadSpeed);
final initSpeed =
(int.tryParse(bind.mainGetUserDefaultOption(key: kKeyTrackpadSpeed)) ??
kDefaultTrackpadSpeed);
final curSpeed = SimpleWrapper(initSpeed);
void onDebouncer(int v) {
bind.mainSetUserDefaultOption(

View File

@@ -156,7 +156,7 @@ class _ScanPageState extends State<ScanPage> {
try {
final sc = ServerConfig.decode(data.substring(7));
Timer(Duration(milliseconds: 60), () {
showServerSettingsWithValue(sc, gFFI.dialogManager);
showServerSettingsWithValue(sc, gFFI.dialogManager, null);
});
} catch (e) {
showToast('Invalid QR code');

View File

@@ -94,7 +94,10 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
var _hideWebSocket = false;
var _enableTrustedDevices = false;
var _enableUdpPunch = false;
var _allowInsecureTlsFallback = false;
var _disableUdp = false;
var _enableIpv6Punch = false;
var _isUsingPublicServer = false;
_SettingsState() {
_enableAbr = option2bool(
@@ -109,6 +112,9 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_enableHardwareCodec = option2bool(kOptionEnableHwcodec,
bind.mainGetOptionSync(key: kOptionEnableHwcodec));
_allowWebSocket = mainGetBoolOptionSync(kOptionAllowWebSocket);
_allowInsecureTlsFallback =
mainGetBoolOptionSync(kOptionAllowInsecureTLSFallback);
_disableUdp = bind.mainGetOptionSync(key: kOptionDisableUdp) == 'Y';
_autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming,
bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming));
_autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing,
@@ -200,6 +206,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
update = true;
_buildDate = buildDate;
}
final isUsingPublicServer = await bind.mainIsUsingPublicServer();
if (_isUsingPublicServer != isUsingPublicServer) {
update = true;
_isUsingPublicServer = isUsingPublicServer;
}
if (update) {
setState(() {});
}
@@ -667,7 +680,10 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
title: Text(translate('ID/Relay Server')),
leading: Icon(Icons.cloud),
onPressed: (context) {
showServerSettings(gFFI.dialogManager);
showServerSettings(gFFI.dialogManager, (callback) async {
_isUsingPublicServer = await bind.mainIsUsingPublicServer();
setState(callback);
});
}),
if (!isIOS && !_hideNetwork && !_hideProxy)
SettingsTile(
@@ -691,6 +707,38 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
});
},
),
if (!_isUsingPublicServer)
SettingsTile.switchTile(
title: Text(translate('Allow insecure TLS fallback')),
initialValue: _allowInsecureTlsFallback,
onToggle: isOptionFixed(kOptionAllowInsecureTLSFallback)
? null
: (v) async {
await mainSetBoolOption(
kOptionAllowInsecureTLSFallback, v);
final newValue = mainGetBoolOptionSync(
kOptionAllowInsecureTLSFallback);
setState(() {
_allowInsecureTlsFallback = newValue;
});
},
),
if (isAndroid && !outgoingOnly && !_isUsingPublicServer)
SettingsTile.switchTile(
title: Text(translate('Disable UDP')),
initialValue: _disableUdp,
onToggle: isOptionFixed(kOptionDisableUdp)
? null
: (v) async {
await bind.mainSetOption(
key: kOptionDisableUdp, value: v ? 'Y' : 'N');
final newValue =
bind.mainGetOptionSync(key: kOptionDisableUdp) == 'Y';
setState(() {
_disableUdp = newValue;
});
},
),
if (!incomingOnly)
SettingsTile.switchTile(
title: Text(translate('Enable UDP hole punching')),

View File

@@ -147,18 +147,22 @@ void setTemporaryPasswordLengthDialog(
}, backDismiss: true, clickMaskDismiss: true);
}
void showServerSettings(OverlayDialogManager dialogManager) async {
void showServerSettings(OverlayDialogManager dialogManager,
void Function(VoidCallback) setState) async {
Map<String, dynamic> options = {};
try {
options = jsonDecode(await bind.mainGetOptions());
} catch (e) {
print("Invalid server config: $e");
}
showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager);
showServerSettingsWithValue(
ServerConfig.fromOptions(options), dialogManager, setState);
}
void showServerSettingsWithValue(
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
ServerConfig serverConfig,
OverlayDialogManager dialogManager,
void Function(VoidCallback)? upSetState) async {
var isInProgress = false;
final idCtrl = TextEditingController(text: serverConfig.idServer);
final relayCtrl = TextEditingController(text: serverConfig.relayServer);
@@ -288,6 +292,7 @@ void showServerSettingsWithValue(
if (await submit()) {
close();
showToast(translate('Successful'));
upSetState?.call(() {});
} else {
showToast(translate('Failed'));
}

View File

@@ -1,7 +1,9 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:http/http.dart' as http;
import '../models/platform_model.dart';
import 'package:flutter_hbb/common.dart';
export 'package:http/http.dart' show Response;
enum HttpMethod { get, post, put, delete }
@@ -15,11 +17,19 @@ class HttpService {
}) async {
headers ??= {'Content-Type': 'application/json'};
// Determine if there is currently a proxy setting, and if so, use FFI to call the Rust HTTP method.
final isProxy = await bind.mainGetProxyStatus();
// Use Rust HTTP implementation for non-web platforms for consistency.
var useFlutterHttp = (isWeb || kIsWeb);
if (!useFlutterHttp) {
final enableFlutterHttpOnRust =
mainGetLocalBoolOptionSync(kOptionEnableFlutterHttpOnRust);
// Use flutter http if:
// Not `enableFlutterHttpOnRust` and no proxy is set
useFlutterHttp =
!(enableFlutterHttpOnRust || await bind.mainGetProxyStatus());
}
if (!isProxy) {
return await _pollFultterHttp(url, method, headers: headers, body: body);
if (useFlutterHttp) {
return await _pollFlutterHttp(url, method, headers: headers, body: body);
}
String headersJson = jsonEncode(headers);
@@ -34,7 +44,7 @@ class HttpService {
return _parseHttpResponse(resJson);
}
Future<http.Response> _pollFultterHttp(
Future<http.Response> _pollFlutterHttp(
Uri url,
HttpMethod method, {
Map<String, String>? headers,
@@ -87,7 +97,8 @@ class HttpService {
int statusCode = parsedJson['status_code'];
return http.Response(body, statusCode, headers: headers);
} catch (e) {
throw Exception('Failed to parse response: $e');
print('Failed to parse response\n$responseJson\nError:\n$e');
throw Exception('Failed to parse response.\n$responseJson');
}
}
}