diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 3c1c3576..e450090b 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -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 { diff --git a/src/utils/trojan-uri.peg b/src/utils/trojan-uri.peg deleted file mode 100644 index 28a8b879..00000000 --- a/src/utils/trojan-uri.peg +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/src/utils/trojan-uri.ts b/src/utils/trojan-uri.ts deleted file mode 100644 index 54437944..00000000 --- a/src/utils/trojan-uri.ts +++ /dev/null @@ -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; -} diff --git a/src/utils/uri-parser.ts b/src/utils/uri-parser.ts index 6d6131e5..8dc4bf8d 100644 --- a/src/utils/uri-parser.ts +++ b/src/utils/uri-parser.ts @@ -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; }