From 2d8243692c96ee214df157a382325f6e7035877d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com>
Date: Wed, 11 Sep 2024 17:00:02 +0800
Subject: [PATCH] =?UTF-8?q?feature:=20=E6=96=B0=E5=A2=9E=20`pac.txt`?=
=?UTF-8?q?=EF=BC=88=E5=8D=B3=20`GFW`=20=E5=88=97=E8=A1=A8=EF=BC=89?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD=20(#352)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/modules/plugin/overwall/config.js | 3 +-
packages/gui/src/view/pages/plugin/node.vue | 4 +-
.../gui/src/view/pages/plugin/overwall.vue | 17 ++++
.../src/lib/proxy/middleware/overwall.js | 98 ++++++++++++++++++-
packages/mitmproxy/src/options.js | 43 +++++---
5 files changed, 149 insertions(+), 16 deletions(-)
diff --git a/packages/core/src/modules/plugin/overwall/config.js b/packages/core/src/modules/plugin/overwall/config.js
index a41a64d5..0eb29f6e 100644
--- a/packages/core/src/modules/plugin/overwall/config.js
+++ b/packages/core/src/modules/plugin/overwall/config.js
@@ -42,7 +42,8 @@ module.exports = {
},
pac: {
enabled: true,
- // update: [ 'https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt' ],
+ autoUpdate: true,
+ pacFileUpdateUrl: 'https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt',
pacFileAbsolutePath: null, // 自定义 pac.txt 文件位置,可以是本地文件路径
pacFilePath: './extra/pac/pac.txt' // 内置 pac.txt 文件路径
}
diff --git a/packages/gui/src/view/pages/plugin/node.vue b/packages/gui/src/view/pages/plugin/node.vue
index ce6d9f7b..446d71fc 100644
--- a/packages/gui/src/view/pages/plugin/node.vue
+++ b/packages/gui/src/view/pages/plugin/node.vue
@@ -62,10 +62,10 @@
某些库需要自己设置镜像变量,才能下载,比如:electron
-
+
-
+
diff --git a/packages/gui/src/view/pages/plugin/overwall.vue b/packages/gui/src/view/pages/plugin/overwall.vue
index 52e615a8..4a3d6e09 100644
--- a/packages/gui/src/view/pages/plugin/overwall.vue
+++ b/packages/gui/src/view/pages/plugin/overwall.vue
@@ -19,12 +19,29 @@
声明:仅供技术学习与探讨!
+
启用PAC
PAC内收录了常见的被封杀的域名,当里面某些域名你不想被拦截时,可以关闭PAC
+
+
+ 是否自动更新PAC
+
+
+ 开启自动更新后,启动代理服务时,将会异步从下面的远程地址下载PAC文件到本地。
+ 注:只要下载成功后,即使关闭自动更新功能,也会优先读取最近下载的PAC文件!
+
+
+
+
+
+ 远程PAC文件内容可以是 base64 编码格式,也可以是未经过编码的
+
+
+
diff --git a/packages/mitmproxy/src/lib/proxy/middleware/overwall.js b/packages/mitmproxy/src/lib/proxy/middleware/overwall.js
index 7ae47828..bd5a4352 100644
--- a/packages/mitmproxy/src/lib/proxy/middleware/overwall.js
+++ b/packages/mitmproxy/src/lib/proxy/middleware/overwall.js
@@ -1,8 +1,12 @@
const url = require('url')
+const request = require('request')
const lodash = require('lodash')
const pac = require('./source/pac')
const matchUtil = require('../../../utils/util.match')
const log = require('../../../utils/util.log')
+const path = require('path')
+const fs = require('fs')
+const { Buffer } = require('buffer')
let pacClient = null
function matched (hostname, overWallTargetMap) {
@@ -23,7 +27,93 @@ function matched (hostname, overWallTargetMap) {
}
}
-module.exports = function createOverWallIntercept (overWallConfig) {
+function getUserBasePath () {
+ const userHome = process.env.USERPROFILE || process.env.HOME || '/'
+ return path.resolve(userHome, './.dev-sidecar')
+}
+
+// 下载的 pac.txt 文件保存路径
+function getTmpPacFilePath () {
+ return path.join(getUserBasePath(), '/pac.txt')
+}
+
+function loadPacLastModifiedTime (pacTxt) {
+ const matched = pacTxt.match(/(?<=! Last Modified: )[^\n]+/g)
+ if (matched && matched.length > 0) {
+ try {
+ return new Date(matched[0])
+ } catch (ignore) {
+ return null
+ }
+ }
+}
+
+// 保存 pac 内容到 `~/pac.txt` 文件中
+function savePacFile (pacTxt) {
+ const pacFilePath = getTmpPacFilePath()
+ fs.writeFileSync(pacFilePath, pacTxt)
+ log.info('保存 pac.txt 文件成功:', pacFilePath)
+
+ // 尝试解析和修改 pac.txt 文件时间
+ const lastModifiedTime = loadPacLastModifiedTime(pacTxt)
+ if (lastModifiedTime) {
+ fs.stat(pacFilePath, (err, stats) => {
+ if (err) {
+ log.error('修改 pac.txt 文件时间失败:', err)
+ return
+ }
+
+ // 修改文件的访问时间和修改时间为当前时间
+ fs.utimes(pacFilePath, lastModifiedTime, lastModifiedTime, (utimesErr) => {
+ if (utimesErr) {
+ log.error('修改 pac.txt 文件时间失败:', utimesErr)
+ } else {
+ log.info(`${pacFilePath} 文件时间已被修改其最近更新时间 '${lastModifiedTime}'`)
+ }
+ })
+ })
+ }
+
+ return pacFilePath
+}
+
+// 异步下载 pac.txt ,避免影响代理服务的启动速度
+async function downloadPacAsync (pacConfig) {
+ const remotePacFileUrl = pacConfig.pacFileUpdateUrl
+ log.info('开始下载远程 pac.txt 文件:', remotePacFileUrl)
+ request(remotePacFileUrl, (error, response, body) => {
+ if (error) {
+ log.error('下载远程 pac.txt 文件失败, error:', error, ', response:', response, ', body:', body)
+ return
+ }
+ if (response && response.statusCode === 200) {
+ if (body == null || body.length < 100) {
+ log.warn('下载远程 pac.txt 文件成功,但内容为空或内容太短,判断为无效的 pax.txt 文件:', remotePacFileUrl, ', body:', body)
+ return
+ } else {
+ log.info('下载远程 pac.txt 文件成功:', remotePacFileUrl)
+ }
+
+ // 尝试解析Base64(注:https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt 下载下来的是Base64格式)
+ let pacTxt = body
+ try {
+ pacTxt = Buffer.from(pacTxt, 'base64').toString('utf8')
+ } catch (e) {
+ if (pacTxt.indexOf('||') < 0) { // TODO: 待优化,需要判断下载的 pac.txt 文件内容是否正确,目前暂时先简单判断一下
+ log.error(`远程 pac.txt 文件内容即不是base64格式,也不是要求的格式,url: ${remotePacFileUrl},body: ${body}`)
+ return
+ }
+ }
+
+ // 保存到本地
+ savePacFile(pacTxt)
+ } else {
+ log.error('下载远程 pac.txt 文件失败, response:', response, ', body:', body)
+ }
+ })
+}
+
+function createOverwallMiddleware (overWallConfig) {
if (!overWallConfig || overWallConfig.enabled !== true) {
return null
}
@@ -109,3 +199,9 @@ module.exports = function createOverWallIntercept (overWallConfig) {
}
}
}
+
+module.exports = {
+ getTmpPacFilePath,
+ downloadPacAsync,
+ createOverwallMiddleware
+}
diff --git a/packages/mitmproxy/src/options.js b/packages/mitmproxy/src/options.js
index e9620278..c821cb10 100644
--- a/packages/mitmproxy/src/options.js
+++ b/packages/mitmproxy/src/options.js
@@ -3,9 +3,10 @@ const dnsUtil = require('./lib/dns')
const log = require('./utils/util.log')
const matchUtil = require('./utils/util.match')
const path = require('path')
+const fs = require('fs')
const scriptInterceptor = require('./lib/interceptor/impl/res/script')
-const createOverwallMiddleware = require('./lib/proxy/middleware/overwall')
+const { getTmpPacFilePath, downloadPacAsync, createOverwallMiddleware } = require('./lib/proxy/middleware/overwall')
// 处理拦截配置
function buildIntercepts (intercepts) {
@@ -15,12 +16,11 @@ function buildIntercepts (intercepts) {
return intercepts
}
-module.exports = (config) => {
- const intercepts = matchUtil.domainMapRegexply(buildIntercepts(config.intercepts))
- const whiteList = matchUtil.domainMapRegexply(config.whiteList)
+module.exports = (serverConfig) => {
+ const intercepts = matchUtil.domainMapRegexply(buildIntercepts(serverConfig.intercepts))
+ const whiteList = matchUtil.domainMapRegexply(serverConfig.whiteList)
- const dnsMapping = config.dns.mapping
- const serverConfig = config
+ const dnsMapping = serverConfig.dns.mapping
const setting = serverConfig.setting
if (!setting.script.dirAbsolutePath) {
@@ -30,16 +30,35 @@ module.exports = (config) => {
setting.verifySsl = true
}
- const overwallConfig = serverConfig.plugin.overwall
- if (!overwallConfig.pac.pacFileAbsolutePath) {
- overwallConfig.pac.pacFileAbsolutePath = path.join(setting.rootDir, overwallConfig.pac.pacFilePath)
+ const overWallConfig = serverConfig.plugin.overwall
+ if (overWallConfig.pac && overWallConfig.pac.enabled) {
+ const pacConfig = overWallConfig.pac
+
+ // 自动更新 pac.txt
+ if (!pacConfig.pacFileAbsolutePath && pacConfig.autoUpdate) {
+ // 异步下载远程 pac.txt 文件,并保存到本地;下载成功后,需要重启代理服务才会生效
+ downloadPacAsync(pacConfig)
+ }
+
+ // 优先使用本地已下载的 pac.txt 文件
+ if (!pacConfig.pacFileAbsolutePath && fs.existsSync(getTmpPacFilePath())) {
+ pacConfig.pacFileAbsolutePath = getTmpPacFilePath()
+ log.info('读取已下载的 pac.txt 文件:', pacConfig.pacFileAbsolutePath)
+ }
+
+ if (!pacConfig.pacFileAbsolutePath) {
+ pacConfig.pacFileAbsolutePath = path.join(setting.rootDir, pacConfig.pacFilePath)
+ if (pacConfig.autoUpdate) {
+ log.warn('远程 pac.txt 文件下载失败或还在下载中,现使用内置 pac.txt 文件:', pacConfig.pacFileAbsolutePath)
+ }
+ }
}
// 插件列表
const middlewares = []
// 梯子插件:如果启用了,则添加到插件列表中
- const overwallMiddleware = createOverwallMiddleware(overwallConfig)
+ const overwallMiddleware = createOverwallMiddleware(overWallConfig)
if (overwallMiddleware) {
middlewares.push(overwallMiddleware)
}
@@ -48,9 +67,9 @@ module.exports = (config) => {
host: serverConfig.host,
port: serverConfig.port,
dnsConfig: {
- providers: dnsUtil.initDNS(serverConfig.dns.providers, matchUtil.domainMapRegexply(config.preSetIpList)),
+ providers: dnsUtil.initDNS(serverConfig.dns.providers, matchUtil.domainMapRegexply(serverConfig.preSetIpList)),
mapping: matchUtil.domainMapRegexply(dnsMapping),
- speedTest: config.dns.speedTest
+ speedTest: serverConfig.dns.speedTest
},
setting,
sniConfig: serverConfig.sniList,