Compare commits

..

10 Commits

Author SHA1 Message Date
xream
feb207b333 fix: servername/sni priority over wss host 2023-08-22 17:28:39 +08:00
xream
9ac1112b37 fix: VMess URI alterId parseInt 2023-08-22 15:29:55 +08:00
xream
96769598ef fix: QX tls 2023-08-22 00:42:53 +08:00
xream
f8ed6a3342 fix: QX tls 2023-08-22 00:08:53 +08:00
xream
99b19c410d fix: vmess/vless http-opts.path/http-opts.headers.Host must be an array in some clients 2023-08-21 22:16:07 +08:00
xream
9e54507bbb fix: double quotes in Surge vmess ws-headers Host 2023-08-21 21:20:31 +08:00
xream
20afa0ad22 Surge 默认模块不带 ability 参数; 分离出固定带参和不带参的模块 2023-08-20 17:22:51 +08:00
walkxspace
c5b6960b35 Update geo.js (#231) 2023-08-19 11:44:55 +08:00
xream
4dd86cb368 feat: Added replaceArtifact API 2023-08-18 13:48:37 +08:00
xream
4a0319e95f fix: flexible cipher for Loon 2023-08-15 21:22:33 +08:00
16 changed files with 184 additions and 84 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.12",
"version": "2.14.20",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {

View File

@@ -1,5 +1,5 @@
import download from '@/utils/download';
import { isIPv4, isIPv6 } from '@/utils';
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
import PROXY_PREPROCESSORS from './preprocessors';
import PROXY_PRODUCERS from './producers';
@@ -36,6 +36,10 @@ function parse(raw) {
if (lastParser) {
const [proxy, error] = tryParse(lastParser, line);
if (!error) {
// 前面已经处理过普通情况下的 SNI, 这里显式设置 SNI, 防止之后解析成 IP 后丢失域名 SNI
if (proxy.tls && !proxy.sni && !isIP(proxy.server)) {
proxy.sni = proxy.server;
}
proxies.push(proxy);
success = true;
}
@@ -182,3 +186,7 @@ function safeMatch(parser, line) {
return false;
}
}
function isIP(ip) {
return isIPv4(ip) || isIPv6(ip);
}

View File

@@ -218,12 +218,16 @@ function URI_VMess() {
port: params.port,
cipher: getIfPresent(params.scy, 'auto'),
uuid: params.id,
alterId: getIfPresent(params.aid, 0),
alterId: parseInt(getIfPresent(params.aid, 0)),
tls: params.tls === 'tls' || params.tls === true,
'skip-cert-verify': isPresent(params.verify_cert)
? !params.verify_cert
: undefined,
};
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
if (proxy.tls && proxy.sni) {
proxy.sni = params.sni;
}
// handle obfs
if (params.net === 'ws') {
proxy.network = 'ws';
@@ -231,7 +235,9 @@ function URI_VMess() {
path: getIfNotBlank(params.path),
headers: { Host: getIfNotBlank(params.host) },
};
if (proxy.tls && params.host) {
// https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml#L413
// sni 优先级应高于 host
if (proxy.tls && !proxy.sni && params.host) {
proxy.sni = params.host;
}
}

View File

@@ -25,6 +25,9 @@ const grammars = String.raw`
proxy.network = "ws";
$set(proxy, "ws-opts.path", obfs.path);
$set(proxy, "ws-opts.headers", obfs['ws-headers']);
if (proxy['ws-opts'] && proxy['ws-opts']['headers'] && proxy['ws-opts']['headers'].Host) {
proxy['ws-opts']['headers'].Host = proxy['ws-opts']['headers'].Host.replace(/^"(.*)"$/, '$1')
}
}
}
}

View File

@@ -23,6 +23,9 @@
proxy.network = "ws";
$set(proxy, "ws-opts.path", obfs.path);
$set(proxy, "ws-opts.headers", obfs['ws-headers']);
if (proxy['ws-opts'] && proxy['ws-opts']['headers'] && proxy['ws-opts']['headers'].Host) {
proxy['ws-opts']['headers'].Host = proxy['ws-opts']['headers'].Host.replace(/^"(.*)"$/, '$1')
}
}
}
}

View File

@@ -55,6 +55,26 @@ export default function Clash_Producer() {
}
}
if (
['vmess', 'vless'].includes(proxy.type) &&
proxy.network === 'http'
) {
let httpPath = proxy['http-opts']?.path;
if (
isPresent(proxy, 'http-opts.path') &&
!Array.isArray(httpPath)
) {
proxy['http-opts'].path = [httpPath];
}
let httpHost = proxy['http-opts']?.headers?.Host;
if (
isPresent(proxy, 'http-opts.headers.Host') &&
!Array.isArray(httpHost)
) {
proxy['http-opts'].headers.Host = [httpHost];
}
}
delete proxy['tls-fingerprint'];
return ' - ' + JSON.stringify(proxy) + '\n';
})

View File

@@ -127,9 +127,7 @@ function trojan(proxy) {
function vmess(proxy) {
const result = new Result(proxy);
result.append(
`${proxy.name}=vmess,${proxy.server},${proxy.port},${
proxy.cipher === 'auto' ? 'none' : proxy.cipher
},"${proxy.uuid}"`,
`${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
);
// transport
@@ -146,12 +144,14 @@ function vmess(proxy) {
);
} else if (proxy.network === 'http') {
result.append(`,transport=http`);
let httpPath = proxy['http-opts']?.path;
let httpHost = proxy['http-opts']?.headers?.Host;
result.appendIfPresent(
`,path=${proxy['http-opts'].path}`,
`,path=${Array.isArray(httpPath) ? httpPath[0] : httpPath}`,
'http-opts.path',
);
result.appendIfPresent(
`,host=${proxy['http-opts'].headers.Host}`,
`,host=${Array.isArray(httpHost) ? httpHost[0] : httpHost}`,
'http-opts.headers.Host',
);
} else {
@@ -208,12 +208,14 @@ function vless(proxy) {
);
} else if (proxy.network === 'http') {
result.append(`,transport=http`);
let httpPath = proxy['http-opts']?.path;
let httpHost = proxy['http-opts']?.headers?.Host;
result.appendIfPresent(
`,path=${proxy['http-opts'].path}`,
`,path=${Array.isArray(httpPath) ? httpPath[0] : httpPath}`,
'http-opts.path',
);
result.appendIfPresent(
`,host=${proxy['http-opts'].headers.Host}`,
`,host=${Array.isArray(httpHost) ? httpHost[0] : httpHost}`,
'http-opts.headers.Host',
);
} else {

View File

@@ -62,18 +62,20 @@ function shadowsocks(proxy) {
);
}
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
if (needTls(proxy)) {
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -150,18 +152,20 @@ function trojan(proxy) {
append(`,over-tls=true`);
}
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
if (needTls(proxy)) {
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -206,12 +210,14 @@ function vmess(proxy) {
} else {
throw new Error(`network ${proxy.network} is unsupported`);
}
let httpPath = proxy[`${proxy.network}-opts`]?.path;
let httpHost = proxy[`${proxy.network}-opts`]?.headers?.Host;
appendIfPresent(
`,obfs-uri=${proxy[`${proxy.network}-opts`].path}`,
`,obfs-uri=${Array.isArray(httpPath) ? httpPath[0] : httpPath}`,
`${proxy.network}-opts.path`,
);
appendIfPresent(
`,obfs-host=${proxy[`${proxy.network}-opts`].headers.Host}`,
`,obfs-host=${Array.isArray(httpHost) ? httpHost[0] : httpHost}`,
`${proxy.network}-opts.headers.Host`,
);
} else {
@@ -219,18 +225,20 @@ function vmess(proxy) {
if (proxy.tls) append(`,obfs=over-tls`);
}
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
if (needTls(proxy)) {
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// AEAD
if (isPresent(proxy, 'aead')) {
@@ -266,18 +274,20 @@ function http(proxy) {
}
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
if (needTls(proxy)) {
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -306,18 +316,20 @@ function socks5(proxy) {
}
appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
if (needTls(proxy)) {
// tls fingerprint
appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
// tls verification
appendIfPresent(
`,tls-verification=${!proxy['skip-cert-verify']}`,
'skip-cert-verify',
);
appendIfPresent(`,tls-host=${proxy.sni}`, 'sni');
}
// tfo
appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -332,11 +344,5 @@ function socks5(proxy) {
}
function needTls(proxy) {
return (
proxy.tls ||
proxy.sni ||
typeof proxy['skip-cert-verify'] !== 'undefined' ||
typeof proxy['tls-fingerprint'] !== 'undefined' ||
typeof proxy['tls-host'] !== 'undefined'
);
return proxy.tls;
}

View File

@@ -58,6 +58,26 @@ export default function Stash_Producer() {
}
}
if (
['vmess', 'vless'].includes(proxy.type) &&
proxy.network === 'http'
) {
let httpPath = proxy['http-opts']?.path;
if (
isPresent(proxy, 'http-opts.path') &&
!Array.isArray(httpPath)
) {
proxy['http-opts'].path = [httpPath];
}
let httpHost = proxy['http-opts']?.headers?.Host;
if (
isPresent(proxy, 'http-opts.headers.Host') &&
!Array.isArray(httpHost)
) {
proxy['http-opts'].headers.Host = [httpHost];
}
}
delete proxy['tls-fingerprint'];
return ' - ' + JSON.stringify(proxy) + '\n';
})

View File

@@ -301,7 +301,13 @@ function handleTransport(result, proxy) {
if (isPresent(proxy, 'ws-opts.headers')) {
const headers = proxy['ws-opts'].headers;
const value = Object.keys(headers)
.map((k) => `${k}:${headers[k]}`)
.map((k) => {
let v = headers[k];
if (['Host'].includes(k)) {
v = `"${v}"`;
}
return `${k}:${v}`;
})
.join('|');
if (isNotBlank(value)) {
result.append(`,ws-headers=${value}`);

View File

@@ -65,10 +65,15 @@ export default function URI_Producer() {
net: proxy.network || 'tcp',
tls: proxy.tls ? 'tls' : '',
};
if (proxy.tls && proxy.sni) {
result.sni = proxy.sni;
}
// obfs
if (proxy.network === 'ws') {
result.path = proxy['ws-opts'].path || '/';
result.host = proxy['ws-opts'].headers.Host || proxy.server;
if (proxy['ws-opts'].headers.Host) {
result.host = proxy['ws-opts'].headers.Host;
}
}
result = 'vmess://' + Base64.encode(JSON.stringify(result));
break;

View File

@@ -19,7 +19,10 @@ export default function register($app) {
if (!$.read(ARTIFACTS_KEY)) $.write({}, ARTIFACTS_KEY);
// RESTful APIs
$app.route('/api/artifacts').get(getAllArtifacts).post(createArtifact);
$app.route('/api/artifacts')
.get(getAllArtifacts)
.post(createArtifact)
.put(replaceArtifact);
$app.route('/api/artifact/:name')
.get(getArtifact)
@@ -32,6 +35,12 @@ function getAllArtifacts(req, res) {
success(res, allArtifacts);
}
function replaceArtifact(req, res) {
const allArtifacts = req.body;
$.write(allArtifacts, ARTIFACTS_KEY);
success(res);
}
async function getArtifact(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
@@ -131,7 +140,7 @@ async function deleteArtifact(req, res) {
files[encodeURIComponent(artifact.name)] = {
content: '',
};
// 当别的Sub 删了同步订阅 或 gist里面删了 当前设备没有删除 时 无法删除的bug
// 当别的Sub 删了同步订阅 或 gist里面删了 当前设备没有删除 时 无法删除的bug
try {
await syncToGist(files);
} catch (i) {

View File

@@ -155,7 +155,7 @@ export function getFlag(name) {
'🇲🇴': ['Macao', '澳门', '澳門', 'CTM'],
'🇲🇹': ['Malta', '马耳他'],
'🇲🇽': ['Mexico', '墨西哥'],
'🇲🇾': ['Malaysia', '马来西亚', '馬來西亞', '吉隆坡', '大馬'],
'🇲🇾': ['Malaysia', '马来', '馬來', '吉隆坡', '大馬'],
'🇳🇱': ['Netherlands', '荷兰', '荷蘭', '尼德蘭', '阿姆斯特丹'],
'🇳🇴': ['Norway', '挪威'],
'🇳🇵': ['Nepal', '尼泊尔'],

View File

@@ -1,5 +1,5 @@
#!name=Sub-Store
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用原版
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用带 ability 参数
[MITM]
hostname = %APPEND% sub.store

View File

@@ -0,0 +1,11 @@
#!name=Sub-Store
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 可能会爆内存, 如果不需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用不带 ability 参数版本
[MITM]
hostname = %APPEND% sub.store
[Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js

View File

@@ -1,10 +1,11 @@
#!name=Sub-Store
#!desc=高级订阅管理工具 @Peng-YM
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用带 ability 参数
[MITM]
hostname=%APPEND% sub.store
hostname = %APPEND% sub.store
[Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js