Refactored proxy-utils structure

This commit is contained in:
Peng-YM
2022-06-16 12:58:24 +08:00
parent f91f34d644
commit 6bfe2700cf
12 changed files with 6 additions and 6 deletions

View 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(),
];

View 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;
}

View 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 [^=,]+

View 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;
}

View 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" }

View 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;
}

View 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 [^=,]+