Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: windows 的系统代理排除列表中,排除掉中国域名白名单,并提供自动更新中国域名白名单的功能 #366

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/core/src/modules/proxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ module.exports = {
other: [],
proxyHttp: false, // false=只代理HTTPS请求 true=同时代理HTTP和HTTPS请求
setEnv: false,

// 排除中国域名 所需配置
excludeChinaDomainAllowList: true, // 是否排除中国域名,默认:需要排除
autoUpdateChinaDomainAllowList: true, // 是否自动更新中国域名
remoteChinaDomainAllowListFileUrl: 'https://raw.githubusercontent.com/pluwen/china-domain-allowlist/refs/heads/main/allow-list.sorl',
chinaDomainAllowListFileAbsolutePath: null, // 自定义 china-domain-allowlist.txt 文件位置,可以是本地文件路径
chinaDomainAllowListFilePath: './extra/proxy/china-domain-allowlist.txt', // 内置中国域名文件

// 自定义系统代理排除列表
excludeIpList: {
// region 常用国内可访问域名

Expand Down
152 changes: 152 additions & 0 deletions packages/core/src/shell/scripts/set-system-proxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const execute = Shell.execute
const execFile = Shell.execFile
const log = require('../../../utils/util.log')
const extraPath = require('../extra-path/index')
const fs = require('fs')
const path = require('path')
const request = require('request')

let config = null
function loadConfig () {
Expand Down Expand Up @@ -47,6 +50,137 @@ async function _winUnsetProxy (exec, setEnv) {
}
}

function getChinaDomainAllowListTmpFilePath () {
return path.join(config.get().server.setting.userBasePath, '/china-domain-allowlist.txt')
}

async function downloadChinaDomainAllowListAsync () {
loadConfig()

const remoteFileUrl = config.get().proxy.remoteChinaDomainAllowListFileUrl
log.info('开始下载远程 china-domain-allowlist.txt 文件:', remoteFileUrl)
request(remoteFileUrl, (error, response, body) => {
if (error) {
log.error('下载远程 china-domain-allowlist.txt 文件失败, error:', error, ', response:', response, ', body:', body)
return
}
if (response && response.statusCode === 200) {
if (body == null || body.length < 100) {
log.warn('下载远程 china-domain-allowlist.txt 文件成功,但内容为空或内容太短,判断为无效的 china-domain-allowlist.txt 文件:', remoteFileUrl, ', body:', body)
return
} else {
log.info('下载远程 china-domain-allowlist.txt 文件成功:', remoteFileUrl)
}

let fileTxt = body
try {
if (fileTxt.indexOf('*.') < 0) {
fileTxt = Buffer.from(fileTxt, 'base64').toString('utf8')
// log.debug('解析 base64 后的 china-domain-allowlist:', fileTxt)
}
} catch (e) {
if (fileTxt.indexOf('*.') < 0) {
log.error(`远程 china-domain-allowlist.txt 文件内容即不是base64格式,也不是要求的格式,url: ${remoteFileUrl},body: ${body}`)
return
}
}

// 保存到本地
saveChinaDomainAllowListFile(fileTxt)
} else {
log.error('下载远程 china-domain-allowlist.txt 文件失败, response:', response, ', body:', body)
}
})
}

function loadLastModifiedTimeFromTxt (fileTxt) {
const matched = fileTxt.match(/(?<=; Update Date: )[^\r\n]+/g)
if (matched && matched.length > 0) {
try {
return new Date(matched[0])
} catch (ignore) {
return null
}
}
}

// 保存 中国域名白名单 内容到 `~/china-domain-allowlist.txt.txt` 文件中
function saveChinaDomainAllowListFile (fileTxt) {
const filePath = getChinaDomainAllowListTmpFilePath()
fs.writeFileSync(filePath, fileTxt.replaceAll(/\r\n?/g, '\n'))
log.info('保存 china-domain-allowlist.txt 文件成功:', filePath)

// 尝试解析和修改 china-domain-allowlist.txt 文件时间
const lastModifiedTime = loadLastModifiedTimeFromTxt(fileTxt)
if (lastModifiedTime) {
fs.stat(filePath, (err, stats) => {
if (err) {
log.error('修改 china-domain-allowlist.txt 文件时间失败:', err)
return
}

// 修改文件的访问时间和修改时间为当前时间
fs.utimes(filePath, lastModifiedTime, lastModifiedTime, (utimesErr) => {
if (utimesErr) {
log.error('修改 china-domain-allowlist.txt 文件时间失败:', utimesErr)
} else {
log.info(`'${filePath}' 文件的修改时间已更新为其最近更新时间 '${formatDate(lastModifiedTime)}'`)
}
})
})
}

return filePath
}

function formatDate (date) {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}

function getChinaDomainAllowList () {
loadConfig()

if (!config.get().proxy.excludeChinaDomainAllowList) {
return null
}

// 判断是否需要自动更新中国域名
let fileAbsolutePath = config.get().proxy.chinaDomainAllowListFileAbsolutePath
if (!fileAbsolutePath && config.get().proxy.autoUpdateChinaDomainAllowList) {
// 异步下载,下载成功后,下次系统代理生效
downloadChinaDomainAllowListAsync().then()
}

// 加载本地文件
if (!fileAbsolutePath) {
const tmpFilePath = getChinaDomainAllowListTmpFilePath()
if (fs.existsSync(tmpFilePath)) {
// 如果临时文件已存在,则使用临时文件
fileAbsolutePath = tmpFilePath
log.info('读取已下载的 china-domain-allowlist.txt 文件:', fileAbsolutePath)
} else {
// 如果临时文件不存在,则使用内置文件
fileAbsolutePath = path.join(__dirname, '../../gui/', config.get().proxy.chinaDomainAllowListFilePath)
log.info('读取内置的 china-domain-allowlist.txt 文件:', fileAbsolutePath)
}
} else {
log.info('读取自定义路径的 china-domain-allowlist.txt 文件:', fileAbsolutePath)
}

try {
return fs.readFileSync(fileAbsolutePath).toString()
} catch (e) {
log.error('读取 china-domain-allowlist.txt 文件失败:', fileAbsolutePath)
return null
}
}

async function _winSetProxy (exec, ip, port, setEnv) {
// 延迟加载config
loadConfig()
Expand All @@ -58,6 +192,24 @@ async function _winSetProxy (exec, ip, port, setEnv) {
}
}

// 排除中国域名
if (config.get().proxy.excludeChinaDomainAllowList) {
try {
let chinaDomainAllowList = getChinaDomainAllowList()
if (chinaDomainAllowList) {
chinaDomainAllowList = (chinaDomainAllowList + '\n').replaceAll(/[\r\n]+/g, '\n').replaceAll(/[^\n]*[^*.a-zA-Z\d-\n]+[^\n]*\r?\n/g, '').replaceAll(/\s*\n+\s*/g, ';')
if (chinaDomainAllowList) {
excludeIpStr += chinaDomainAllowList
log.info('系统代理排除列表拼接中国域名')
} else {
log.info('中国域名为空,不进行系统代理排除列表拼接中国域名')
}
}
} catch (e) {
log.error('系统代理排除列表拼接中国域名失败:', e)
}
}

const proxyPath = extraPath.getProxyExePath()
const execFun = 'global'

Expand Down
Loading