mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-05-10 00:44:12 +08:00
chunk编码
This commit is contained in:
@@ -17,7 +17,7 @@ void main() async {
|
||||
await windowManager.ensureInitialized();
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
minimumSize: const Size(930, 500),
|
||||
size: const Size(1200, 700),
|
||||
size: const Size(1080, 700),
|
||||
center: true,
|
||||
titleBarStyle: Platform.isMacOS ? TitleBarStyle.hidden : TitleBarStyle.normal);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
|
||||
@@ -82,7 +82,7 @@ class ProxyServer {
|
||||
_concatCommands([
|
||||
'networksetup -setwebproxy wi-fi $host $port',
|
||||
enableSsl ? 'networksetup -setsecurewebproxy wi-fi $host $port' : '',
|
||||
'networksetup -setproxybypassdomains wi-fi 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 127.0.0.1, localhost, *.local, timestamp.apple.com, sequoia.apple.com, seed-sequoia.siri.apple.com',
|
||||
'networksetup -setproxybypassdomains wi-fi 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 127.0.0.1, localhost, *.local, timestamp.apple.com, sequoia.apple.com, seed-sequoia.siri.apple.com, *.google.com, *.googleapis.com',
|
||||
])
|
||||
]);
|
||||
print('set proxyServer, exitCode: ${results.exitCode}, stdout: ${results.stdout}');
|
||||
|
||||
@@ -245,7 +245,7 @@ class Network {
|
||||
_channelInitializer.call(channel);
|
||||
channel.pipeline.channelActive(channel);
|
||||
socket.listen((data) => _onEvent(data, channel),
|
||||
onError: (error, trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace),
|
||||
onError: (error, StackTrace trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace),
|
||||
onDone: () => channel.pipeline.channelInactive(channel));
|
||||
return channel;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
@override
|
||||
void channelRead(Channel channel, HttpRequest msg) async {
|
||||
forward(channel, msg).catchError((error, trace) {
|
||||
channel.close();
|
||||
if (error is SocketException &&
|
||||
(error.message.contains("Failed host lookup") || error.message.contains("Connection timed out"))) {
|
||||
log.e("连接失败 ${error.message}");
|
||||
channel.close();
|
||||
return;
|
||||
}
|
||||
log.e("转发请求失败", error, trace);
|
||||
@@ -46,7 +46,6 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
super.channelInactive(channel);
|
||||
Channel? remoteChannel = channel.getAttribute(channel.id);
|
||||
remoteChannel?.close();
|
||||
}
|
||||
@@ -77,7 +76,7 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
}
|
||||
}
|
||||
|
||||
void _crtDownload(Channel channel, HttpRequest request) async{
|
||||
void _crtDownload(Channel channel, HttpRequest request) async {
|
||||
const String fileMimeType = 'application/x-x509-ca-cert';
|
||||
var body = await rootBundle.load('assets/certs/ca.crt');
|
||||
var response = HttpResponse(request.protocolVersion, HttpStatus.ok);
|
||||
@@ -145,6 +144,7 @@ class RelayHandler extends ChannelHandler<Object> {
|
||||
//发送给客户端
|
||||
remoteChannel.write(msg);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
remoteChannel.close();
|
||||
|
||||
94
lib/network/http/chunked_codec.dart
Normal file
94
lib/network/http/chunked_codec.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../../utils/num.dart';
|
||||
import 'codec.dart';
|
||||
|
||||
///一个ChunkedInput,它逐块获取数据,用于HTTP分块传输。
|
||||
/// 请确保您的HTTP响应标头包含传输编码:chunked。
|
||||
class ChunkedInput {
|
||||
final BytesBuilder _buffer = BytesBuilder();
|
||||
|
||||
///chunked编码 剩余未读取的chunk大小
|
||||
int _chunkReadableSize = 0;
|
||||
|
||||
int _offset = 0;
|
||||
ChunkedState _state = ChunkedState.readChunkSize;
|
||||
|
||||
///读取chunk
|
||||
ChunkedContent readChunked(Uint8List data) {
|
||||
_offset = 0;
|
||||
|
||||
while (_offset < data.length) {
|
||||
//读取chunk length
|
||||
if (_state == ChunkedState.readChunkSize) {
|
||||
_chunkReadableSize = _readChunkSize(data);
|
||||
|
||||
if (_chunkReadableSize == 0) {
|
||||
//chunked编码结束
|
||||
_state = ChunkedState.done;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_chunkReadableSize == -1) {
|
||||
continue;
|
||||
}
|
||||
_state = ChunkedState.readChunkedContent;
|
||||
}
|
||||
|
||||
//读取chunk内容
|
||||
if (_state == ChunkedState.readChunkedContent) {
|
||||
int end = min(data.length, _offset + _chunkReadableSize);
|
||||
_buffer.add(data.sublist(_offset, end));
|
||||
|
||||
//可读大小
|
||||
_chunkReadableSize -= (end - _offset);
|
||||
_offset = end;
|
||||
if (_chunkReadableSize == 0) {
|
||||
_state = ChunkedState.readChunkSize;
|
||||
_offset += 2; //内容结尾\r\n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ChunkedContent(_state, _buffer.toBytes());
|
||||
}
|
||||
|
||||
int _readChunkSize(Uint8List data) {
|
||||
var line = parseLine(data);
|
||||
if (line.isEmpty) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return hexToInt(String.fromCharCodes(line));
|
||||
}
|
||||
|
||||
Uint8List parseLine(Uint8List data) {
|
||||
if (_offset >= data.length) {
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
for (int i = _offset; i < data.length; i++) {
|
||||
if (_isLineEnd(data, i)) {
|
||||
var line = data.sublist(_offset, i - 1);
|
||||
_offset = i + 1;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
throw Exception('Invalid chunked encoding line: ${String.fromCharCodes(data)}');
|
||||
}
|
||||
|
||||
bool _isLineEnd(List<int> data, int index) {
|
||||
return data[index] == HttpConstants.lf && data[index - 1] == HttpConstants.cr;
|
||||
}
|
||||
}
|
||||
|
||||
enum ChunkedState { readChunkSize, readChunkedContent, done }
|
||||
|
||||
class ChunkedContent {
|
||||
final ChunkedState state;
|
||||
final Uint8List content;
|
||||
|
||||
ChunkedContent(this.state, this.content);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network/utils/num.dart';
|
||||
import 'package:network/network/http/chunked_codec.dart';
|
||||
|
||||
import '../channel.dart';
|
||||
import '../../utils/compress.dart';
|
||||
import '../channel.dart';
|
||||
import 'http.dart';
|
||||
import 'http_headers.dart';
|
||||
|
||||
@@ -25,9 +25,8 @@ class HttpConstants {
|
||||
enum State {
|
||||
readInitial,
|
||||
readHeader,
|
||||
readVariableLengthContent,
|
||||
readFixedLengthContent,
|
||||
readChunkedContent,
|
||||
readChunked,
|
||||
done,
|
||||
}
|
||||
|
||||
@@ -36,19 +35,20 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
final HttpParse _httpParse = HttpParse();
|
||||
State _state = State.readInitial;
|
||||
|
||||
///chunked编码 剩余未读取的chunk大小
|
||||
int _chunkReadableSize = 0;
|
||||
|
||||
late T message;
|
||||
|
||||
final BytesBuilder _bodyBuffer = BytesBuilder();
|
||||
ChunkedInput? chunkedInput;
|
||||
final BytesBuilder buffer = BytesBuilder();
|
||||
|
||||
T createMessage(List<String> reqLine);
|
||||
|
||||
@override
|
||||
T? decode(Uint8List data) {
|
||||
_httpParse.index = 0;
|
||||
|
||||
//请求行
|
||||
if (_state == State.readInitial) {
|
||||
init();
|
||||
var initialLine = _readInitialLine(data);
|
||||
message = createMessage(initialLine);
|
||||
_state = State.readHeader;
|
||||
@@ -57,28 +57,33 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
//请求头
|
||||
if (_state == State.readHeader) {
|
||||
_readHeader(data, message);
|
||||
_state = message.headers.isChunked ? State.readVariableLengthContent : State.readFixedLengthContent;
|
||||
_state = message.headers.isChunked ? State.readChunked : State.readFixedLengthContent;
|
||||
}
|
||||
|
||||
//chunked编码
|
||||
if (_state == State.readChunkedContent) {
|
||||
_bodyBuffer.add(data.sublist(0, min(_chunkReadableSize, data.length)));
|
||||
if (data.length < _chunkReadableSize) {
|
||||
_chunkReadableSize = _chunkReadableSize - data.length;
|
||||
return null;
|
||||
//固定长度请求体
|
||||
if (_state == State.readFixedLengthContent) {
|
||||
if (message.contentLength > 0) {
|
||||
buffer.add(data.sublist(_httpParse.index));
|
||||
}
|
||||
_httpParse.index = _chunkReadableSize + 2;
|
||||
_state = State.readVariableLengthContent; //读取下一个chunk
|
||||
}
|
||||
//请求体
|
||||
if (_state == State.readFixedLengthContent || _state == State.readVariableLengthContent) {
|
||||
_readBody(data, message);
|
||||
if ((message.contentLength == -1 && !message.headers.isChunked) || _bodyBuffer.length == message.contentLength) {
|
||||
|
||||
if (message.contentLength == -1 || buffer.length >= message.contentLength) {
|
||||
message.body = buffer.length == 0 ? null : buffer.toBytes();
|
||||
buffer.clear();
|
||||
_state = State.done;
|
||||
}
|
||||
}
|
||||
|
||||
//chunked编码
|
||||
if (_state == State.readChunked) {
|
||||
ChunkedContent content = chunkedInput!.readChunked(data.sublist(_httpParse.index));
|
||||
if (content.state == ChunkedState.done) {
|
||||
message.body = content.content;
|
||||
_state = State.done;
|
||||
}
|
||||
}
|
||||
|
||||
if (_state == State.done) {
|
||||
message.body = _convertBody();
|
||||
message.body = _convertBody(message.body);
|
||||
_state = State.readInitial;
|
||||
return message;
|
||||
}
|
||||
@@ -86,6 +91,11 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
void init() {
|
||||
_httpParse.reset();
|
||||
buffer.clear();
|
||||
chunkedInput = ChunkedInput();
|
||||
}
|
||||
void initialLine(BytesBuilder buffer, T message);
|
||||
|
||||
@override
|
||||
@@ -123,7 +133,6 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
|
||||
//读取起始行
|
||||
List<String> _readInitialLine(Uint8List data) {
|
||||
_httpParse.reset();
|
||||
int maxSize = min(data.length, Codec.defaultMaxInitialLineLength);
|
||||
return _httpParse.parseInitialLine(data, maxSize);
|
||||
}
|
||||
@@ -134,43 +143,14 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
message.contentLength = message.headers.contentLength;
|
||||
}
|
||||
|
||||
//读取请求体
|
||||
void _readBody(Uint8List data, T message) {
|
||||
if (_state == State.readVariableLengthContent) {
|
||||
while (_httpParse.index < data.length) {
|
||||
var parseLine = _httpParse.parseLine(data);
|
||||
if (parseLine.isEmpty) {
|
||||
_httpParse.index = 0;
|
||||
return;
|
||||
}
|
||||
int length = hexToInt(String.fromCharCodes(parseLine));
|
||||
//chunked编码结束 最后以length = 0 结束
|
||||
if (length == 0) {
|
||||
_state = State.done;
|
||||
return;
|
||||
}
|
||||
_bodyBuffer.add(data.sublist(_httpParse.index, min(_httpParse.index + length, data.length)));
|
||||
if (_httpParse.index + length > data.length) {
|
||||
_state = State.readChunkedContent;
|
||||
_chunkReadableSize = length - (data.length - _httpParse.index);
|
||||
}
|
||||
_httpParse.index += length + 2; //跳过\r\n
|
||||
}
|
||||
|
||||
_httpParse.index = 0;
|
||||
} else if (message.contentLength > 0) {
|
||||
_bodyBuffer.add(data.sublist(_httpParse.index));
|
||||
_httpParse.index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//转换body
|
||||
List<int> _convertBody() {
|
||||
List<int> bytes = _bodyBuffer.toBytes();
|
||||
if (message.headers.isGzip) {
|
||||
bytes = gzipDecode(bytes);
|
||||
List<int>? _convertBody(List<int>? bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
if (message.headers.isGzip) {
|
||||
bytes = gzipDecode(bytes);
|
||||
}
|
||||
_bodyBuffer.clear();
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'logger.dart';
|
||||
|
||||
void main() {
|
||||
print(HostFilter.filter("www.apple.com"));
|
||||
print(HostFilter.filter("stackoverflow.com"));
|
||||
}
|
||||
|
||||
class HostFilter {
|
||||
@@ -25,7 +25,7 @@ class HostFilter {
|
||||
|
||||
//如果白名单不为空,不在白名单里都是黑名单
|
||||
if (whites.enabled) {
|
||||
return whites.list.any((element) => !element.hasMatch(host));
|
||||
return whites.list.every((element) => !element.hasMatch(host));
|
||||
}
|
||||
if (blacklist.enabled) {
|
||||
return blacklist.list.any((element) => element.hasMatch(host));
|
||||
@@ -46,7 +46,7 @@ abstract class HostList {
|
||||
final List<RegExp> list = [];
|
||||
bool enabled = false;
|
||||
|
||||
List<Function> _initListens = [];
|
||||
final List<Function> _initListens = [];
|
||||
bool _inited = false;
|
||||
|
||||
void addInitListen(void Function() action) {
|
||||
|
||||
@@ -33,15 +33,15 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener {
|
||||
@override
|
||||
void onWindowMinimize() {
|
||||
started = false;
|
||||
setState(() {});
|
||||
widget.proxyServer.stop();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowClose() {
|
||||
started = false;
|
||||
setState(() {});
|
||||
widget.proxyServer.stop();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -72,28 +72,26 @@ class _SslState extends State<SslWidget> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return SimpleDialog(
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
title: const Text("手机https抓包配置", style: TextStyle(fontSize: 16)),
|
||||
return const SimpleDialog(
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
title: Text("手机https抓包配置", style: TextStyle(fontSize: 16)),
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Text("1. 根证书安装到本系统(已完成忽略)"),
|
||||
const SizedBox(height: 10),
|
||||
const Text.rich(TextSpan(text: "2.配置手机Wifi代理 ")),
|
||||
const SizedBox(height: 10),
|
||||
Text("1. 根证书安装到本系统(已完成忽略)"),
|
||||
SizedBox(height: 10),
|
||||
Text.rich(TextSpan(text: "2.配置手机Wifi代理 ")),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
const Text("3.打开手机系统自带浏览器访问:"),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse("http://proxy.pin/ssl"));
|
||||
},
|
||||
child:
|
||||
const Text("http://proxy.pin/ssl", style: TextStyle(decoration: TextDecoration.underline)))
|
||||
Text("3.打开手机系统自带浏览器访问:\t"),
|
||||
SelectableText.rich(
|
||||
TextSpan(text: "http://proxy.pin/ssl", style: TextStyle(decoration: TextDecoration.underline)))
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text("4.打开手机设置下载安装证书(Profile)和信任证书(Certificate) \n\t 设置 > 通用 > 关于本机 > 证书信任设置"),
|
||||
SizedBox(height: 10),
|
||||
Text("4.打开手机设置下载安装证书(Profile)和信任证书(Certificate) \n\t 设置 > 通用 > 关于本机 > 证书信任设置"),
|
||||
SizedBox(height: 20),
|
||||
Text(" 微信小程序ios需要开启本地网络权限", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ int hexToInt(String hex) {
|
||||
// a..f
|
||||
val += (hexDigit - 87) * (1 << (4 * (len - 1 - i)));
|
||||
} else {
|
||||
throw const FormatException("Invalid hexadecimal value");
|
||||
throw FormatException("Invalid hexadecimal value $hex");
|
||||
}
|
||||
}
|
||||
return val;
|
||||
|
||||
4
test/tests.dart
Normal file
4
test/tests.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
void main() {
|
||||
RegExp reg = RegExp("stackoverflow.com");
|
||||
print(reg.hasMatch("stackoverflow.com"));
|
||||
}
|
||||
Reference in New Issue
Block a user