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);