diff --git a/android/app/src/main/kotlin/com/network/proxy/vpn/transport/protocol/TCPPacketFactory.kt b/android/app/src/main/kotlin/com/network/proxy/vpn/transport/protocol/TCPPacketFactory.kt index dcba660..a9183ae 100644 --- a/android/app/src/main/kotlin/com/network/proxy/vpn/transport/protocol/TCPPacketFactory.kt +++ b/android/app/src/main/kotlin/com/network/proxy/vpn/transport/protocol/TCPPacketFactory.kt @@ -245,6 +245,7 @@ object TCPPacketFactory { tcp.setIsACK(true) tcp.setIsFIN(true) + tcp.dataOffset = 5 tcp.options = null //窗口大小应为零 tcp.windowSize = 0 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bd7c882..25cc178 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,9 +41,9 @@ NSCameraUsageDescription - 扫描二维码 + Scan QR code NSPhotoLibraryUsageDescription - 请求重写选择文件 + Access to Photo Library UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/ios/Runner/en.lproj/InfoPlist.strings b/ios/Runner/en.lproj/InfoPlist.strings index 9485d55..b46b6a0 100644 --- a/ios/Runner/en.lproj/InfoPlist.strings +++ b/ios/Runner/en.lproj/InfoPlist.strings @@ -1,2 +1,3 @@ "NSCameraUsageDescription"="Scan QR code"; -"NSPhotoLibraryUsageDescription"="Request to rewrite selection file"; +"NSPhotoLibraryUsageDescription"="Access to Photo Library"; +"PhotoLibraryAddUsageDescription"= "Save image to Photo Library"; diff --git a/ios/Runner/zh-Hans.lproj/InfoPlist.strings b/ios/Runner/zh-Hans.lproj/InfoPlist.strings index d8f9439..f3cf53e 100644 --- a/ios/Runner/zh-Hans.lproj/InfoPlist.strings +++ b/ios/Runner/zh-Hans.lproj/InfoPlist.strings @@ -1,2 +1,3 @@ "NSCameraUsageDescription"="扫描二维码"; -"NSPhotoLibraryUsageDescription"="请求重写选择文件"; +"NSPhotoLibraryUsageDescription"="访问相册"; +"PhotoLibraryAddUsageDescription"= "保存图片到相册"; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fded419..ec8dafc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -284,7 +284,14 @@ "gray" : "Gray", "underline" : "Underline", - "requestBlock": "Request Block" - + "requestBlock": "Request Block", + "other": "Other", + "qrCode": "QR Code", + "scanQrCode": "Scan QR Code", + "generateQRcode": "Generate QR Code", + "saveImage": "Save Image", + "selectImage": "Select Image", + "inputContent": "Input Content", + "errorCorrectLevel": "Error Correct Level" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 568ff7b..2cf73a6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -283,5 +283,14 @@ "gray": "灰色", "underline": "下划线", - "requestBlock": "请求屏蔽" + "requestBlock": "请求屏蔽", + + "other": "其他", + "qrCode": "二维码", + "generateQRcode": "生成二维码", + "scanQrCode": "扫描二维码", + "saveImage": "保存图片", + "selectImage": "选择图片", + "inputContent": "输入内容", + "errorCorrectLevel": "纠错等级" } \ No newline at end of file diff --git a/lib/network/util/byte_buf.dart b/lib/network/util/byte_buf.dart index a65ad64..f44df52 100644 --- a/lib/network/util/byte_buf.dart +++ b/lib/network/util/byte_buf.dart @@ -28,7 +28,7 @@ class ByteBuf { _buffer = Uint8List.fromList(bytes); writerIndex = bytes.length; } else { - _buffer = Uint8List(256); // Initial buffer size + _buffer = Uint8List(0); // Initial buffer size } } @@ -47,9 +47,10 @@ class ByteBuf { writerIndex = 0; } + ///释放已读的空间 void clearRead() { if (readerIndex > 0) { - _buffer.setRange(0, writerIndex - readerIndex, _buffer, readerIndex); + _buffer = Uint8List.sublistView(_buffer, readerIndex, writerIndex); writerIndex -= readerIndex; readerIndex = 0; } @@ -62,18 +63,12 @@ class ByteBuf { Uint8List readAvailableBytes() => readBytes(readableBytes()); Uint8List readBytes(int length) { - if (readerIndex + length > writerIndex) { - throw Exception("Not enough readable bytes"); - } Uint8List result = Uint8List.sublistView(_buffer, readerIndex, readerIndex + length); readerIndex += length; return result; } void skipBytes(int length) { - if (readerIndex + length > writerIndex) { - throw Exception("Not enough readable bytes"); - } readerIndex += length; } @@ -89,9 +84,9 @@ class ByteBuf { int readInt() { int value = (_buffer[readerIndex] << 24) | - (_buffer[readerIndex + 1] << 16) | - (_buffer[readerIndex + 2] << 8) | - _buffer[readerIndex + 3]; + (_buffer[readerIndex + 1] << 16) | + (_buffer[readerIndex + 2] << 8) | + _buffer[readerIndex + 3]; readerIndex += 4; return value; } @@ -102,6 +97,7 @@ class ByteBuf { if (len > readableBytes()) { throw Exception("Insufficient data"); } + writerIndex = readerIndex + len; } @@ -115,7 +111,7 @@ class ByteBuf { void _ensureCapacity(int required) { if (_buffer.length < required) { - int newSize = _buffer.length * 2; + int newSize = _buffer.length <= 1 ? required : _buffer.length * 2; while (newSize < required) { newSize *= 2; } @@ -124,4 +120,4 @@ class ByteBuf { _buffer = newBuffer; } } -} \ No newline at end of file +} diff --git a/lib/network/util/cert/x509.dart b/lib/network/util/cert/x509.dart index 8f15533..f6eef9e 100644 --- a/lib/network/util/cert/x509.dart +++ b/lib/network/util/cert/x509.dart @@ -1,9 +1,11 @@ // ignore_for_file: constant_identifier_names, depend_on_referenced_packages import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'package:basic_utils/basic_utils.dart'; +import 'package:crypto/crypto.dart'; import 'package:network_proxy/network/util/cert/extension.dart'; import 'package:network_proxy/network/util/cert/key_usage.dart' as x509; import 'package:pointycastle/asn1/unsupported_object_identifier_exception.dart'; @@ -13,6 +15,25 @@ import 'basic_constraints.dart'; /// @author wanghongen /// 2023/7/26 +void main() { + var caPem = File('assets/certs/ca.crt').readAsStringSync(); + var certPath = 'assets/certs/ca.crt'; + //生成 公钥和私钥 + var caRoot = X509Utils.x509CertificateFromPem(caPem); + var subject = caRoot.subject; + // Add Issuer + var issuerSeq = ASN1Sequence(); + for (var k in subject.keys) { + var s = X509Generate._identifier(k, subject[k]!); + issuerSeq.add(s); + } + var d = X509Generate.x509NameHashOld(issuerSeq); + + //16进制 + print(d); + print(d.toRadixString(16).padLeft(8, '0')); +} + class X509Generate { static const String BEGIN_CERT = '-----BEGIN CERTIFICATE-----'; static const String END_CERT = '-----END CERTIFICATE-----'; @@ -22,6 +43,14 @@ class X509Generate { static const String SERIAL_NUMBER = "2.5.4.5"; static const String DN_QUALIFIER = "2.5.4.46"; + static int x509NameHashOld(ASN1Object subject) { + // Convert ASN1Object to DER encoded byte array + final derEncoded = subject.encode(); + + var convert = md5.convert(derEncoded!); + return convert.bytes[0] << 24 | convert.bytes[1] << 16 | convert.bytes[2] << 8 | convert.bytes[3]; + } + /// /// Generates a self signed certificate /// diff --git a/lib/network/util/lists.dart b/lib/network/util/lists.dart index 1ac4001..c5c6705 100644 --- a/lib/network/util/lists.dart +++ b/lib/network/util/lists.dart @@ -18,3 +18,10 @@ dynamic getFirstElement(List? list) { List convertList(List list) { return list.map((e) => e as T).toList(); } + +class Lists { + //转换指定类型 + static List convertList(List list) { + return list.map((e) => e as T).toList(); + } +} diff --git a/lib/ui/component/cert_hash.dart b/lib/ui/component/cert_hash.dart new file mode 100644 index 0000000..ea29f90 --- /dev/null +++ b/lib/ui/component/cert_hash.dart @@ -0,0 +1,89 @@ +/* + * Copyright 2024 Hongen Wang All rights reserved. + * + * 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 + * + * https://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:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; + +///证书哈希名称查看 +///@author Hongen Wang +class CertHashWidget extends StatefulWidget { + const CertHashWidget({super.key}); + + @override + State createState() { + return _CertHashWidgetState(); + } +} + +class _CertHashWidgetState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("系统证书名称", style: TextStyle(fontSize: 16)), centerTitle: true), + resizeToAvoidBottomInset: false, + body: ListView(children: [ + Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + //选择文件 + ElevatedButton.icon( + onPressed: () async { + FilePickerResult? result = + await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['js']); + if (result != null) { + // File file = File(result.files.single.path!); + // String content = await file.readAsString(); + setState(() {}); + } + }, + icon: const Icon(Icons.folder_open), + label: const Text("File")), + const SizedBox(width: 15), + FilledButton.icon( + onPressed: () async { + //失去焦点 + FocusScope.of(context).requestFocus(FocusNode()); + }, + icon: const Icon(Icons.play_arrow_rounded), + label: const Text("Run")) + ]), + const SizedBox(width: 10), + Container( + padding: const EdgeInsets.all(10), + height: 320, + child: TextField(maxLines: 100, decoration: decoration('证书文件内容'))), + TextButton(onPressed: () {}, child: const Text("Output:", style: TextStyle(fontSize: 16))), + Expanded( + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + height: 150, + child: TextField(maxLines: 50, decoration: decoration('安卓系统证书Hash 名称')))), + ])); + } + + InputDecoration decoration(String label, {String? hintText}) { + Color color = Theme.of(context).colorScheme.primary; + return InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: label, + hintText: hintText, + hintStyle: TextStyle(color: Colors.grey.shade500), + border: OutlineInputBorder(borderSide: BorderSide(width: 0.8, color: color)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(width: 1.5, color: color)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(width: 2, color: color))); + } +} diff --git a/lib/ui/component/js_run.dart b/lib/ui/component/js_run.dart index 32a63f0..9eabf1b 100644 --- a/lib/ui/component/js_run.dart +++ b/lib/ui/component/js_run.dart @@ -71,7 +71,7 @@ class _JavaScriptState extends State { setState(() {}); } }, - icon: const Icon(Icons.file_download_sharp), + icon: const Icon(Icons.folder_open), label: const Text("File")), const SizedBox(width: 15), FilledButton.icon( diff --git a/lib/ui/component/multi_window.dart b/lib/ui/component/multi_window.dart index 463a212..1d8a2e4 100644 --- a/lib/ui/component/multi_window.dart +++ b/lib/ui/component/multi_window.dart @@ -9,10 +9,12 @@ import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/network/components/script_manager.dart'; import 'package:network_proxy/network/http/http.dart'; +import 'package:network_proxy/network/util/lists.dart'; import 'package:network_proxy/network/util/logger.dart'; import 'package:network_proxy/ui/component/device.dart'; import 'package:network_proxy/ui/component/encoder.dart'; import 'package:network_proxy/ui/component/js_run.dart'; +import 'package:network_proxy/ui/component/qr_code.dart'; import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/ui/content/body.dart'; import 'package:network_proxy/ui/desktop/request/request_editor.dart'; @@ -59,6 +61,10 @@ Widget multiWindow(int windowId, Map argument) { RequestRewrites.instance, (data) => RequestRewriteWidget(windowId: windowId, requestRewrites: data)); } + if (argument['name'] == 'QrCodeWidget') { + return QrCodeWidget(windowId: windowId); + } + //脚本日志 if (argument['name'] == 'ScriptConsoleWidget') { return ScriptConsoleWidget(windowId: windowId); @@ -96,6 +102,24 @@ class MultiWindow { }); } + static Future openWindow(String title, String widgetName) async { + var ratio = 1.0; + if (Platform.isWindows) { + ratio = WindowManager.instance.getDevicePixelRatio(); + } + registerMethodHandler(); + final window = await DesktopMultiWindow.createWindow(jsonEncode( + {'name': widgetName}, + )); + window.setTitle(title); + window + ..setFrame(const Offset(50, -10) & Size(800 * ratio, 680 * ratio)) + ..center(); + window.show(); + + return window; + } + static bool _refreshRewrite = false; static Future _handleRefreshRewrite(Operation operation, Map arguments) async { @@ -172,9 +196,11 @@ void registerMethodHandler() { } if (call.method == 'openFile') { - XTypeGroup typeGroup = XTypeGroup( - extensions: [call.arguments], - uniformTypeIdentifiers: Platform.isMacOS ? const ['public.item'] : null); + List extensions = + call.arguments is List ? Lists.convertList(call.arguments) : [call.arguments]; + + XTypeGroup typeGroup = + XTypeGroup(extensions: extensions, uniformTypeIdentifiers: Platform.isMacOS ? const ['public.item'] : null); final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); if (Platform.isWindows) windowManager.blur(); return file?.path; diff --git a/lib/ui/component/qr_code.dart b/lib/ui/component/qr_code.dart new file mode 100644 index 0000000..1e21fd1 --- /dev/null +++ b/lib/ui/component/qr_code.dart @@ -0,0 +1,308 @@ +/* + * Copyright 2024 Hongen Wang All rights reserved. + * + * 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 + * + * https://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:io'; +import 'dart:ui' as ui; + +import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:easy_permission/easy_permission.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:image_pickers/image_pickers.dart'; +import 'package:network_proxy/ui/component/state_component.dart'; +import 'package:network_proxy/utils/platform.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:qrscan/qrscan.dart' as scanner; +import 'package:qr/qr.dart'; + +///二维码 +///@author Hongen Wang +class QrCodeWidget extends StatefulWidget { + final int? windowId; + + const QrCodeWidget({super.key, this.windowId}); + + @override + State createState() { + return _QrCodeWidgetState(); + } +} + +class _QrCodeWidgetState extends State with SingleTickerProviderStateMixin { + int errorCorrectLevel = QrErrorCorrectLevel.M; + String data = ""; + String? decodeData; + QrCode? qrCode; + GlobalKey repaintKey = GlobalKey(); + + late TabController tabController; + late List tabs = [ + Tab(text: 'Encode'), + Tab(text: 'Decode'), + ]; + + AppLocalizations get localizations => AppLocalizations.of(context)!; + + @override + void initState() { + super.initState(); + + tabController = TabController(initialIndex: 0, length: tabs.length, vsync: this); + } + + @override + void dispose() { + tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + tabs = [ + Tab(text: localizations.encode), + Tab(text: localizations.decode), + ]; + + return Scaffold( + appBar: AppBar( + title: Text(localizations.qrCode, style: TextStyle(fontSize: 16)), + centerTitle: true, + bottom: TabBar(tabs: tabs, controller: tabController)), + resizeToAvoidBottomInset: false, + body: TabBarView( + controller: tabController, + children: [KeepAliveWrapper(child: qrCodeEncode()), KeepAliveWrapper(child: qrCodeDecode())], + )); + } + + //qrCode解码 + Widget qrCodeDecode() { + return ListView(children: [ + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 10), + FilledButton.icon( + onPressed: () async { + String? path = await selectImage(); + print(path); + if (path == null) return; + }, + icon: const Icon(Icons.photo, size: 18), + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)))), + label: Text(localizations.selectImage)), + if (Platforms.isMobile()) + FilledButton.icon( + onPressed: () async { + String scanRes; + if (Platform.isAndroid) { + await EasyPermission.requestPermissions([PermissionType.CAMERA]); + scanRes = await scanner.scan() ?? "-1"; + } else { + scanRes = + await FlutterBarcodeScanner.scanBarcode("#ff6666", localizations.cancel, true, ScanMode.QR); + } + + if (scanRes == "-1") { + if (mounted) FlutterToastr.show(localizations.decodeFail, context, duration: 2); + return; + } + setState(() { + decodeData = scanRes; + }); + }, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)))), + icon: const Icon(Icons.qr_code_scanner_outlined, size: 18), + label: Text(localizations.scanQrCode, style: TextStyle(fontSize: 14))), + const SizedBox(width: 20), + ], + ), + const SizedBox(height: 20), + if (decodeData != null) + Container( + padding: const EdgeInsets.all(10), + height: 300, + child: Column(children: [ + TextFormField( + initialValue: decodeData, + maxLines: 7, + minLines: 7, + readOnly: true, + decoration: decoration(localizations.encodeResult), + ), + SizedBox(height: 8), + TextButton.icon( + icon: const Icon(Icons.copy_rounded), + onPressed: () { + if (decodeData == null) return; + Clipboard.setData(ClipboardData(text: decodeData!)); + FlutterToastr.show(localizations.copied, context); + }, + label: Text(localizations.copy), + ), + ])), + SizedBox(height: 10), + ]); + } + + //生成二维码 + Widget qrCodeEncode() { + return ListView(children: [ + Container( + padding: const EdgeInsets.all(10), + height: 160, + child: TextField( + maxLines: 8, + onTapOutside: (event) => FocusManager.instance.primaryFocus?.unfocus(), + onChanged: (value) { + data = value; + }, + decoration: decoration(localizations.inputContent))), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 10), + Row(children: [ + Text("${localizations.errorCorrectLevel}: "), + DropdownButton( + value: errorCorrectLevel, + items: QrErrorCorrectLevel.levels + .map((e) => DropdownMenuItem(value: e, child: Text(QrErrorCorrectLevel.getName(e)))) + .toList(), + onChanged: (value) { + setState(() { + errorCorrectLevel = value!; + }); + }), + ]), + const SizedBox(width: 20), + FilledButton.icon( + onPressed: () { + setState(() { + qrCode = QrCode.fromData(data: data, errorCorrectLevel: errorCorrectLevel); + }); + }, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)))), + icon: const Icon(Icons.qr_code, size: 18), + label: Text(localizations.generateQRcode, style: TextStyle(fontSize: 14))), + const SizedBox(width: 20), + ], + ), + const SizedBox(height: 10), + if (qrCode != null) + Column( + children: [ + Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + TextButton.icon( + onPressed: () async { + await saveImage(); + }, + icon: const Icon(Icons.download_rounded), + label: Text(localizations.saveImage)), + SizedBox(width: 20), + ]), + SizedBox(height: 5), + RepaintBoundary( + key: repaintKey, + child: SizedBox( + height: 300, + width: 300, + child: Center(child: QrImageView.withQr(qr: qrCode!, backgroundColor: Colors.white, size: 300)))), + ], + ), + SizedBox(height: 15), + ]); + } + + //选择照片 + Future selectImage() async { + if (Platforms.isMobile()) { + List listImagePaths = await ImagePickers.pickerPaths(showCamera: true); + if (listImagePaths.isEmpty) return null; + return listImagePaths[0].path; + } + + if (Platforms.isDesktop()) { + String? file = await DesktopMultiWindow.invokeMethod(0, 'openFile', ['jpg', 'png', 'jpeg']); + WindowController.fromWindowId(widget.windowId!).show(); + return file; + } + + return null; + } + + //保存相册 + saveImage() async { + if (qrCode == null) { + return; + } + + if (Platforms.isMobile()) { + var imageBytes = await toImageBytes(qrCode!); + String? path = await ImagePickers.saveByteDataImageToGallery(imageBytes); + if (path != null && mounted) { + FlutterToastr.show(localizations.saveSuccess, context, duration: 2, rootNavigator: true); + } + return; + } + + if (Platforms.isDesktop()) { + String? path = await DesktopMultiWindow.invokeMethod(0, 'getSaveLocation', "qrcode.png"); + WindowController.fromWindowId(widget.windowId!).show(); + if (path == null) return; + + var imageBytes = await toImageBytes(qrCode!); + await File(path).writeAsBytes(imageBytes); + if (mounted) { + FlutterToastr.show(localizations.saveSuccess, context, duration: 2); + } + } + } + + Future toImageBytes(QrCode qrCode) async { + RenderRepaintBoundary? boundary = repaintKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; + if (boundary == null) { + return Uint8List(0); + } + ui.Image image = (await boundary.toImage()); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + + return byteData!.buffer.asUint8List(); + } + + InputDecoration decoration(String label, {String? hintText}) { + Color color = Theme.of(context).colorScheme.primary; + return InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: label, + hintText: hintText, + hintStyle: TextStyle(color: Colors.grey.shade500), + border: OutlineInputBorder(borderSide: BorderSide(width: 0.8, color: color)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(width: 1.3, color: color)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(width: 2, color: color))); + } +} diff --git a/lib/ui/component/toolbox.dart b/lib/ui/component/toolbox.dart index dbedad7..b92667d 100644 --- a/lib/ui/component/toolbox.dart +++ b/lib/ui/component/toolbox.dart @@ -8,6 +8,7 @@ import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/ui/component/encoder.dart'; import 'package:network_proxy/ui/component/js_run.dart'; import 'package:network_proxy/ui/component/multi_window.dart'; +import 'package:network_proxy/ui/component/qr_code.dart'; import 'package:network_proxy/ui/mobile/request/request_editor.dart'; import 'package:network_proxy/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; @@ -70,7 +71,7 @@ class _ToolboxState extends State { }, child: Container( padding: const EdgeInsets.all(10), - child: const Column(children: [Icon(Icons.javascript), Text("JavaScript")]), + child: const Column(children: [Icon(Icons.javascript), SizedBox(height: 3), Text("JavaScript")]), )), ]), const Divider(thickness: 0.3), @@ -81,31 +82,56 @@ class _ToolboxState extends State { onTap: () => encodeWindow(EncoderType.url, context), child: Container( padding: const EdgeInsets.all(10), - child: const Column(children: [Icon(Icons.link), Text(' URL')]), + child: const Column(children: [Icon(Icons.link), SizedBox(height: 3), Text(' URL')]), )), const SizedBox(width: 10), InkWell( onTap: () => encodeWindow(EncoderType.base64, context), child: Container( padding: const EdgeInsets.all(10), - child: const Column(children: [Icon(Icons.currency_bitcoin), Text('Base64')]), + child: const Column(children: [Icon(Icons.currency_bitcoin), SizedBox(height: 3), Text('Base64')]), )), const SizedBox(width: 15), InkWell( onTap: () => encodeWindow(EncoderType.unicode, context), child: Container( padding: const EdgeInsets.all(10), - child: const Column(children: [Icon(Icons.format_underline), Text('Unicode')]), + child: const Column(children: [Icon(Icons.format_underline), SizedBox(height: 3), Text('Unicode')]), )), const SizedBox(width: 15), InkWell( onTap: () => encodeWindow(EncoderType.md5, context), child: Container( padding: const EdgeInsets.all(10), - child: const Column(children: [Icon(Icons.enhanced_encryption), Text('MD5')]), + child: const Column(children: [Icon(Icons.enhanced_encryption), SizedBox(height: 3), Text('MD5')]), )), ], - ) + ), + const Divider(thickness: 0.3), + Text(localizations.other, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + Row( + children: [ + // IconText( + // onTap: () async { + // if (Platforms.isMobile()) { + // Navigator.of(context).push(MaterialPageRoute(builder: (context) => const CertHashWidget())); + // return; + // } + // }, + // icon: Icons.near_me, + // text: "证书Hash名称"), + IconText( + onTap: () async { + if (Platforms.isMobile()) { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QrCodeWidget())); + return; + } + MultiWindow.openWindow(localizations.qrCode, 'QrCodeWidget'); + }, + icon: Icons.qr_code_2, + text: localizations.qrCode), + ], + ), ], ), ); @@ -134,3 +160,23 @@ class _ToolboxState extends State { ..show(); } } + +class IconText extends StatelessWidget { + final IconData icon; + final String text; + + /// Called when the user taps this part of the material. + final GestureTapCallback? onTap; + + const IconText({super.key, required this.icon, required this.text, this.onTap}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(10), + child: Column(children: [Icon(icon), SizedBox(height: 3), Text(text)]), + )); + } +} diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index 5b87041..8f7bcf3 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -181,6 +181,8 @@ class MobileHomeState extends State implements EventListener, Li child: Theme( data: Theme.of(context).copyWith(splashColor: Colors.transparent), child: BottomNavigationBar( + selectedIconTheme: const IconThemeData(size: 27), + unselectedIconTheme: const IconThemeData(size: 27), selectedFontSize: 0, items: [ BottomNavigationBarItem( @@ -409,7 +411,7 @@ class RequestPageState extends State { try { var response = await HttpClients.get("http://${remoteDevice.value.host}:${remoteDevice.value.port}/ping") - .timeout(const Duration(seconds: 1)); + .timeout(const Duration(seconds: 3)); if (response.bodyAsString == "pong") { retry = 0; return; @@ -418,7 +420,7 @@ class RequestPageState extends State { retry++; } - if (retry > 5) { + if (retry > 3) { retry = 0; if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( diff --git a/lib/ui/mobile/request/request_editor.dart b/lib/ui/mobile/request/request_editor.dart index fd3b039..f455382 100644 --- a/lib/ui/mobile/request/request_editor.dart +++ b/lib/ui/mobile/request/request_editor.dart @@ -581,7 +581,7 @@ class KeyValState extends State { ], ), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: Text(localizations.cancel)), + TextButton(onPressed: () => Navigator.pop(ctx), child: Text(localizations.cancel)), TextButton( onPressed: () { setState(() { @@ -589,7 +589,7 @@ class KeyValState extends State { keyVal.value = val; }); notifierChange(); - Navigator.pop(context); + Navigator.pop(ctx); }, child: Text(localizations.modify)), ], @@ -605,12 +605,12 @@ class KeyValState extends State { return AlertDialog( title: Text(localizations.deleteHeaderConfirm, style: const TextStyle(fontSize: 18)), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: Text(localizations.cancel)), + TextButton(onPressed: () => Navigator.pop(ctx), child: Text(localizations.cancel)), TextButton( onPressed: () { setState(() => _params.remove(keyVal)); notifierChange(); - Navigator.pop(context); + Navigator.pop(ctx); }, child: Text(localizations.delete)), ], diff --git a/pubspec.yaml b/pubspec.yaml index bb0a666..5d9ac00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,12 +26,12 @@ dependencies: path_provider: ^2.1.4 url_launcher: ^6.3.1 proxy_manager: ^0.0.3 - qr_flutter: ^4.1.0 easy_permission: ^1.0.0 + qr_flutter: ^4.1.0 qrscan: ^0.3.3 flutter_barcode_scanner: ^2.0.0 flutter_toastr: ^1.0.3 - share_plus: ^10.0.2 + share_plus: ^10.0.3 brotli: ^0.6.0 flutter_js: ^0.8.1 flutter_code_editor: ^0.3.2 @@ -41,11 +41,12 @@ dependencies: win32audio: ^1.3.1 device_info_plus: ^10.1.2 shared_preferences: ^2.3.2 + image_pickers: ^2.0.5+2 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 # The following section is specific to Flutter packages. flutter: diff --git a/test/cert_test.dart b/test/cert_test.dart index 15919dd..479671b 100644 --- a/test/cert_test.dart +++ b/test/cert_test.dart @@ -61,8 +61,8 @@ NQIDAQAB // ExtendedKeyUsage.SERVER_AUTH // ]); - var generatePkcs12 = Pkcs12Utils.generatePkcs12(readAsString, [crt], password: '123'); - await File('/Users/wanghongen/Downloads/server.p12').writeAsBytes(generatePkcs12); + // var generatePkcs12 = Pkcs12Utils.generatePkcs12(readAsString, [crt], password: '123'); + // await File('/Users/wanghongen/Downloads/server.p12').writeAsBytes(generatePkcs12); } /// 生成证书 diff --git a/test/x509_test.dart b/test/x509_test.dart index e15a3b7..96e49b3 100644 --- a/test/x509_test.dart +++ b/test/x509_test.dart @@ -1,19 +1,32 @@ +import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'package:basic_utils/basic_utils.dart'; +import 'package:crypto/crypto.dart'; import 'package:network_proxy/network/util/cert/basic_constraints.dart'; import 'package:pointycastle/pointycastle.dart'; -void main() { - encoding(); +void main() async { + // encoding(); // Add ext key usage 2.5.29.37 // // Add key usage 2.5.29.15 // var keyUsage = [KeyUsage.KEY_CERT_SIGN, KeyUsage.CRL_SIGN]; // // var encode = keyUsageSequence(keyUsage)?.encode(); // print(Int8List.view(encode!.buffer)); + + var caPem = await File('assets/certs/ca.crt').readAsString(); + var certPath = 'assets/certs/ca.crt'; + //生成 公钥和私钥 + var caRoot = X509Utils.x509CertificateFromPem(caPem); + print(caRoot.tbsCertificate.); + caRoot.subject = X509Utils.getSubject(caRoot.subject); } + +//获取证书 subject hash + void encoding() { var basicConstraints = BasicConstraints(isCA: true);