fix: trojan uri parser

This commit is contained in:
MystiPanda 2024-07-07 11:43:00 +08:00
parent 14cd3b99cc
commit 18196c4a77
No known key found for this signature in database
4 changed files with 110 additions and 299 deletions

View File

@ -260,8 +260,17 @@ interface RealityOptions {
"public-key"?: string;
"short-id"?: string;
}
type NetworkType = "ws" | "http" | "h2" | "grpc";
type ClientFingerprint =
| "chrome"
| "firefox"
| "safari"
| "iOS"
| "android"
| "edge"
| "360"
| "qq"
| "random";
type NetworkType = "ws" | "http" | "h2" | "grpc" | "tcp";
type CipherType =
| "none"
| "auto"
@ -376,7 +385,7 @@ interface IProxyTrojanConfig extends IProxyBaseConfig {
method?: string;
password?: string;
};
"client-fingerprint"?: string;
"client-fingerprint"?: ClientFingerprint;
}
// tuic
interface IProxyTuicConfig extends IProxyBaseConfig {
@ -438,7 +447,7 @@ interface IProxyVlessConfig extends IProxyBaseConfig {
"skip-cert-verify"?: boolean;
fingerprint?: string;
servername?: string;
"client-fingerprint"?: string;
"client-fingerprint"?: ClientFingerprint;
}
// vmess
interface IProxyVmessConfig extends IProxyBaseConfig {
@ -466,7 +475,7 @@ interface IProxyVmessConfig extends IProxyBaseConfig {
"packet-encoding"?: string;
"global-padding"?: boolean;
"authenticated-length"?: boolean;
"client-fingerprint"?: string;
"client-fingerprint"?: ClientFingerprint;
}
interface WireGuardPeerOptions {
server?: string;
@ -574,7 +583,7 @@ interface IProxyShadowsocksConfig extends IProxyBaseConfig {
};
"udp-over-tcp"?: boolean;
"udp-over-tcp-version"?: number;
"client-fingerprint"?: string;
"client-fingerprint"?: ClientFingerprint;
}
// shadowsocksR
interface IProxyshadowsocksRConfig extends IProxyBaseConfig {

View File

@ -1,131 +0,0 @@
// 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;
}
function toBool(str) {
if (typeof str === 'undefined' || str === null) return undefined;
return /(TRUE)|1/i.test(str);
}
}}
{
const proxy = {};
const obfs = {};
const $ = {};
const params = {};
}
start = (trojan) {
return proxy
}
trojan = "trojan://" password:password "@" server:server ":" port:port "/"? params? name:name?{
proxy.type = "trojan";
proxy.password = password;
proxy.server = server;
proxy.port = port;
proxy.name = name;
// name may be empty
if (!proxy.name) {
proxy.name = server + ":" + port;
}
};
password = match:$[^@]+ {
return decodeURIComponent(match);
};
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 >= 0 && port <= 65535) {
return port;
} else {
throw new Error("Invalid port: " + port);
}
}
params = "?" head:param tail:("&"@param)* {
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
proxy.sni = params["sni"] || params["peer"];
if (toBool(params["ws"])) {
proxy.network = "ws";
$set(proxy, "ws-opts.path", params["wspath"]);
}
if (params["type"]) {
let httpupgrade
proxy.network = params["type"]
if(proxy.network === 'httpupgrade') {
proxy.network = 'ws'
httpupgrade = true
}
if (['grpc'].includes(proxy.network)) {
proxy[proxy.network + '-opts'] = {
'grpc-service-name': params["serviceName"],
'_grpc-type': params["mode"],
};
} else {
if (params["path"]) {
$set(proxy, proxy.network+"-opts.path", decodeURIComponent(params["path"]));
}
if (params["host"]) {
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
}
if (httpupgrade) {
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade", true);
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
}
}
}
proxy.udp = toBool(params["udp"]);
proxy.tfo = toBool(params["tfo"]);
}
param = kv/single;
kv = key:$[a-z]i+ "=" value:$[^&#]i* {
params[key] = value;
}
single = key:$[a-z]i+ {
params[key] = true;
};
name = "#" + match:$.* {
return decodeURIComponent(match);
}

View File

