diff --git a/android/app/build.gradle b/android/app/build.gradle index d1f731e..135ec62 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -21,6 +21,12 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" @@ -53,16 +59,30 @@ android { versionName flutterVersionName } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { release { // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug { + signingConfig signingConfigs.release + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } } } diff --git a/android/key.properties b/android/key.properties new file mode 100644 index 0000000..e2eadef --- /dev/null +++ b/android/key.properties @@ -0,0 +1,4 @@ +storePassword=proxypin +keyPassword=proxypin +keyAlias=proxypin +storeFile=../keystore.jks diff --git a/android/keystore.jks b/android/keystore.jks new file mode 100644 index 0000000..b32e351 Binary files /dev/null and b/android/keystore.jks differ diff --git a/lib/network/network.dart b/lib/network/network.dart index a38d226..14032a3 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -92,7 +92,6 @@ class Network { } String? host = hostAndPort?.host; host ??= TLS.getDomain(data); - //ssl自签证书 var certificate = await CertificateManager.getCertificateContext(host!); //服务端等待客户端ssl握手 diff --git a/lib/network/util/tls.dart b/lib/network/util/tls.dart index 908577c..bfd6a0f 100644 --- a/lib/network/util/tls.dart +++ b/lib/network/util/tls.dart @@ -1,6 +1,32 @@ +/* + * Copyright 2023 WangHongEn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + import 'dart:typed_data'; class TLS { + ///判断是否是TLS Client Hello + static bool isTLSClientHello(Uint8List data) { + if (data.length < 43) return false; + if (data[0] != 0x16 /* handshake */) return false; + if (data[1] != 0x03 || data[2] < 0x00 || data[2] > 0x03) return false; + if (data[5] != 0x01 /* client_hello */) return false; + if (data[9] != 0x03 || data[10] < 0x00 || data[10] > 0x03) return false; + return true; + } + ///从TLS Client Hello 解析域名 static String? getDomain(Uint8List data) { try { diff --git a/lib/ui/desktop/toolbar/setting/script.dart b/lib/ui/desktop/toolbar/setting/script.dart index e546112..cf258e0 100644 --- a/lib/ui/desktop/toolbar/setting/script.dart +++ b/lib/ui/desktop/toolbar/setting/script.dart @@ -321,7 +321,7 @@ class ScriptList extends StatefulWidget { } class _ScriptListState extends State { - Map selected = {}; + int selected = -1; @override Widget build(BuildContext context) { @@ -354,47 +354,9 @@ class _ScriptListState extends State { } }); }, - onSecondaryTapDown: (details) { - showContextMenu(context, details.globalPosition, items: [ - PopupMenuItem( - height: 35, - child: const Text("编辑"), - onTap: () async { - String script = await (await ScriptManager.instance).getScript(list[index]); - if (!context.mounted) { - return; - } - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => ScriptEdit(scriptItem: list[index], script: script)).then((value) { - if (value != null) { - setState(() {}); - } - }); - }), - PopupMenuItem(height: 35, child: const Text("导出"), onTap: () => export(list[index])), - PopupMenuItem( - height: 35, - child: list[index].enabled ? const Text("禁用") : const Text("启用"), - onTap: () { - list[index].enabled = !list[index].enabled; - setState(() {}); - }), - const PopupMenuDivider(), - PopupMenuItem( - height: 35, - child: const Text("删除"), - onTap: () async { - (await ScriptManager.instance).removeScript(index); - _refreshScript(); - setState(() {}); - if (context.mounted) FlutterToastr.show('删除成功', context); - }), - ]); - }, + onSecondaryTapDown: (details) => showMenus(details, index), child: Container( - color: selected[index] == true + color: selected == index ? primaryColor.withOpacity(0.8) : index.isEven ? Colors.grey.withOpacity(0.1) @@ -421,6 +383,52 @@ class _ScriptListState extends State { }); } + //点击菜单 + showMenus(TapDownDetails details, int index) { + setState(() { + selected = index; + }); + showContextMenu(context, details.globalPosition, items: [ + PopupMenuItem( + height: 35, + child: const Text("编辑"), + onTap: () async { + String script = await (await ScriptManager.instance).getScript(widget.scripts[index]); + if (!context.mounted) { + return; + } + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => ScriptEdit(scriptItem: widget.scripts[index], script: script)).then((value) { + if (value != null) { + setState(() {}); + } + }); + }), + PopupMenuItem(height: 35, child: const Text("导出"), onTap: () => export(widget.scripts[index])), + PopupMenuItem( + height: 35, + child: widget.scripts[index].enabled ? const Text("禁用") : const Text("启用"), + onTap: () { + widget.scripts[index].enabled = !widget.scripts[index].enabled; + }), + const PopupMenuDivider(), + PopupMenuItem( + height: 35, + child: const Text("删除"), + onTap: () async { + (await ScriptManager.instance).removeScript(index); + _refreshScript(); + if (context.mounted) FlutterToastr.show('删除成功', context); + }), + ]).then((value) { + setState(() { + selected = -1; + }); + }); + } + //导出js export(ScriptItem item) async { //文件名称 diff --git a/lib/ui/desktop/toolbar/setting/theme.dart b/lib/ui/desktop/toolbar/setting/theme.dart index 35776e6..29af605 100644 --- a/lib/ui/desktop/toolbar/setting/theme.dart +++ b/lib/ui/desktop/toolbar/setting/theme.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:network_proxy/main.dart'; class ThemeSetting extends StatelessWidget { - const ThemeSetting({Key? key}) : super(key: key); + const ThemeSetting({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/launch/launch.dart b/lib/ui/launch/launch.dart index ad86751..3f83611 100644 --- a/lib/ui/launch/launch.dart +++ b/lib/ui/launch/launch.dart @@ -12,7 +12,8 @@ class SocketLaunch extends StatefulWidget { final bool startup; final Function? onStart; final Function? onStop; - final bool serverLaunch; + + final bool serverLaunch; //是否启动代理服务器 const SocketLaunch( {super.key, @@ -30,7 +31,7 @@ class SocketLaunch extends StatefulWidget { } class _SocketLaunchState extends State with WindowListener, WidgetsBindingObserver { - bool started = false; + static bool started = false; @override void initState() { diff --git a/lib/ui/mobile/menu.dart b/lib/ui/mobile/menu.dart index 703585e..c19929a 100644 --- a/lib/ui/mobile/menu.dart +++ b/lib/ui/mobile/menu.dart @@ -4,6 +4,7 @@ import 'package:easy_permission/easy_permission.dart'; import 'package:flutter/material.dart'; import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:network_proxy/native/vpn.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/http_client.dart'; import 'package:network_proxy/network/util/host_filter.dart'; @@ -211,7 +212,7 @@ class MoreEnum extends StatelessWidget { hostname: response.headers.get("hostname")); if (context.mounted && Navigator.canPop(context)) { - FlutterToastr.show("连接成功${proxyServer.isRunning ? '' : ',手机需要开启抓包才可以抓取请求哦'}", context, duration: 3); + FlutterToastr.show("连接成功${Vpn.isVpnStarted ? '' : ',手机需要开启抓包才可以抓取请求哦'}", context, duration: 3); Navigator.pop(context); } } diff --git a/lib/ui/mobile/setting/script.dart b/lib/ui/mobile/setting/script.dart index 32b1c10..fbcd2f6 100644 --- a/lib/ui/mobile/setting/script.dart +++ b/lib/ui/mobile/setting/script.dart @@ -117,7 +117,7 @@ class _MobileScriptState extends State { } try { - var json = jsonDecode(await file.readAsString()); + var json = jsonDecode(utf8.decode(await file.readAsBytes())); var scriptItem = ScriptItem.fromJson(json); (await ScriptManager.instance).addScript(scriptItem, json['script']); _refreshScript(); @@ -148,7 +148,7 @@ class ScriptEdit extends StatefulWidget { final ScriptItem? scriptItem; final String? script; - const ScriptEdit({Key? key, this.scriptItem, this.script}) : super(key: key); + const ScriptEdit({super.key, this.scriptItem, this.script}); @override State createState() => _ScriptEditState(); @@ -230,8 +230,7 @@ class _ScriptEditState extends State { CodeTheme( data: CodeThemeData(styles: monokaiSublimeTheme), child: SingleChildScrollView( - child: CodeField( - textStyle: const TextStyle(fontSize: 14), controller: script))) + child: CodeField(textStyle: const TextStyle(fontSize: 14), controller: script))) ], )))); } @@ -271,14 +270,19 @@ class ScriptList extends StatefulWidget { } class _ScriptListState extends State { + int selected = -1; + @override Widget build(BuildContext context) { return Column(children: rows(widget.scripts)); } List rows(List list) { + var primaryColor = Theme.of(context).primaryColor; + return List.generate(list.length, (index) { return InkWell( + splashColor: primaryColor.withOpacity(0.3), onDoubleTap: () async { String script = await (await ScriptManager.instance).getScript(list[index]); if (!context.mounted) { @@ -292,47 +296,13 @@ class _ScriptListState extends State { } }); }, - onTapDown: (details) { - showContextMenu(context, details.globalPosition, items: [ - PopupMenuItem( - height: 35, - child: const Text("编辑"), - onTap: () async { - String script = await (await ScriptManager.instance).getScript(list[index]); - if (!context.mounted) { - return; - } - Navigator.of(context) - .push(MaterialPageRoute( - builder: (context) => ScriptEdit(scriptItem: list[index], script: script))) - .then((value) { - if (value != null) { - setState(() {}); - } - }); - }), - PopupMenuItem(height: 35, child: const Text("分享"), onTap: () => export(list[index])), - PopupMenuItem( - height: 35, - child: list[index].enabled ? const Text("禁用") : const Text("启用"), - onTap: () { - list[index].enabled = !list[index].enabled; - setState(() {}); - }), - const PopupMenuDivider(), - PopupMenuItem( - height: 35, - child: const Text("删除"), - onTap: () async { - (await ScriptManager.instance).removeScript(index); - _refreshScript(); - setState(() {}); - if (context.mounted) FlutterToastr.show('删除成功', context); - }), - ]); - }, + onTapDown: (details) => showMenus(details, index), child: Container( - color: index.isEven ? Colors.grey.withOpacity(0.1) : null, + color: selected == index + ? primaryColor.withOpacity(0.8) + : index.isEven + ? Colors.grey.withOpacity(0.1) + : null, height: 45, padding: const EdgeInsets.all(5), child: Row( @@ -358,6 +328,52 @@ class _ScriptListState extends State { }); } + //点击菜单 + showMenus(TapDownDetails details, int index) { + setState(() { + selected = index; + }); + showContextMenu(context, details.globalPosition, items: [ + PopupMenuItem( + height: 35, + child: const Text("编辑"), + onTap: () async { + String script = await (await ScriptManager.instance).getScript(widget.scripts[index]); + if (!context.mounted) { + return; + } + Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => ScriptEdit(scriptItem: widget.scripts[index], script: script))) + .then((value) { + if (value != null) { + setState(() {}); + } + }); + }), + PopupMenuItem(height: 35, child: const Text("分享"), onTap: () => export(widget.scripts[index])), + PopupMenuItem( + height: 35, + child: widget.scripts[index].enabled ? const Text("禁用") : const Text("启用"), + onTap: () { + widget.scripts[index].enabled = !widget.scripts[index].enabled; + }), + const PopupMenuDivider(), + PopupMenuItem( + height: 35, + child: const Text("删除"), + onTap: () async { + (await ScriptManager.instance).removeScript(index); + _refreshScript(); + if (context.mounted) FlutterToastr.show('删除成功', context); + }), + ]).then((value) { + setState(() { + selected = -1; + }); + }); + } + //导出js export(ScriptItem item) async { //文件名称 diff --git a/pubspec.lock b/pubspec.lock index 04cd7eb..fb9530a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -267,10 +267,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: ad76540d21c066228ee3f9d1dad64a9f7e46530e8bb7c85011a88bc1fd874bc5 + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "3.0.1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -337,6 +337,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.18.1" js: dependency: transitive description: @@ -353,6 +361,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.8.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7e108028e3d258667d079986da8c0bc32da4cb57431c2af03b1dc1038621a9dc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.0.13" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" linked_scroll_controller: dependency: transitive description: @@ -397,18 +421,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.flutter-io.cn" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: @@ -734,14 +758,30 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.flutter-io.cn" + source: hosted + version: "13.0.0" web: dependency: transitive description: name: web - sha256: "14f1f70c51119012600c5f1f60ca68efda5a9b6077748163c6af2893ec5df8fc" + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.1-beta" + version: "0.3.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" win32: dependency: transitive description: @@ -767,5 +807,5 @@ packages: source: hosted version: "1.0.3" sdks: - dart: ">=3.2.0-157.0.dev <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7aee169..06305a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.0 + flutter_lints: ^3.0.1 # The following section is specific to Flutter packages. flutter: