mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-05-20 16:15:47 +08:00
socks5 (#291)
This commit is contained in:
@@ -37,6 +37,9 @@ class Configuration {
|
||||
//代理忽略域名
|
||||
String proxyPassDomains = SystemProxy.proxyPassDomains;
|
||||
|
||||
//enabled socks5 proxy
|
||||
bool enableSocks5 = true;
|
||||
|
||||
//外部代理
|
||||
ProxyInfo? externalProxy;
|
||||
|
||||
@@ -84,6 +87,7 @@ class Configuration {
|
||||
enableSsl = config['enableSsl'] == true;
|
||||
startup = config['startup'] ?? Platforms.isDesktop();
|
||||
enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true);
|
||||
enableSocks5 = config['enableSocks5'] ?? true;
|
||||
proxyPassDomains = config['proxyPassDomains'] ?? SystemProxy.proxyPassDomains;
|
||||
historyCacheTime = config['historyCacheTime'] ?? 0;
|
||||
if (config['externalProxy'] != null) {
|
||||
@@ -136,6 +140,7 @@ class Configuration {
|
||||
'enableSsl': enableSsl,
|
||||
'startup': startup,
|
||||
'enableSystemProxy': enableSystemProxy,
|
||||
'enableSocks5': enableSocks5,
|
||||
'proxyPassDomains': proxyPassDomains,
|
||||
'externalProxy': externalProxy?.toJson(),
|
||||
'appWhitelist': appWhitelist,
|
||||
|
||||
@@ -107,7 +107,7 @@ class Channel {
|
||||
bool get isSsl => _socket is SecureSocket;
|
||||
|
||||
Future<void> write(Object obj) async {
|
||||
var data = pipeline._encoder.encode(obj);
|
||||
var data = pipeline.encoder.encode(obj);
|
||||
await writeBytes(data);
|
||||
}
|
||||
|
||||
@@ -251,18 +251,22 @@ class ChannelContext {
|
||||
}
|
||||
|
||||
class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
late Decoder _decoder;
|
||||
late Encoder _encoder;
|
||||
late Decoder decoder;
|
||||
late Encoder encoder;
|
||||
late ChannelHandler handler;
|
||||
|
||||
final ByteBuf buffer = ByteBuf();
|
||||
|
||||
handle(Decoder decoder, Encoder encoder, ChannelHandler handler) {
|
||||
_encoder = encoder;
|
||||
_decoder = decoder;
|
||||
this.encoder = encoder;
|
||||
this.decoder = decoder;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
channelHandle(Codec codec, ChannelHandler handler) {
|
||||
handle(codec, codec, handler);
|
||||
}
|
||||
|
||||
/// 监听
|
||||
void listen(Channel channel, ChannelContext channelContext) {
|
||||
buffer.clear();
|
||||
@@ -297,8 +301,8 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
/// 转发请求
|
||||
void relay(ChannelContext channelContext, Channel clientChannel, Channel remoteChannel) {
|
||||
var rawCodec = RawCodec();
|
||||
clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel));
|
||||
remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel));
|
||||
clientChannel.pipeline.channelHandle(rawCodec, RelayHandler(remoteChannel));
|
||||
remoteChannel.pipeline.channelHandle(rawCodec, RelayHandler(clientChannel));
|
||||
|
||||
var body = buffer.bytes;
|
||||
buffer.clear();
|
||||
@@ -326,7 +330,7 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
return;
|
||||
}
|
||||
|
||||
var decodeResult = _decoder.decode(channelContext, buffer);
|
||||
var decodeResult = decoder.decode(channelContext, buffer);
|
||||
if (!decodeResult.isDone) {
|
||||
return;
|
||||
}
|
||||
@@ -376,7 +380,8 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
//websocket协议
|
||||
if (data is HttpResponse && data.isWebSocket && remoteChannel != null) {
|
||||
data.request?.response = data;
|
||||
channelContext.host = channelContext.host?.copyWith(scheme: channel.isSsl ? HostAndPort.wssScheme : HostAndPort.wsScheme);
|
||||
channelContext.host =
|
||||
channelContext.host?.copyWith(scheme: channel.isSsl ? HostAndPort.wssScheme : HostAndPort.wsScheme);
|
||||
channelContext.currentRequest?.hostAndPort = channelContext.host;
|
||||
|
||||
logger.d("webSocket ${data.request?.hostAndPort}");
|
||||
@@ -385,8 +390,8 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
channelContext.listener?.onResponse(channelContext, data);
|
||||
|
||||
var rawCodec = RawCodec();
|
||||
channel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(remoteChannel, data));
|
||||
remoteChannel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(channel, data.request!));
|
||||
channel.pipeline.channelHandle(rawCodec, WebSocketChannelHandler(remoteChannel, data));
|
||||
remoteChannel.pipeline.channelHandle(rawCodec, WebSocketChannelHandler(channel, data.request!));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -408,10 +413,10 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
}
|
||||
}
|
||||
|
||||
class RawCodec extends Codec<dynamic> {
|
||||
class RawCodec extends Codec<Uint8List, List<int>> {
|
||||
@override
|
||||
DecoderResult<dynamic> decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) {
|
||||
var decoderResult = DecoderResult()..data = byteBuf.readAvailableBytes();
|
||||
DecoderResult<Uint8List> decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) {
|
||||
var decoderResult = DecoderResult<Uint8List>()..data = byteBuf.readAvailableBytes();
|
||||
return decoderResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -207,6 +207,12 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
}
|
||||
|
||||
HostAndPort remoteAddress = hostAndPort;
|
||||
|
||||
final ProxyInfo? socksProxy = channelContext.getAttribute(AttributeKeys.socks5Proxy);
|
||||
if (socksProxy != null) {
|
||||
remoteAddress = hostAndPort.copyWith(host: socksProxy.host, port: socksProxy.port!);
|
||||
}
|
||||
|
||||
for (var interceptor in interceptors) {
|
||||
remoteAddress = await interceptor.preConnect(remoteAddress);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -71,13 +70,13 @@ abstract interface class Encoder<T> {
|
||||
}
|
||||
|
||||
/// 编解码器
|
||||
abstract class Codec<T> implements Decoder<T>, Encoder<T> {
|
||||
abstract class Codec<D, E> implements Decoder<D>, Encoder<E> {
|
||||
static const int defaultMaxInitialLineLength = 1024000; // 1M
|
||||
static const int maxBodyLength = 4096000; // 4M
|
||||
}
|
||||
|
||||
/// http编解码
|
||||
abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
abstract class HttpCodec<T extends HttpMessage> implements Codec<T, T> {
|
||||
final HttpParse _httpParse = HttpParse();
|
||||
Http2Codec<T>? _h2Codec;
|
||||
State _state = State.readInitial;
|
||||
@@ -266,3 +265,33 @@ class HttpResponseCodec extends HttpCodec<HttpResponse> {
|
||||
buffer.addByte(HttpConstants.lf);
|
||||
}
|
||||
}
|
||||
|
||||
class HttpServerCodec extends Codec<HttpRequest, HttpResponse> {
|
||||
HttpRequestCodec requestCodec = HttpRequestCodec();
|
||||
HttpResponseCodec responseCodec = HttpResponseCodec();
|
||||
|
||||
@override
|
||||
DecoderResult<HttpRequest> decode(ChannelContext channelContext, ByteBuf byteBuf) {
|
||||
return requestCodec.decode(channelContext, byteBuf);
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> encode(HttpResponse data) {
|
||||
return responseCodec.encode(data);
|
||||
}
|
||||
}
|
||||
|
||||
class HttpClientCodec extends Codec<HttpResponse, HttpRequest> {
|
||||
HttpRequestCodec requestCodec = HttpRequestCodec();
|
||||
HttpResponseCodec responseCodec = HttpResponseCodec();
|
||||
|
||||
@override
|
||||
DecoderResult<HttpResponse> decode(ChannelContext channelContext, ByteBuf byteBuf) {
|
||||
return responseCodec.decode(channelContext, byteBuf);
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> encode(HttpRequest data) {
|
||||
return requestCodec.encode(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import 'package:proxypin/network/util/byte_buf.dart';
|
||||
import 'frame.dart';
|
||||
|
||||
/// http编解码
|
||||
abstract class Http2Codec<T extends HttpMessage> implements Codec<T> {
|
||||
abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
|
||||
static const maxFrameSize = 16384;
|
||||
|
||||
static final List<int> connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits;
|
||||
|
||||
@@ -32,7 +32,7 @@ class HttpClients {
|
||||
static Future<Channel> startConnect(
|
||||
HostAndPort hostAndPort, ChannelHandler handler, ChannelContext channelContext) async {
|
||||
var client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
|
||||
..initChannel((channel) => channel.pipeline.channelHandle(HttpClientCodec(), handler));
|
||||
|
||||
return client.connect(hostAndPort, channelContext);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ class HttpClients {
|
||||
static Future<Channel> proxyConnect(HostAndPort hostAndPort, ChannelHandler handler, ChannelContext channelContext,
|
||||
{ProxyInfo? proxyInfo}) async {
|
||||
var client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
|
||||
..initChannel((channel) => channel.pipeline.channelHandle(HttpClientCodec(), handler));
|
||||
|
||||
if (proxyInfo == null) {
|
||||
var proxyTypes = hostAndPort.isSsl() ? ProxyTypes.https : ProxyTypes.http;
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:proxypin/network/bin/configuration.dart';
|
||||
import 'package:proxypin/network/channel.dart';
|
||||
import 'package:proxypin/network/components/host_filter.dart';
|
||||
import 'package:proxypin/network/handler.dart';
|
||||
import 'package:proxypin/network/socksx/socks5.dart';
|
||||
import 'package:proxypin/network/util/attribute_keys.dart';
|
||||
import 'package:proxypin/network/util/crts.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
@@ -54,8 +55,8 @@ abstract class Network {
|
||||
/// 转发请求
|
||||
void relay(Channel clientChannel, Channel remoteChannel) {
|
||||
var rawCodec = RawCodec();
|
||||
clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel));
|
||||
remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel));
|
||||
clientChannel.pipeline.channelHandle(rawCodec, RelayHandler(remoteChannel));
|
||||
remoteChannel.pipeline.channelHandle(rawCodec, RelayHandler(clientChannel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +124,12 @@ class Server extends Network {
|
||||
return;
|
||||
}
|
||||
|
||||
//socks5
|
||||
if (configuration.enableSocks5 && Socks5.isSocks5(data) && channel.pipeline.handler is! SocksServerHandler) {
|
||||
channel.pipeline.channelHandle(
|
||||
RawCodec(), SocksServerHandler(channel.pipeline.decoder, channel.pipeline.encoder, channel.pipeline.handler));
|
||||
}
|
||||
|
||||
channel.pipeline.channelRead(channelContext, channel, data);
|
||||
}
|
||||
|
||||
|
||||
135
lib/network/socksx/socks5.dart
Normal file
135
lib/network/socksx/socks5.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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:typed_data';
|
||||
|
||||
import 'package:proxypin/network/channel.dart';
|
||||
import 'package:proxypin/network/http/codec.dart';
|
||||
import 'package:proxypin/network/util/attribute_keys.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
|
||||
import '../host_port.dart';
|
||||
|
||||
/// @author wanghongen
|
||||
class Socks5 {
|
||||
static const int version = 5;
|
||||
static const int methodNoAuth = 0;
|
||||
static const int methodNoAcceptable = 0xff;
|
||||
|
||||
static const int cmdConnect = 1;
|
||||
|
||||
static const int atypIpv4 = 1;
|
||||
|
||||
static const int repSuccess = 0;
|
||||
static const int repCommandNotSupported = 7;
|
||||
static const int repAddressTypeNotSupported = 8;
|
||||
|
||||
static const int repSocks5ServerAtypIpv4 = 0x01;
|
||||
static const int repSocks5ServerAtypDomain = 0x03;
|
||||
static const int repSocks5ServerAtypIpv6 = 0x04;
|
||||
|
||||
static bool isSocks5(Uint8List data) {
|
||||
return data.length > 2 && data[0] == version;
|
||||
}
|
||||
}
|
||||
|
||||
///Detects the version of the current SOCKS connection and initializes the pipeline with Socks5InitialRequestDecoder.
|
||||
class SocksServerHandler extends ChannelHandler<Uint8List> {
|
||||
late Decoder originalDecoder;
|
||||
late Encoder originalEncoder;
|
||||
final ChannelHandler originalHandler;
|
||||
|
||||
SocksState socksState = SocksState.init;
|
||||
|
||||
SocksServerHandler(this.originalDecoder, this.originalEncoder, this.originalHandler);
|
||||
|
||||
@override
|
||||
void channelRead(ChannelContext channelContext, Channel channel, Uint8List msg) async {
|
||||
int idx = 0;
|
||||
final int version = msg[idx++];
|
||||
if (version != Socks5.version) {
|
||||
await channel.writeBytes(Uint8List.fromList([Socks5.version, Socks5.methodNoAcceptable]));
|
||||
channel.pipeline.exceptionCaught(channelContext, channel, Exception('Unsupported SOCKS version: $version'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (socksState == SocksState.init) {
|
||||
//no auth
|
||||
await channel.writeBytes(Uint8List.fromList([Socks5.version, Socks5.methodNoAuth]));
|
||||
socksState = SocksState.connect;
|
||||
return;
|
||||
}
|
||||
|
||||
if (socksState == SocksState.connect) {
|
||||
final int cmd = msg[idx++];
|
||||
if (cmd != Socks5.cmdConnect) {
|
||||
var out = encodeCommandResponse(Socks5.repCommandNotSupported);
|
||||
await channel.writeBytes(out);
|
||||
channel.pipeline.exceptionCaught(channelContext, channel, Exception('Unsupported SOCKS cmd: $cmd'));
|
||||
return;
|
||||
}
|
||||
|
||||
//skip RSV
|
||||
idx++;
|
||||
|
||||
final int dstAddrType = msg[idx++];
|
||||
if (dstAddrType != Socks5.atypIpv4) {
|
||||
var out = encodeCommandResponse(Socks5.repAddressTypeNotSupported);
|
||||
await channel.writeBytes(out);
|
||||
channel.pipeline.exceptionCaught(channelContext, channel, Exception('Unsupported SOCKS atyp: $dstAddrType'));
|
||||
return;
|
||||
}
|
||||
|
||||
final host = '${msg[idx++]}.${msg[idx++]}.${msg[idx++]}.${msg[idx++]}';
|
||||
final int port = msg[idx++] << 8 | msg[idx++];
|
||||
final proxyInfo = ProxyInfo.of(host, port);
|
||||
|
||||
logger.d('Socks5 connect ${proxyInfo.host}:${proxyInfo.port}');
|
||||
channelContext.putAttribute(AttributeKeys.socks5Proxy, proxyInfo);
|
||||
|
||||
final out = encodeCommandResponse(Socks5.repSuccess, bndAddrType: Socks5.repSocks5ServerAtypIpv4);
|
||||
await channel.writeBytes(out);
|
||||
|
||||
channel.pipeline.handle(originalDecoder, originalEncoder, originalHandler);
|
||||
socksState = SocksState.connected;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List encodeCommandResponse(int status, {int bndAddrType = 0, String? bndAddr, int bndPort = 0}) {
|
||||
var out = BytesBuilder();
|
||||
out.addByte(Socks5.version);
|
||||
out.addByte(status);
|
||||
out.addByte(0x00); //RSV
|
||||
out.addByte(bndAddrType);
|
||||
|
||||
if (bndAddr != null) {
|
||||
out.add(Int8List.fromList(bndAddr.split('.').map((e) => int.parse(e)).toList()));
|
||||
} else {
|
||||
out.add(Int8List.fromList([0, 0, 0, 0]));
|
||||
}
|
||||
out.addByte(bndPort >> 8);
|
||||
out.addByte(bndPort & 0xff);
|
||||
return out.takeBytes();
|
||||
}
|
||||
}
|
||||
|
||||
enum SocksState {
|
||||
init,
|
||||
auth,
|
||||
connect,
|
||||
connected,
|
||||
}
|
||||
@@ -7,5 +7,6 @@ interface class AttributeKeys {
|
||||
static const String request = "REQUEST";
|
||||
static const String remote = "REMOTE";
|
||||
static const String proxyInfo = "PROXY_INFO";
|
||||
static const String socks5Proxy = "SOCKS5_PROXY";
|
||||
static const String processInfo = "PROCESS_INFO";
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import 'package:proxypin/network/components/manager/hosts_manager.dart';
|
||||
import 'package:proxypin/network/components/manager/request_block_manager.dart';
|
||||
import 'package:proxypin/network/util/system_proxy.dart';
|
||||
import 'package:proxypin/ui/component/multi_window.dart';
|
||||
import 'package:proxypin/ui/component/widgets.dart';
|
||||
import 'package:proxypin/ui/desktop/toolbar/setting/external_proxy.dart';
|
||||
import 'package:proxypin/ui/desktop/toolbar/setting/hosts.dart';
|
||||
import 'package:proxypin/ui/desktop/toolbar/setting/request_block.dart';
|
||||
@@ -180,6 +181,21 @@ class _ProxyMenuState extends State<_ProxyMenu> {
|
||||
const Divider(thickness: 0.3, height: 8),
|
||||
setSystemProxy(),
|
||||
const Divider(thickness: 0.3, height: 8),
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
child: Text("SOCKS5", style: const TextStyle(fontSize: 14)))),
|
||||
SwitchWidget(
|
||||
value: configuration.enableSocks5,
|
||||
scale: 0.75,
|
||||
onChanged: (val) {
|
||||
configuration.enableSocks5 = val;
|
||||
changed = true;
|
||||
}),
|
||||
SizedBox(width: 10)
|
||||
]),
|
||||
const Divider(thickness: 0.3, height: 8),
|
||||
const SizedBox(height: 3),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
@@ -225,22 +241,23 @@ class _ProxyMenuState extends State<_ProxyMenu> {
|
||||
///设置系统代理
|
||||
Widget setSystemProxy() {
|
||||
return Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
child: Text(localizations.systemProxy, style: const TextStyle(fontSize: 14))),
|
||||
Expanded(
|
||||
child: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
hoverColor: Colors.transparent,
|
||||
value: configuration.enableSystemProxy,
|
||||
onChanged: (val) {
|
||||
widget.proxyServer.setSystemProxyEnable(val);
|
||||
configuration.enableSystemProxy = val;
|
||||
setState(() {
|
||||
changed = true;
|
||||
});
|
||||
})))
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 15, right: 20),
|
||||
child: Text(localizations.systemProxy, style: const TextStyle(fontSize: 14)))),
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Switch(
|
||||
hoverColor: Colors.transparent,
|
||||
value: configuration.enableSystemProxy,
|
||||
onChanged: (val) {
|
||||
widget.proxyServer.setSystemProxyEnable(val);
|
||||
configuration.enableSystemProxy = val;
|
||||
setState(() {
|
||||
changed = true;
|
||||
});
|
||||
})),
|
||||
SizedBox(width: 10)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:proxypin/network/bin/configuration.dart';
|
||||
import 'package:proxypin/network/bin/server.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
import 'package:proxypin/ui/component/widgets.dart';
|
||||
@@ -25,6 +26,7 @@ class Preference extends StatefulWidget {
|
||||
|
||||
class _PreferenceState extends State<Preference> {
|
||||
late ProxyServer proxyServer;
|
||||
late Configuration configuration;
|
||||
late AppConfiguration appConfiguration;
|
||||
|
||||
final memoryCleanupController = TextEditingController();
|
||||
@@ -34,6 +36,7 @@ class _PreferenceState extends State<Preference> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
proxyServer = widget.proxyServer;
|
||||
configuration = widget.proxyServer.configuration;
|
||||
appConfiguration = widget.appConfiguration;
|
||||
|
||||
if (!memoryCleanupList.contains(appConfiguration.memoryCleanupThreshold)) {
|
||||
@@ -61,6 +64,15 @@ class _PreferenceState extends State<Preference> {
|
||||
proxyServer: proxyServer,
|
||||
title: '${localizations.proxy}${isEn ? ' ' : ''}${localizations.port}',
|
||||
textStyle: const TextStyle(fontSize: 16)),
|
||||
ListTile(
|
||||
title: Text("SOCKS5"),
|
||||
trailing: SwitchWidget(
|
||||
value: configuration.enableSocks5,
|
||||
scale: 0.8,
|
||||
onChanged: (value) {
|
||||
configuration.enableSocks5 = value;
|
||||
proxyServer.configuration.flushConfig();
|
||||
})),
|
||||
ListTile(
|
||||
title: Text(localizations.externalProxy),
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
|
||||
@@ -118,7 +118,7 @@ class _MobileScriptState extends State<MobileScript> {
|
||||
|
||||
//导入js
|
||||
import() async {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']);
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.any);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user