mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2026-04-06 10:43:16 +08:00
Refactored proxy-utils structure
This commit is contained in:
484
backend/src/core/proxy-utils/parsers/index.js
Normal file
484
backend/src/core/proxy-utils/parsers/index.js
Normal file
@@ -0,0 +1,484 @@
|
||||
import getSurgeParser from './peggy/surge';
|
||||
import getLoonParser from './peggy/loon';
|
||||
import getQXParser from './peggy/qx';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||
// reference: https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html
|
||||
function URI_SS() {
|
||||
const name = 'URI SS Parser';
|
||||
const test = (line) => {
|
||||
return /^ss:\/\//.test(line);
|
||||
};
|
||||
const parse = (line) => {
|
||||
const supported = {};
|
||||
// parse url
|
||||
let content = line.split('ss://')[1];
|
||||
|
||||
const proxy = {
|
||||
name: decodeURIComponent(line.split('#')[1]),
|
||||
type: 'ss',
|
||||
supported,
|
||||
};
|
||||
content = content.split('#')[0]; // strip proxy name
|
||||
// handle IPV4 and IPV6
|
||||
const serverAndPort = content.match(/@([^/]*)(\/|$)/)[1];
|
||||
const portIdx = serverAndPort.lastIndexOf(':');
|
||||
proxy.server = serverAndPort.substring(0, portIdx);
|
||||
proxy.port = serverAndPort.substring(portIdx + 1);
|
||||
|
||||
const userInfo = Base64.decode(content.split('@')[0]).split(':');
|
||||
proxy.cipher = userInfo[0];
|
||||
proxy.password = userInfo[1];
|
||||
|
||||
// handle obfs
|
||||
const idx = content.indexOf('?plugin=');
|
||||
if (idx !== -1) {
|
||||
const pluginInfo = (
|
||||
'plugin=' +
|
||||
decodeURIComponent(content.split('?plugin=')[1].split('&')[0])
|
||||
).split(';');
|
||||
const params = {};
|
||||
for (const item of pluginInfo) {
|
||||
const [key, val] = item.split('=');
|
||||
if (key) params[key] = val || true; // some options like "tls" will not have value
|
||||
}
|
||||
switch (params.plugin) {
|
||||
case 'obfs-local':
|
||||
case 'simple-obfs':
|
||||
proxy.plugin = 'obfs';
|
||||
proxy['plugin-opts'] = {
|
||||
mode: params.obfs,
|
||||
host: params['obfs-host'],
|
||||
};
|
||||
break;
|
||||
case 'v2ray-plugin':
|
||||
proxy.supported = {
|
||||
...supported,
|
||||
Loon: false,
|
||||
Surge: false,
|
||||
};
|
||||
proxy.obfs = 'v2ray-plugin';
|
||||
proxy['plugin-opts'] = {
|
||||
mode: 'websocket',
|
||||
host: params['obfs-host'],
|
||||
path: params.path || '',
|
||||
tls: params.tls || false,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported plugin option: ${params.plugin}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
// Parse URI SSR format, such as ssr://xxx
|
||||
function URI_SSR() {
|
||||
const name = 'URI SSR Parser';
|
||||
const test = (line) => {
|
||||
return /^ssr:\/\//.test(line);
|
||||
};
|
||||
const supported = {
|
||||
Surge: false,
|
||||
};
|
||||
|
||||
const parse = (line) => {
|
||||
line = Base64.decode(line.split('ssr://')[1]);
|
||||
|
||||
// handle IPV6 & IPV4 format
|
||||
let splitIdx = line.indexOf(':origin');
|
||||
if (splitIdx === -1) {
|
||||
splitIdx = line.indexOf(':auth_');
|
||||
}
|
||||
const serverAndPort = line.substring(0, splitIdx);
|
||||
const server = serverAndPort.substring(
|
||||
0,
|
||||
serverAndPort.lastIndexOf(':'),
|
||||
);
|
||||
const port = serverAndPort.substring(
|
||||
serverAndPort.lastIndexOf(':') + 1,
|
||||
);
|
||||
|
||||
let params = line
|
||||
.substring(splitIdx + 1)
|
||||
.split('/?')[0]
|
||||
.split(':');
|
||||
let proxy = {
|
||||
type: 'ssr',
|
||||
server,
|
||||
port,
|
||||
protocol: params[0],
|
||||
cipher: params[1],
|
||||
obfs: params[2],
|
||||
password: Base64.decode(params[3]),
|
||||
supported,
|
||||
};
|
||||
// get other params
|
||||
const other_params = {};
|
||||
line = line.split('/?')[1].split('&');
|
||||
if (line.length > 1) {
|
||||
for (const item of line) {
|
||||
const [key, val] = item.split('=');
|
||||
other_params[key] = val.trim();
|
||||
}
|
||||
}
|
||||
proxy = {
|
||||
...proxy,
|
||||
name: other_params.remarks
|
||||
? Base64.decode(other_params.remarks)
|
||||
: proxy.server,
|
||||
'protocol-param': Base64.decode(
|
||||
other_params.protoparam || '',
|
||||
).replace(/\s/g, ''),
|
||||
'obfs-param': Base64.decode(other_params.obfsparam || '').replace(
|
||||
/\s/g,
|
||||
'',
|
||||
),
|
||||
};
|
||||
return proxy;
|
||||
};
|
||||
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
// V2rayN URI VMess format
|
||||
// reference: 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)
|
||||
|
||||
// Quantumult VMess format
|
||||
function URI_VMess() {
|
||||
const name = 'URI VMess Parser';
|
||||
const test = (line) => {
|
||||
return /^vmess:\/\//.test(line);
|
||||
};
|
||||
const parse = (line) => {
|
||||
const supported = {};
|
||||
line = line.split('vmess://')[1];
|
||||
const content = Base64.decode(line);
|
||||
if (/=\s*vmess/.test(content)) {
|
||||
// Quantumult VMess URI format
|
||||
const partitions = content.split(',').map((p) => p.trim());
|
||||
// get keyword params
|
||||
const params = {};
|
||||
for (const part of partitions) {
|
||||
if (part.indexOf('=') !== -1) {
|
||||
const [key, val] = part.split('=');
|
||||
params[key.trim()] = val.trim();
|
||||
}
|
||||
}
|
||||
|
||||
const proxy = {
|
||||
name: partitions[0].split('=')[0].trim(),
|
||||
type: 'vmess',
|
||||
server: partitions[1],
|
||||
port: partitions[2],
|
||||
cipher: partitions[3],
|
||||
uuid: partitions[4].match(/^"(.*)"$/)[1],
|
||||
tls: params.obfs === 'over-tls' || params.obfs === 'wss',
|
||||
};
|
||||
|
||||
if (typeof params['udp-relay'] !== 'undefined')
|
||||
proxy.udp = JSON.parse(params['udp-relay']);
|
||||
if (typeof params['fast-open'] !== 'undefined')
|
||||
proxy.udp = JSON.parse(params['fast-open']);
|
||||
|
||||
// handle ws headers
|
||||
if (params.obfs === 'ws' || params.obfs === 'wss') {
|
||||
proxy.network = 'ws';
|
||||
proxy['ws-opts'].path = (params['obfs-path'] || '"/"').match(
|
||||
/^"(.*)"$/,
|
||||
)[1];
|
||||
let obfs_host = params['obfs-header'];
|
||||
if (obfs_host && obfs_host.indexOf('Host') !== -1) {
|
||||
obfs_host = obfs_host.match(/Host:\s*([a-zA-Z0-9-.]*)/)[1];
|
||||
}
|
||||
proxy['ws-opts'].headers = {
|
||||
Host: obfs_host || proxy.server, // if no host provided, use the same as server
|
||||
};
|
||||
}
|
||||
|
||||
// handle scert
|
||||
if (proxy.tls && params['"tls-verification"'] === 'false') {
|
||||
proxy['skip-cert-verify'] = true;
|
||||
}
|
||||
|
||||
// handle sni
|
||||
if (proxy.tls && params['obfs-host']) {
|
||||
proxy.sni = params['obfs-host'];
|
||||
}
|
||||
|
||||
return proxy;
|
||||
} else {
|
||||
// V2rayN URI format
|
||||
const params = JSON.parse(content);
|
||||
const proxy = {
|
||||
name: params.ps,
|
||||
type: 'vmess',
|
||||
server: params.add,
|
||||
port: params.port,
|
||||
cipher: 'auto', // V2rayN has no default cipher! use aes-128-gcm as default.
|
||||
uuid: params.id,
|
||||
alterId: params.aid || 0,
|
||||
tls: params.tls === 'tls' || params.tls === true,
|
||||
supported,
|
||||
};
|
||||
// handle obfs
|
||||
if (params.net === 'ws') {
|
||||
proxy.network = 'ws';
|
||||
proxy['ws-opts'] = {
|
||||
path: params.path,
|
||||
headers: { Host: params.host || params.add },
|
||||
};
|
||||
if (proxy.tls && params.host) {
|
||||
proxy.sni = params.host;
|
||||
}
|
||||
}
|
||||
// handle scert
|
||||
if (params.verify_cert === false) {
|
||||
proxy['skip-cert-verify'] = true;
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
// Trojan URI format
|
||||
function URI_Trojan() {
|
||||
const name = 'URI Trojan Parser';
|
||||
const test = (line) => {
|
||||
return /^trojan:\/\//.test(line);
|
||||
};
|
||||
|
||||
const parse = (line) => {
|
||||
const supported = {};
|
||||
line = line.split('trojan://')[1];
|
||||
const [server, port] = line.split('@')[1].split('?')[0].split(':');
|
||||
const name = decodeURIComponent(line.split('#')[1].trim());
|
||||
let paramArr = line.split('?');
|
||||
let sni = null;
|
||||
if (paramArr.length > 1) {
|
||||
paramArr = paramArr[1].split('#')[0].split('&');
|
||||
const params = new Map(
|
||||
paramArr.map((item) => {
|
||||
return item.split('=');
|
||||
}),
|
||||
);
|
||||
sni = params.get('sni');
|
||||
}
|
||||
|
||||
return {
|
||||
name: name || `[Trojan] ${server}`, // trojan uri may have no server tag!
|
||||
type: 'trojan',
|
||||
server,
|
||||
port,
|
||||
password: line.split('@')[0],
|
||||
sni,
|
||||
supported,
|
||||
};
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Clash_All() {
|
||||
const name = 'Clash Parser';
|
||||
const test = (line) => {
|
||||
try {
|
||||
JSON.parse(line);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const parse = (line) => JSON.parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function QX_SS() {
|
||||
const name = 'QX SS Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^shadowsocks\s*=/.test(line.split(',')[0].trim()) &&
|
||||
line.indexOf('ssr-protocol') === -1
|
||||
);
|
||||
};
|
||||
const parse = (line) => {
|
||||
const parser = getQXParser();
|
||||
return parser.parse(line);
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function QX_SSR() {
|
||||
const name = 'QX SSR Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^shadowsocks\s*=/.test(line.split(',')[0].trim()) &&
|
||||
line.indexOf('ssr-protocol') !== -1
|
||||
);
|
||||
};
|
||||
const parse = (line) => getQXParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function QX_VMess() {
|
||||
const name = 'QX VMess Parser';
|
||||
const test = (line) => {
|
||||
return /^vmess\s*=/.test(line.split(',')[0].trim());
|
||||
};
|
||||
const parse = (line) => getQXParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function QX_Trojan() {
|
||||
const name = 'QX Trojan Parser';
|
||||
const test = (line) => {
|
||||
return /^trojan\s*=/.test(line.split(',')[0].trim());
|
||||
};
|
||||
const parse = (line) => getQXParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function QX_Http() {
|
||||
const name = 'QX HTTP Parser';
|
||||
const test = (line) => {
|
||||
return /^http\s*=/.test(line.split(',')[0].trim());
|
||||
};
|
||||
const parse = (line) => getQXParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Loon_SS() {
|
||||
const name = 'Loon SS Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
line.split(',')[0].split('=')[1].trim().toLowerCase() ===
|
||||
'shadowsocks'
|
||||
);
|
||||
};
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Loon_SSR() {
|
||||
const name = 'Loon SSR Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
line.split(',')[0].split('=')[1].trim().toLowerCase() ===
|
||||
'shadowsocksr'
|
||||
);
|
||||
};
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Loon_VMess() {
|
||||
const name = 'Loon VMess Parser';
|
||||
const test = (line) => {
|
||||
// distinguish between surge vmess
|
||||
return (
|
||||
/^.*=\s*vmess/i.test(line.split(',')[0]) &&
|
||||
line.indexOf('username') === -1
|
||||
);
|
||||
};
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Loon_Trojan() {
|
||||
const name = 'Loon Trojan Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^.*=\s*trojan/i.test(line.split(',')[0]) &&
|
||||
line.indexOf('password') === -1
|
||||
);
|
||||
};
|
||||
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Loon_Http() {
|
||||
const name = 'Loon HTTP Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^.*=\s*http/i.test(line.split(',')[0]) &&
|
||||
line.split(',').length === 5 &&
|
||||
line.indexOf('username') === -1 &&
|
||||
line.indexOf('password') === -1
|
||||
);
|
||||
};
|
||||
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Surge_SS() {
|
||||
const name = 'Surge SS Parser';
|
||||
const test = (line) => {
|
||||
return /^.*=\s*ss/.test(line.split(',')[0]);
|
||||
};
|
||||
const parse = (line) => getSurgeParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Surge_VMess() {
|
||||
const name = 'Surge VMess Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^.*=\s*vmess/.test(line.split(',')[0]) &&
|
||||
line.indexOf('username') !== -1
|
||||
);
|
||||
};
|
||||
const parse = (line) => getSurgeParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Surge_Trojan() {
|
||||
const name = 'Surge Trojan Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^.*=\s*trojan/.test(line.split(',')[0]) &&
|
||||
line.indexOf('sni') !== -1
|
||||
);
|
||||
};
|
||||
const parse = (line) => getSurgeParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Surge_Http() {
|
||||
const name = 'Surge HTTP Parser';
|
||||
const test = (line) => {
|
||||
return (
|
||||
/^.*=\s*https?/.test(line.split(',')[0]) && !Loon_Http().test(line)
|
||||
);
|
||||
};
|
||||
const parse = (line) => getSurgeParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
export default [
|
||||
URI_SS(),
|
||||
URI_SSR(),
|
||||
URI_VMess(),
|
||||
URI_Trojan(),
|
||||
Clash_All(),
|
||||
Surge_SS(),
|
||||
Surge_VMess(),
|
||||
Surge_Trojan(),
|
||||
Surge_Http(),
|
||||
Loon_SS(),
|
||||
Loon_SSR(),
|
||||
Loon_VMess(),
|
||||
Loon_Trojan(),
|
||||
Loon_Http(),
|
||||
QX_SS(),
|
||||
QX_SSR(),
|
||||
QX_VMess(),
|
||||
QX_Trojan(),
|
||||
QX_Http(),
|
||||
];
|
||||
179
backend/src/core/proxy-utils/parsers/peggy/loon.js
Normal file
179
backend/src/core/proxy-utils/parsers/peggy/loon.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import * as peggy from 'peggy';
|
||||
const grammars = String.raw`
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
}}
|
||||
|
||||
// per-parser initializer
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const transport = {};
|
||||
const $ = {};
|
||||
|
||||
function handleTransport() {
|
||||
if (transport.type === "tcp") { /* do nothing */ }
|
||||
else if (transport.type === "ws") {
|
||||
proxy.network = "ws";
|
||||
$set(proxy, "ws-opts.path", transport.path);
|
||||
$set(proxy, "ws-opts.headers.Host", transport.host);
|
||||
} else if (transport.type === "http") {
|
||||
proxy.network = "http";
|
||||
$set(proxy, "http-opts.path", transport.path);
|
||||
$set(proxy, "http-opts.headers.Host", transport.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
proxy.obfs = obfs.type;
|
||||
}
|
||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
proxy.plugin = "obfs";
|
||||
$set(proxy, "plugin-opts.mode", obfs.type);
|
||||
$set(proxy, "plugin-opts.host", obfs.host);
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
handleTransport();
|
||||
}
|
||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vless";
|
||||
handleTransport();
|
||||
}
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleTransport();
|
||||
}
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
}
|
||||
|
||||
server = ip/domain
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = j;
|
||||
$.ip = input.substring(start, j).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
throw new Error("Invalid domain: " + domain);
|
||||
}
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
throw new Error("Invalid port number: " + port);
|
||||
}
|
||||
|
||||
method = comma cipher:cipher { proxy.cipher = cipher; }
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
let start, end;
|
||||
let first = true;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ',') {
|
||||
if (first) {
|
||||
start = j + 1;
|
||||
first = false;
|
||||
} else {
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
const match = input.substring(start, end);
|
||||
if (match.indexOf("=") === -1) {
|
||||
$.username = match;
|
||||
peg$currPos = end;
|
||||
return true;
|
||||
}
|
||||
} { proxy.username = $.username; }
|
||||
password = comma '"' match:[^"]* '"' { proxy.password = match.join(""); }
|
||||
uuid = comma '"' match:[^"]+ '"' { proxy.uuid = match.join(""); }
|
||||
|
||||
obfs_ss = comma "obfs-name" equals type:("http"/"tls") { obfs.type = type; }
|
||||
|
||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; }
|
||||
obfs_ssr_param = comma "obfs-param" equals match:$[^,]+ { proxy["obfs-param"] = match; }
|
||||
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
||||
obfs_uri = comma "obfs-uri" equals uri:uri { obfs.path = uri; }
|
||||
uri = $[^,]+
|
||||
|
||||
transport = comma "transport" equals type:("tcp"/"ws"/"http") { transport.type = type; }
|
||||
transport_host = comma "host" equals host:domain { transport.host = host; }
|
||||
transport_path = comma "path" equals path:uri { transport.path = path; }
|
||||
|
||||
ssr_protocol = comma "protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; }
|
||||
ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
||||
|
||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma "tls-name" equals host:domain { proxy.sni = host; }
|
||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
|
||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
_ = [ \r\t]*
|
||||
bool = b:("true"/"false") { return b === "true" }
|
||||
others = comma [^=,]+ equals [^=,]+
|
||||
`;
|
||||
let parser;
|
||||
export default function getParser() {
|
||||
if (!parser) {
|
||||
parser = peggy.generate(grammars);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
169
backend/src/core/proxy-utils/parsers/peggy/loon.peg
Normal file
169
backend/src/core/proxy-utils/parsers/peggy/loon.peg
Normal file
@@ -0,0 +1,169 @@
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
}}
|
||||
|
||||
// per-parser initializer
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const transport = {};
|
||||
const $ = {};
|
||||
|
||||
function handleTransport() {
|
||||
if (transport.type === "tcp") { /* do nothing */ }
|
||||
else if (transport.type === "ws") {
|
||||
proxy.network = "ws";
|
||||
$set(proxy, "ws-opts.path", transport.path);
|
||||
$set(proxy, "ws-opts.headers.Host", transport.host);
|
||||
} else if (transport.type === "http") {
|
||||
proxy.network = "http";
|
||||
$set(proxy, "http-opts.path", transport.path);
|
||||
$set(proxy, "http-opts.headers.Host", transport.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
proxy.obfs = obfs.type;
|
||||
}
|
||||
shadowsocks = tag equals "shadowsocks"i address method password (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
proxy.plugin = "obfs";
|
||||
$set(proxy, "plugin-opts.mode", obfs.type);
|
||||
$set(proxy, "plugin-opts.host", obfs.host);
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
handleTransport();
|
||||
}
|
||||
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vless";
|
||||
handleTransport();
|
||||
}
|
||||
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleTransport();
|
||||
}
|
||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
}
|
||||
|
||||
server = ip/domain
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = j;
|
||||
$.ip = input.substring(start, j).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
throw new Error("Invalid domain: " + domain);
|
||||
}
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
throw new Error("Invalid port number: " + port);
|
||||
}
|
||||
|
||||
method = comma cipher:cipher { proxy.cipher = cipher; }
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
let start, end;
|
||||
let first = true;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ',') {
|
||||
if (first) {
|
||||
start = j + 1;
|
||||
first = false;
|
||||
} else {
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
const match = input.substring(start, end);
|
||||
if (match.indexOf("=") === -1) {
|
||||
$.username = match;
|
||||
peg$currPos = end;
|
||||
return true;
|
||||
}
|
||||
} { proxy.username = $.username; }
|
||||
password = comma '"' match:[^"]* '"' { proxy.password = match.join(""); }
|
||||
uuid = comma '"' match:[^"]+ '"' { proxy.uuid = match.join(""); }
|
||||
|
||||
obfs_ss = comma "obfs-name" equals type:("http"/"tls") { obfs.type = type; }
|
||||
|
||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; }
|
||||
obfs_ssr_param = comma "obfs-param" equals match:$[^,]+ { proxy["obfs-param"] = match; }
|
||||
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
||||
obfs_uri = comma "obfs-uri" equals uri:uri { obfs.path = uri; }
|
||||
uri = $[^,]+
|
||||
|
||||
transport = comma "transport" equals type:("tcp"/"ws"/"http") { transport.type = type; }
|
||||
transport_host = comma "host" equals host:domain { transport.host = host; }
|
||||
transport_path = comma "path" equals path:uri { transport.path = path; }
|
||||
|
||||
ssr_protocol = comma "protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; }
|
||||
ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
||||
|
||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma "tls-name" equals host:domain { proxy.sni = host; }
|
||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
|
||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
_ = [ \r\t]*
|
||||
bool = b:("true"/"false") { return b === "true" }
|
||||
others = comma [^=,]+ equals [^=,]+
|
||||
178
backend/src/core/proxy-utils/parsers/peggy/qx.js
Normal file
178
backend/src/core/proxy-utils/parsers/peggy/qx.js
Normal file
@@ -0,0 +1,178 @@
|
||||
import * as peggy from 'peggy';
|
||||
const grammars = String.raw`
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
}}
|
||||
|
||||
// per-parse initializer
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const $ = {};
|
||||
|
||||
function handleObfs() {
|
||||
if (obfs.type === "ws" || obfs.type === "wss") {
|
||||
proxy.network = "ws";
|
||||
if (obfs.type === 'wss') {
|
||||
proxy.tls = true;
|
||||
}
|
||||
$set(proxy, "ws-opts.path", obfs.path);
|
||||
$set(proxy, "ws-opts.headers.Host", obfs.host);
|
||||
} else if (obfs.type === "over-tls") {
|
||||
proxy.tls = true;
|
||||
proxy.sni = proxy.sni || proxy.server;
|
||||
} else if (obfs.type === "http") {
|
||||
proxy.network = "http";
|
||||
$set(proxy, "http-opts.path", obfs.path);
|
||||
$set(proxy, "http-opts.headers.Host", obfs.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = (trojan/shadowsocks/vmess/http/socks5) {
|
||||
return proxy
|
||||
}
|
||||
|
||||
trojan = "trojan" equals address
|
||||
(password/over_tls/tls_host/tls_verification/obfs/obfs_host/obfs_uri/tag/udp_relay/udp_over_tcp/fast_open/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleObfs();
|
||||
}
|
||||
|
||||
shadowsocks = "shadowsocks" equals address
|
||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/others)* {
|
||||
if (proxy.protocol) {
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
if (obfs.host) proxy["obfs-param"] = obfs.host;
|
||||
if (obfs.type) proxy.obfs = obfs.type;
|
||||
} else {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
proxy.plugin = "obfs";
|
||||
$set(proxy, "plugin-opts", {
|
||||
mode: obfs.type
|
||||
});
|
||||
} else if (obfs.type === "ws" || obfs.type === "wss") {
|
||||
proxy.plugin = "v2ray-plugin";
|
||||
$set(proxy, "plugin-opts.mode", "websocket");
|
||||
if (obfs.type === "wss") {
|
||||
$set(proxy, "plugin-opts.tls", true);
|
||||
}
|
||||
} else if (obfs.type === 'over-tls') {
|
||||
throw new Error('ss over-tls is not supported');
|
||||
}
|
||||
if (obfs.type) {
|
||||
$set(proxy, "plugin-opts.host", obfs.host);
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vmess = "vmess" equals address
|
||||
(uuid/method/over_tls/tls_host/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/others)* {
|
||||
proxy.type = "vmess";
|
||||
handleObfs();
|
||||
}
|
||||
|
||||
http = "http" equals address
|
||||
(username/password/over_tls/tls_host/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)*{
|
||||
proxy.type = "http";
|
||||
}
|
||||
|
||||
socks5 = "socks5" equals address
|
||||
(username/password/password/over_tls/tls_host/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
address = server:server ":" port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
}
|
||||
server = ip/domain
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let end;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
if (input[j] === ":") end = j;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = end || j;
|
||||
$.ip = input.substring(start, end).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
|
||||
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
|
||||
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
|
||||
method = comma "method" equals cipher:cipher { proxy.cipher = cipher };
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
aead = comma "aead" equals flag:bool { proxy.alterId = 0; }
|
||||
|
||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma "tls-host" equals sni:domain { proxy.sni = sni; }
|
||||
tls_verification = comma "tls-verification" equals flag:bool {
|
||||
if (!flag) {
|
||||
proxy["skip-cert-verify"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws") { obfs.type = type; return type; }
|
||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
||||
obfs = comma "obfs" equals type:("wss"/"ws"/"over-tls"/"http") { obfs.type = type; return type; };
|
||||
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
||||
obfs_uri = comma "obfs-uri" equals uri:uri { obfs.path = uri; }
|
||||
|
||||
ssr_protocol = comma "ssr-protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; return protocol; }
|
||||
ssr_protocol_param = comma "ssr-protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
||||
|
||||
uri = $[^,]+
|
||||
|
||||
tag = comma "tag" equals tag:[^=,]+ { proxy.name = tag.join(""); }
|
||||
others = comma [^=,]+ equals [^=,]+
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
_ = [ \r\t]*
|
||||
bool = b:("true"/"false") { return b === "true" }
|
||||
`;
|
||||
let parser;
|
||||
export default function getParser() {
|
||||
if (!parser) {
|
||||
parser = peggy.generate(grammars);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
168
backend/src/core/proxy-utils/parsers/peggy/qx.peg
Normal file
168
backend/src/core/proxy-utils/parsers/peggy/qx.peg
Normal file
@@ -0,0 +1,168 @@
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
}}
|
||||
|
||||
// per-parse initializer
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const $ = {};
|
||||
|
||||
function handleObfs() {
|
||||
if (obfs.type === "ws" || obfs.type === "wss") {
|
||||
proxy.network = "ws";
|
||||
if (obfs.type === 'wss') {
|
||||
proxy.tls = true;
|
||||
}
|
||||
$set(proxy, "ws-opts.path", obfs.path);
|
||||
$set(proxy, "ws-opts.headers.Host", obfs.host);
|
||||
} else if (obfs.type === "over-tls") {
|
||||
proxy.tls = true;
|
||||
proxy.sni = proxy.sni || proxy.server;
|
||||
} else if (obfs.type === "http") {
|
||||
proxy.network = "http";
|
||||
$set(proxy, "http-opts.path", obfs.path);
|
||||
$set(proxy, "http-opts.headers.Host", obfs.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = (trojan/shadowsocks/vmess/http/socks5) {
|
||||
return proxy
|
||||
}
|
||||
|
||||
trojan = "trojan" equals address
|
||||
(password/over_tls/tls_host/tls_verification/obfs/obfs_host/obfs_uri/tag/udp_relay/udp_over_tcp/fast_open/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleObfs();
|
||||
}
|
||||
|
||||
shadowsocks = "shadowsocks" equals address
|
||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/others)* {
|
||||
if (proxy.protocol) {
|
||||
proxy.type = "ssr";
|
||||
// handle ssr obfs
|
||||
if (obfs.host) proxy["obfs-param"] = obfs.host;
|
||||
if (obfs.type) proxy.obfs = obfs.type;
|
||||
} else {
|
||||
proxy.type = "ss";
|
||||
// handle ss obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
proxy.plugin = "obfs";
|
||||
$set(proxy, "plugin-opts", {
|
||||
mode: obfs.type
|
||||
});
|
||||
} else if (obfs.type === "ws" || obfs.type === "wss") {
|
||||
proxy.plugin = "v2ray-plugin";
|
||||
$set(proxy, "plugin-opts.mode", "websocket");
|
||||
if (obfs.type === "wss") {
|
||||
$set(proxy, "plugin-opts.tls", true);
|
||||
}
|
||||
} else if (obfs.type === 'over-tls') {
|
||||
throw new Error('ss over-tls is not supported');
|
||||
}
|
||||
if (obfs.type) {
|
||||
$set(proxy, "plugin-opts.host", obfs.host);
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vmess = "vmess" equals address
|
||||
(uuid/method/over_tls/tls_host/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/others)* {
|
||||
proxy.type = "vmess";
|
||||
handleObfs();
|
||||
}
|
||||
|
||||
http = "http" equals address
|
||||
(username/password/over_tls/tls_host/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)*{
|
||||
proxy.type = "http";
|
||||
}
|
||||
|
||||
socks5 = "socks5" equals address
|
||||
(username/password/password/over_tls/tls_host/tls_verification/tag/fast_open/udp_relay/udp_over_tcp/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
address = server:server ":" port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
}
|
||||
server = ip/domain
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let end;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
if (input[j] === ":") end = j;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = end || j;
|
||||
$.ip = input.substring(start, end).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
|
||||
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
|
||||
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
|
||||
method = comma "method" equals cipher:cipher { proxy.cipher = cipher };
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
aead = comma "aead" equals flag:bool { proxy.alterId = 0; }
|
||||
|
||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma "tls-host" equals sni:domain { proxy.sni = sni; }
|
||||
tls_verification = comma "tls-verification" equals flag:bool {
|
||||
if (!flag) {
|
||||
proxy["skip-cert-verify"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws") { obfs.type = type; return type; }
|
||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
||||
obfs = comma "obfs" equals type:("wss"/"ws"/"over-tls"/"http") { obfs.type = type; return type; };
|
||||
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
||||
obfs_uri = comma "obfs-uri" equals uri:uri { obfs.path = uri; }
|
||||
|
||||
ssr_protocol = comma "ssr-protocol" equals protocol:("origin"/"auth_sha1_v4"/"auth_aes128_md5"/"auth_aes128_sha1"/"auth_chain_a"/"auth_chain_b") { proxy.protocol = protocol; return protocol; }
|
||||
ssr_protocol_param = comma "ssr-protocol-param" equals param:$[^=,]+ { proxy["protocol-param"] = param; }
|
||||
|
||||
uri = $[^,]+
|
||||
|
||||
tag = comma "tag" equals tag:[^=,]+ { proxy.name = tag.join(""); }
|
||||
others = comma [^=,]+ equals [^=,]+
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
_ = [ \r\t]*
|
||||
bool = b:("true"/"false") { return b === "true" }
|
||||
179
backend/src/core/proxy-utils/parsers/peggy/surge.js
Normal file
179
backend/src/core/proxy-utils/parsers/peggy/surge.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import * as peggy from 'peggy';
|
||||
const grammars = String.raw`
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
}}
|
||||
|
||||
// per-parser initializer
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const $ = {};
|
||||
|
||||
function handleWebsocket() {
|
||||
if (obfs.type === "ws") {
|
||||
proxy.network = "ws";
|
||||
$set(proxy, "ws-opts.path", obfs.path);
|
||||
$set(proxy, "ws-opts.headers.Host", obfs.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
proxy.plugin = "obfs";
|
||||
$set(proxy, "plugin-opts.mode", obfs.type);
|
||||
$set(proxy, "plugin-opts.host", obfs.host);
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
handleWebsocket();
|
||||
}
|
||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleWebsocket();
|
||||
}
|
||||
https = tag equals "https" address (username password)? (sni/tls_verification/fast_open/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http" address (username password)? (fast_open/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
||||
proxy.type = "snell";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
$set(proxy, "obfs-opts.mode", obfs.type);
|
||||
$set(proxy, "obfs-opts.host", obfs.host);
|
||||
$set(proxy, "obfs-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_verification/fast_open/others)* {
|
||||
proxy.type = "socks5";
|
||||
proxy.tls = true;
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
}
|
||||
|
||||
server = ip/domain
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = j;
|
||||
$.ip = input.substring(start, j).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
let start, end;
|
||||
let first = true;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ',') {
|
||||
if (first) {
|
||||
start = j + 1;
|
||||
first = false;
|
||||
} else {
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
const match = input.substring(start, end);
|
||||
if (match.indexOf("=") === -1) {
|
||||
$.username = match;
|
||||
peg$currPos = end;
|
||||
return true;
|
||||
}
|
||||
} { proxy.username = $.username; }
|
||||
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
||||
|
||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||
sni = comma "sni" equals sni:domain { proxy.sni = sni; }
|
||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||
|
||||
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.alterId = 0; }
|
||||
|
||||
method = comma "encrypt-method" equals cipher:cipher {
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
|
||||
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
||||
ws_headers = comma "ws-headers" equals "Host:" host:domain {
|
||||
obfs.host = host;
|
||||
}
|
||||
ws_path = comma "ws-path" equals path:uri { obfs.path = path; }
|
||||
|
||||
obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
||||
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
||||
uri = $[^,]+
|
||||
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
|
||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
_ = [ \r\t]*
|
||||
bool = b:("true"/"false") { return b === "true" }
|
||||
others = comma [^=,]+ equals [^=,]+
|
||||
`;
|
||||
let parser;
|
||||
export default function getParser() {
|
||||
if (!parser) {
|
||||
parser = peggy.generate(grammars);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
169
backend/src/core/proxy-utils/parsers/peggy/surge.peg
Normal file
169
backend/src/core/proxy-utils/parsers/peggy/surge.peg
Normal file
@@ -0,0 +1,169 @@
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
}}
|
||||
|
||||
// per-parser initializer
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const $ = {};
|
||||
|
||||
function handleWebsocket() {
|
||||
if (obfs.type === "ws") {
|
||||
proxy.network = "ws";
|
||||
$set(proxy, "ws-opts.path", obfs.path);
|
||||
$set(proxy, "ws-opts.headers.Host", obfs.host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
proxy.plugin = "obfs";
|
||||
$set(proxy, "plugin-opts.mode", obfs.type);
|
||||
$set(proxy, "plugin-opts.host", obfs.host);
|
||||
$set(proxy, "plugin-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
handleWebsocket();
|
||||
}
|
||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "trojan";
|
||||
handleWebsocket();
|
||||
}
|
||||
https = tag equals "https" address (username password)? (sni/tls_verification/fast_open/others)* {
|
||||
proxy.type = "http";
|
||||
proxy.tls = true;
|
||||
}
|
||||
http = tag equals "http" address (username password)? (fast_open/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/fast_open/udp_relay/others)* {
|
||||
proxy.type = "snell";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
$set(proxy, "obfs-opts.mode", obfs.type);
|
||||
$set(proxy, "obfs-opts.host", obfs.host);
|
||||
$set(proxy, "obfs-opts.path", obfs.path);
|
||||
}
|
||||
}
|
||||
socks5 = tag equals "socks5" address (username password)? (fast_open/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (sni/tls_verification/fast_open/others)* {
|
||||
proxy.type = "socks5";
|
||||
proxy.tls = true;
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
}
|
||||
|
||||
server = ip/domain
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = j;
|
||||
$.ip = input.substring(start, j).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
let start, end;
|
||||
let first = true;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ',') {
|
||||
if (first) {
|
||||
start = j + 1;
|
||||
first = false;
|
||||
} else {
|
||||
end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
const match = input.substring(start, end);
|
||||
if (match.indexOf("=") === -1) {
|
||||
$.username = match;
|
||||
peg$currPos = end;
|
||||
return true;
|
||||
}
|
||||
} { proxy.username = $.username; }
|
||||
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
||||
|
||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||
sni = comma "sni" equals sni:domain { proxy.sni = sni; }
|
||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||
|
||||
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.alterId = 0; }
|
||||
|
||||
method = comma "encrypt-method" equals cipher:cipher {
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
|
||||
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
||||
ws_headers = comma "ws-headers" equals "Host:" host:domain {
|
||||
obfs.host = host;
|
||||
}
|
||||
ws_path = comma "ws-path" equals path:uri { obfs.path = path; }
|
||||
|
||||
obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
|
||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
||||
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
||||
uri = $[^,]+
|
||||
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
|
||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||
comma = _ "," _
|
||||
equals = _ "=" _
|
||||
_ = [ \r\t]*
|
||||
bool = b:("true"/"false") { return b === "true" }
|
||||
others = comma [^=,]+ equals [^=,]+
|
||||
Reference in New Issue
Block a user