mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-04-23 22:29:32 +08:00
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:
parent
98bb91babd
commit
275097f58b
@ -3,5 +3,12 @@
|
||||
[
|
||||
"@babel/preset-env"
|
||||
]
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
6
backend/dist/sub-store-parser.loon.min.js
vendored
6
backend/dist/sub-store-parser.loon.min.js
vendored
File diff suppressed because one or more lines are too long
@ -4,65 +4,67 @@ import gulp from 'gulp';
|
||||
import prettier from 'gulp-prettier';
|
||||
import header from 'gulp-header';
|
||||
import eslint from 'gulp-eslint-new';
|
||||
import newFile from 'gulp-file';
|
||||
import path from 'path';
|
||||
import tap from 'gulp-tap';
|
||||
|
||||
import pkg from './package.json';
|
||||
|
||||
export function peggy() {
|
||||
return gulp.src('src/**/*.peg')
|
||||
.pipe(tap(function (file) {
|
||||
const filename = path.basename(file.path).split(".")[0] + ".js";
|
||||
const raw = fs.readFileSync(file.path, 'utf8');
|
||||
const contents = `const grammars = String.raw\`\n${raw}\n\`;\nexport default grammars;`;
|
||||
return newFile(filename, contents)
|
||||
.pipe(gulp.dest(path.dirname(file.path)))
|
||||
}));
|
||||
}
|
||||
|
||||
export function lint() {
|
||||
return gulp
|
||||
.src('src/**/*.js')
|
||||
.pipe(eslint({ fix: true }))
|
||||
.pipe(eslint.fix())
|
||||
.pipe(eslint.format())
|
||||
.pipe(eslint.failAfterError());
|
||||
return gulp
|
||||
.src('src/**/*.js')
|
||||
.pipe(eslint({fix: true}))
|
||||
.pipe(eslint.fix())
|
||||
.pipe(eslint.format())
|
||||
.pipe(eslint.failAfterError());
|
||||
}
|
||||
|
||||
export function styles() {
|
||||
return gulp
|
||||
.src('src/**/*.js')
|
||||
.pipe(
|
||||
prettier({
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
tabWidth: 4,
|
||||
bracketSpacing: true
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest((file) => file.base));
|
||||
return gulp
|
||||
.src('src/**/*.js')
|
||||
.pipe(prettier({
|
||||
singleQuote: true, trailingComma: 'all', tabWidth: 4, bracketSpacing: true
|
||||
}))
|
||||
.pipe(gulp.dest((file) => file.base));
|
||||
}
|
||||
|
||||
function scripts(src, dest) {
|
||||
return () => {
|
||||
return browserify(src)
|
||||
.transform('babelify', {
|
||||
presets: [ [ '@babel/preset-env' ] ]
|
||||
})
|
||||
.plugin('tinyify')
|
||||
.bundle()
|
||||
.pipe(fs.createWriteStream(dest));
|
||||
};
|
||||
return () => {
|
||||
return browserify(src)
|
||||
.transform('babelify', {
|
||||
presets: [['@babel/preset-env']]
|
||||
})
|
||||
.plugin('tinyify')
|
||||
.bundle()
|
||||
.pipe(fs.createWriteStream(dest));
|
||||
};
|
||||
}
|
||||
|
||||
function banner(dest) {
|
||||
return () => gulp
|
||||
.src(dest)
|
||||
.pipe(header(fs.readFileSync('./banner', 'utf-8'), { pkg, updated: new Date().toLocaleString() }))
|
||||
.pipe(gulp.dest((file) => file.base));
|
||||
return () => gulp
|
||||
.src(dest)
|
||||
.pipe(header(fs.readFileSync('./banner', 'utf-8'), {pkg, updated: new Date().toLocaleString()}))
|
||||
.pipe(gulp.dest((file) => file.base));
|
||||
}
|
||||
|
||||
const artifacts = [
|
||||
{ src: 'src/main.js', dest: 'sub-store.min.js' },
|
||||
{ src: 'src/products/resource-parser.loon.js', dest: 'dist/sub-store-parser.loon.min.js'}
|
||||
]
|
||||
{src: 'src/main.js', dest: 'sub-store.min.js'},
|
||||
{src: 'src/products/resource-parser.loon.js', dest: 'dist/sub-store-parser.loon.min.js'}
|
||||
];
|
||||
|
||||
export const build = gulp.series(
|
||||
gulp.parallel(artifacts.map(artifact => scripts(artifact.src, artifact.dest))),
|
||||
gulp.parallel(artifacts.map(artifact => banner(artifact.dest)))
|
||||
);
|
||||
export const build = gulp.series(gulp.parallel(artifacts.map(artifact => scripts(artifact.src, artifact.dest))), gulp.parallel(artifacts.map(artifact => banner(artifact.dest))));
|
||||
|
||||
const all = gulp.series(
|
||||
lint,
|
||||
styles,
|
||||
build
|
||||
)
|
||||
const all = gulp.series(peggy, lint, styles, build)
|
||||
|
||||
export default all;
|
||||
|
2070
backend/package-lock.json
generated
2070
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "1.6",
|
||||
"version": "2.0",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "gulp peggy && npx cross-env BABEL_ENV=test mocha --require @babel/register --recursive",
|
||||
"serve": "node sub-store.min.js",
|
||||
"start": "nodemon --exec node_modules/.bin/babel-node src/main.js",
|
||||
"build": "gulp"
|
||||
@ -29,15 +29,21 @@
|
||||
"babelify": "^10.0.0",
|
||||
"browser-pack-flat": "^3.4.2",
|
||||
"browserify": "^17.0.0",
|
||||
"chai": "^4.3.6",
|
||||
"eslint": "^8.16.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-eslint-new": "^1.4.4",
|
||||
"gulp-file": "^0.4.0",
|
||||
"gulp-header": "^2.0.9",
|
||||
"gulp-prettier": "^4.0.0",
|
||||
"gulp-tap": "^2.0.0",
|
||||
"mocha": "^10.0.0",
|
||||
"nodemon": "^2.0.16",
|
||||
"peggy": "^2.0.1",
|
||||
"prettier": "2.6.2",
|
||||
"prettier-plugin-sort-imports": "^1.6.1",
|
||||
"through2": "^4.0.2",
|
||||
"tinyify": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
172
backend/src/core/proxy-utils/grammars/loon.js
Normal file
172
backend/src/core/proxy-utils/grammars/loon.js
Normal 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;
|
169
backend/src/core/proxy-utils/grammars/loon.peg
Normal file
169
backend/src/core/proxy-utils/grammars/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 [^=,]+
|
171
backend/src/core/proxy-utils/grammars/qx.js
Normal file
171
backend/src/core/proxy-utils/grammars/qx.js
Normal 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;
|
168
backend/src/core/proxy-utils/grammars/qx.peg
Normal file
168
backend/src/core/proxy-utils/grammars/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" }
|
172
backend/src/core/proxy-utils/grammars/surge.js
Normal file
172
backend/src/core/proxy-utils/grammars/surge.js
Normal 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;
|
169
backend/src/core/proxy-utils/grammars/surge.peg
Normal file
169
backend/src/core/proxy-utils/grammars/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 [^=,]+
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
8
backend/src/test.js
Normal file
8
backend/src/test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import loon from './core/proxy-utils/grammars/loon';
|
||||
import * as peggy from 'peggy';
|
||||
|
||||
const parser = peggy.generate(loon);
|
||||
|
||||
const raw = String.raw`vmess4 = vmess,example.com,10086,aes-128-gcm,"52396e06-041a-4cc2-be5c-8525eb457809",transport=ws,alterId=0,path=/,host=v3-dy-y.ixigua.com,over-tls=true,tls-name=example.com,skip-cert-verify=true`;
|
||||
|
||||
console.log(JSON.stringify(parser.parse(raw), null, 2));
|
6
backend/sub-store.min.js
vendored
6
backend/sub-store.min.js
vendored
File diff suppressed because one or more lines are too long
145
backend/test/proxy-parsers/loon.spec.js
Normal file
145
backend/test/proxy-parsers/loon.spec.js
Normal file
@ -0,0 +1,145 @@
|
||||
import { expect } from 'chai';
|
||||
import * as peggy from 'peggy';
|
||||
import loon from '../../src/core/proxy-utils/grammars/loon';
|
||||
import testcases from './testcases';
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
const parser = peggy.generate(loon);
|
||||
|
||||
describe('Loon', function() {
|
||||
describe('shadowsocks', function() {
|
||||
it('test shadowsocks simple', function() {
|
||||
const { input, expected } = testcases.SS.SIMPLE;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks obfs + tls', function() {
|
||||
const { input, expected } = testcases.SS.OBFS_TLS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks obfs + http', function() {
|
||||
const { input, expected } = testcases.SS.OBFS_HTTP;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shadowsocksr', function() {
|
||||
it('test shadowsocksr simple', function() {
|
||||
const { input, expected } = testcases.SSR.SIMPLE;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trojan', function() {
|
||||
it('test trojan simple', function() {
|
||||
const { input, expected } = testcases.TROJAN.SIMPLE;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test trojan + ws', function() {
|
||||
const { input, expected } = testcases.TROJAN.WS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test trojan + wss', function() {
|
||||
const { input, expected } = testcases.TROJAN.WSS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vmess', function() {
|
||||
it('test vmess simple', function() {
|
||||
const { input, expected } = testcases.VMESS.SIMPLE;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vmess + aead', function() {
|
||||
const { input, expected } = testcases.VMESS.AEAD;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vmess + ws', function() {
|
||||
const { input, expected } = testcases.VMESS.WS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vmess + wss', function() {
|
||||
const { input, expected } = testcases.VMESS.WSS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vmess + http', function() {
|
||||
const { input, expected } = testcases.VMESS.HTTP;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vmess + http + tls', function() {
|
||||
const { input, expected } = testcases.VMESS.HTTP_TLS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vless', function() {
|
||||
it('test vless simple', function() {
|
||||
const { input, expected } = testcases.VLESS.SIMPLE;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon)
|
||||
});
|
||||
|
||||
it('test vless + ws', function() {
|
||||
const { input, expected } = testcases.VLESS.WS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vless + wss', function() {
|
||||
const { input, expected } = testcases.VLESS.WSS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vless + http', function() {
|
||||
const { input, expected } = testcases.VLESS.HTTP;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
|
||||
it('test vless + http + tls', function() {
|
||||
const { input, expected } = testcases.VLESS.HTTP_TLS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected.Loon);
|
||||
});
|
||||
});
|
||||
|
||||
describe('http(s)', function() {
|
||||
it('test http simple', function () {
|
||||
const { input, expected } = testcases.HTTP.SIMPLE;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test http with authentication', function () {
|
||||
const { input, expected } = testcases.HTTP.AUTH;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test https', function () {
|
||||
const { input, expected } = testcases.HTTP.TLS;
|
||||
const proxy = parser.parse(input.Loon);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
139
backend/test/proxy-parsers/qx.spec.js
Normal file
139
backend/test/proxy-parsers/qx.spec.js
Normal file
@ -0,0 +1,139 @@
|
||||
import * as peggy from 'peggy';
|
||||
import qx from '../../src/core/proxy-utils/grammars/qx';
|
||||
import { expect } from 'chai';
|
||||
import testcases from './testcases';
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
|
||||
|
||||
const parser = peggy.generate(qx);
|
||||
|
||||
describe('QX', function() {
|
||||
describe('shadowsocks', function() {
|
||||
it('test shadowsocks simple', function() {
|
||||
const { input, expected } = testcases.SS.SIMPLE;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks obfs + tls', function() {
|
||||
const { input, expected } = testcases.SS.OBFS_TLS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks obfs + http', function() {
|
||||
const { input, expected } = testcases.SS.OBFS_HTTP;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks v2ray-plugin + ws', function() {
|
||||
const { input, expected } = testcases.SS.V2RAY_PLUGIN_WS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks v2ray-plugin + wss', function() {
|
||||
const { input, expected } = testcases.SS.V2RAY_PLUGIN_WSS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shadowsocksr', function() {
|
||||
it('test shadowsocksr simple', function() {
|
||||
const { input, expected } = testcases.SSR.SIMPLE;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trojan', function() {
|
||||
it('test trojan simple', function() {
|
||||
const { input, expected } = testcases.TROJAN.SIMPLE;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test trojan + ws', function() {
|
||||
const { input, expected } = testcases.TROJAN.WS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test trojan + wss', function() {
|
||||
const { input, expected } = testcases.TROJAN.WSS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vmess', function() {
|
||||
it('test vmess simple', function() {
|
||||
const { input, expected } = testcases.VMESS.SIMPLE;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected.QX);
|
||||
});
|
||||
|
||||
it('test vmess aead', function() {
|
||||
const { input, expected } = testcases.VMESS.AEAD;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected.QX);
|
||||
});
|
||||
|
||||
it('test vmess + ws', function() {
|
||||
const { input, expected } = testcases.VMESS.WS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected.QX);
|
||||
});
|
||||
|
||||
it('test vmess + wss', function() {
|
||||
const { input, expected } = testcases.VMESS.WSS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected.QX);
|
||||
});
|
||||
|
||||
it('test vmess + http', function() {
|
||||
const { input, expected } = testcases.VMESS.HTTP;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected.QX);
|
||||
});
|
||||
});
|
||||
|
||||
describe('http', function () {
|
||||
it('test http simple', function () {
|
||||
const { input, expected } = testcases.HTTP.SIMPLE;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test http with authentication', function () {
|
||||
const { input, expected } = testcases.HTTP.AUTH;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test https', function () {
|
||||
const { input, expected } = testcases.HTTP.TLS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('socks5', function () {
|
||||
it('test socks5 simple', function () {
|
||||
const { input, expected } = testcases.SOCKS5.SIMPLE;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test socks5 with authentication', function () {
|
||||
const { input, expected } = testcases.SOCKS5.AUTH;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test socks5 + tls', function () {
|
||||
const { input, expected } = testcases.SOCKS5.TLS;
|
||||
const proxy = parser.parse(input.QX);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
134
backend/test/proxy-parsers/surge.spec.js
Normal file
134
backend/test/proxy-parsers/surge.spec.js
Normal file
@ -0,0 +1,134 @@
|
||||
import * as peggy from 'peggy';
|
||||
import surge from '../../src/core/proxy-utils/grammars/surge';
|
||||
import { expect } from 'chai';
|
||||
import testcases from './testcases';
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
|
||||
const parser = peggy.generate(surge);
|
||||
|
||||
describe('Surge', function() {
|
||||
describe('shadowsocks', function() {
|
||||
it('test shadowsocks simple', function() {
|
||||
const { input, expected } = testcases.SS.SIMPLE;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks obfs + tls', function() {
|
||||
const { input, expected } = testcases.SS.OBFS_TLS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
it('test shadowsocks obfs + http', function() {
|
||||
const { input, expected } = testcases.SS.OBFS_HTTP;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trojan', function() {
|
||||
it('test trojan simple', function() {
|
||||
const { input, expected } = testcases.TROJAN.SIMPLE;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test trojan + ws', function() {
|
||||
const { input, expected } = testcases.TROJAN.WS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test trojan + wss', function() {
|
||||
const { input, expected } = testcases.TROJAN.WSS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vmess', function() {
|
||||
it('test vmess simple', function() {
|
||||
const { input, expected } = testcases.VMESS.SIMPLE;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected.Surge);
|
||||
});
|
||||
|
||||
it('test vmess aead', function() {
|
||||
const { input, expected } = testcases.VMESS.AEAD;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected.Surge);
|
||||
});
|
||||
|
||||
it('test vmess + ws', function() {
|
||||
const { input, expected } = testcases.VMESS.WS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected.Surge);
|
||||
});
|
||||
|
||||
it('test vmess + wss', function() {
|
||||
const { input, expected } = testcases.VMESS.WSS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected.Surge);
|
||||
});
|
||||
});
|
||||
|
||||
describe('http', function () {
|
||||
it('test http simple', function () {
|
||||
const { input, expected } = testcases.HTTP.SIMPLE;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test http with authentication', function () {
|
||||
const { input, expected } = testcases.HTTP.AUTH;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test https', function () {
|
||||
const { input, expected } = testcases.HTTP.TLS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('socks5', function () {
|
||||
it('test socks5 simple', function () {
|
||||
const { input, expected } = testcases.SOCKS5.SIMPLE;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test socks5 with authentication', function () {
|
||||
const { input, expected } = testcases.SOCKS5.AUTH;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test socks5 + tls', function () {
|
||||
const { input, expected } = testcases.SOCKS5.TLS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('snell', function () {
|
||||
it('test snell simple', function () {
|
||||
const { input, expected } = testcases.SNELL.SIMPLE;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test snell obfs + http', function () {
|
||||
const { input, expected } = testcases.SNELL.OBFS_HTTP;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
|
||||
it('test snell obfs + tls', function () {
|
||||
const { input, expected } = testcases.SNELL.OBFS_TLS;
|
||||
const proxy = parser.parse(input.Surge);
|
||||
expect(proxy).eql(expected);
|
||||
});
|
||||
})
|
||||
});
|
572
backend/test/proxy-parsers/testcases.js
Normal file
572
backend/test/proxy-parsers/testcases.js
Normal file
@ -0,0 +1,572 @@
|
||||
function createTestCases() {
|
||||
const name = 'name';
|
||||
const server = 'example.com';
|
||||
const port = 10086;
|
||||
|
||||
const cipher = 'chacha20';
|
||||
|
||||
const username = 'username';
|
||||
const password = 'password';
|
||||
|
||||
const obfs_host = 'obfs.com';
|
||||
const obfs_path = '/resource/file';
|
||||
|
||||
const ssr_protocol = 'auth_chain_b';
|
||||
const ssr_protocol_param = 'def';
|
||||
const ssr_obfs = 'tls1.2_ticket_fastauth';
|
||||
const ssr_obfs_param = 'obfs.com';
|
||||
|
||||
const uuid = '23ad6b10-8d1a-40f7-8ad0-e3e35cd32291';
|
||||
|
||||
const sni = 'sni.com';
|
||||
|
||||
const SS = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
Loon: `${name}=shadowsocks,${server},${port},${cipher},"${password}"`,
|
||||
QX: `shadowsocks=${server}:${port},method=${cipher},password=${password},tag=${name}`,
|
||||
Surge: `${name}=ss,${server},${port},encrypt-method=${cipher},password=${password}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'ss',
|
||||
name, server, port, cipher, password,
|
||||
},
|
||||
},
|
||||
OBFS_TLS: {
|
||||
input: {
|
||||
Loon: `${name}=shadowsocks,${server},${port},${cipher},"${password}",obfs-name=tls,obfs-uri=${obfs_path},obfs-host=${obfs_host}`,
|
||||
QX: `shadowsocks=${server}:${port},method=${cipher},password=${password},obfs=tls,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
Surge: `${name}=ss,${server},${port},encrypt-method=${cipher},password=${password},obfs=tls,obfs-host=${obfs_host},obfs-uri=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'ss',
|
||||
name, server, port, cipher, password,
|
||||
plugin: 'obfs',
|
||||
'plugin-opts': {
|
||||
mode: 'tls',
|
||||
path: obfs_path,
|
||||
host: obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
OBFS_HTTP: {
|
||||
input: {
|
||||
Loon: `${name}=shadowsocks,${server},${port},${cipher},"${password}",obfs-name=http,obfs-uri=${obfs_path},obfs-host=${obfs_host}`,
|
||||
QX: `shadowsocks=${server}:${port},method=${cipher},password=${password},obfs=http,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
Surge: `${name}=ss,${server},${port},encrypt-method=${cipher},password=${password},obfs=http,obfs-host=${obfs_host},obfs-uri=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'ss',
|
||||
name, server, port, cipher, password,
|
||||
plugin: 'obfs',
|
||||
'plugin-opts': {
|
||||
mode: 'http',
|
||||
path: obfs_path,
|
||||
host: obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
V2RAY_PLUGIN_WS: {
|
||||
input: {
|
||||
QX: `shadowsocks=${server}:${port},method=${cipher},password=${password},obfs=ws,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'ss',
|
||||
name, server, port, cipher, password,
|
||||
plugin: 'v2ray-plugin',
|
||||
'plugin-opts': {
|
||||
mode: 'websocket',
|
||||
path: obfs_path,
|
||||
host: obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
V2RAY_PLUGIN_WSS: {
|
||||
input: {
|
||||
QX: `shadowsocks=${server}:${port},method=${cipher},password=${password},obfs=wss,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'ss',
|
||||
name, server, port, cipher, password,
|
||||
plugin: 'v2ray-plugin',
|
||||
'plugin-opts': {
|
||||
mode: 'websocket',
|
||||
path: obfs_path,
|
||||
host: obfs_host,
|
||||
tls: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const SSR = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
QX: `shadowsocks=${server}:${port},method=${cipher},password=${password},ssr-protocol=${ssr_protocol},ssr-protocol-param=${ssr_protocol_param},obfs=${ssr_obfs},obfs-host=${ssr_obfs_param},tag=${name}`,
|
||||
Loon: `${name}=shadowsocksr,${server},${port},${cipher},"${password}",protocol=${ssr_protocol},protocol-param=${ssr_protocol_param},obfs=${ssr_obfs},obfs-param=${ssr_obfs_param}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'ssr',
|
||||
name, server, port, cipher, password,
|
||||
obfs: ssr_obfs,
|
||||
protocol: ssr_protocol,
|
||||
'obfs-param': ssr_obfs_param,
|
||||
'protocol-param': ssr_protocol_param,
|
||||
},
|
||||
},
|
||||
};
|
||||
const TROJAN = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
QX: `trojan=${server}:${port},password=${password},tag=${name}`,
|
||||
Loon: `${name}=trojan,${server},${port},"${password}"`,
|
||||
Surge: `${name}=trojan,${server},${port},password=${password}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'trojan',
|
||||
name, server, port, password,
|
||||
},
|
||||
},
|
||||
WS: {
|
||||
input: {
|
||||
QX: `trojan=${server}:${port},password=${password},obfs=ws,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
Loon: `${name}=trojan,${server},${port},"${password}",transport=ws,path=${obfs_path},host=${obfs_host}`,
|
||||
Surge: `${name}=trojan,${server},${port},password=${password},ws=true,ws-path=${obfs_path},ws-headers=Host:${obfs_host}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'trojan',
|
||||
name, server, port, password,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
}
|
||||
,
|
||||
},
|
||||
WSS: {
|
||||
input: {
|
||||
QX: `trojan=${server}:${port},password=${password},obfs=wss,obfs-host=${obfs_host},obfs-uri=${obfs_path},tls-verification=false,tls-host=${sni},tag=${name}`,
|
||||
Loon: `${name}=trojan,${server},${port},"${password}",transport=ws,path=${obfs_path},host=${obfs_host},over-tls=true,tls-name=${sni},skip-cert-verify=true`,
|
||||
Surge: `${name}=trojan,${server},${port},password=${password},ws=true,ws-path=${obfs_path},ws-headers=Host:${obfs_host},skip-cert-verify=true,sni=${sni},tls=true`,
|
||||
},
|
||||
expected: {
|
||||
type: 'trojan',
|
||||
name, server, port, password,
|
||||
network: 'ws',
|
||||
tls: true,
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
},
|
||||
};
|
||||
const VMESS = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
QX: `vmess=${server}:${port},method=${cipher},password=${uuid},tag=${name}`,
|
||||
Loon: `${name}=vmess,${server},${port},${cipher},"${uuid}"`,
|
||||
Surge: `${name}=vmess,${server},${port},username=${uuid}`,
|
||||
},
|
||||
expected: {
|
||||
QX: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
},
|
||||
Loon: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
},
|
||||
Surge: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, // Surge lacks support for specifying cipher for vmess protocol!
|
||||
},
|
||||
},
|
||||
},
|
||||
AEAD: {
|
||||
input: {
|
||||
QX: `vmess=${server}:${port},method=${cipher},password=${uuid},aead=true,tag=${name}`,
|
||||
Loon: `${name}=vmess,${server},${port},${cipher},"${uuid}",alterId=0`,
|
||||
Surge: `${name}=vmess,${server},${port},username=${uuid},vmess-aead=true`,
|
||||
},
|
||||
expected: {
|
||||
QX: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher, alterId: 0,
|
||||
},
|
||||
Loon: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher, alterId: 0,
|
||||
},
|
||||
Surge: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, // Surge lacks support for specifying cipher for vmess protocol!
|
||||
alterId: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
WS: {
|
||||
input: {
|
||||
QX: `vmess=${server}:${port},method=${cipher},password=${uuid},obfs=ws,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
Loon: `${name}=vmess,${server},${port},${cipher},"${uuid}",transport=ws,host=${obfs_host},path=${obfs_path}`,
|
||||
Surge: `${name}=vmess,${server},${port},username=${uuid},ws=true,ws-path=${obfs_path},ws-headers=Host:${obfs_host}`,
|
||||
},
|
||||
expected: {
|
||||
QX: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
Loon: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
Surge: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, // Surge lacks support for specifying cipher for vmess protocol!
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
WSS: {
|
||||
input: {
|
||||
QX: `vmess=${server}:${port},method=${cipher},password=${uuid},obfs=wss,obfs-host=${obfs_host},obfs-uri=${obfs_path},tls-verification=false,tls-host=${sni},tag=${name}`,
|
||||
Loon: `${name}=vmess,${server},${port},${cipher},"${uuid}",transport=ws,host=${obfs_host},path=${obfs_path},over-tls=true,tls-name=${sni},skip-cert-verify=true`,
|
||||
Surge: `${name}=vmess,${server},${port},username=${uuid},ws=true,ws-path=${obfs_path},ws-headers=Host:${obfs_host},skip-cert-verify=true,sni=${sni},tls=true`,
|
||||
},
|
||||
expected: {
|
||||
QX: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
tls: true,
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
Loon: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
tls: true,
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
Surge: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, // Surge lacks support for specifying cipher for vmess protocol!
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
tls: true,
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: {
|
||||
input: {
|
||||
QX: `vmess=${server}:${port},method=${cipher},password=${uuid},obfs=http,obfs-host=${obfs_host},obfs-uri=${obfs_path},tag=${name}`,
|
||||
Loon: `${name}=vmess,${server},${port},${cipher},"${uuid}",transport=http,host=${obfs_host},path=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
QX: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'http',
|
||||
'http-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
Loon: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'http',
|
||||
'http-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP_TLS: {
|
||||
input: {
|
||||
Loon: `${name}=vmess,${server},${port},${cipher},"${uuid}",transport=http,host=${obfs_host},path=${obfs_path},over-tls=true,tls-name=${sni},skip-cert-verify=true`,
|
||||
},
|
||||
expected: {
|
||||
Loon: {
|
||||
type: 'vmess',
|
||||
name, server, port, uuid, cipher,
|
||||
network: 'http',
|
||||
'http-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
tls: true,
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const VLESS = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
Loon: `${name}=vless,${server},${port},"${uuid}"`,
|
||||
},
|
||||
expected: {
|
||||
Loon: {
|
||||
type: 'vless',
|
||||
name, server, port, uuid,
|
||||
},
|
||||
},
|
||||
},
|
||||
WS: {
|
||||
input: {
|
||||
Loon: `${name}=vless,${server},${port},"${uuid}",transport=ws,host=${obfs_host},path=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
|
||||
Loon: {
|
||||
type: 'vless',
|
||||
name, server, port, uuid,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
WSS: {
|
||||
input: {
|
||||
Loon: `${name}=vless,${server},${port},"${uuid}",transport=ws,host=${obfs_host},path=${obfs_path},over-tls=true,tls-name=${sni},skip-cert-verify=true`,
|
||||
},
|
||||
expected: {
|
||||
|
||||
Loon: {
|
||||
type: 'vless',
|
||||
name, server, port, uuid,
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
tls: true,
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
HTTP: {
|
||||
input: {
|
||||
Loon: `${name}=vless,${server},${port},"${uuid}",transport=http,host=${obfs_host},path=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
|
||||
Loon: {
|
||||
type: 'vless',
|
||||
name, server, port, uuid,
|
||||
network: 'http',
|
||||
'http-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP_TLS: {
|
||||
input: {
|
||||
Loon: `${name}=vless,${server},${port},"${uuid}",transport=http,host=${obfs_host},path=${obfs_path},over-tls=true,tls-name=${sni},skip-cert-verify=true`,
|
||||
},
|
||||
expected: {
|
||||
Loon: {
|
||||
type: 'vless',
|
||||
name, server, port, uuid,
|
||||
network: 'http',
|
||||
'http-opts': {
|
||||
path: obfs_path,
|
||||
'headers': {
|
||||
'Host': obfs_host,
|
||||
},
|
||||
},
|
||||
tls: true,
|
||||
'skip-cert-verify': true,
|
||||
sni,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const HTTP = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
Loon: `${name}=http,${server},${port}`,
|
||||
QX: `http=${server}:${port},tag=${name}`,
|
||||
Surge: `${name}=http,${server},${port}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'http',
|
||||
name, server, port,
|
||||
}
|
||||
},
|
||||
AUTH: {
|
||||
input: {
|
||||
Loon: `${name}=http,${server},${port},${username},"${password}"`,
|
||||
QX: `http=${server}:${port},tag=${name},username=${username},password=${password}`,
|
||||
Surge: `${name}=http,${server},${port},${username},${password}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'http',
|
||||
name, server, port, username, password
|
||||
}
|
||||
},
|
||||
TLS: {
|
||||
input: {
|
||||
Loon: `${name}=https,${server},${port},${username},"${password}",tls-name=${sni},skip-cert-verify=true`,
|
||||
QX: `http=${server}:${port},username=${username},password=${password},over-tls=true,tls-host=${sni},tls-verification=false,tag=${name}`,
|
||||
Surge: `${name}=https,${server},${port},${username},${password},sni=${sni},skip-cert-verify=true`,
|
||||
},
|
||||
expected: {
|
||||
type: 'http',
|
||||
name, server, port, username, password,
|
||||
sni,
|
||||
'skip-cert-verify': true,
|
||||
tls: true
|
||||
}
|
||||
}
|
||||
};
|
||||
const SOCKS5 = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
QX: `socks5=${server}:${port},tag=${name}`,
|
||||
Surge: `${name}=socks5,${server},${port}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'socks5',
|
||||
name, server, port,
|
||||
}
|
||||
},
|
||||
AUTH: {
|
||||
input: {
|
||||
QX: `socks5=${server}:${port},tag=${name},username=${username},password=${password}`,
|
||||
Surge: `${name}=socks5,${server},${port},${username},${password}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'socks5',
|
||||
name, server, port, username, password
|
||||
}
|
||||
},
|
||||
TLS: {
|
||||
input: {
|
||||
QX: `socks5=${server}:${port},username=${username},password=${password},over-tls=true,tls-host=${sni},tls-verification=false,tag=${name}`,
|
||||
Surge: `${name}=socks5-tls,${server},${port},${username},${password},sni=${sni},skip-cert-verify=true`,
|
||||
},
|
||||
expected: {
|
||||
type: 'socks5',
|
||||
name, server, port, username, password,
|
||||
sni,
|
||||
'skip-cert-verify': true,
|
||||
tls: true
|
||||
}
|
||||
}
|
||||
};
|
||||
const SNELL = {
|
||||
SIMPLE: {
|
||||
input: {
|
||||
Surge: `${name}=snell,${server},${port},psk=${password},version=3`,
|
||||
},
|
||||
expected: {
|
||||
type: 'snell',
|
||||
name, server, port, psk: password, version: 3
|
||||
}
|
||||
},
|
||||
OBFS_HTTP: {
|
||||
input: {
|
||||
Surge: `${name}=snell,${server},${port},psk=${password},version=3,obfs=http,obfs-host=${obfs_host},obfs-uri=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'snell',
|
||||
name, server, port, psk: password, version: 3,
|
||||
'obfs-opts': {
|
||||
mode: 'http',
|
||||
host: obfs_host,
|
||||
path: obfs_path,
|
||||
}
|
||||
}
|
||||
},
|
||||
OBFS_TLS: {
|
||||
input: {
|
||||
Surge: `${name}=snell,${server},${port},psk=${password},version=3,obfs=tls,obfs-host=${obfs_host},obfs-uri=${obfs_path}`,
|
||||
},
|
||||
expected: {
|
||||
type: 'snell',
|
||||
name, server, port, psk: password, version: 3,
|
||||
'obfs-opts': {
|
||||
mode: 'tls',
|
||||
host: obfs_host,
|
||||
path: obfs_path,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
return {
|
||||
SS, SSR, VMESS, VLESS, TROJAN, HTTP, SOCKS5, SNELL,
|
||||
};
|
||||
}
|
||||
|
||||
export default createTestCases();
|
Loading…
x
Reference in New Issue
Block a user