import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; import 'package:network_proxy/network/util/file_read.dart'; import 'package:network_proxy/network/util/logger.dart'; import 'package:network_proxy/utils/lang.dart'; /// @author wanghongen /// 2023/7/26 /// 请求重写 class RequestRewrites { static String separator = Platform.pathSeparator; //重写规则 final Map> rewriteItemsCache = {}; //单例 static RequestRewrites? _instance; RequestRewrites._(); static Future get instance async { if (_instance == null) { var config = await _loadRequestRewriteConfig(); _instance = RequestRewrites._(); await _instance!.reload(config); } return _instance!; } bool enabled = true; List rules = []; //重新加载配置 Future reload(Map? map) async { rewriteItemsCache.clear(); if (map == null) { return; } enabled = map['enabled'] == true; List list = map['rules'] ?? []; rules.clear(); bool flush = false; for (var element in list) { try { bool oldVersion = false; // body("重写消息体"), 兼容旧版本 if (element['requestBody']?.isNotEmpty == true || element['queryParam']?.isNotEmpty == true) { element['type'] = RuleType.requestReplace.name; List items = []; if (element['requestBody']?.isNotEmpty == true) { RewriteItem item = RewriteItem(RewriteType.replaceRequestBody, true); item.body = element['requestBody']; items.add(item); } if (element['queryParam']?.isNotEmpty == true) { RewriteItem item = RewriteItem(RewriteType.replaceRequestLine, true); item.queryParam = element['queryParam']; items.add(item); } var rule = RequestRewriteRule.formJson(element); await addRule(rule, items); oldVersion = true; } if (element['responseBody']?.isNotEmpty == true) { element['type'] = RuleType.responseReplace.name; RewriteItem item = RewriteItem(RewriteType.replaceResponseBody, true); item.body = element['responseBody']; var rule = RequestRewriteRule.formJson(element); await addRule(rule, [item]); oldVersion = true; continue; } if (element['redirectUrl']?.isNotEmpty == true) { RewriteItem item = RewriteItem(RewriteType.redirect, true); item.redirectUrl = element['redirectUrl']; var rule = RequestRewriteRule.formJson(element); await addRule(rule, [item]); oldVersion = true; continue; } if (oldVersion) { flush = true; continue; } rules.add(RequestRewriteRule.formJson(element)); } catch (e) { logger.e('加载请求重写配置失败 $element', error: e); } } if (flush) { await flushRequestRewriteConfig(); } } ///重新加载请求重写 Future reloadRequestRewrite() async { var config = await _loadRequestRewriteConfig(); reload(config); } ///同步配置 Future syncConfig(Map? config) async { if (config == null) { return; } rewriteItemsCache.clear(); enabled = config['enabled'] == true; List list = config['rules'] ?? []; rules.clear(); for (var element in list) { try { var rule = RequestRewriteRule.formJson(element); List list = element['items'] as List; List items = list.map((e) => RewriteItem.fromJson(e)).toList(); await addRule(rule, items); } catch (e) { logger.e('加载请求重写配置失败 $element', error: e); } } flushRequestRewriteConfig(); } /// 加载请求重写配置文件 static Future?> _loadRequestRewriteConfig() async { var home = await FileRead.homeDir(); var file = File('${home.path}${Platform.pathSeparator}request_rewrite.json'); var exits = await file.exists(); if (!exits) { return null; } Map config = jsonDecode(await file.readAsString()); logger.i('加载请求重写配置文件 [$file]'); return config; } /// 保存请求重写配置文件 Future flushRequestRewriteConfig() async { var home = await FileRead.homeDir(); var file = File('${home.path}${Platform.pathSeparator}request_rewrite.json'); bool exists = await file.exists(); if (!exists) { await file.create(recursive: true); } var json = jsonEncode(toJson()); logger.i('刷新请求重写配置文件 ${file.path}'); await file.writeAsString(json); } ///添加规则 Future addRule(RequestRewriteRule rule, List items) async { final home = await FileRead.homeDir(); String rewritePath = "${separator}rewrite$separator${DateTime.now().millisecondsSinceEpoch}.json"; var file = File(home.path + rewritePath); await file.create(recursive: true); file.writeAsString(jsonEncode(items.map((e) => e.toJson()).toList())); rule.rewritePath = rewritePath; rules.add(rule); rewriteItemsCache[rule] = items; } ///更新规则 Future updateRule(int index, RequestRewriteRule rule, List? items) async { rewriteItemsCache.remove(rules[index]); final home = await FileRead.homeDir(); rule._updatePathReg(); rules[index] = rule; if (items == null) { return; } bool isExist = rule.rewritePath != null; if (rule.rewritePath == null) { String rewritePath = "${separator}rewrite$separator${DateTime.now().millisecondsSinceEpoch}.json"; rule.rewritePath = rewritePath; } File file = File(home.path + rule.rewritePath!); if (!isExist) { await file.create(recursive: true); } await file.writeAsString(jsonEncode(items.map((e) => e.toJson()).toList())); rewriteItemsCache[rule] = items; } removeIndex(List indexes) async { for (var i in indexes) { var rule = rules.removeAt(i); rewriteItemsCache.remove(rule); //删除缓存 if (rule.rewritePath != null) { File home = await FileRead.homeDir(); try { await File(home.path + rule.rewritePath!).delete(); } catch (e) { logger.e('删除请求重写配置文件失败 ${home.path + rule.rewritePath!}', error: e); } rule.rewritePath = null; } } } ///获取重定向 Future getRedirectRule(String? url) async { var rewriteRule = getRewriteRule(url, [RuleType.redirect]); if (rewriteRule == null) { return null; } var rewriteItems = await getRewriteItems(rewriteRule); var redirectUrl = rewriteItems.firstWhereOrNull((element) => element.enabled)?.redirectUrl; if (rewriteRule.url.contains("*") && redirectUrl?.contains("*") == true) { String ruleUrl = rewriteRule.url.replaceAll("*", ""); redirectUrl = redirectUrl?.replaceAll("*", url!.replaceAll(ruleUrl, "")); } return redirectUrl; } RequestRewriteRule? getRewriteRule(String? url, List types) { if (url == null || !enabled) { return null; } for (var rule in rules) { if (rule.match(url) && types.contains(rule.type)) { return rule; } } return null; } /// 获取重写规则 Future> getRewriteItems(RequestRewriteRule rule) async { if (rewriteItemsCache.containsKey(rule)) { return rewriteItemsCache[rule]!; } if (rule.rewritePath == null) { return []; } final home = await FileRead.homeDir(); List items = []; try { var json = await File(home.path + rule.rewritePath!).readAsString(); List? list = jsonDecode(json); list?.forEach((element) => items.add(RewriteItem.fromJson(element))); rewriteItemsCache[rule] = items; } catch (e) { logger.e('加载请求重写配置文件失败 ${home.path + rule.rewritePath!}', error: e); } return items; } /// 查找重写规则 Future requestRewrite(HttpRequest request) async { var url = request.requestUrl; var rewriteRule = getRewriteRule(url, [RuleType.requestReplace, RuleType.requestUpdate]); if (rewriteRule?.type == RuleType.requestReplace) { var rewriteItems = await getRewriteItems(rewriteRule!); for (var item in rewriteItems) { if (item.enabled) { await _replaceRequest(request, item); } } return; } if (rewriteRule?.type == RuleType.requestUpdate) { var rewriteItems = await getRewriteItems(rewriteRule!); rewriteItems.where((item) => item.enabled).forEach((item) => _updateRequest(request, item)); return; } } _updateRequest(HttpRequest request, RewriteItem item) { var paramTypes = [RewriteType.addQueryParam, RewriteType.removeQueryParam, RewriteType.updateQueryParam]; if (paramTypes.contains(item.type)) { var requestUri = request.requestUri; Map queryParameters = LinkedHashMap.from(requestUri!.queryParameters); switch (item.type) { case RewriteType.addQueryParam: queryParameters[item.key!] = item.value; break; case RewriteType.removeQueryParam: if (item.value?.isNotEmpty == true) { var val = queryParameters[item.key!]; if (val != null && item.value != null && RegExp(item.value!).hasMatch(val)) { return; } } queryParameters.remove(item.value); break; case RewriteType.updateQueryParam: var pair = item.key?.split("="); var val = queryParameters[pair!.first]; if (val != null && RegExp(pair.last).hasMatch(val)) { var split = item.value?.split("="); queryParameters.remove(pair.first); queryParameters[split!.first] = split.last; } break; default: return; } requestUri = requestUri.replace(queryParameters: queryParameters); request.uri = requestUri.path + (requestUri.hasQuery ? "?${requestUri.query}" : ""); return; } _updateMessage(request, item); } //替换请求 Future _replaceRequest(HttpRequest request, RewriteItem item) async { if (item.type == RewriteType.replaceRequestLine) { request.method = item.method ?? request.method; Uri uri = Uri.parse(request.requestUrl).replace(path: item.path, query: item.queryParam); request.uri = uri.path + (uri.hasQuery ? "?${uri.query}" : ""); return; } await _replaceHttpMessage(request, item); } /// 查找重写规则 Future responseRewrite(String? url, HttpResponse response) async { var rewriteRule = getRewriteRule(url, [RuleType.responseReplace, RuleType.responseUpdate]); if (rewriteRule == null) { return; } if (rewriteRule.type == RuleType.responseReplace) { var rewriteItems = await getRewriteItems(rewriteRule); for (var item in rewriteItems) { if (item.enabled) { await _replaceResponse(response, item); } } // logger.d('rewrite response $response'); return; } if (rewriteRule.type == RuleType.responseUpdate) { var rewriteItems = await getRewriteItems(rewriteRule); rewriteItems.where((item) => item.enabled).forEach((item) => _updateMessage(response, item)); } } //修改消息 _updateMessage(HttpMessage message, RewriteItem item) { if (item.type == RewriteType.updateBody && message.body != null) { message.body = utf8.encode(message.bodyAsString.replaceAllMapped(RegExp(item.key!), (match) { if (match.groupCount > 0 && item.value?.contains("\$1") == true) { return item.value!.replaceAll("\$1", match.group(1)!); } return item.value ?? ''; })); message.headers.remove(HttpHeaders.CONTENT_ENCODING); message.headers.contentLength = message.body!.length; return; } if (item.type == RewriteType.addHeader) { message.headers.set(item.key!, item.value ?? ''); return; } if (item.type == RewriteType.removeHeader) { if (item.value?.isNotEmpty == true) { var val = message.headers.get(item.key!); if (val != null && item.value != null && RegExp(item.value!).hasMatch(val)) { return; } } message.headers.remove(item.key!); return; } if (item.type == RewriteType.updateHeader) { var pair = item.key?.split(":"); var val = message.headers.get(pair!.first); if (val != null && RegExp(pair.last.trim()).hasMatch(val)) { var split = item.value?.split(":"); message.headers.remove(pair.first); message.headers.set(split!.first, split.last.trim()); } return; } } //替换相应 Future _replaceResponse(HttpResponse response, RewriteItem item) async { if (item.type == RewriteType.replaceResponseStatus && item.statusCode != null) { response.status = HttpStatus.valueOf(item.statusCode!); return; } await _replaceHttpMessage(response, item); } Future _replaceHttpMessage(HttpMessage message, RewriteItem item) async { if (item.type == RewriteType.replaceResponseHeader && item.headers != null) { item.headers?.forEach((key, value) => message.headers.set(key, value)); return; } if (item.type == RewriteType.replaceResponseBody || item.type == RewriteType.replaceRequestBody) { if (item.bodyType == ReplaceBodyType.file.name) { if (item.bodyFile == null) return; message.body = await FileRead.readFile(item.bodyFile!); message.headers.contentLength = message.body!.length; return; } if (item.body != null) { message.body = utf8.encode(item.body!); message.headers.contentLength = message.body!.length; } return; } } toJson() { return { 'enabled': enabled, 'rules': rules.map((e) => e.toJson()).toList(), }; } Future> toFullJson() async { var rulesJson = []; for (var rule in rules) { var json = rule.toJson(); json['items'] = await getRewriteItems(rule); rulesJson.add(json); } return { 'enabled': enabled, 'rules': rulesJson, }; } } enum RuleType { // body("重写消息体"), //OLD VERSION requestReplace("替换请求"), responseReplace("替换响应"), requestUpdate("修改请求"), responseUpdate("修改响应"), redirect("重定向"); //名称 final String label; const RuleType(this.label); static RuleType fromName(String name) { return values.firstWhere((element) => element.name == name || element.label == name); } } class RequestRewriteRule { bool enabled; RuleType type; String? name; String url; RegExp _urlReg; String? rewritePath; RequestRewriteRule({this.enabled = true, this.name, required this.url, required this.type, this.rewritePath}) : _urlReg = RegExp(url.replaceAll("*", ".*").replaceAll('?', '\\?')); bool match(String url, {RuleType? type}) { return enabled && (type == null || this.type == type) && _urlReg.hasMatch(url); } bool matchUrl(String url, RuleType type) { return this.type == type && _urlReg.hasMatch(url); } /// 从json中创建 factory RequestRewriteRule.formJson(Map map) { return RequestRewriteRule( enabled: map['enabled'] == true, name: map['name'], url: map['url'] ?? map['domain'] + map['path'], type: RuleType.fromName(map['type']), rewritePath: map['rewritePath']); } void _updatePathReg() { _urlReg = RegExp(url.replaceAll("*", ".*")); } toJson() { return { 'name': name, 'enabled': enabled, 'url': url, 'type': type.name, 'rewritePath': rewritePath, }; } } enum ReplaceBodyType { text("文本"), file("文件"); final String label; const ReplaceBodyType(this.label); } class RewriteItem { bool enabled; RewriteType type; //key redirectUrl, method, path, queryParam, headers, body, statusCode final Map values = {}; RewriteItem(this.type, this.enabled, {Map? values}) { if (values != null) { this.values.addAll(Map.from(values)); } } factory RewriteItem.fromJson(Map map) { return RewriteItem(RewriteType.fromName(map['type']), map['enabled'], values: map['values']); } //key String? get key => values['key']; set key(String? key) => values['key'] = key; String? get value => values['value']; set value(String? value) => values['value'] = value; //redirectUrl String? get redirectUrl => values['redirectUrl']; set redirectUrl(String? redirectUrl) => values['redirectUrl'] = redirectUrl; //method HttpMethod? get method => values['method'] == null ? null : HttpMethod.values.firstWhereOrNull((element) => element.name == values['method']); String? get path => values['path']; //queryParam String? get queryParam => values['queryParam']; set queryParam(String? queryParam) => values['queryParam'] = queryParam; //statusCode int? get statusCode => values['statusCode']; set statusCode(int? statusCode) => values['statusCode'] = statusCode; //headers Map? get headers => values['headers'] == null ? null : Map.from(values['headers']); set headers(Map? headers) => values['headers'] = headers; //body String? get body => values['body']; set body(String? body) => values['body'] = body; String? get bodyType => values['bodyType']; set bodyType(String? bodyType) => values['bodyType'] = bodyType; String? get bodyFile => values['bodyFile']; set bodyFile(String? bodyFile) => values['bodyFile'] = bodyFile; Map toJson() { return { 'enabled': enabled, 'type': type.name, 'values': values, }; } @override String toString() { return toJson().toString(); } } enum RewriteType { //重定向 redirect("重定向"), //替换请求 replaceRequestLine("请求行"), replaceRequestHeader("请求头"), replaceRequestBody("请求体"), replaceResponseStatus("状态码"), replaceResponseHeader("响应头"), replaceResponseBody("响应体"), //修改请求 updateBody("修改Body"), addQueryParam("添加参数"), removeQueryParam("删除参数"), updateQueryParam("修改参数"), addHeader("添加头部"), removeHeader("删除头部"), updateHeader("修改头部"), ; static List updateRequest = [ updateBody, addQueryParam, updateQueryParam, removeQueryParam, addHeader, updateHeader, removeHeader ]; static List updateResponse = [updateBody, addHeader, updateHeader, removeHeader]; final String label; const RewriteType(this.label); static RewriteType fromName(String name) { return values.firstWhere((element) => element.name == name); } String getDescribe(bool isCN) { if (isCN) { return label; } return name.replaceFirst("replace", "").replaceFirst("Query", ""); } }