diff --git a/Cargo.lock b/Cargo.lock index ce54262ea..340dc52f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" +checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" dependencies = [ "flate2", "futures-core", @@ -517,6 +517,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -1825,9 +1831,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if 1.0.0", ] @@ -2825,9 +2831,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2876,6 +2882,7 @@ version = "0.1.0" dependencies = [ "anyhow", "backtrace", + "base64 0.22.0", "bytes", "chrono", "confy", @@ -2887,6 +2894,7 @@ dependencies = [ "flexi_logger", "futures", "futures-util", + "httparse", "lazy_static", "libc", "log", @@ -2898,6 +2906,8 @@ dependencies = [ "quinn", "rand 0.8.5", "regex", + "rustls-pki-types", + "rustls-platform-verifier", "serde 1.0.190", "serde_derive", "serde_json 1.0.107", @@ -2906,9 +2916,12 @@ dependencies = [ "sysinfo", "thiserror", "tokio", - "tokio-socks", + "tokio-native-tls", + "tokio-rustls 0.26.0", + "tokio-socks 0.5.1-2", "tokio-util", "toml 0.7.8", + "url", "uuid", "winapi 0.3.9", "zstd 0.13.0", @@ -2985,9 +2998,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -2996,9 +3009,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -3038,9 +3051,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -3053,7 +3066,7 @@ dependencies = [ "httpdate", "itoa 1.0.9", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -3071,7 +3084,7 @@ dependencies = [ "hyper", "rustls 0.21.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] @@ -4565,7 +4578,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa" dependencies = [ - "base64", + "base64 0.21.5", "indexmap 1.9.3", "line-wrap", "quick-xml", @@ -4838,7 +4851,7 @@ dependencies = [ "ring 0.16.20", "rustc-hash", "rustls 0.20.9", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "slab", "thiserror", "tinyvec", @@ -5173,10 +5186,10 @@ dependencies = [ [[package]] name = "reqwest" version = "0.11.23" -source = "git+https://github.com/rustdesk-org/reqwest" +source = "git+https://github.com/rustdesk-org/reqwest#9cb758c9fb2f4edc62eb790acfd45a6a3da21ed3" dependencies = [ "async-compression", - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -5196,8 +5209,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.10", - "rustls-native-certs", - "rustls-pemfile", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.3", "serde 1.0.190", "serde_json 1.0.107", "serde_urlencoded", @@ -5205,14 +5218,15 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.24.1", + "tokio-socks 0.5.1", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg 0.50.0", ] @@ -5358,7 +5372,6 @@ dependencies = [ "arboard", "async-process", "async-trait", - "base64", "bytes", "cc", "cfg-if 1.0.0", @@ -5519,10 +5532,25 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring 0.17.5", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" +dependencies = [ + "log", + "once_cell", + "ring 0.17.5", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -5530,7 +5558,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.3", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "schannel", "security-framework", ] @@ -5541,9 +5582,52 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64", + "base64 0.21.5", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +dependencies = [ + "core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.8.4", + "jni 0.19.0", + "log", + "once_cell", + "rustls 0.23.4", + "rustls-native-certs 0.7.0", + "rustls-platform-verifier-android", + "rustls-webpki 0.102.2", + "security-framework", + "security-framework-sys", + "webpki-roots 0.26.1", + "winapi 0.3.9", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -5554,6 +5638,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.5", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -5666,22 +5761,23 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation-sys 0.8.4", "libc", + "num-bigint", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys 0.8.4", "libc", @@ -6404,6 +6500,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-socks" version = "0.5.1-2" @@ -6420,6 +6527,18 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -6637,9 +6756,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -7081,9 +7200,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "weezl" @@ -7856,6 +7984,12 @@ dependencies = [ "syn 2.0.55", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 8a9d3d5fb..a9bbcef9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,6 @@ samplerate = { version = "0.2", optional = true } uuid = { version = "1.3", features = ["v4"] } clap = "4.2" rpassword = "7.2" -base64 = "0.21" num_cpus = "1.15" bytes = { version = "1.4", features = ["serde"] } default-net = "0.14" @@ -145,10 +144,10 @@ wallpaper = { git = "https://github.com/21pages/wallpaper.rs" } [target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] # https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support -reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "native-tls", "gzip"], default-features=false } +reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "native-tls", "gzip"], default-features=false } [target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies] -reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false } +reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false } [target.'cfg(target_os = "linux")'.dependencies] psimple = { package = "libpulse-simple-binding", version = "2.27" } diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 7eedb1d5b..17998dd9a 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1117,7 +1117,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { child: Column(children: [ server(enabled), _Card(title: 'Proxy', children: [ - _Button('Socks5 Proxy', changeSocks5Proxy, + _Button('Socks5/Http(s) Proxy', changeSocks5Proxy, enabled: enabled), ]), ]), @@ -2047,7 +2047,7 @@ void changeSocks5Proxy() async { } return CustomAlertDialog( - title: Text(translate('Socks5 Proxy')), + title: Text(translate('Socks5/Http(s) Proxy')), content: ConstrainedBox( constraints: const BoxConstraints(minWidth: 500), child: Column( @@ -2064,7 +2064,9 @@ void changeSocks5Proxy() async { Expanded( child: TextField( decoration: InputDecoration( - errorText: proxyMsg.isNotEmpty ? proxyMsg : null), + errorText: proxyMsg.isNotEmpty ? proxyMsg : null, + hintText: translate('Default protocol and port are Socks5 and 1080'), + ), controller: proxyController, autofocus: true, ), diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index b166817c5..9849e39bf 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -10,8 +10,8 @@ import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:bot_toast/bot_toast.dart'; -import 'package:http/http.dart' as http; +import '../utils/http_service.dart' as http; import '../common.dart'; final syncAbOption = 'sync-ab-with-recent-sessions'; diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index b8bc2722d..c2a50afb3 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -7,7 +7,7 @@ import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'dart:convert'; -import 'package:http/http.dart' as http; +import '../utils/http_service.dart' as http; class GroupModel { final RxBool groupLoading = false.obs; diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index 04b7ebb5b..4e7f881ad 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:get/get.dart'; -import 'package:http/http.dart' as http; import '../common.dart'; +import '../utils/http_service.dart' as http; import 'model.dart'; import 'platform_model.dart'; @@ -136,7 +136,6 @@ class UserModel { Future login(LoginRequest loginRequest) async { final url = await bind.mainGetApiServer(); final resp = await http.post(Uri.parse('$url/api/login'), - headers: {'Content-Type': 'application/json'}, body: jsonEncode(loginRequest.toJson())); final Map body; diff --git a/flutter/lib/utils/http_service.dart b/flutter/lib/utils/http_service.dart new file mode 100644 index 000000000..49855017b --- /dev/null +++ b/flutter/lib/utils/http_service.dart @@ -0,0 +1,115 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import '../models/platform_model.dart'; +export 'package:http/http.dart' show Response; + +enum HttpMethod { get, post, put, delete } + +class HttpService { + Future sendRequest( + Uri url, + HttpMethod method, { + Map? headers, + dynamic body, + }) 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(); + + if (!isProxy) { + return await _pollFultterHttp(url, method, headers: headers, body: body); + } + + String headersJson = jsonEncode(headers); + String methodName = method.toString().split('.').last; + await bind.mainHttpRequest( + url: url.toString(), + method: methodName.toLowerCase(), + body: body, + header: headersJson); + + var resJson = await _pollForResponse(url.toString()); + return _parseHttpResponse(resJson); + } + + Future _pollFultterHttp( + Uri url, + HttpMethod method, { + Map? headers, + dynamic body, + }) async { + var response = http.Response('', 400); + + switch (method) { + case HttpMethod.get: + response = await http.get(url, headers: headers); + break; + case HttpMethod.post: + response = await http.post(url, headers: headers, body: body); + break; + case HttpMethod.put: + response = await http.put(url, headers: headers, body: body); + break; + case HttpMethod.delete: + response = await http.delete(url, headers: headers, body: body); + break; + default: + throw Exception('Unsupported HTTP method'); + } + + return response; + } + + Future _pollForResponse(String url) async { + String? responseJson = " "; + while (responseJson == " ") { + responseJson = await bind.mainGetHttpStatus(url: url); + if (responseJson == null) { + throw Exception('The HTTP request failed'); + } + if (responseJson == " ") { + await Future.delayed(const Duration(milliseconds: 100)); + } + } + return responseJson!; + } + + http.Response _parseHttpResponse(String responseJson) { + try { + var parsedJson = jsonDecode(responseJson); + String body = parsedJson['body']; + Map headers = {}; + for (var key in parsedJson['headers'].keys) { + headers[key] = parsedJson['headers'][key]; + } + int statusCode = parsedJson['status_code']; + return http.Response(body, statusCode, headers: headers); + } catch (e) { + throw Exception('Failed to parse response: $e'); + } + } +} + +Future get(Uri url, {Map? headers}) async { + return await HttpService().sendRequest(url, HttpMethod.get, headers: headers); +} + +Future post(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return await HttpService() + .sendRequest(url, HttpMethod.post, body: body, headers: headers); +} + +Future put(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return await HttpService() + .sendRequest(url, HttpMethod.put, body: body, headers: headers); +} + +Future delete(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return await HttpService() + .sendRequest(url, HttpMethod.delete, body: body, headers: headers); +} diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index fd0d7189b..91e7e9711 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -770,6 +770,24 @@ class RustdeskImpl { throw UnimplementedError(); } + Future mainGetProxyStatus({dynamic hint}) { + return Future(() => false); + } + + Future mainHttpRequest({ + required String url, + required String method, + String? body, + required String header, + dynamic hint, + }) { + throw UnimplementedError(); + } + + Future mainGetHttpStatus({required String url, dynamic hint}){ + throw UnimplementedError(); + } + String mainGetLocalOption({required String key, dynamic hint}) { return js.context.callMethod('getByName', ['option:local', key]); } diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index 9f77abf0e..38c02ceae 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -41,10 +41,19 @@ uuid = { version = "1.3", features = ["v4"] } # crash, versions >= 0.29.1 are affected by #GuillaumeGomez/sysinfo/1052 sysinfo = { git = "https://github.com/rustdesk-org/sysinfo" } thiserror = "1.0" +httparse = "1.5" +base64 = "0.22" +url = "2.2" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] mac_address = "1.1" machine-uid = { git = "https://github.com/21pages/machine-uid" } +[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies] +tokio-rustls = { version = "0.26", features = ["logging", "tls12", "ring"], default-features = false } +rustls-platform-verifier = "0.3.1" +rustls-pki-types = "1.4" +[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] +tokio-native-tls ="0.3" [features] quic = [] diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index eed2331fb..57d38db09 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -16,6 +16,7 @@ use std::{ }; pub use tokio; pub use tokio_util; +pub mod proxy; pub mod socket_client; pub mod tcp; pub mod udp; @@ -51,6 +52,7 @@ pub use serde_json; pub use sysinfo; pub use toml; pub use uuid; +pub use base64; pub use thiserror; #[cfg(feature = "quic")] diff --git a/libs/hbb_common/src/proxy.rs b/libs/hbb_common/src/proxy.rs new file mode 100644 index 000000000..34d2c5109 --- /dev/null +++ b/libs/hbb_common/src/proxy.rs @@ -0,0 +1,561 @@ +use std::{ + io::Error as IoError, + net::{SocketAddr, ToSocketAddrs}, +}; + +use base64::{engine::general_purpose, Engine}; +use httparse::{Error as HttpParseError, Response, EMPTY_HEADER}; +use log::info; +use thiserror::Error as ThisError; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufStream}; +#[cfg(any(target_os = "windows", target_os = "macos"))] +use tokio_native_tls::{native_tls, TlsConnector, TlsStream}; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] +use tokio_rustls::{client::TlsStream, TlsConnector}; +use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr}; +use tokio_util::codec::Framed; +use url::Url; + +use crate::{ + bytes_codec::BytesCodec, + config::Socks5Server, + tcp::{DynTcpStream, FramedStream}, + ResultType, +}; + +#[derive(Debug, ThisError)] +pub enum ProxyError { + #[error("IO Error: {0}")] + IoError(#[from] IoError), + #[error("Target parse error: {0}")] + TargetParseError(String), + #[error("HTTP parse error: {0}")] + HttpParseError(#[from] HttpParseError), + #[error("The maximum response header length is exceeded: {0}")] + MaximumResponseHeaderLengthExceeded(usize), + #[error("The end of file is reached")] + EndOfFile, + #[error("The url is error: {0}")] + UrlBadScheme(String), + #[error("The url parse error: {0}")] + UrlParseScheme(#[from] url::ParseError), + #[error("No HTTP code was found in the response")] + NoHttpCode, + #[error("The HTTP code is not equal 200: {0}")] + HttpCode200(u16), + #[error("The proxy address resolution failed: {0}")] + AddressResolutionFailed(String), + #[cfg(any(target_os = "windows", target_os = "macos"))] + #[error("The native tls error: {0}")] + NativeTlsError(#[from] tokio_native_tls::native_tls::Error), +} + +const MAXIMUM_RESPONSE_HEADER_LENGTH: usize = 4096; +/// The maximum HTTP Headers, which can be parsed. +const MAXIMUM_RESPONSE_HEADERS: usize = 16; +const DEFINE_TIME_OUT: u64 = 600; + +pub trait IntoUrl { + + // Besides parsing as a valid `Url`, the `Url` must be a valid + // `http::Uri`, in that it makes sense to use in a network request. + fn into_url(self) -> Result; + + fn as_str(&self) -> &str; +} + +impl IntoUrl for Url { + fn into_url(self) -> Result { + if self.has_host() { + Ok(self) + } else { + Err(ProxyError::UrlBadScheme(self.to_string())) + } + } + + fn as_str(&self) -> &str { + self.as_ref() + } +} + +impl<'a> IntoUrl for &'a str { + fn into_url(self) -> Result { + Url::parse(self) + .map_err(ProxyError::UrlParseScheme)? + .into_url() + } + + fn as_str(&self) -> &str { + self + } +} + +impl<'a> IntoUrl for &'a String { + fn into_url(self) -> Result { + (&**self).into_url() + } + + fn as_str(&self) -> &str { + self.as_ref() + } +} + +impl<'a> IntoUrl for String { + fn into_url(self) -> Result { + (&*self).into_url() + } + + fn as_str(&self) -> &str { + self.as_ref() + } +} + +#[derive(Clone)] +pub struct Auth { + user_name: String, + password: String, +} + +impl Auth { + fn get_proxy_authorization(&self) -> String { + format!( + "Proxy-Authorization: Basic {}\r\n", + self.get_basic_authorization() + ) + } + + pub fn get_basic_authorization(&self) -> String { + let authorization = format!("{}:{}", &self.user_name, &self.password); + general_purpose::STANDARD.encode(authorization.as_bytes()) + } +} + +#[derive(Clone)] +pub enum ProxyScheme { + Http { + auth: Option, + host: String, + }, + Https { + auth: Option, + host: String, + }, + Socks5 { + addr: SocketAddr, + auth: Option, + remote_dns: bool, + }, +} + +impl ProxyScheme { + pub fn maybe_auth(&self) -> Option<&Auth> { + match self { + ProxyScheme::Http { auth, .. } + | ProxyScheme::Https { auth, .. } + | ProxyScheme::Socks5 { auth, .. } => auth.as_ref(), + } + } + + fn socks5(addr: SocketAddr) -> Result { + Ok(ProxyScheme::Socks5 { + addr, + auth: None, + remote_dns: false, + }) + } + + fn http(host: &str) -> Result { + Ok(ProxyScheme::Http { + auth: None, + host: host.to_string(), + }) + } + fn https(host: &str) -> Result { + Ok(ProxyScheme::Https { + auth: None, + host: host.to_string(), + }) + } + + fn set_basic_auth, U: Into>(&mut self, username: T, password: U) { + let auth = Auth { + user_name: username.into(), + password: password.into(), + }; + match self { + ProxyScheme::Http { auth: a, .. } => *a = Some(auth), + ProxyScheme::Https { auth: a, .. } => *a = Some(auth), + ProxyScheme::Socks5 { auth: a, .. } => *a = Some(auth), + } + } + + fn parse(url: Url) -> Result { + use url::Position; + + // Resolve URL to a host and port + let to_addr = || { + let addrs = url.socket_addrs(|| match url.scheme() { + "socks5" => Some(1080), + _ => None, + })?; + addrs + .into_iter() + .next() + .ok_or_else(|| ProxyError::UrlParseScheme(url::ParseError::EmptyHost)) + }; + + let mut scheme: Self = match url.scheme() { + "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?, + "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?, + "socks5" => Self::socks5(to_addr()?)?, + e => return Err(ProxyError::UrlBadScheme(e.to_string())), + }; + + if let Some(pwd) = url.password() { + let username = url.username(); + scheme.set_basic_auth(username, pwd); + } + + Ok(scheme) + } + pub async fn socket_addrs(&self) -> Result { + info!("Resolving socket address"); + match self { + ProxyScheme::Http { host, .. } => self.resolve_host(host, 80).await, + ProxyScheme::Https { host, .. } => self.resolve_host(host, 443).await, + ProxyScheme::Socks5 { addr, .. } => Ok(addr.clone()), + } + } + + async fn resolve_host(&self, host: &str, default_port: u16) -> Result { + let (host_str, port) = match host.split_once(':') { + Some((h, p)) => (h, p.parse::().ok()), + None => (host, None), + }; + let addr = (host_str, port.unwrap_or(default_port)) + .to_socket_addrs()? + .next() + .ok_or_else(|| ProxyError::AddressResolutionFailed(host.to_string()))?; + Ok(addr) + } + + pub fn get_domain(&self) -> Result { + match self { + ProxyScheme::Http { host, .. } | ProxyScheme::Https { host, .. } => { + let domain = host + .split(':') + .next() + .ok_or_else(|| ProxyError::AddressResolutionFailed(host.clone()))?; + Ok(domain.to_string()) + } + ProxyScheme::Socks5 { addr, .. } => match addr { + SocketAddr::V4(addr_v4) => Ok(addr_v4.ip().to_string()), + SocketAddr::V6(addr_v6) => Ok(addr_v6.ip().to_string()), + }, + } + } + pub fn get_host_and_port(&self) -> Result { + match self { + ProxyScheme::Http { host, .. } => Ok(self.append_default_port(host, 80)), + ProxyScheme::Https { host, .. } => Ok(self.append_default_port(host, 443)), + ProxyScheme::Socks5 { addr, .. } => Ok(format!("{}", addr)), + } + } + fn append_default_port(&self, host: &str, default_port: u16) -> String { + if host.contains(':') { + host.to_string() + } else { + format!("{}:{}", host, default_port) + } + } +} + +pub trait IntoProxyScheme { + fn into_proxy_scheme(self) -> Result; +} + +impl IntoProxyScheme for S { + fn into_proxy_scheme(self) -> Result { + // validate the URL + let url = match self.as_str().into_url() { + Ok(ok) => ok, + Err(e) => { + match e { + // If the string does not contain protocol headers, try to parse it using the socks5 protocol + ProxyError::UrlParseScheme(_source) => { + let try_this = format!("socks5://{}", self.as_str()); + try_this.into_url()? + } + _ => { + return Err(e); + } + } + } + }; + ProxyScheme::parse(url) + } +} + +impl IntoProxyScheme for ProxyScheme { + fn into_proxy_scheme(self) -> Result { + Ok(self) + } +} + +#[derive(Clone)] +pub struct Proxy { + pub intercept: ProxyScheme, + ms_timeout: u64, +} + +impl Proxy { + pub fn new(proxy_scheme: U, ms_timeout: u64) -> Result { + Ok(Self { + intercept: proxy_scheme.into_proxy_scheme()?, + ms_timeout, + }) + } + + pub fn is_http_or_https(&self) -> bool { + return match self.intercept { + ProxyScheme::Socks5 { .. } => false, + _ => true, + }; + } + + pub fn from_conf(conf: &Socks5Server, ms_timeout: Option) -> Result { + let mut proxy; + match ms_timeout { + None => { + proxy = Self::new(&conf.proxy, DEFINE_TIME_OUT)?; + } + Some(time_out) => { + proxy = Self::new(&conf.proxy, time_out)?; + } + } + + if !conf.password.is_empty() && !conf.username.is_empty() { + proxy = proxy.basic_auth(&conf.username, &conf.password); + } + Ok(proxy) + } + + pub async fn proxy_addrs(&self) -> Result { + self.intercept.socket_addrs().await + } + + fn basic_auth(mut self, username: &str, password: &str) -> Proxy { + self.intercept.set_basic_auth(username, password); + self + } + + pub async fn connect<'t, T>( + self, + target: T, + local_addr: Option, + ) -> ResultType + where + T: IntoTargetAddr<'t>, + { + info!("Connect to proxy server"); + let proxy = self.proxy_addrs().await?; + + let local = if let Some(addr) = local_addr { + addr + } else { + crate::config::Config::get_any_listen_addr(proxy.is_ipv4()) + }; + + let stream = super::timeout( + self.ms_timeout, + crate::tcp::new_socket(local, true)?.connect(proxy), + ) + .await??; + stream.set_nodelay(true).ok(); + + let addr = stream.local_addr()?; + + return match self.intercept { + ProxyScheme::Http { .. } => { + info!("Connect to remote http proxy server: {}", proxy); + let stream = + super::timeout(self.ms_timeout, self.http_connect(stream, target)).await??; + Ok(FramedStream( + Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()), + addr, + None, + 0, + )) + } + ProxyScheme::Https { .. } => { + info!("Connect to remote https proxy server: {}", proxy); + let stream = + super::timeout(self.ms_timeout, self.https_connect(stream, target)).await??; + Ok(FramedStream( + Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()), + addr, + None, + 0, + )) + } + ProxyScheme::Socks5 { .. } => { + info!("Connect to remote socket5 proxy server: {}", proxy); + let stream = if let Some(auth) = self.intercept.maybe_auth() { + super::timeout( + self.ms_timeout, + Socks5Stream::connect_with_password_and_socket( + stream, + target, + &auth.user_name, + &auth.password, + ), + ) + .await?? + } else { + super::timeout( + self.ms_timeout, + Socks5Stream::connect_with_socket(stream, target), + ) + .await?? + }; + Ok(FramedStream( + Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()), + addr, + None, + 0, + )) + } + }; + } + + #[cfg(any(target_os = "windows", target_os = "macos"))] + pub async fn https_connect<'a, Input, T>( + self, + io: Input, + target: T, + ) -> Result>, ProxyError> + where + Input: AsyncRead + AsyncWrite + Unpin, + T: IntoTargetAddr<'a>, + { + let tls_connector = TlsConnector::from(native_tls::TlsConnector::new()?); + let stream = tls_connector + .connect(&self.intercept.get_domain()?, io) + .await?; + self.http_connect(stream, target).await + } + + #[cfg(not(any(target_os = "windows", target_os = "macos")))] + pub async fn https_connect<'a, Input, T>( + self, + io: Input, + target: T, + ) -> Result>, ProxyError> + where + Input: AsyncRead + AsyncWrite + Unpin, + T: IntoTargetAddr<'a>, + { + use std::convert::TryFrom; + let verifier = rustls_platform_verifier::tls_config(); + let url_domain = self.intercept.get_domain()?; + + let domain = rustls_pki_types::ServerName::try_from(url_domain.as_str()) + .map_err(|e| ProxyError::AddressResolutionFailed(e.to_string()))? + .to_owned(); + + let tls_connector = TlsConnector::from(std::sync::Arc::new(verifier)); + let stream = tls_connector.connect(domain, io).await?; + self.http_connect(stream, target).await + } + + pub async fn http_connect<'a, Input, T>( + self, + io: Input, + target: T, + ) -> Result, ProxyError> + where + Input: AsyncRead + AsyncWrite + Unpin, + T: IntoTargetAddr<'a>, + { + let mut stream = BufStream::new(io); + let (domain, port) = get_domain_and_port(target)?; + + let request = self.make_request(&domain, port); + stream.write_all(request.as_bytes()).await?; + stream.flush().await?; + recv_and_check_response(&mut stream).await?; + Ok(stream) + } + + fn make_request(&self, host: &str, port: u16) -> String { + let mut request = format!( + "CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n", + host = host, + port = port + ); + + if let Some(auth) = self.intercept.maybe_auth() { + request = format!("{}{}", request, auth.get_proxy_authorization()); + } + + request.push_str("\r\n"); + request + } +} + +fn get_domain_and_port<'a, T: IntoTargetAddr<'a>>(target: T) -> Result<(String, u16), ProxyError> { + let target_addr = target + .into_target_addr() + .map_err(|e| ProxyError::TargetParseError(e.to_string()))?; + match target_addr { + tokio_socks::TargetAddr::Ip(addr) => Ok((addr.ip().to_string(), addr.port())), + tokio_socks::TargetAddr::Domain(name, port) => Ok((name.to_string(), port)), + } +} + +async fn get_response(stream: &mut BufStream) -> Result +where + IO: AsyncRead + AsyncWrite + Unpin, +{ + use tokio::io::AsyncBufReadExt; + let mut response = String::new(); + + loop { + if stream.read_line(&mut response).await? == 0 { + return Err(ProxyError::EndOfFile); + } + + if MAXIMUM_RESPONSE_HEADER_LENGTH < response.len() { + return Err(ProxyError::MaximumResponseHeaderLengthExceeded( + response.len(), + )); + } + + if response.ends_with("\r\n\r\n") { + return Ok(response); + } + } +} + +async fn recv_and_check_response(stream: &mut BufStream) -> Result<(), ProxyError> +where + IO: AsyncRead + AsyncWrite + Unpin, +{ + let response_string = get_response(stream).await?; + + let mut response_headers = [EMPTY_HEADER; MAXIMUM_RESPONSE_HEADERS]; + let mut response = Response::new(&mut response_headers); + let response_bytes = response_string.into_bytes(); + response.parse(&response_bytes)?; + + return match response.code { + Some(code) => { + if code == 200 { + Ok(()) + } else { + Err(ProxyError::HttpCode200(code)) + } + } + None => Err(ProxyError::NoHttpCode), + }; +} diff --git a/libs/hbb_common/src/socket_client.rs b/libs/hbb_common/src/socket_client.rs index 2d9b5a984..aaafeb861 100644 --- a/libs/hbb_common/src/socket_client.rs +++ b/libs/hbb_common/src/socket_client.rs @@ -1,11 +1,13 @@ use crate::{ config::{Config, NetworkType}, + proxy::IntoProxyScheme, tcp::FramedStream, udp::FramedSocket, ResultType, }; use anyhow::Context; use std::net::SocketAddr; +use log::info; use tokio::net::ToSocketAddrs; use tokio_socks::{IntoTargetAddr, TargetAddr}; @@ -50,19 +52,15 @@ pub fn increase_port(host: T, offset: i32) -> String { } pub fn test_if_valid_server(host: &str) -> String { - let host = check_port(host, 0); - + info!("Testing server validity for host: {}", host); use std::net::ToSocketAddrs; - match Config::get_network_type() { - NetworkType::Direct => match host.to_socket_addrs() { - Err(err) => err.to_string(), - Ok(_) => "".to_owned(), - }, - NetworkType::ProxySocks => match &host.into_target_addr() { - Err(err) => err.to_string(), - Ok(_) => "".to_owned(), - }, - } + // Even if the current network type is a proxy type, + // the system DNS should be used to resolve the proxy server address. + host.into_proxy_scheme() + .and_then(|scheme| scheme.get_host_and_port()) + .and_then(|domain| domain.to_socket_addrs().map_err(Into::into)) + .map(|_| "".to_owned()) // on success, return an empty string + .unwrap_or_else(|e| e.to_string()) // on error, convert the error into a string } pub trait IsResolvedSocketAddr { @@ -107,15 +105,7 @@ pub async fn connect_tcp_local< ms_timeout: u64, ) -> ResultType { if let Some(conf) = Config::get_socks() { - return FramedStream::connect( - conf.proxy.as_str(), - target, - local, - conf.username.as_str(), - conf.password.as_str(), - ms_timeout, - ) - .await; + return FramedStream::connect(target, local, &conf, ms_timeout).await; } if let Some(target) = target.resolve() { if let Some(local) = local { diff --git a/libs/hbb_common/src/tcp.rs b/libs/hbb_common/src/tcp.rs index 71aa46ec4..17f360ff9 100644 --- a/libs/hbb_common/src/tcp.rs +++ b/libs/hbb_common/src/tcp.rs @@ -1,4 +1,4 @@ -use crate::{bail, bytes_codec::BytesCodec, ResultType}; +use crate::{bail, bytes_codec::BytesCodec, ResultType, config::Socks5Server, proxy::Proxy}; use anyhow::Context as AnyhowCtx; use bytes::{BufMut, Bytes, BytesMut}; use futures::{SinkExt, StreamExt}; @@ -18,20 +18,20 @@ use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, net::{lookup_host, TcpListener, TcpSocket, ToSocketAddrs}, }; -use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr, ToProxyAddrs}; +use tokio_socks::IntoTargetAddr; use tokio_util::codec::Framed; pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {} -pub struct DynTcpStream(Box); +pub struct DynTcpStream(pub(crate) Box); #[derive(Clone)] pub struct Encrypt(Key, u64, u64); pub struct FramedStream( - Framed, - SocketAddr, - Option, - u64, + pub(crate) Framed, + pub(crate) SocketAddr, + pub(crate) Option, + pub(crate) u64, ); impl Deref for FramedStream { @@ -62,7 +62,7 @@ impl DerefMut for DynTcpStream { } } -fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result { +pub(crate) fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result { let socket = match addr { std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?, std::net::SocketAddr::V6(..) => TcpSocket::new_v6()?, @@ -109,51 +109,17 @@ impl FramedStream { bail!(format!("Failed to connect to {remote_addr}")); } - pub async fn connect<'a, 't, P, T>( - proxy: P, + pub async fn connect<'t, T>( target: T, local_addr: Option, - username: &'a str, - password: &'a str, + proxy_conf: &Socks5Server, ms_timeout: u64, ) -> ResultType where - P: ToProxyAddrs, T: IntoTargetAddr<'t>, { - if let Some(Ok(proxy)) = proxy.to_proxy_addrs().next().await { - let local = if let Some(addr) = local_addr { - addr - } else { - crate::config::Config::get_any_listen_addr(proxy.is_ipv4()) - }; - let stream = - super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy)).await??; - stream.set_nodelay(true).ok(); - let stream = if username.trim().is_empty() { - super::timeout( - ms_timeout, - Socks5Stream::connect_with_socket(stream, target), - ) - .await?? - } else { - super::timeout( - ms_timeout, - Socks5Stream::connect_with_password_and_socket( - stream, target, username, password, - ), - ) - .await?? - }; - let addr = stream.local_addr()?; - return Ok(Self( - Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()), - addr, - None, - 0, - )); - } - bail!("could not resolve to any address"); + let proxy = Proxy::from_conf(proxy_conf, Some(ms_timeout))?; + proxy.connect::(target, local_addr).await } pub fn local_addr(&self) -> SocketAddr { diff --git a/src/common.rs b/src/common.rs index 4d2f4c62b..41b872afc 100644 --- a/src/common.rs +++ b/src/common.rs @@ -5,6 +5,8 @@ use std::{ task::Poll, }; +use serde_json::Value; + #[derive(Debug, Eq, PartialEq)] pub enum GrabState { Ready, @@ -123,7 +125,7 @@ use hbb_common::compress::decompress; use hbb_common::{ allow_err, anyhow::{anyhow, Context}, - bail, + bail, base64, bytes::Bytes, compress::compress as compress_func, config::{self, Config, CONNECT_TIMEOUT, READ_TIMEOUT}, @@ -145,7 +147,10 @@ use hbb_common::{ // #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))] use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all}; -use crate::ui_interface::{get_option, set_option}; +use crate::{ + hbbs_http::create_http_client_async, + ui_interface::{get_option, set_option}, +}; pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future; @@ -972,7 +977,7 @@ pub fn check_software_update() { #[tokio::main(flavor = "current_thread")] async fn check_software_update_() -> hbb_common::ResultType<()> { let url = "https://github.com/rustdesk/rustdesk/releases/latest"; - let latest_release_response = reqwest::get(url).await?; + let latest_release_response = create_http_client_async().get(url).send().await?; let latest_release_version = latest_release_response .url() .path() @@ -1067,7 +1072,7 @@ pub fn get_audit_server(api: String, custom: String, typ: String) -> String { } pub async fn post_request(url: String, body: String, header: &str) -> ResultType { - let mut req = reqwest::Client::new().post(url); + let mut req = create_http_client_async().post(url); if !header.is_empty() { let tmp: Vec<&str> = header.split(": ").collect(); if tmp.len() == 2 { @@ -1084,6 +1089,65 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul post_request(url, body, header).await } +#[tokio::main(flavor = "current_thread")] +pub async fn http_request_sync( + url: String, + method: String, + body: Option, + header: String, +) -> ResultType { + let http_client = create_http_client_async(); + let mut http_client = match method.as_str() { + "get" => http_client.get(url), + "post" => http_client.post(url), + "put" => http_client.put(url), + "delete" => http_client.delete(url), + _ => return Err(anyhow!("The HTTP request method is not supported!")), + }; + let v = serde_json::from_str(header.as_str())?; + + if let Value::Object(obj) = v { + for (key, value) in obj.iter() { + http_client = http_client.header(key, value.as_str().unwrap_or_default()); + } + } else { + return Err(anyhow!("HTTP header information parsing failed!")); + } + + if let Some(b) = body { + http_client = http_client.body(b); + } + + let response = http_client + .timeout(std::time::Duration::from_secs(12)) + .send() + .await?; + + // Serialize response headers + let mut response_headers = serde_json::map::Map::new(); + for (key, value) in response.headers() { + response_headers.insert( + key.to_string(), + serde_json::json!(value.to_str().unwrap_or("")), + ); + } + + let status_code = response.status().as_u16(); + let response_body = response.text().await?; + + // Construct the JSON object + let mut result = serde_json::map::Map::new(); + result.insert("status_code".to_string(), serde_json::json!(status_code)); + result.insert( + "headers".to_string(), + serde_json::Value::Object(response_headers), + ); + result.insert("body".to_string(), serde_json::json!(response_body)); + + // Convert map to JSON string + serde_json::to_string(&result).map_err(|e| anyhow!("Failed to serialize response: {}", e)) +} + #[inline] pub fn make_privacy_mode_msg_with_details( state: back_notification::PrivacyModeState, diff --git a/src/custom_server.rs b/src/custom_server.rs index 22bb9ee8f..58d34d853 100644 --- a/src/custom_server.rs +++ b/src/custom_server.rs @@ -1,5 +1,9 @@ -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use hbb_common::{bail, sodiumoxide::crypto::sign, ResultType}; +use hbb_common::{ + bail, + base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}, + sodiumoxide::crypto::sign, + ResultType, +}; use serde_derive::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)] diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index e210fbd67..016263e74 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -749,6 +749,10 @@ pub fn main_get_async_status() -> String { get_async_job_status() } +pub fn main_get_http_status(url: String) -> Option { + get_async_http_status(url) +} + pub fn main_get_option(key: String) -> String { get_option(key) } @@ -805,6 +809,10 @@ pub fn main_set_socks(proxy: String, username: String, password: String) { set_socks(proxy, username, password) } +pub fn main_get_proxy_status() -> bool { + get_proxy_status() +} + pub fn main_get_socks() -> Vec { get_socks() } @@ -878,9 +886,8 @@ pub fn main_get_api_server() -> String { get_api_server() } -// This function doesn't seem to be used. -pub fn main_post_request(url: String, body: String, header: String) { - post_request(url, body, header) +pub fn main_http_request(url: String, method: String, body: Option, header: String) { + http_request(url,method, body, header) } pub fn main_get_local_option(key: String) -> SyncReturn { diff --git a/src/hbbs_http.rs b/src/hbbs_http.rs index 76ced87a0..71fff7ca8 100644 --- a/src/hbbs_http.rs +++ b/src/hbbs_http.rs @@ -4,8 +4,11 @@ use serde_json::{Map, Value}; #[cfg(feature = "flutter")] pub mod account; +mod http_client; pub mod record_upload; pub mod sync; +pub use http_client::create_http_client; +pub use http_client::create_http_client_async; #[derive(Debug)] pub enum HbbHttpResponse { diff --git a/src/hbbs_http/account.rs b/src/hbbs_http/account.rs index 3f1a7f1c1..8d4eb28b1 100644 --- a/src/hbbs_http/account.rs +++ b/src/hbbs_http/account.rs @@ -1,4 +1,5 @@ use super::HbbHttpResponse; +use crate::hbbs_http::create_http_client; use hbb_common::{config::LocalConfig, log, ResultType}; use reqwest::blocking::Client; use serde_derive::{Deserialize, Serialize}; @@ -130,7 +131,7 @@ impl Default for UserStatus { impl OidcSession { fn new() -> Self { Self { - client: Client::new(), + client: create_http_client(), state_msg: REQUESTING_ACCOUNT_AUTH, failed_msg: "".to_owned(), code_url: None, @@ -168,7 +169,7 @@ impl OidcSession { id: &str, uuid: &str, ) -> ResultType> { - let url = reqwest::Url::parse_with_params( + let url = Url::parse_with_params( &format!("{}/api/oidc/auth-query", api_server), &[("code", code), ("id", id), ("uuid", uuid)], )?; diff --git a/src/hbbs_http/http_client.rs b/src/hbbs_http/http_client.rs new file mode 100644 index 000000000..c4bb2452c --- /dev/null +++ b/src/hbbs_http/http_client.rs @@ -0,0 +1,71 @@ +use hbb_common::config::Config; +use hbb_common::log::info; +use hbb_common::proxy::{Proxy, ProxyScheme}; +use reqwest::blocking::Client as SyncClient; +use reqwest::Client as AsyncClient; + +macro_rules! configure_http_client { + ($builder:expr, $Client: ty) => {{ + let mut builder = $builder; + let client = if let Some(conf) = Config::get_socks() { + let proxy_result = Proxy::from_conf(&conf, None); + + match proxy_result { + Ok(proxy) => { + let proxy_setup = match &proxy.intercept { + ProxyScheme::Http { host, .. } =>{ reqwest::Proxy::http(format!("http://{}", host))}, + ProxyScheme::Https { host, .. } => {reqwest::Proxy::https(format!("https://{}", host))}, + ProxyScheme::Socks5 { addr, .. } => { reqwest::Proxy::all(&format!("socks5://{}", addr)) } + }; + + match proxy_setup { + Ok(p) => { + builder = builder.proxy(p); + if let Some(auth) = proxy.intercept.maybe_auth() { + let basic_auth = + format!("Basic {}", auth.get_basic_authorization()); + builder = builder.default_headers( + vec![( + reqwest::header::PROXY_AUTHORIZATION, + basic_auth.parse().unwrap(), + )] + .into_iter() + .collect(), + ); + } + builder.build().unwrap_or_else(|e| { + info!("Failed to create a proxied client: {}", e); + <$Client>::new() + }) + } + Err(e) => { + info!("Failed to set up proxy: {}", e); + <$Client>::new() + } + } + } + Err(e) => { + info!("Failed to configure proxy: {}", e); + <$Client>::new() + } + } + } else { + builder.build().unwrap_or_else(|e| { + info!("Failed to create a client: {}", e); + <$Client>::new() + }) + }; + + client + }}; +} + +pub fn create_http_client() -> SyncClient { + let builder = SyncClient::builder(); + configure_http_client!(builder, SyncClient) +} + +pub fn create_http_client_async() -> AsyncClient { + let builder = AsyncClient::builder(); + configure_http_client!(builder, AsyncClient) +} diff --git a/src/hbbs_http/record_upload.rs b/src/hbbs_http/record_upload.rs index 79e836988..a25aae42d 100644 --- a/src/hbbs_http/record_upload.rs +++ b/src/hbbs_http/record_upload.rs @@ -1,3 +1,4 @@ +use crate::hbbs_http::create_http_client; use bytes::Bytes; use hbb_common::{bail, config::Config, lazy_static, log, ResultType}; use reqwest::blocking::{Body, Client}; @@ -25,7 +26,7 @@ pub fn is_enable() -> bool { pub fn run(rx: Receiver) { let mut uploader = RecordUploader { - client: Client::new(), + client: create_http_client(), api_server: crate::get_api_server( Config::get_option("api-server"), Config::get_option("custom-rendezvous-server"), diff --git a/src/ipc.rs b/src/ipc.rs index 3a2b88aed..3ee18be43 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -904,6 +904,9 @@ pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> { Ok(()) } +pub fn get_proxy_status() -> bool { + Config::get_socks().is_some() +} #[tokio::main(flavor = "current_thread")] pub async fn test_rendezvous_server() -> ResultType<()> { let mut c = connect(1000, "").await?; diff --git a/src/lang/ar.rs b/src/lang/ar.rs index bdfd83cc1..2b3d9faec 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "فارغ"), ("Invalid folder name", "اسم المجلد غير صحيح"), ("Socks5 Proxy", "وكيل Socks5"), + ("Socks5/Http(s) Proxy", "وكيل Socks5/Http(s)"), ("Discovered", "المكتشفة"), ("install_daemon_tip", "للبدء مع بدء تشغيل النظام. تحتاج الى تثبيت خدمة النظام."), ("Remote ID", "المعرف البعيد"), diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 3a3a40ee9..6384083d6 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", ""), ("Invalid folder name", ""), ("Socks5 Proxy", "Socks5 прокси"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) прокси"), ("Discovered", ""), ("install_daemon_tip", "За стартиране с компютъра трябва да инсталирате системна услуга."), ("Remote ID", ""), diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 610282b1a..8cf9738c4 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Buit"), ("Invalid folder name", "Nom de carpeta incorrecte"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Descobert"), ("install_daemon_tip", ""), ("Remote ID", "ID remot"), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 0309cf562..25851db83 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -239,6 +239,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "空空如也"), ("Invalid folder name", "无效文件夹名称"), ("Socks5 Proxy", "Socks5 代理"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) 代理"), + ("Default protocol and port are Socks5 and 1080", "默认代理协议及端口为Socks5和1080"), ("Discovered", "已发现"), ("install_daemon_tip", "为了开机启动,请安装系统服务。"), ("Remote ID", "远程 ID"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 23cb7442e..10c78f118 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Prázdné"), ("Invalid folder name", "Neplatný název složky"), ("Socks5 Proxy", "Socks5 proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) proxy"), ("Discovered", "Objeveno"), ("install_daemon_tip", "Pokud má být spouštěno při startu systému, je třeba nainstalovat systémovou službu."), ("Remote ID", "Vzdálené ID"), diff --git a/src/lang/da.rs b/src/lang/da.rs index c20f15128..9e28f4b4d 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Tom"), ("Invalid folder name", "Ugyldigt mappenavn"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Fundet"), ("install_daemon_tip", "For at starte efter PC'en er startet op, skal du installere systemtjenesten"), ("Remote ID", "Fjern-ID"), diff --git a/src/lang/de.rs b/src/lang/de.rs index 73262e267..ed97679b2 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Keine Einträge"), ("Invalid folder name", "Ungültiger Ordnername"), ("Socks5 Proxy", "SOCKS5-Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s)-Proxy"), ("Discovered", "Im LAN erkannt"), ("install_daemon_tip", "Um mit System zu starten, muss der Systemdienst installiert sein."), ("Remote ID", "Entfernte ID"), diff --git a/src/lang/el.rs b/src/lang/el.rs index 7d4966120..8fcb403b9 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Άδειο"), ("Invalid folder name", "Μη έγκυρο όνομα φακέλου"), ("Socks5 Proxy", "Διαμεσολαβητής Socks5"), + ("Socks5/Http(s) Proxy", "Διαμεσολαβητής Socks5/Http(s)"), ("Discovered", "Ανακαλύφθηκαν"), ("install_daemon_tip", "Για να ξεκινά με την εκκίνηση του υπολογιστή, πρέπει να εγκαταστήσετε την υπηρεσία συστήματος"), ("Remote ID", "Απομακρυσμένο ID"), diff --git a/src/lang/en.rs b/src/lang/en.rs index ac6cd2cd3..35e4eff53 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -60,6 +60,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Favorites", "Add to favorites"), ("Remove from Favorites", "Remove from favorites"), ("Socks5 Proxy", "Socks5 proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) proxy"), + ("Default protocol and port are Socks5 and 1080", "Default protocol and port are Socks5 and 1080"), ("install_daemon_tip", "For starting on boot, you need to install system service."), ("Are you sure to close the connection?", "Are you sure you want to close the connection?"), ("One-Finger Tap", "One-finger tap"), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index e1bab260d..0e38c6d9f 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Malplena"), ("Invalid folder name", "Dosiernomo nevalida"), ("Socks5 Proxy", "Socks5 prokura servilo"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) prokura servilo"), ("Discovered", "Malkovritaj"), ("install_daemon_tip", ""), ("Remote ID", "Fora identigilo"), diff --git a/src/lang/es.rs b/src/lang/es.rs index f037ac2c1..92ebb67e9 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Vacío"), ("Invalid folder name", "Nombre de carpeta incorrecto"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Descubierto"), ("install_daemon_tip", "Para comenzar en el encendido, debe instalar el servicio del sistema."), ("Remote ID", "ID remoto"), diff --git a/src/lang/et.rs b/src/lang/et.rs index 2e49ddee7..7bb359cd8 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", ""), ("Invalid folder name", ""), ("Socks5 Proxy", "Socks5 proksi"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) proksi"), ("Discovered", ""), ("install_daemon_tip", "Süsteemikäivitusel käivitamiseks tuleb paigaldada süsteemiteenus."), ("Remote ID", ""), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 6eb59377d..8cef3ff08 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "موردی وجود ندارد"), ("Invalid folder name", "نام پوشه نامعتبر است"), ("Socks5 Proxy", "Socks5 پروکسی"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) پروکسی"), ("Discovered", "پیدا شده"), ("install_daemon_tip", "برای شروع در هنگام راه اندازی، باید سرویس سیستم را نصب کنید"), ("Remote ID", "شناسه راه دور"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index ff644806e..4353b5889 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Vide"), ("Invalid folder name", "Nom de dossier invalide"), ("Socks5 Proxy", "Socks5 Agents"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Agents"), ("Discovered", "Découvert"), ("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."), ("Remote ID", "ID de l'appareil distant"), diff --git a/src/lang/he.rs b/src/lang/he.rs index 2da9339a2..d9b3d0c54 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", ""), ("Invalid folder name", ""), ("Socks5 Proxy", "פרוקסי Socks5"), + ("Socks5/Http(s) Proxy", "פרוקסי Socks5/Http(s)"), ("Discovered", ""), ("install_daemon_tip", "לצורך הפעלה בעת הפעלת המחשב, עליך להתקין שירות מערכת."), ("Remote ID", ""), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index bdae7d9bb..6cd18ce80 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Üres"), ("Invalid folder name", "Helytelen mappa név"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Felfedezett"), ("install_daemon_tip", "Az automatikus indításhoz szükséges a szolgáltatás telepítése"), ("Remote ID", "Távoli azonosító"), diff --git a/src/lang/id.rs b/src/lang/id.rs index 9a0fc988f..696e75b81 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Kosong"), ("Invalid folder name", "Nama folder tidak valid"), ("Socks5 Proxy", "Proksi Socks5"), + ("Socks5/Http(s) Proxy", "Proksi Socks5/Http(s)"), ("Discovered", "Telah ditemukan"), ("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."), ("Remote ID", "ID Remote"), diff --git a/src/lang/it.rs b/src/lang/it.rs index a362e2348..bb27633ac 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Vuoto"), ("Invalid folder name", "Nome della cartella non valido"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Rilevate"), ("install_daemon_tip", "Per avviare il programma all'accensione, è necessario installarlo come servizio di sistema."), ("Remote ID", "ID remoto"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 400ae1329..6d1164115 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "空"), ("Invalid folder name", "無効なフォルダ名"), ("Socks5 Proxy", "SOCKS5プロキシ"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s)プロキシ"), ("Discovered", "探知済み"), ("install_daemon_tip", "起動時に開始するには、システムサービスをインストールする必要があります。"), ("Remote ID", "リモートのID"), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index e0fc91a06..95c003de6 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "비어 있음"), ("Invalid folder name", "유효하지 않은 폴더명"), ("Socks5 Proxy", "Socks5 프록시"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) 프록시"), ("Discovered", "찾음"), ("install_daemon_tip", "부팅된 이후 시스템 서비스에 설치해야 합니다."), ("Remote ID", "원격 ID"), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 7bffe6a2e..9ef2113cb 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Бос"), ("Invalid folder name", "Бұрыс бума атауы"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Табылды"), ("install_daemon_tip", "Бут кезінде қосылу үшін жүйелік сербесті орнатуыныз керек."), ("Remote ID", "Қашықтағы ID"), diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 06d3ce2bd..5f21af1c1 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Tuščia"), ("Invalid folder name", "Neteisingas aplanko pavadinimas"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Aptikta tinkle"), ("install_daemon_tip", "Norėdami, kad RustDesk startuotų automatiškai, turite ją įdiegti"), ("Remote ID", "Nuotolinis ID"), diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 9e0b496ab..6e0fd5a84 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Tukšs"), ("Invalid folder name", "Nederīgs mapes nosaukums"), ("Socks5 Proxy", "Socks5 starpniekserveris"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) starpniekserveris"), ("Discovered", "Atklāts"), ("install_daemon_tip", "Lai palaistu pie startēšanas, ir jāinstalē sistēmas serviss."), ("Remote ID", "Attālais ID"), diff --git a/src/lang/nb.rs b/src/lang/nb.rs index d4251a554..e6d6b3e7b 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Tom"), ("Invalid folder name", "Ugyldig mappenavn"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Oppdaget"), ("install_daemon_tip", "For å starte når PC'en har startet opp, må du installere systemtjenesten"), ("Remote ID", "Fjern-ID"), diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 705b34f16..23f7b721a 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Leeg"), ("Invalid folder name", "Ongeldige mapnaam"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Ontdekt"), ("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet u de systeemservice installeren."), ("Remote ID", "Externe ID"), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 06adebc09..9f8124b15 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Pusto"), ("Invalid folder name", "Nieprawidłowa nazwa folderu"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Wykryte"), ("install_daemon_tip", "By uruchomić RustDesk przy starcie systemu, musisz zainstalować usługę systemową."), ("Remote ID", "Zdalne ID"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 76fa2c214..d483a7ed3 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Vazio"), ("Invalid folder name", "Nome de diretório inválido"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Descoberto"), ("install_daemon_tip", "Para inicialização junto do sistema, deve instalar o serviço de sistema."), ("Remote ID", "ID Remoto"), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 2958878a5..cf71f8a57 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Vazio"), ("Invalid folder name", "Nome de diretório inválido"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Descoberto"), ("install_daemon_tip", "Para inicialização junto ao sistema, você deve instalar o serviço de sistema."), ("Remote ID", "ID Remoto"), diff --git a/src/lang/ro.rs b/src/lang/ro.rs index c7780c371..abf0e078c 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Gol"), ("Invalid folder name", "Denumire folder nevalidă"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Descoperite"), ("install_daemon_tip", "Pentru executare la pornirea sistemului, instalează serviciul de sistem."), ("Remote ID", "ID dispozitiv la distanță"), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 3e28744c5..ab9fcc0b1 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Пусто"), ("Invalid folder name", "Недопустимое имя папки"), ("Socks5 Proxy", "SOCKS5-прокси"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s)-прокси"), ("Discovered", "Найдено"), ("install_daemon_tip", "Для запуска при загрузке необходимо установить системную службу"), ("Remote ID", "Удалённый ID"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 1abfe46a3..3c63acdf5 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Prázdne"), ("Invalid folder name", "Neplatný názov adresára"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Objavené"), ("install_daemon_tip", "Ak chcete, aby sa spúšťal pri štarte systému, musíte nainštalovať systémovú službu."), ("Remote ID", "Vzdialené ID"), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index cdc37594d..6559c0cf1 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Prazno"), ("Invalid folder name", "Napačno ime mape"), ("Socks5 Proxy", "Socks5 posredniški strežnik"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) posredniški strežnik"), ("Discovered", "Odkriti"), ("install_daemon_tip", "Za samodejni zagon ob vklopu računalnika je potrebno dodati sistemsko storitev"), ("Remote ID", "Oddaljeni ID"), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 6a7ee5d63..1a1f9c291 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Bosh"), ("Invalid folder name", "Emri i dosjes i pavlefshëm"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "I pambuluar"), ("install_daemon_tip", "Për të nisur në boot, duhet të instaloni shërbimin e sistemit"), ("Remote ID", "ID në distancë"), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index e03383415..18918f645 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Prazno"), ("Invalid folder name", "Pogrešno ime direktorijuma"), ("Socks5 Proxy", "Socks5 proksi"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) proksi"), ("Discovered", "Otkriveno"), ("install_daemon_tip", "Za pokretanje pri startu sistema, treba da instalirate sistemski servis."), ("Remote ID", "Udaljeni ID"), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index b78677fe6..c2a0a73f3 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Tom"), ("Invalid folder name", "Ogiltigt mappnamn"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Upptäckt"), ("install_daemon_tip", "För att starta efter boot måste du installera systemtjänsten."), ("Remote ID", "Fjärr ID"), diff --git a/src/lang/template.rs b/src/lang/template.rs index 02aa92450..3a2a007db 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", ""), ("Invalid folder name", ""), ("Socks5 Proxy", ""), + ("Socks5/Http(s) Proxy", ""), ("Discovered", ""), ("install_daemon_tip", ""), ("Remote ID", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index ce262d19a..05285579d 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "ว่างเปล่า"), ("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"), ("Socks5 Proxy", "พรอกซี Socks5"), + ("Socks5/Http(s) Proxy", "พรอกซี Socks5/Http(s)"), ("Discovered", "ค้นพบ"), ("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"), ("Remote ID", "ID ปลายทาง"), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 27fca3807..0b2de53d5 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Boş"), ("Invalid folder name", "Geçersiz klasör adı"), ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"), ("Discovered", "Keşfedilenler"), ("install_daemon_tip", "Başlangıçta başlamak için sistem hizmetini yüklemeniz gerekir."), ("Remote ID", "Uzak ID"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 4e86fe760..c7ae7e427 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "空空如也"), ("Invalid folder name", "資料夾名稱無效"), ("Socks5 Proxy", "Socks5 代理伺服器"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) 代理伺服器"), ("Discovered", "已探索"), ("install_daemon_tip", "若要在開機時啟動,您需要安裝系統服務。"), ("Remote ID", "遠端 ID"), diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 63a8800f5..6d18e7a70 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Пусто"), ("Invalid folder name", "Неприпустима назва теки"), ("Socks5 Proxy", "Проксі-сервер Socks5"), + ("Socks5/Http(s) Proxy", "Проксі-сервер Socks5/Http(s)"), ("Discovered", "Знайдено"), ("install_daemon_tip", "Для запуску під час завантаження, вам необхідно встановити системну службу"), ("Remote ID", "Віддалений ідентифікатор"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 96cbbee7e..117442756 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Empty", "Trống"), ("Invalid folder name", "Tên thư mục không hợp lệ"), ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), ("Discovered", "Đuợc phát hiện"), ("install_daemon_tip", "Để chạy lúc khởi động máy, bạn cần phải cài dịch vụ hệ thống."), ("Remote ID", "ID từ xa"), diff --git a/src/naming.rs b/src/naming.rs index bae4d0f9d..0436a23f2 100644 --- a/src/naming.rs +++ b/src/naming.rs @@ -1,6 +1,5 @@ mod custom_server; -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use hbb_common::ResultType; +use hbb_common::{ResultType, base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}}; use custom_server::*; fn gen_name(lic: &CustomServer) -> ResultType { diff --git a/src/plugin/callback_msg.rs b/src/plugin/callback_msg.rs index e634595e8..2a23b03dd 100644 --- a/src/plugin/callback_msg.rs +++ b/src/plugin/callback_msg.rs @@ -1,4 +1,5 @@ use super::*; +use crate::hbbs_http::create_http_client; use crate::{ flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS}, ui_interface::get_api_server, @@ -280,7 +281,7 @@ fn request_plugin_sign(id: String, msg_to_rustdesk: MsgToRustDesk) -> PluginRetu ); thread::spawn(move || { let sign_url = format!("{}/lic/web/api/plugin-sign", get_api_server()); - let client = reqwest::blocking::Client::new(); + let client = create_http_client(); let req = PluginSignReq { plugin_id: id.clone(), version: signature_data.version, diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 507df441e..25e73541e 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -3,6 +3,7 @@ use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *}; use crate::flutter; +use crate::hbbs_http::create_http_client; use hbb_common::{allow_err, bail, log, tokio, toml}; use serde_derive::{Deserialize, Serialize}; use serde_json; @@ -67,7 +68,7 @@ fn get_source_plugins() -> HashMap { let mut plugins = HashMap::new(); for source in get_plugin_source_list().into_iter() { let url = format!("{}/meta.toml", source.url); - match reqwest::blocking::get(&url) { + match create_http_client().get(&url).send() { Ok(resp) => { if !resp.status().is_success() { log::error!( @@ -441,6 +442,7 @@ fn update_uninstall_id_set(set: HashSet) -> ResultType<()> { // install process pub(super) mod install { use super::IPC_PLUGIN_POSTFIX; + use crate::hbbs_http::create_http_client; use crate::{ ipc::{connect, Data}, plugin::ipc::{InstallStatus, Plugin}, @@ -469,7 +471,7 @@ pub(super) mod install { } fn download_to_file(url: &str, file: File) -> ResultType<()> { - let resp = match reqwest::blocking::get(url) { + let resp = match create_http_client().get(url).send() { Ok(resp) => resp, Err(e) => { bail!("get plugin from '{}', {}", url, e); diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index 0bdd6388d..58c4fee15 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -15,6 +15,7 @@ use hbb_common::{ config::{self, Config, CONNECT_TIMEOUT, READ_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT}, futures::future::join_all, log, + proxy::Proxy, protobuf::Message as _, rendezvous_proto::*, sleep, @@ -388,7 +389,14 @@ impl RendezvousMediator { pub async fn start(server: ServerPtr, host: String) -> ResultType<()> { log::info!("start rendezvous mediator of {}", host); - if cfg!(debug_assertions) && option_env!("TEST_TCP").is_some() { + //If the investment agent type is http or https, then tcp forwarding is enabled. + let is_http_proxy = if let Some(conf) = Config::get_socks() { + let proxy = Proxy::from_conf(&conf, None)?; + proxy.is_http_or_https() + } else { + false + }; + if (cfg!(debug_assertions) && option_env!("TEST_TCP").is_some()) || is_http_proxy { Self::start_tcp(server, host).await } else { Self::start_udp(server, host).await diff --git a/src/ui.rs b/src/ui.rs index 10aefe5ff..2e9ea2f91 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -548,6 +548,10 @@ impl UI { change_id_shared(id, old_id); } + fn http_request(&self, url: String, method: String, body: Option, header: String) { + http_request(url, method, body, header) + } + fn post_request(&self, url: String, body: String, header: String) { post_request(url, body, header) } @@ -560,6 +564,10 @@ impl UI { get_async_job_status() } + fn get_http_status(&self, url: String) -> Option { + get_async_http_status(url) + } + fn t(&self, name: String) -> String { crate::client::translate(name) } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index d8a9996c0..313b6e562 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -4,7 +4,7 @@ use hbb_common::{ allow_err, bytes::Bytes, config::{ - self, Config, LocalConfig, PeerConfig, CONNECT_TIMEOUT, HARD_SETTINGS, RENDEZVOUS_PORT, + self, Config, LocalConfig, PeerConfig, CONNECT_TIMEOUT, RENDEZVOUS_PORT, }, directories_next, futures::future::join_all, @@ -65,6 +65,7 @@ lazy_static::lazy_static! { id: "".to_owned(), })); static ref ASYNC_JOB_STATUS : Arc> = Default::default(); + static ref ASYNC_HTTP_STATUS : Arc>> = Arc::new(Mutex::new(HashMap::new())); static ref TEMPORARY_PASSWD : Arc> = Arc::new(Mutex::new("".to_owned())); } @@ -421,6 +422,16 @@ pub fn set_socks(proxy: String, username: String, password: String) { .ok(); } +#[inline] +pub fn get_proxy_status() -> bool { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + return ipc::get_proxy_status(); + + // Currently, only the desktop version has proxy settings. + #[cfg(any(target_os = "android", target_os = "ios"))] + return false; +} + #[cfg(any(target_os = "android", target_os = "ios"))] pub fn set_socks(_: String, _: String, _: String) {} @@ -708,6 +719,28 @@ pub fn change_id(id: String) { }); } +#[inline] +pub fn http_request(url: String, method: String, body: Option, header: String) { + // Respond to concurrent requests for resources + let current_request = ASYNC_HTTP_STATUS.clone(); + current_request.lock().unwrap().insert(url.clone()," ".to_owned()); + std::thread::spawn(move || { + let res = match crate::http_request_sync(url.clone(), method, body, header) { + Err(err) => { log::error!("{}", err); err.to_string() }, + Ok(text) => text, + }; + current_request.lock().unwrap().insert(url,res); + }); +} +#[inline] +pub fn get_async_http_status(url: String) -> Option { + match ASYNC_HTTP_STATUS.lock().unwrap().get(&url) { + None => {None} + Some(_str) => {Some(_str.to_string())} + } +} + + #[inline] pub fn post_request(url: String, body: String, header: String) { *ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned();