Forráskód Böngészése

fix: trojan uri parser

MystiPanda 11 hónapja
szülő
commit
18196c4a77
4 módosított fájl, 110 hozzáadás és 299 törlés
  1. 15 6
      src/services/types.d.ts
  2. 0 131
      src/utils/trojan-uri.peg
  3. 0 141
      src/utils/trojan-uri.ts
  4. 95 21
      src/utils/uri-parser.ts

+ 15 - 6
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 {

+ 0 - 131
src/utils/trojan-uri.peg

@@ -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);
-}

+ 0 - 141
src/utils/trojan-uri.ts

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

+ 95 - 21
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;
 }