@ -1,141 +0,0 @@
import * as peggy from "peggy";
const grammars = String.raw`
// global initializer
{{
function $set(obj, path, value) {
if (Object(obj) !== obj) return obj;
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
path
.slice(0, -1)
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
path[path.length - 1]
] = value;
return obj;
}
function toBool(str) {
if (typeof str === 'undefined' || str === null) return undefined;
return /(TRUE)|1/i.test(str);
}
}}
{
const proxy = {};
const obfs = {};
const $ = {};
const params = {};
}
start = (trojan) {
return proxy
}
trojan = "trojan://" password:password "@" server:server ":" port:port "/"? params? name:name?{
proxy.type = "trojan";
proxy.password = password;
proxy.server = server;
proxy.port = port;
proxy.name = name;
// name may be empty
if (!proxy.name) {
proxy.name = server + ":" + port;
}
};
password = match:$[^@]+ {
return decodeURIComponent(match);
};
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 >= 0 && port <= 65535) {
return port;
} else {
throw new Error("Invalid port: " + port);
}
}
params = "?" head:param tail:("&"@param)* {
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
proxy.sni = params["sni"] || params["peer"];
if (toBool(params["ws"])) {
proxy.network = "ws";
$set(proxy, "ws-opts.path", params["wspath"]);
}
if (params["type"]) {
let httpupgrade
proxy.network = params["type"]
if(proxy.network === 'httpupgrade') {
proxy.network = 'ws'
httpupgrade = true
}
if (['grpc'].includes(proxy.network)) {
proxy[proxy.network + '-opts'] = {
'grpc-service-name': params["serviceName"],
'_grpc-type': params["mode"],
};
} else {
if (params["path"]) {
$set(proxy, proxy.network+"-opts.path", decodeURIComponent(params["path"]));
}
if (params["host"]) {
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
}
if (httpupgrade) {
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade", true);
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
}
}
}
proxy.udp = toBool(params["udp"]);
proxy.tfo = toBool(params["tfo"]);
}
param = kv/single;
kv = key:$[a-z]i+ "=" value:$[^&#]i* {
params[key] = value;
}
single = key:$[a-z]i+ {
params[key] = true;
};
name = "#" + match:$.* {
return decodeURIComponent(match);
}
`;
let parser: any;
export default function getParser() {
if (!parser) {
parser = peggy.generate(grammars);
}
return parser;
}

View File

@ -1,5 +1,3 @@
import getTrojanURIParser from "@/utils/trojan-uri";
export default function parseUri(uri: string): IProxyConfig {
const head = uri.split("://")[0];
switch (head) {
@ -467,7 +465,19 @@ function URI_VMESS(line: string): IProxyVmessConfig {
opts["v2ray-http-upgrade"] = true;
opts["v2ray-http-upgrade-fast-open"] = true;
}
proxy[`${proxy.network}-opts`] = opts;
switch (proxy.network) {
case "ws":
proxy["ws-opts"] = opts;
break;
case "http":
proxy["http-opts"] = opts;
break;
case "h2":
proxy["h2-opts"] = opts;
break;
default:
break;
}
}
} else {
delete proxy.network;
@ -530,16 +540,7 @@ function URI_VLESS(line: string): IProxyVlessConfig {
proxy.servername = params.sni || params.peer;
proxy.flow = params.flow ? "xtls-rprx-vision" : undefined;
proxy["client-fingerprint"] = params.fp as
| "chrome"
| "firefox"
| "safari"
| "iOS"
| "android"
| "edge"
| "360"
| "qq"
| "random";
proxy["client-fingerprint"] = params.fp as ClientFingerprint;
proxy.alpn = params.alpn ? params.alpn.split(",") : undefined;
proxy["skip-cert-verify"] = /(TRUE)|1/i.test(params.allowInsecure);
@ -635,16 +636,89 @@ function URI_VLESS(line: string): IProxyVlessConfig {
}
function URI_Trojan(line: string): IProxyTrojanConfig {
let [newLine, name] = line.split(/#(.+)/, 2);
const parser = getTrojanURIParser();
const proxy: IProxyTrojanConfig = parser.parse(newLine);
if (isNotBlank(name)) {
try {
proxy.name = decodeURIComponent(name).trim();
} catch (e) {
throw Error("Can not get proxy name");
line = line.split("trojan://")[1];
let [__, password, server, ___, port, ____, addons = "", name] =
/^(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line) || [];
let portNum = parseInt(`${port}`, 10);
if (isNaN(portNum)) {
portNum = 443;
}
password = decodeURIComponent(password);
let decodedName = trimStr(decodeURIComponent(name));
name = decodedName ?? `Trojan ${server}:${portNum}`;
const proxy: IProxyTrojanConfig = {
type: "trojan",
name,
server,
port: portNum,
password,
};
let host = "";
let path = "";
for (const addon of addons.split("&")) {
let [key, value] = addon.split("=");
value = decodeURIComponent(value);
switch (key) {
case "type":
if (["ws", "h2"].includes(value)) {
proxy.network = value as NetworkType;
} else {
proxy.network = "tcp";
}
break;
case "host":
host = value;
break;
case "path":
path = value;
break;
case "alpn":
proxy["alpn"] = value ? value.split(",") : undefined;
break;
case "sni":
proxy["sni"] = value;
break;
case "skip-cert-verify":
proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value);
break;
case "fingerprint":
proxy["fingerprint"] = value;
break;
case "fp":
proxy["fingerprint"] = value;
break;
case "encryption":
let encryption = value.split(";");
if (encryption.length === 3) {
proxy["ss-opts"] = {
enabled: true,
method: encryption[1],
password: encryption[2],
};
}
case "client-fingerprint":
proxy["client-fingerprint"] = value as ClientFingerprint;
break;
default:
break;
}
}
if (proxy.network === "ws") {
proxy["ws-opts"] = {
headers: { Host: host },
path,
} as WsOptions;
} else if (proxy.network === "grpc") {
proxy["grpc-opts"] = {
"grpc-service-name": path,
} as GrpcOptions;
}
return proxy;
}