diff --git a/app/lib/features/tor/presentation/screens/tor_proxy.dart b/app/lib/features/tor/presentation/screens/tor_proxy.dart index faaabd1c..2bdcd8d4 100644 --- a/app/lib/features/tor/presentation/screens/tor_proxy.dart +++ b/app/lib/features/tor/presentation/screens/tor_proxy.dart @@ -258,8 +258,8 @@ class TorProxyScreen extends HookConsumerWidget { left: 56, right: 24, ), - title: Text('Direct Connection'), - subtitle: Text( + title: const Text('Direct Connection'), + subtitle: const Text( 'The best way to connect to Tor if Tor is not blocked', ), ), @@ -285,8 +285,8 @@ class TorProxyScreen extends HookConsumerWidget { left: 56, right: 24, ), - title: Text('obfs4'), - subtitle: Text( + title: const Text('obfs4'), + subtitle: const Text( 'Suitable for light censorship and high bandwidth needs', ), ), @@ -312,8 +312,8 @@ class TorProxyScreen extends HookConsumerWidget { left: 56, right: 24, ), - title: Text('Snowflake'), - subtitle: Text('Suitable for heavy censorship'), + title: const Text('Snowflake'), + subtitle: const Text('Suitable for heavy censorship'), ), ), CheckboxListTile.adaptive( diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 8706962d..e0f286a1 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -74,7 +74,7 @@ dependencies: text_scroll: ^0.2.1 timeago: ^3.7.1 tor: - path: ./packages/tor + path: ../packages/tor universal_io: ^2.2.2 uri_to_file: git: diff --git a/packages/pluggable_transports_proxy/lib/src/data/service/moat_api.dart b/packages/pluggable_transports_proxy/lib/src/data/service/moat_api.dart index 9a26cf62..c6504a48 100644 --- a/packages/pluggable_transports_proxy/lib/src/data/service/moat_api.dart +++ b/packages/pluggable_transports_proxy/lib/src/data/service/moat_api.dart @@ -31,14 +31,20 @@ class MoatApi { MoatApi(this._client); Future settings([SettingsRequest? request]) async { - final response = await _post('settings', request ?? SettingsRequest()); + final response = await _post( + 'settings', + request ?? const SettingsRequest(), + ); return SettingsResponse.fromJson( jsonDecode(response.body) as Map, ); } Future defaults([SettingsRequest? request]) async { - final response = await _post('defaults', request ?? SettingsRequest()); + final response = await _post( + 'defaults', + request ?? const SettingsRequest(), + ); return SettingsResponse.fromJson( jsonDecode(response.body) as Map, ); diff --git a/packages/pluggable_transports_proxy/lib/src/data/service/moat_service.dart b/packages/pluggable_transports_proxy/lib/src/data/service/moat_service.dart index be121cd8..a3c111cd 100644 --- a/packages/pluggable_transports_proxy/lib/src/data/service/moat_service.dart +++ b/packages/pluggable_transports_proxy/lib/src/data/service/moat_service.dart @@ -21,7 +21,6 @@ import 'dart:io'; import 'package:http/io_client.dart'; import 'package:pluggable_transports_proxy/pluggable_transports_proxy.dart'; -import 'package:pluggable_transports_proxy/src/data/models/moat.dart'; import 'package:pluggable_transports_proxy/src/data/service/moat_api.dart'; import 'package:socks5_proxy/socks_client.dart'; diff --git a/packages/tor/analysis_options.yaml b/packages/tor/analysis_options.yaml index 193ecf6c..8da17cd8 100644 --- a/packages/tor/analysis_options.yaml +++ b/packages/tor/analysis_options.yaml @@ -1,11 +1,39 @@ -# SPDX-FileCopyrightText: 2022 Foundation Devices Inc. -# SPDX-FileCopyrightText: 2024 Foundation Devices Inc. +# This file configures the static analysis results for your project (errors, +# warnings, and lints). # -# SPDX-License-Identifier: MIT +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. -include: package:flutter_lints/flutter.yaml +include: package:lint/strict.yaml +# Uncomment the following section to specify additional rules. + +linter: + rules: + unawaited_futures: true + discarded_futures: true + collection_methods_unrelated_type: true analyzer: + errors: + overridden_fields: ignore + plugins: + - custom_lint exclude: - - 'lib/generated*' - - 'cargokit' \ No newline at end of file + - "**.drift" + - "**.g.dart" + - "**.swagger.dart" + - "**.freezed.dart" + - "**.chopper.dart" + - "lib/generated_bindings.dart" +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/tor/lib/socks_socket.dart b/packages/tor/lib/socks_socket.dart index 97c311da..93d0b9f4 100644 --- a/packages/tor/lib/socks_socket.dart +++ b/packages/tor/lib/socks_socket.dart @@ -95,19 +95,28 @@ class SOCKSSocket { /// Is SSL enabled? final bool sslEnabled; + /// Constructor. + SOCKSSocket({ + required this.proxyHost, + required this.proxyPort, + required this.sslEnabled, + }) { + unawaited(_init()); + } + /// Private constructor. SOCKSSocket._(this.proxyHost, this.proxyPort, this.sslEnabled); - /// Provides a stream of data as List. + /// Provides a stream of data as List``. Stream> get inputStream => sslEnabled ? _secureResponseController.stream : _responseController.stream; - /// Provides a StreamSink compatible with List for sending data. + /// Provides a StreamSink compatible with List`` for sending data. StreamSink> get outputStream { // Create a simple StreamSink wrapper for _socksSocket and // _secureSocksSocket that accepts List and forwards it to write method. - var sink = StreamController>(); + final sink = StreamController>(); sink.stream.listen((data) { if (sslEnabled) { _secureSocksSocket.add(data); @@ -129,12 +138,13 @@ class SOCKSSocket { /// /// Returns: /// A Future that resolves to a SOCKSSocket instance. - static Future create( - {required String proxyHost, - required int proxyPort, - bool sslEnabled = false}) async { + static Future create({ + required String proxyHost, + required int proxyPort, + bool sslEnabled = false, + }) async { // Create a SOCKS socket instance. - var instance = SOCKSSocket._(proxyHost, proxyPort, sslEnabled); + final instance = SOCKSSocket._(proxyHost, proxyPort, sslEnabled); // Initialize the SOCKS socket. await instance._init(); @@ -143,14 +153,6 @@ class SOCKSSocket { return instance; } - /// Constructor. - SOCKSSocket( - {required this.proxyHost, - required this.proxyPort, - required this.sslEnabled}) { - _init(); - } - /// Initializes the SOCKS socket. /// /// This method is a private method that is called by the constructor. @@ -159,10 +161,7 @@ class SOCKSSocket { /// A Future that resolves to void. Future _init() async { // Connect to the SOCKS proxy server. - _socksSocket = await Socket.connect( - proxyHost, - proxyPort, - ); + _socksSocket = await Socket.connect(proxyHost, proxyPort); // Listen to the socket. _subscription = _socksSocket.listen( @@ -196,12 +195,13 @@ class SOCKSSocket { _socksSocket.add([0x05, 0x01, 0x00]); // Wait for server response. - var response = await _responseController.stream.first; + final response = await _responseController.stream.first; // Check if the connection was successful. if (response[1] != 0x00) { throw Exception( - 'socks_socket.connect(): Failed to connect to SOCKS5 proxy.'); + 'socks_socket.connect(): Failed to connect to SOCKS5 proxy.', + ); } return; @@ -217,7 +217,7 @@ class SOCKSSocket { /// A Future that resolves to void. Future connectTo(String domain, int port) async { // Connect command. - var request = [ + final request = [ 0x05, // SOCKS version. 0x01, // Connect command. 0x00, // Reserved. @@ -225,19 +225,20 @@ class SOCKSSocket { domain.length, ...domain.codeUnits, (port >> 8) & 0xFF, - port & 0xFF + port & 0xFF, ]; // Send the connect command to the SOCKS proxy server. _socksSocket.add(request); // Wait for server response. - var response = await _responseController.stream.first; + final response = await _responseController.stream.first; // Check if the connection was successful. if (response[1] != 0x00) { throw Exception( - 'socks_socket.connectTo(): Failed to connect to target through SOCKS5 proxy.'); + 'socks_socket.connectTo(): Failed to connect to target through SOCKS5 proxy.', + ); } // Upgrade to SSL if needed. @@ -265,9 +266,9 @@ class SOCKSSocket { _secureResponseController.addError("$e"); // TODO make sure sending error as string is acceptable. }, - onDone: () { + onDone: () async { // Close the response controller when the socket is closed. - _secureResponseController.close(); + await _secureResponseController.close(); }, ); } @@ -288,7 +289,7 @@ class SOCKSSocket { if (object == null) return; // Write the data to the socket. - List data = utf8.encode(object.toString()); + final List data = utf8.encode(object.toString()); if (sslEnabled) { _secureSocksSocket.add(data); } else { @@ -310,9 +311,9 @@ class SOCKSSocket { } finally { await _subscription?.cancel(); await _socksSocket.close(); - _responseController.close(); + await _responseController.close(); if (sslEnabled) { - _secureResponseController.close(); + await _secureResponseController.close(); } } } @@ -355,7 +356,7 @@ class SOCKSSocket { _socksSocket.writeln(command); // Wait for the response from the proxy server. - var responseData = await _responseController.stream.first; + final responseData = await _responseController.stream.first; if (kDebugMode) { print("responseData: ${utf8.decode(responseData)}"); } @@ -364,7 +365,7 @@ class SOCKSSocket { _secureSocksSocket.writeln(command); // Wait for the response from the proxy server. - var responseData = await _secureResponseController.stream.first; + final responseData = await _secureResponseController.stream.first; if (kDebugMode) { print("secure responseData: ${utf8.decode(responseData)}"); } diff --git a/packages/tor/lib/tor.dart b/packages/tor/lib/tor.dart index c8591035..06cf1e09 100644 --- a/packages/tor/lib/tor.dart +++ b/packages/tor/lib/tor.dart @@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; import 'package:tor/generated_bindings.dart' as rust; -DynamicLibrary load(name) { +DynamicLibrary load(String name) { if (Platform.isAndroid || Platform.isLinux) { return DynamicLibrary.open('lib$name.so'); } else if (Platform.isIOS || Platform.isMacOS) { @@ -33,7 +33,9 @@ class CouldntBootstrapDirectory implements Exception { } class NotSupportedPlatform implements Exception { - NotSupportedPlatform(String s); + String reason; + + NotSupportedPlatform(this.reason); } class ClientNotActive implements Exception {} @@ -97,8 +99,8 @@ class Tor { /// Returns a Future that completes when the Tor service has started. /// /// Throws an exception if the Tor service fails to start. - static Future init({enabled = true}) async { - var singleton = Tor._instance; + static Future init({bool enabled = true}) async { + final singleton = Tor._instance; singleton._enabled = enabled; return singleton; } @@ -117,15 +119,15 @@ class Tor { } Future _getRandomUnusedPort({List excluded = const []}) async { - var random = Random.secure(); + final random = Random.secure(); int potentialPort = 0; retry: while (potentialPort <= 0 || excluded.contains(potentialPort)) { potentialPort = random.nextInt(65535); try { - var socket = await ServerSocket.bind("0.0.0.0", potentialPort); - socket.close(); + final socket = await ServerSocket.bind("0.0.0.0", potentialPort); + await socket.close(); return potentialPort; } catch (_) { continue retry; @@ -142,33 +144,39 @@ class Tor { /// Throws an exception if the Tor service fails to start. /// /// Returns a Future that completes when the Tor service has started. - Future start( - {int? obfs4Port, int? snowflakePort, String? bridgeLines}) async { + Future start({ + int? obfs4Port, + int? snowflakePort, + String? bridgeLines, + }) async { broadcastState(); // Set the state and cache directories. final Directory appSupportDir = await getApplicationSupportDirectory(); - final stateDir = - await Directory('${appSupportDir.path}/tor_state').create(); - final cacheDir = - await Directory('${appSupportDir.path}/tor_cache').create(); + final stateDir = await Directory( + '${appSupportDir.path}/tor_state', + ).create(); + final cacheDir = await Directory( + '${appSupportDir.path}/tor_cache', + ).create(); // Generate a random port. - int newPort = await _getRandomUnusedPort(); + final newPort = await _getRandomUnusedPort(); // Start the Tor service in an isolate. - final tor = await Isolate.run(() async { + final tor = await Isolate.run(() { // Load the Tor library. - var lib = rust.NativeLibrary(load(libName)); + final lib = rust.NativeLibrary(load(libName)); // Start the Tor service. final tor = lib.tor_start( - newPort, - stateDir.path.toNativeUtf8() as Pointer, - cacheDir.path.toNativeUtf8() as Pointer, - obfs4Port ?? -1, - snowflakePort ?? -1, - bridgeLines?.toNativeUtf8() as Pointer? ?? nullptr); + newPort, + stateDir.path.toNativeUtf8() as Pointer, + cacheDir.path.toNativeUtf8() as Pointer, + obfs4Port ?? -1, + snowflakePort ?? -1, + bridgeLines?.toNativeUtf8() as Pointer? ?? nullptr, + ); // Throw an exception if the Tor service fails to start. if (tor.client == nullptr) { @@ -190,24 +198,30 @@ class Tor { broadcastState(); } - Future reconfigure( - {int? obfs4Port, int? snowflakePort, String? bridgeLines}) async { + Future reconfigure({ + int? obfs4Port, + int? snowflakePort, + String? bridgeLines, + }) async { final lib = rust.NativeLibrary(_lib); // Set the state and cache directories. final Directory appSupportDir = await getApplicationSupportDirectory(); - final stateDir = - await Directory('${appSupportDir.path}/tor_state').create(); - final cacheDir = - await Directory('${appSupportDir.path}/tor_cache').create(); + final stateDir = await Directory( + '${appSupportDir.path}/tor_state', + ).create(); + final cacheDir = await Directory( + '${appSupportDir.path}/tor_cache', + ).create(); final reconfigured = lib.tor_reconfigure( - _clientPtr, - stateDir.path.toNativeUtf8() as Pointer, - cacheDir.path.toNativeUtf8() as Pointer, - obfs4Port ?? -1, - snowflakePort ?? -1, - bridgeLines?.toNativeUtf8() as Pointer? ?? nullptr); + _clientPtr, + stateDir.path.toNativeUtf8() as Pointer, + cacheDir.path.toNativeUtf8() as Pointer, + obfs4Port ?? -1, + snowflakePort ?? -1, + bridgeLines?.toNativeUtf8() as Pointer? ?? nullptr, + ); if (!reconfigured) { throwRustException(lib); @@ -270,25 +284,29 @@ class Tor { Future isReady() async { return await Future.doWhile( - () => Future.delayed(const Duration(seconds: 1)).then((_) { - // We are waiting and making absolutely no request unless: - // Tor is disabled - if (!enabled) { - return false; - } + () => Future.delayed(const Duration(seconds: 1)).then((_) { + // We are waiting and making absolutely no request unless: + // Tor is disabled + if (!enabled) { + return false; + } - // ...or Tor circuit is established - if (bootstrapped) { - return false; - } + // ...or Tor circuit is established + if (bootstrapped) { + return false; + } - // This way we avoid making clearnet req's while Tor is initialising - return true; - })); + // This way we avoid making clearnet req's while Tor is initialising + return true; + }), + ); } - static throwRustException(rust.NativeLibrary lib) { - String rustError = lib.tor_last_error_message().cast().toDartString(); + static void throwRustException(rust.NativeLibrary lib) { + final String rustError = lib + .tor_last_error_message() + .cast() + .toDartString(); throw _getRustException(rustError); } diff --git a/packages/tor/pubspec.yaml b/packages/tor/pubspec.yaml index 9aadd7f2..da072ef1 100644 --- a/packages/tor/pubspec.yaml +++ b/packages/tor/pubspec.yaml @@ -20,17 +20,17 @@ environment: flutter: '>=3.3.0' dependencies: + ffi: ^2.0.1 flutter: sdk: flutter path_provider: ^2.1.4 - ffi: ^2.0.1 plugin_platform_interface: ^2.0.2 dev_dependencies: ffigen: ^19.1.0 flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + lint: ^2.8.0 ffigen: output: 'lib/generated_bindings.dart'