fix: 优化混合订阅生成逻辑,修复不规范IP格式处理,增强订阅内容的安全性

This commit is contained in:
cmliu
2025-12-11 17:30:56 +08:00
parent f5c80f5a38
commit 84781a3c78

View File

@@ -8,7 +8,7 @@ export default {
const url = new URL(request.url);
const UA = request.headers.get('User-Agent') || 'null';
const upgradeHeader = request.headers.get('Upgrade');
const 管理员密码 = env.ADMIN || env.admin || env.PASSWORD || env.password || env.pswd || env.TOKEN || env.KEY;
const 管理员密码 = env.ADMIN || env.admin || env.PASSWORD || env.password || env.pswd || env.TOKEN || env.KEY || env.UUID || env.uuid;
const 加密秘钥 = env.KEY || '勿动此默认密钥有需求请自行通过添加变量KEY进行修改';
const userIDMD5 = await MD5MD5(管理员密码 + 加密秘钥);
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
@@ -229,53 +229,68 @@ export default {
if (订阅类型 === 'mixed') {
const 节点路径 = config_JSON.启用0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH;
const TLS分片参数 = config_JSON.TLS分片 == 'Shadowrocket' ? `&fragment=${encodeURIComponent('1,40-60,30-50,tlshello')}` : config_JSON.TLS分片 == 'Happ' ? `&fragment=${encodeURIComponent('3,1,tlshello')}` : '';
const 完整优选列表 = config_JSON.优选订阅生成.本地IP库.随机IP ? (await 生成随机IP(request, config_JSON.优选订阅生成.本地IP库.随机数量, config_JSON.优选订阅生成.本地IP库.指定端口))[0] : await env.KV.get('ADD.txt') ? await 整理成数组(await env.KV.get('ADD.txt')) : (await 生成随机IP(request, config_JSON.优选订阅生成.本地IP库.随机数量, config_JSON.优选订阅生成.本地IP库.指定端口))[0];
const 优选API = [], 优选IP = [], 其他节点 = [];
for (const 元素 of 完整优选列表) {
if (元素.toLowerCase().startsWith('https://')) 优选API.push(元素);
else if (元素.toLowerCase().includes('://')) 其他节点.push(元素);
else 优选IP.push(元素);
}
const 其他节点LINK = 其他节点.join('\n') + '\n';
let 完整优选IP = [], 其他节点LINK = '';
if (!url.searchParams.has('sub') && config_JSON.优选订阅生成.local) { // 本地生成订阅
const 完整优选列表 = config_JSON.优选订阅生成.本地IP库.随机IP ? (await 生成随机IP(request, config_JSON.优选订阅生成.本地IP库.随机数量, config_JSON.优选订阅生成.本地IP库.指定端口))[0] : await env.KV.get('ADD.txt') ? await 整理成数组(await env.KV.get('ADD.txt')) : (await 生成随机IP(request, config_JSON.优选订阅生成.本地IP库.随机数量, config_JSON.优选订阅生成.本地IP库.指定端口))[0];
const 优选API = [], 优选IP = [], 其他节点 = [];
for (const 元素 of 完整优选列表) {
if (元素.toLowerCase().startsWith('https://')) 优选API.push(元素);
else if (元素.toLowerCase().includes('://')) 其他节点.push(元素);
else 优选IP.push(元素);
}
其他节点LINK = 其他节点.join('\n') + '\n';
const 优选API的IP = await 请求优选API(优选API);
const 完整优选IP = [...new Set(优选IP.concat(优选API的IP))];
订阅内容 = 完整优选IP.map(原始地址 => {
// 统一正则: 匹配 域名/IPv4/IPv6地址 + 可选端口 + 可选备注
// 示例:
// - 域名: hj.xmm1993.top:2096#备注 或 example.com
// - IPv4: 166.0.188.128:443#Los Angeles 或 166.0.188.128
// - IPv6: [2606:4700::]:443#CMCC 或 [2606:4700::]
const regex = /^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/;
const match = 原始地址.match(regex);
let 节点地址, 节点端口 = "443", 节点备注;
if (match) {
节点地址 = match[1]; // IP地址或域名(可能带方括号)
节点端口 = match[2] || "443"; // 端口,默认443
节点备注 = match[3] || 节点地址; // 备注,默认为地址本身
} else {
// 不规范的格式跳过处理返回null
console.warn(`[订阅内容] 不规范的IP格式已忽略: ${原始地址}`);
return null;
}
const 节点HOST = 随机替换通配符(host);
return `${协议类型}://${config_JSON.UUID}@${节点地址}:${节点端口}?security=tls&type=${config_JSON.传输协议}&host=${节点HOST}&sni=${节点HOST}&path=${encodeURIComponent(config_JSON.随机路径 ? 随机路径() + 节点路径 : 节点路径) + TLS分片参数}&encryption=none${config_JSON.跳过证书验证 ? '&allowInsecure=1' : ''}#${encodeURIComponent(节点备注)}`;
}).filter(item => item !== null).join('\n');
订阅内容 = btoa(其他节点LINK + 订阅内容);
完整优选IP = [...new Set(优选IP.concat(优选API的IP))];
} else { // 优选订阅生成器
let 优选订阅生成器HOST = url.searchParams.get('sub') || config_JSON.优选订阅生成.SUB;
优选订阅生成器HOST = 优选订阅生成器HOST && !/^https?:\/\//i.test(优选订阅生成器HOST) ? `https://${优选订阅生成器HOST}` : 优选订阅生成器HOST;
const 优选订阅生成器URL = `${优选订阅生成器HOST}/sub?host=example.com&${协议类型 === ('v' + 'le' + 'ss') ? 'uuid' : 'pw'}=00000000-0000-4000-8000-000000000000&path=${encodeURIComponent(config_JSON.随机路径 ? 随机路径() + 节点路径 : 节点路径) + TLS分片参数}&type=${config_JSON.传输协议}`;
const 优选订阅生成器URL = `${优选订阅生成器HOST}/sub?host=example.com&uuid=00000000-0000-4000-8000-000000000000`;
try {
const response = await fetch(优选订阅生成器URL, { headers: { 'User-Agent': 'v2rayN/edge' + 'tunnel (https://github.com/cmliu/edge' + 'tunnel)' } });
if (response.ok) 订阅内容 = btoa(其他节点LINK + atob(await response.text()));
else return new Response('优选订阅生成器异常:' + response.statusText, { status: response.status });
if (!response.ok) return new Response('优选订阅生成器异常:' + response.statusText, { status: response.status });
const 优选订阅生成器返回订阅内容 = atob(await response.text());
const 订阅行列表 = 优选订阅生成器返回订阅内容.includes('\r\n') ? 优选订阅生成器返回订阅内容.split('\r\n') : 优选订阅生成器返回订阅内容.split('\n');
for (const 行内容 of 订阅行列表) {
if (!行内容.trim()) continue; // 跳过空行
if (行内容.includes('00000000-0000-4000-8000-000000000000') && 行内容.includes('example.com')) { // 这是优选IP行提取 域名:端口#备注
const 地址匹配 = 行内容.match(/:\/\/[^@]+@([^?]+)/);
if (地址匹配) {
let 地址端口 = 地址匹配[1], 备注 = ''; // 域名:端口 或 IP:端口
const 备注匹配 = 行内容.match(/#(.+)$/);
if (备注匹配) 备注 = '#' + decodeURIComponent(备注匹配[1]);
完整优选IP.push(地址端口 + 备注);
}
} else 其他节点LINK += 行内容 + '\n';
}
} catch (error) {
return new Response('优选订阅生成器异常:' + error.message, { status: 403 });
}
}
订阅内容 = 完整优选IP.map(原始地址 => {
// 统一正则: 匹配 域名/IPv4/IPv6地址 + 可选端口 + 可选备注
// 示例:
// - 域名: hj.xmm1993.top:2096#备注 或 example.com
// - IPv4: 166.0.188.128:443#Los Angeles 或 166.0.188.128
// - IPv6: [2606:4700::]:443#CMCC 或 [2606:4700::]
const regex = /^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/;
const match = 原始地址.match(regex);
let 节点地址, 节点端口 = "443", 节点备注;
if (match) {
节点地址 = match[1]; // IP地址或域名(可能带方括号)
节点端口 = match[2] || "443"; // 端口,默认443
节点备注 = match[3] || 节点地址; // 备注,默认为地址本身
} else {
// 不规范的格式跳过处理返回null
console.warn(`[订阅内容] 不规范的IP格式已忽略: ${原始地址}`);
return null;
}
return `${协议类型}://00000000-0000-4000-8000-000000000000@${节点地址}:${节点端口}?security=tls&type=${config_JSON.传输协议}&host=example.com&sni=example.com&path=${encodeURIComponent(config_JSON.随机路径 ? 随机路径() + 节点路径 : 节点路径) + TLS分片参数}&encryption=none${config_JSON.跳过证书验证 ? '&allowInsecure=1' : ''}#${encodeURIComponent(节点备注)}`;
}).filter(item => item !== null).join('\n');
} else { // 订阅转换
const 订阅转换URL = `${config_JSON.订阅转换配置.SUBAPI}/sub?target=${订阅类型}&url=${encodeURIComponent(url.protocol + '//' + url.host + '/sub?target=mixed&token=' + 订阅TOKEN + (url.searchParams.has('sub') && url.searchParams.get('sub') != '' ? `&sub=${url.searchParams.get('sub')}` : ''))}&config=${encodeURIComponent(config_JSON.订阅转换配置.SUBCONFIG)}&emoji=${config_JSON.订阅转换配置.SUBEMOJI}&scv=${config_JSON.跳过证书验证}`;
try {
@@ -288,10 +303,11 @@ export default {
return new Response('订阅转换后端异常:' + error.message, { status: 403 });
}
}
if (订阅类型 === 'mixed') {
订阅内容 = 批量替换域名(atob(订阅内容).replace(/00000000-0000-4000-8000-000000000000/g, config_JSON.UUID), host);
if (!ua.includes('mozilla')) 订阅内容 = btoa(订阅内容);
} else 订阅内容 = 批量替换域名(订阅内容.replace(/00000000-0000-4000-8000-000000000000/g, config_JSON.UUID), host);
if (!ua.includes('subconverter')) 订阅内容 = 批量替换域名(订阅内容.replace(/00000000-0000-4000-8000-000000000000/g, config_JSON.UUID), host)
if (!ua.includes('mozilla') && 订阅类型 === 'mixed') 订阅内容 = btoa(订阅内容);
if (订阅类型 === 'singbox') {
订阅内容 = JSON.stringify(JSON.parse(订阅内容), null, 2);
responseHeaders["content-type"] = 'application/json; charset=utf-8';