Sub-Store 2.0 major release

- Used Peggy.js to replace the original parsers for Loon, QX and Surge.
- Added support for vmess + ws, vmess + http, snell, socks 5 parsing.
- Added various test cases for parsing.
This commit is contained in:
Peng-YM
2022-06-16 00:14:59 +08:00
parent b9f4c2e596
commit 4a35f1293c
19 changed files with 4159 additions and 506 deletions

View File

@@ -0,0 +1,172 @@
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 [^=,]+
`;
export default grammars;

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,171 @@
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" }
`;
export default grammars;

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,172 @@
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 [^=,]+
`;
export default grammars;

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

View File

@@ -53,9 +53,7 @@ function parse(raw) {
}
proxies.push(proxy);
} catch (err) {
$.error(
`Failed to parse line: \n ${line}\n Reason: ${err.stack}`,
);
$.error(`Failed to parse line: \n ${line}\n Reason: ${err}`);
}
}
}

View File

@@ -1,4 +1,31 @@
import surge from './grammars/surge';
import loon from './grammars/loon';
import { Base64 } from 'js-base64';
import qx from './grammars/qx';
import * as peggy from 'peggy';
let QXParser, LoonParser, SurgeParser;
function getQXParser() {
if (!QXParser) {
QXParser = peggy.generate(qx);
}
return QXParser;
}
function getLoonParser() {
if (!LoonParser) {
LoonParser = peggy.generate(loon);
}
return LoonParser;
}
function getSurgeParser() {
if (!SurgeParser) {
SurgeParser = peggy.generate(surge);
}
return SurgeParser;
}
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
// reference: https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html
@@ -304,53 +331,8 @@ function QX_SS() {
);
};
const parse = (line) => {
const supported = {};
const params = getQXParams(line);
const proxy = {
name: params.tag,
type: 'ss',
server: params.server,
port: params.port,
cipher: params.method,
password: params.password,
udp: JSON.parse(params['udp-relay'] || 'false'),
tfo: JSON.parse(params['fast-open'] || 'false'),
supported,
};
// handle obfs options
if (params.obfs) {
proxy['plugin-opts'] = {
host: params['obfs-host'] || proxy.server,
};
switch (params.obfs) {
case 'http':
case 'tls':
proxy.plugin = 'obfs';
proxy['plugin-opts'].mode = params.obfs;
break;
case 'ws':
case 'wss':
proxy['plugin-opts'] = {
...proxy['plugin-opts'],
mode: 'websocket',
path: params['obfs-uri'] || '/',
tls: params.obfs === 'wss',
};
if (
proxy['plugin-opts'].tls &&
typeof params['tls-verification'] !== 'undefined'
) {
proxy['plugin-opts']['skip-cert-verify'] =
params['tls-verification'];
}
proxy.plugin = 'v2ray-plugin';
// Surge and Loon lack support for v2ray-plugin obfs
proxy.supported.Surge = false;
proxy.supported.Loon = false;
break;
}
}
return proxy;
const parser = getQXParser();
return parser.parse(line);
};
return { name, test, parse };
}
@@ -363,33 +345,7 @@ function QX_SSR() {
line.indexOf('ssr-protocol') !== -1
);
};
const parse = (line) => {
const supported = {
Surge: false,
};
const params = getQXParams(line);
const proxy = {
name: params.tag,
type: 'ssr',
server: params.server,
port: params.port,
cipher: params.method,
password: params.password,
protocol: params['ssr-protocol'],
obfs: 'plain', // default obfs
'protocol-param': params['ssr-protocol-param'],
udp: JSON.parse(params['udp-relay'] || 'false'),
tfo: JSON.parse(params['fast-open'] || 'false'),
supported,
};
// handle obfs options
if (params.obfs) {
proxy.obfs = params.obfs;
proxy['obfs-param'] = params['obfs-host'];
}
return proxy;
};
const parse = (line) => getQXParser().parse(line);
return { name, test, parse };
}
@@ -398,39 +354,7 @@ function QX_VMess() {
const test = (line) => {
return /^vmess\s*=/.test(line.split(',')[0].trim());
};
const parse = (line) => {
const params = getQXParams(line);
const proxy = {
type: 'vmess',
name: params.tag,
server: params.server,
port: params.port,
cipher: params.method || 'none',
uuid: params.password,
alterId: 0,
tls: params.obfs === 'over-tls' || params.obfs === 'wss',
udp: JSON.parse(params['udp-relay'] || 'false'),
tfo: JSON.parse(params['fast-open'] || 'false'),
};
if (proxy.tls) {
proxy.sni = params['obfs-host'] || params.server;
proxy['skip-cert-verify'] = !JSON.parse(
params['tls-verification'] || 'true',
);
}
// handle ws headers
if (params.obfs === 'ws' || params.obfs === 'wss') {
proxy.network = 'ws';
proxy['ws-opts'] = {
path: params['obfs-uri'],
headers: {
Host: params['obfs-host'] || params.server, // if no host provided, use the same as server
},
};
}
return proxy;
};
const parse = (line) => getQXParser().parse(line);
return { name, test, parse };
}
@@ -439,23 +363,7 @@ function QX_Trojan() {
const test = (line) => {
return /^trojan\s*=/.test(line.split(',')[0].trim());
};
const parse = (line) => {
const params = getQXParams(line);
const proxy = {
type: 'trojan',
name: params.tag,
server: params.server,
port: params.port,
password: params.password,
sni: params['tls-host'] || params.server,
udp: JSON.parse(params['udp-relay'] || 'false'),
tfo: JSON.parse(params['fast-open'] || 'false'),
};
proxy['skip-cert-verify'] = !JSON.parse(
params['tls-verification'] || 'true',
);
return proxy;
};
const parse = (line) => getQXParser().parse(line);
return { name, test, parse };
}
@@ -464,53 +372,10 @@ function QX_Http() {
const test = (line) => {
return /^http\s*=/.test(line.split(',')[0].trim());
};
const parse = (line) => {
const params = getQXParams(line);
const proxy = {
type: 'http',
name: params.tag,
server: params.server,
port: params.port,
tls: JSON.parse(params['over-tls'] || 'false'),
udp: JSON.parse(params['udp-relay'] || 'false'),
tfo: JSON.parse(params['fast-open'] || 'false'),
};
if (params.username && params.username !== 'none')
proxy.username = params.username;
if (params.password && params.password !== 'none')
proxy.password = params.password;
if (proxy.tls) {
proxy.sni = params['tls-host'] || proxy.server;
proxy['skip-cert-verify'] = !JSON.parse(
params['tls-verification'] || 'true',
);
}
return proxy;
};
const parse = (line) => getQXParser().parse(line);
return { name, test, parse };
}
function getQXParams(line) {
const groups = line.split(',');
const params = {};
const protocols = ['shadowsocks', 'vmess', 'http', 'trojan'];
groups.forEach((g) => {
let [key, value] = g.split('=');
key = key.trim();
value = value.trim();
if (protocols.indexOf(key) !== -1) {
params.type = key;
const conf = value.split(':');
params.server = conf[0];
params.port = conf[1];
} else {
params[key.trim()] = value.trim();
}
});
return params;
}
function Loon_SS() {
const name = 'Loon SS Parser';
const test = (line) => {
@@ -519,26 +384,7 @@ function Loon_SS() {
'shadowsocks'
);
};
const parse = (line) => {
const params = line.split('=')[1].split(',');
const proxy = {
name: line.split('=')[0].trim(),
type: 'ss',
server: params[1],
port: params[2],
cipher: params[3],
password: params[4].replace(/"/g, ''),
};
// handle obfs
if (params.length > 5) {
proxy.plugin = 'obfs';
proxy['plugin-opts'] = {
mode: params[5],
host: params[6],
};
}
return proxy;
};
const parse = (line) => getLoonParser().parse(line);
return { name, test, parse };
}
@@ -550,25 +396,7 @@ function Loon_SSR() {
'shadowsocksr'
);
};
const parse = (line) => {
const params = line.split('=')[1].split(',');
const supported = {
Surge: false,
};
return {
name: line.split('=')[0].trim(),
type: 'ssr',
server: params[1],
port: params[2],
cipher: params[3],
password: params[4].replace(/"/g, ''),
protocol: params[5],
'protocol-param': params[6].match(/{(.*)}/)[1],
supported,
obfs: params[7],
'obfs-param': params[8].match(/{(.*)}/)[1],
};
};
const parse = (line) => getLoonParser().parse(line);
return { name, test, parse };
}
@@ -581,49 +409,7 @@ function Loon_VMess() {
line.indexOf('username') === -1
);
};
const parse = (line) => {
let params = line.split('=')[1].split(',');
const proxy = {
name: line.split('=')[0].trim(),
type: 'vmess',
server: params[1],
port: params[2],
cipher: params[3] || 'none',
uuid: params[4].replace(/"/g, ''),
alterId: 0,
};
// get transport options
params = params.splice(5);
for (const item of params) {
const [key, val] = item.split(':');
params[key] = val;
}
proxy.tls = JSON.parse(params['over-tls'] || 'false');
if (proxy.tls) {
proxy.sni = params['tls-name'] || proxy.server;
proxy['skip-cert-verify'] = JSON.parse(
params['skip-cert-verify'] || 'false',
);
}
switch (params.transport) {
case 'tcp':
break;
case 'ws':
proxy.network = params.transport;
proxy['ws-opts'] = {
path: params.path,
headers: {
Host: params.host,
},
};
}
if (proxy.tls) {
proxy['skip-cert-verify'] = JSON.parse(
params['skip-cert-verify'] || 'false',
);
}
return proxy;
};
const parse = (line) => getLoonParser().parse(line);
return { name, test, parse };
}
@@ -636,28 +422,7 @@ function Loon_Trojan() {
);
};
const parse = (line) => {
const params = line.split('=')[1].split(',');
const proxy = {
name: line.split('=')[0].trim(),
type: 'trojan',
server: params[1],
port: params[2],
password: params[3].replace(/"/g, ''),
sni: params[1], // default sni is the server itself
'skip-cert-verify': JSON.parse(
params['skip-cert-verify'] || 'false',
),
};
// trojan sni
if (params.length > 4) {
const [key, val] = params[4].split(':');
if (key === 'tls-name') proxy.sni = val;
else throw new Error(`Unknown option ${key} for line: \n${line}`);
}
return proxy;
};
const parse = (line) => getLoonParser().parse(line);
return { name, test, parse };
}
@@ -672,27 +437,7 @@ function Loon_Http() {
);
};
const parse = (line) => {
const params = line.split('=')[1].split(',');
const proxy = {
name: line.split('=')[0].trim(),
type: 'http',
server: params[1],
port: params[2],
tls: params[2] === '443', // port 443 is considered as https type
};
if (params[3]) proxy.username = params[3];
if (params[4]) proxy.password = params[4];
if (proxy.tls) {
proxy.sni = params['tls-name'] || proxy.server;
proxy['skip-cert-verify'] = JSON.parse(
params['skip-cert-verify'] || 'false',
);
}
return proxy;
};
const parse = (line) => getLoonParser().parse(line);
return { name, test, parse };
}
@@ -701,28 +446,7 @@ function Surge_SS() {
const test = (line) => {
return /^.*=\s*ss/.test(line.split(',')[0]);
};
const parse = (line) => {
const params = getSurgeParams(line);
const proxy = {
name: params.name,
type: 'ss',
server: params.server,
port: params.port,
cipher: params['encrypt-method'],
password: params.password,
tfo: JSON.parse(params.tfo || 'false'),
udp: JSON.parse(params['udp-relay'] || 'false'),
};
// handle obfs
if (params.obfs) {
proxy.plugin = 'obfs';
proxy['plugin-opts'] = {
mode: params.obfs,
host: params['obfs-host'],
};
}
return proxy;
};
const parse = (line) => getSurgeParser().parse(line);
return { name, test, parse };
}
@@ -734,44 +458,7 @@ function Surge_VMess() {
line.indexOf('username') !== -1
);
};
const parse = (line) => {
const params = getSurgeParams(line);
const proxy = {
name: params.name,
type: 'vmess',
server: params.server,
port: params.port,
uuid: params.username,
alterId: 0, // surge does not have this field
cipher: 'none', // surge does not have this field
tls: JSON.parse(params.tls || 'false'),
tfo: JSON.parse(params.tfo || 'false'),
};
if (proxy.tls) {
if (typeof params['skip-cert-verify'] !== 'undefined') {
proxy['skip-cert-verify'] =
params['skip-cert-verify'] === true ||
params['skip-cert-verify'] === '1';
}
proxy.sni = params['sni'] || params.server;
}
// use websocket
if (JSON.parse(params.ws || 'false')) {
proxy.network = 'ws';
proxy['ws-opts'] = {
path: params['ws-path'],
};
const res = params['ws-headers'].match(
/(,|^|\s)*HOST:\s*(.*?)(,|$)/,
);
const host = res ? res[2] : proxy.server;
proxy['ws-opts'].headers = {
Host: host || params.server,
};
}
return proxy;
};
const parse = (line) => getSurgeParser().parse(line);
return { name, test, parse };
}
@@ -783,25 +470,7 @@ function Surge_Trojan() {
line.indexOf('sni') !== -1
);
};
const parse = (line) => {
const params = getSurgeParams(line);
const proxy = {
name: params.name,
type: 'trojan',
server: params.server,
port: params.port,
password: params.password,
sni: params.sni || params.server,
tfo: JSON.parse(params.tfo || 'false'),
};
if (typeof params['skip-cert-verify'] !== 'undefined') {
proxy['skip-cert-verify'] =
params['skip-cert-verify'] === true ||
params['skip-cert-verify'] === '1';
}
return proxy;
};
const parse = (line) => getSurgeParser().parse(line);
return { name, test, parse };
}
@@ -812,50 +481,10 @@ function Surge_Http() {
/^.*=\s*https?/.test(line.split(',')[0]) && !Loon_Http().test(line)
);
};
const parse = (line) => {
const params = getSurgeParams(line);
const tls = /^.*?=\s?https/.test(line);
const proxy = {
name: params.name,
type: 'http',
server: params.server,
port: params.port,
tls: JSON.parse(tls || 'false'),
tfo: JSON.parse(params.tfo || 'false'),
};
if (proxy.tls) {
if (typeof params['skip-cert-verify'] !== 'undefined') {
proxy['skip-cert-verify'] =
params['skip-cert-verify'] === true ||
params['skip-cert-verify'] === '1';
}
proxy.sni = params.sni || params.server;
}
if (params.username && params.username !== 'none')
proxy.username = params.username;
if (params.password && params.password !== 'none')
proxy.password = params.password;
return proxy;
};
const parse = (line) => getSurgeParser().parse(line);
return { name, test, parse };
}
function getSurgeParams(line) {
const params = {};
params.name = line.split('=')[0].trim();
const segments = line.split(',');
params.server = segments[1].trim();
params.port = segments[2].trim();
for (let i = 3; i < segments.length; i++) {
const item = segments[i];
if (item.indexOf('=') !== -1) {
const [key, value] = item.split('=');
params[key.trim()] = value.trim();
}
}
return params;
}
export default [
URI_SS(),
URI_SSR(),