/* * Copyright 2023 the original author or authors. * * 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:async'; import 'dart:convert'; import 'dart:io'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; import 'package:network_proxy/network/util/attribute_keys.dart'; import 'package:network_proxy/network/util/file_read.dart'; import 'package:network_proxy/network/util/host_filter.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; import 'package:network_proxy/network/util/script_manager.dart'; import 'package:network_proxy/utils/ip.dart'; import 'channel.dart'; import 'http/codec.dart'; import 'http_client.dart'; ///请求和响应事件监听 abstract class EventListener { void onRequest(Channel channel, HttpRequest request); void onResponse(Channel channel, HttpResponse response); } /// http请求处理器 class HttpChannelHandler extends ChannelHandler { EventListener? listener; RequestRewrites? requestRewrites; HttpChannelHandler({this.listener, this.requestRewrites}); @override void channelRead(Channel channel, HttpRequest msg) async { channel.putAttribute(AttributeKeys.request, msg); if (msg.uri == 'http://proxy.pin/ssl' || msg.requestUrl == 'http://127.0.0.1:${channel.socket.port}/ssl') { _crtDownload(channel, msg); return; } //请求本服务 if ((await localIps()).contains(msg.hostAndPort?.host) && msg.hostAndPort?.port == channel.socket.port) { localRequest(msg, channel); return; } //转发请求 forward(channel, msg).catchError((error, trace) { exceptionCaught(channel, error, trace: trace); }); } @override void exceptionCaught(Channel channel, error, {StackTrace? trace}) { super.exceptionCaught(channel, error, trace: trace); _exceptionHandler(channel, channel.getAttribute(AttributeKeys.request), error); } @override void channelInactive(Channel channel) { Channel? remoteChannel = channel.getAttribute(channel.id); remoteChannel?.close(); // log.i("[${channel.id}] close ${channel.error}"); } //请求本服务 localRequest(HttpRequest msg, Channel channel) async { //获取配置 if (msg.path() == '/config') { var response = HttpResponse(HttpStatus.ok, protocolVersion: msg.protocolVersion); var body = { "requestRewrites": requestRewrites?.toJson(), 'whitelist': HostFilter.whitelist.toJson(), 'blacklist': HostFilter.blacklist.toJson(), }; response.body = utf8.encode(json.encode(body)); channel.writeAndClose(response); return; } var response = HttpResponse(HttpStatus.ok, protocolVersion: msg.protocolVersion); response.body = utf8.encode('pong'); response.headers.set("os", Platform.operatingSystem); response.headers.set("hostname", Platform.isAndroid ? Platform.operatingSystem : Platform.localHostname); channel.writeAndClose(response); } /// 转发请求 Future forward(Channel channel, HttpRequest httpRequest) async { // log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}"); if (channel.error != null) { _exceptionHandler(channel, httpRequest, channel.error); return; } //获取远程连接 Channel remoteChannel; try { remoteChannel = await _getRemoteChannel(channel, httpRequest); remoteChannel.putAttribute(remoteChannel.id, channel); } catch (error) { channel.error = error; //记录异常 //https代理新建连接请求 if (httpRequest.method == HttpMethod.connect) { await channel.write( HttpResponse(HttpStatus.ok.reason('Connection established'), protocolVersion: httpRequest.protocolVersion)); } return; } //实现抓包代理转发 if (httpRequest.method != HttpMethod.connect) { // log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}"); if (HostFilter.filter(httpRequest.hostAndPort?.host)) { await remoteChannel.write(httpRequest); return; } //脚本替换 var scriptManager = await ScriptManager.instance; httpRequest = await scriptManager.runScript(httpRequest); //替换请求体 rewriteBody(httpRequest); listener?.onRequest(channel, httpRequest); //重定向 var redirectRewrite = requestRewrites?.findRequestRewrite(httpRequest.hostAndPort?.host, httpRequest.path(), RuleType.redirect); if (redirectRewrite?.redirectUrl?.isNotEmpty == true) { var proxyHandler = HttpResponseProxyHandler(channel, listener: listener, requestRewrites: requestRewrites); httpRequest.uri = redirectRewrite!.redirectUrl!; httpRequest.headers.host = Uri.parse(redirectRewrite.redirectUrl!).host; var redirectChannel = await HttpClients.connect(Uri.parse(redirectRewrite.redirectUrl!), proxyHandler); await redirectChannel.write(httpRequest); return; } await remoteChannel.write(httpRequest); } } //替换请求体 rewriteBody(HttpRequest httpRequest) { var rewrite = requestRewrites?.findRequestRewrite(httpRequest.hostAndPort?.host, httpRequest.path(), RuleType.body); if (rewrite?.requestBody?.isNotEmpty == true) { httpRequest.body = utf8.encode(rewrite!.requestBody!); } if (rewrite?.queryParam?.isNotEmpty == true) { httpRequest.uri = httpRequest.requestUri?.replace(query: rewrite!.queryParam!).toString() ?? httpRequest.uri; } } /// 下载证书 void _crtDownload(Channel channel, HttpRequest request) async { const String fileMimeType = 'application/x-x509-ca-cert'; var response = HttpResponse(HttpStatus.ok); response.headers.set(HttpHeaders.CONTENT_TYPE, fileMimeType); response.headers.set("Content-Disposition", 'inline;filename=ProxyPinCA.crt'); response.headers.set("Connection", 'close'); var body = await FileRead.read('assets/certs/ca.crt'); response.headers.set("Content-Length", body.lengthInBytes.toString()); if (request.method == HttpMethod.head) { channel.writeAndClose(response); return; } response.body = body.buffer.asUint8List(); channel.writeAndClose(response); } /// 获取远程连接 Future _getRemoteChannel(Channel clientChannel, HttpRequest httpRequest) async { String clientId = clientChannel.id; //客户端连接 作为缓存 Channel? remoteChannel = clientChannel.getAttribute(clientId); if (remoteChannel != null) { return remoteChannel; } var hostAndPort = getHostAndPort(httpRequest); clientChannel.putAttribute(AttributeKeys.host, hostAndPort); var proxyHandler = HttpResponseProxyHandler(clientChannel, listener: listener, requestRewrites: requestRewrites); //远程转发 HostAndPort? remote = clientChannel.getAttribute(AttributeKeys.remote); //外部代理 ProxyInfo? proxyInfo = clientChannel.getAttribute(AttributeKeys.proxyInfo); if (remote != null || proxyInfo != null) { HostAndPort connectHost = remote ?? HostAndPort.host(proxyInfo!.host, proxyInfo.port!); var proxyChannel = await HttpClients.startConnect(connectHost, proxyHandler); clientChannel.putAttribute(clientId, proxyChannel); proxyChannel.write(httpRequest); return proxyChannel; } var proxyChannel = await HttpClients.startConnect(hostAndPort, proxyHandler); clientChannel.putAttribute(clientId, proxyChannel); //https代理新建连接请求 if (httpRequest.method == HttpMethod.connect) { await clientChannel.write( HttpResponse(HttpStatus.ok.reason('Connection established'), protocolVersion: httpRequest.protocolVersion)); } return proxyChannel; } /// 异常处理 _exceptionHandler(Channel channel, HttpRequest? request, error) { HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host); hostAndPort ??= HostAndPort.host(scheme: HostAndPort.httpScheme, channel.remoteAddress.host, channel.remotePort); String message = error.toString(); HttpStatus status = HttpStatus(-1, message); if (error is HandshakeException) { status = HttpStatus(-2, 'SSL握手失败'); } else if (error is ParserException) { status = HttpStatus(-3, error.message); } else if (error is SocketException) { status = HttpStatus(-4, error.message); } else if (error is SignalException) { status.reason('执行脚本异常'); } request ??= HttpRequest(HttpMethod.connect, hostAndPort.domain) ..body = message.codeUnits ..headers.contentLength = message.codeUnits.length ..hostAndPort = hostAndPort; request.response = HttpResponse(status) ..headers.contentType = 'text/plain' ..headers.contentLength = message.codeUnits.length ..body = message.codeUnits; listener?.onRequest(channel, request); listener?.onResponse(channel, request.response!); } } /// http响应代理 class HttpResponseProxyHandler extends ChannelHandler { //客户端的连接 final Channel clientChannel; EventListener? listener; RequestRewrites? requestRewrites; HttpResponseProxyHandler(this.clientChannel, {this.listener, this.requestRewrites}); @override void channelRead(Channel channel, HttpResponse msg) async { msg.request = clientChannel.getAttribute(AttributeKeys.request); msg.request?.response = msg; //域名是否过滤 if (HostFilter.filter(msg.request?.hostAndPort?.host) || msg.request?.method == HttpMethod.connect) { await clientChannel.write(msg); return; } // log.i("[${clientChannel.id}] Response $msg"); //脚本替换 var scriptManager = await ScriptManager.instance; try { msg = await scriptManager.runResponseScript(msg); } catch (e, t) { msg.status = HttpStatus(-1, '执行脚本异常'); msg.body = "$e\n${msg.bodyAsString}".codeUnits; log.e('[${clientChannel.id}] 执行脚本异常 ', error: e, stackTrace: t); } var replaceBody = requestRewrites?.findResponseReplaceWith(msg.request?.hostAndPort?.host, msg.request?.path()); if (replaceBody?.isNotEmpty == true) { msg.body = utf8.encode(replaceBody!); } listener?.onResponse(clientChannel, msg); //发送给客户端 await clientChannel.write(msg); } @override void channelInactive(Channel channel) { clientChannel.close(); } } class RelayHandler extends ChannelHandler { final Channel remoteChannel; RelayHandler(this.remoteChannel); @override void channelRead(Channel channel, Object msg) async { //发送给客户端 remoteChannel.write(msg); } @override void channelInactive(Channel channel) { remoteChannel.close(); } }