diff --git a/backend/package.json b/backend/package.json index 28097dc..bd4fabd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.30", + "version": "2.14.31", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/parsers/index.js b/backend/src/core/proxy-utils/parsers/index.js index a8708ec..cae041a 100644 --- a/backend/src/core/proxy-utils/parsers/index.js +++ b/backend/src/core/proxy-utils/parsers/index.js @@ -306,6 +306,85 @@ function URI_VMess() { return { name, test, parse }; } +function URI_VLESS() { + const name = 'URI VLESS Parser'; + const test = (line) => { + return /^vless:\/\//.test(line); + }; + const parse = (line) => { + line = line.split('vless://')[1]; + // eslint-disable-next-line no-unused-vars + let [__, uuid, server, port, addons, name] = + /^(.*?)@(.*?):(\d+)\/?\?(.*?)(?:#(.*?))$/.exec(line); + port = parseInt(`${port}`, 10); + uuid = decodeURIComponent(uuid); + name = decodeURIComponent(name) ?? `VLESS ${server}:${port}`; + const proxy = { + type: 'vless', + name, + server, + port, + uuid, + }; + const params = {}; + for (const addon of addons.split('&')) { + const [key, valueRaw] = addon.split('='); + let value = valueRaw; + value = decodeURIComponent(valueRaw); + params[key] = value; + } + + proxy.tls = params.security && params.security !== 'none'; + proxy.sni = params.sni; + proxy.flow = params.flow; + proxy['client-fingerprint'] = params.fp; + proxy.alpn = params.alpn ? params.alpn.split(',') : undefined; + proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.allowInsecure); + + if (['reality'].includes(params.security)) { + const opts = {}; + if (params.pbk) { + opts['public-key'] = params.pbk; + } + if (params.sid) { + opts['short-id'] = params.sid; + } + if (Object.keys(opts).length > 0) { + proxy[`${params.security}-opts`] = opts; + } + } + + proxy.network = params.type; + if (proxy.network && !['tcp', 'none'].includes(proxy.network)) { + const opts = {}; + if (params.path) { + opts.path = params.path; + } + if (params.host) { + opts.headers = { Host: params.host }; + } + if (params.serviceName) { + opts[`${proxy.network}-service-name`] = params.serviceName; + } + if (Object.keys(opts).length > 0) { + proxy[`${proxy.network}-opts`] = opts; + } + } + + if (proxy.tls && !proxy.sni) { + if (proxy.network === 'ws') { + proxy.sni = proxy['ws-opts']?.headers?.Host; + } else if (proxy.network === 'http') { + let httpHost = proxy['http-opts']?.headers?.Host; + proxy.sni = Array.isArray(httpHost) ? httpHost[0] : httpHost; + } + } + + return proxy; + }; + return { name, test, parse }; +} + // Trojan URI format function URI_Trojan() { const name = 'URI Trojan Parser'; @@ -680,6 +759,7 @@ export default [ URI_SS(), URI_SSR(), URI_VMess(), + URI_VLESS(), URI_Trojan(), Clash_All(), Surge_SS(), diff --git a/backend/src/core/proxy-utils/producers/uri.js b/backend/src/core/proxy-utils/producers/uri.js index a29cce0..34b6234 100644 --- a/backend/src/core/proxy-utils/producers/uri.js +++ b/backend/src/core/proxy-utils/producers/uri.js @@ -94,6 +94,89 @@ export default function URI_Producer() { } result = 'vmess://' + Base64.encode(JSON.stringify(result)); break; + case 'vless': + let security = 'none'; + const isReality = proxy['reality-opts']; + let sid = ''; + let pbk = ''; + if (isReality) { + security = 'reality'; + const publicKey = proxy['reality-opts']?.['public-key']; + if (publicKey) { + pbk = `&pbk=${encodeURIComponent(publicKey)}`; + } + const shortId = proxy['reality-opts']?.['short-id']; + if (shortId) { + sid = `&sid=${encodeURIComponent(shortId)}`; + } + } else if (proxy.tls) { + security = 'tls'; + } + let alpn = ''; + if (proxy.alpn) { + alpn = `&alpn=${encodeURIComponent( + Array.isArray(proxy.alpn) + ? proxy.alpn + : proxy.alpn.join(','), + )}`; + } + let allowInsecure = ''; + if (proxy['skip-cert-verify']) { + allowInsecure = `&allowInsecure=1`; + } + let sni = ''; + if (proxy.sni) { + sni = `&sni=${encodeURIComponent(proxy.sni)}`; + } + let fp = ''; + if (proxy['client-fingerprint']) { + fp = `&fp=${encodeURIComponent( + proxy['client-fingerprint'], + )}`; + } + let flow = ''; + if (proxy.flow) { + flow = `&flow=${encodeURIComponent(proxy.flow)}`; + } + let vlessTransport = `&type=${encodeURIComponent( + proxy.network, + )}`; + + let vlessTransportServiceName = + proxy[`${proxy.network}-opts`]?.[ + `${proxy.network}-service-name` + ]; + let vlessTransportPath = proxy[`${proxy.network}-opts`]?.path; + let vlessTransportHost = + proxy[`${proxy.network}-opts`]?.headers?.Host; + if (vlessTransportPath) { + vlessTransport += `&path=${encodeURIComponent( + Array.isArray(vlessTransportPath) + ? vlessTransportPath[0] + : vlessTransportPath, + )}`; + } + if (vlessTransportHost) { + vlessTransport += `&host=${encodeURIComponent( + Array.isArray(vlessTransportHost) + ? vlessTransportHost[0] + : vlessTransportHost, + )}`; + } + if (vlessTransportServiceName) { + vlessTransport += `&serviceName=${encodeURIComponent( + vlessTransportServiceName, + )}`; + } + + result = `vless://${proxy.uuid}@${proxy.server}:${ + proxy.port + }?${vlessTransport}&security=${encodeURIComponent( + security, + )}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent( + proxy.name, + )}`; + break; case 'trojan': let trojanTransport = ''; if (proxy.network) {