uri-parser.ts 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  1. import getTrojanURIParser from "@/utils/trojan-uri";
  2. export default function parseUri(uri: string): IProxyConfig {
  3. const head = uri.split("://")[0];
  4. switch (head) {
  5. case "ss":
  6. return URI_SS(uri);
  7. case "ssr":
  8. return URI_SSR(uri);
  9. case "vmess":
  10. return URI_VMESS(uri);
  11. case "vless":
  12. return URI_VLESS(uri);
  13. case "trojan":
  14. return URI_Trojan(uri);
  15. case "hysteria2":
  16. return URI_Hysteria2(uri);
  17. case "hy2":
  18. return URI_Hysteria2(uri);
  19. case "hysteria":
  20. return URI_Hysteria(uri);
  21. case "hy":
  22. return URI_Hysteria(uri);
  23. case "tuic":
  24. return URI_TUIC(uri);
  25. case "wireguard":
  26. return URI_Wireguard(uri);
  27. case "wg":
  28. return URI_Wireguard(uri);
  29. case "http":
  30. return URI_HTTP(uri);
  31. case "socks5":
  32. return URI_SOCKS(uri);
  33. default:
  34. throw Error(`Unknown uri type: ${head}`);
  35. }
  36. }
  37. function getIfNotBlank(
  38. value: string | undefined,
  39. dft?: string
  40. ): string | undefined {
  41. return value && value.trim() !== "" ? value : dft;
  42. }
  43. function getIfPresent(value: any, dft?: any): any {
  44. return value ? value : dft;
  45. }
  46. function isPresent(value: any): boolean {
  47. return value !== null && value !== undefined;
  48. }
  49. function trimStr(str: string | undefined): string | undefined {
  50. return str ? str.trim() : str;
  51. }
  52. function isNotBlank(name: string) {
  53. return name.trim().length !== 0;
  54. }
  55. function isIPv4(address: string): boolean {
  56. // Check if the address is IPv4
  57. const ipv4Regex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
  58. return ipv4Regex.test(address);
  59. }
  60. function isIPv6(address: string): boolean {
  61. // Check if the address is IPv6
  62. const ipv6Regex =
  63. /^((?=.*(::))(?!.*\3.+)(::)?)([0-9A-Fa-f]{1,4}(\3|:\b)|\3){7}[0-9A-Fa-f]{1,4}$/;
  64. return ipv6Regex.test(address);
  65. }
  66. function decodeBase64OrOriginal(str: string): string {
  67. try {
  68. return atob(str);
  69. } catch {
  70. return str;
  71. }
  72. }
  73. function URI_SS(line: string): IProxyShadowsocksConfig {
  74. // parse url
  75. let content = line.split("ss://")[1];
  76. const proxy: IProxyShadowsocksConfig = {
  77. name: decodeURIComponent(line.split("#")[1]).trim(),
  78. type: "ss",
  79. server: "",
  80. port: 0,
  81. };
  82. content = content.split("#")[0]; // strip proxy name
  83. // handle IPV4 and IPV6
  84. let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
  85. let userInfoStr = decodeBase64OrOriginal(content.split("@")[0]);
  86. let query = "";
  87. if (!serverAndPortArray) {
  88. if (content.includes("?")) {
  89. const parsed = content.match(/^(.*)(\?.*)$/);
  90. content = parsed?.[1] ?? "";
  91. query = parsed?.[2] ?? "";
  92. }
  93. content = decodeBase64OrOriginal(content);
  94. if (query) {
  95. if (/(&|\?)v2ray-plugin=/.test(query)) {
  96. const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
  97. let v2rayPlugin = parsed![2];
  98. if (v2rayPlugin) {
  99. proxy.plugin = "v2ray-plugin";
  100. proxy["plugin-opts"] = JSON.parse(
  101. decodeBase64OrOriginal(v2rayPlugin)
  102. );
  103. }
  104. }
  105. content = `${content}${query}`;
  106. }
  107. userInfoStr = content.split("@")[0];
  108. serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
  109. }
  110. const serverAndPort = serverAndPortArray?.[1];
  111. const portIdx = serverAndPort?.lastIndexOf(":") ?? 0;
  112. proxy.server = serverAndPort?.substring(0, portIdx) ?? "";
  113. proxy.port = parseInt(
  114. `${serverAndPort?.substring(portIdx + 1)}`.match(/\d+/)?.[0] ?? ""
  115. );
  116. const userInfo = userInfoStr.match(/(^.*?):(.*$)/);
  117. proxy.cipher = userInfo?.[1];
  118. proxy.password = userInfo?.[2];
  119. // handle obfs
  120. const idx = content.indexOf("?plugin=");
  121. if (idx !== -1) {
  122. const pluginInfo = (
  123. "plugin=" + decodeURIComponent(content.split("?plugin=")[1].split("&")[0])
  124. ).split(";");
  125. const params: Record<string, any> = {};
  126. for (const item of pluginInfo) {
  127. const [key, val] = item.split("=");
  128. if (key) params[key] = val || true; // some options like "tls" will not have value
  129. }
  130. switch (params.plugin) {
  131. case "obfs-local":
  132. case "simple-obfs":
  133. proxy.plugin = "obfs";
  134. proxy["plugin-opts"] = {
  135. mode: params.obfs,
  136. host: getIfNotBlank(params["obfs-host"]),
  137. };
  138. break;
  139. case "v2ray-plugin":
  140. proxy.plugin = "v2ray-plugin";
  141. proxy["plugin-opts"] = {
  142. mode: "websocket",
  143. host: getIfNotBlank(params["obfs-host"]),
  144. path: getIfNotBlank(params.path),
  145. tls: getIfPresent(params.tls),
  146. };
  147. break;
  148. default:
  149. throw new Error(`Unsupported plugin option: ${params.plugin}`);
  150. }
  151. }
  152. if (/(&|\?)uot=(1|true)/i.test(query)) {
  153. proxy["udp-over-tcp"] = true;
  154. }
  155. if (/(&|\?)tfo=(1|true)/i.test(query)) {
  156. proxy.tfo = true;
  157. }
  158. return proxy;
  159. }
  160. function URI_SSR(line: string): IProxyshadowsocksRConfig {
  161. line = decodeBase64OrOriginal(line.split("ssr://")[1]);
  162. // handle IPV6 & IPV4 format
  163. let splitIdx = line.indexOf(":origin");
  164. if (splitIdx === -1) {
  165. splitIdx = line.indexOf(":auth_");
  166. }
  167. const serverAndPort = line.substring(0, splitIdx);
  168. const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":"));
  169. const port = parseInt(
  170. serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1)
  171. );
  172. let params = line
  173. .substring(splitIdx + 1)
  174. .split("/?")[0]
  175. .split(":");
  176. let proxy: IProxyshadowsocksRConfig = {
  177. name: "SSR",
  178. type: "ssr",
  179. server,
  180. port,
  181. protocol: params[0],
  182. cipher: params[1],
  183. obfs: params[2],
  184. password: decodeBase64OrOriginal(params[3]),
  185. };
  186. // get other params
  187. const other_params: Record<string, string> = {};
  188. const paramsArray = line.split("/?")[1]?.split("&") || [];
  189. for (const item of paramsArray) {
  190. const [key, val] = item.split("=");
  191. if (val?.trim().length > 0) {
  192. other_params[key] = val.trim();
  193. }
  194. }
  195. proxy = {
  196. ...proxy,
  197. name: other_params.remarks
  198. ? decodeBase64OrOriginal(other_params.remarks).trim()
  199. : proxy.server ?? "",
  200. "protocol-param": getIfNotBlank(
  201. decodeBase64OrOriginal(other_params.protoparam || "").replace(/\s/g, "")
  202. ),
  203. "obfs-param": getIfNotBlank(
  204. decodeBase64OrOriginal(other_params.obfsparam || "").replace(/\s/g, "")
  205. ),
  206. };
  207. return proxy;
  208. }
  209. function URI_VMESS(line: string): IProxyVmessConfig {
  210. line = line.split("vmess://")[1];
  211. let content = decodeBase64OrOriginal(line);
  212. if (/=\s*vmess/.test(content)) {
  213. // Quantumult VMess URI format
  214. const partitions = content.split(",").map((p) => p.trim());
  215. const params: Record<string, string> = {};
  216. for (const part of partitions) {
  217. if (part.indexOf("=") !== -1) {
  218. const [key, val] = part.split("=");
  219. params[key.trim()] = val.trim();
  220. }
  221. }
  222. const proxy: IProxyVmessConfig = {
  223. name: partitions[0].split("=")[0].trim(),
  224. type: "vmess",
  225. server: partitions[1],
  226. port: parseInt(partitions[2], 10),
  227. cipher: getIfNotBlank(partitions[3], "auto"),
  228. uuid: partitions[4].match(/^"(.*)"$/)?.[1] || "",
  229. tls: params.obfs === "wss",
  230. udp: getIfPresent(params["udp-relay"]),
  231. tfo: getIfPresent(params["fast-open"]),
  232. "skip-cert-verify": isPresent(params["tls-verification"])
  233. ? !params["tls-verification"]
  234. : undefined,
  235. };
  236. if (isPresent(params.obfs)) {
  237. if (params.obfs === "ws" || params.obfs === "wss") {
  238. proxy.network = "ws";
  239. proxy["ws-opts"] = {
  240. path:
  241. (getIfNotBlank(params["obfs-path"]) || '"/"').match(
  242. /^"(.*)"$/
  243. )?.[1] || "/",
  244. headers: {
  245. Host:
  246. params["obfs-header"]?.match(/Host:\s*([a-zA-Z0-9-.]*)/)?.[1] ||
  247. "",
  248. },
  249. };
  250. } else {
  251. throw new Error(`Unsupported obfs: ${params.obfs}`);
  252. }
  253. }
  254. return proxy;
  255. } else {
  256. let params: Record<string, any> = {};
  257. try {
  258. // V2rayN URI format
  259. params = JSON.parse(content);
  260. } catch (e) {
  261. // Shadowrocket URI format
  262. const match = /(^[^?]+?)\/?\?(.*)$/.exec(line);
  263. if (match) {
  264. let [_, base64Line, qs] = match;
  265. content = decodeBase64OrOriginal(base64Line);
  266. for (const addon of qs.split("&")) {
  267. const [key, valueRaw] = addon.split("=");
  268. const value = decodeURIComponent(valueRaw);
  269. if (value.indexOf(",") === -1) {
  270. params[key] = value;
  271. } else {
  272. params[key] = value.split(",");
  273. }
  274. }
  275. const contentMatch = /(^[^:]+?):([^:]+?)@(.*):(\d+)$/.exec(content);
  276. if (contentMatch) {
  277. let [__, cipher, uuid, server, port] = contentMatch;
  278. params.scy = cipher;
  279. params.id = uuid;
  280. params.port = port;
  281. params.add = server;
  282. }
  283. }
  284. }
  285. const server = params.add;
  286. const port = parseInt(getIfPresent(params.port), 10);
  287. const proxy: IProxyVmessConfig = {
  288. name:
  289. trimStr(params.ps) ??
  290. trimStr(params.remarks) ??
  291. trimStr(params.remark) ??
  292. `VMess ${server}:${port}`,
  293. type: "vmess",
  294. server,
  295. port,
  296. cipher: getIfPresent(params.scy, "auto"),
  297. uuid: params.id,
  298. tls: ["tls", true, 1, "1"].includes(params.tls),
  299. "skip-cert-verify": isPresent(params.verify_cert)
  300. ? !params.verify_cert
  301. : undefined,
  302. };
  303. proxy.alterId = parseInt(getIfPresent(params.aid ?? params.alterId, 0), 10);
  304. if (proxy.tls && params.sni) {
  305. proxy.servername = params.sni;
  306. }
  307. let httpupgrade = false;
  308. if (params.net === "ws" || params.obfs === "websocket") {
  309. proxy.network = "ws";
  310. } else if (
  311. ["http"].includes(params.net) ||
  312. ["http"].includes(params.obfs) ||
  313. ["http"].includes(params.type)
  314. ) {
  315. proxy.network = "http";
  316. } else if (["grpc"].includes(params.net)) {
  317. proxy.network = "grpc";
  318. } else if (params.net === "httpupgrade") {
  319. proxy.network = "ws";
  320. httpupgrade = true;
  321. } else if (params.net === "h2" || proxy.network === "h2") {
  322. proxy.network = "h2";
  323. }
  324. if (proxy.network) {
  325. let transportHost = params.host ?? params.obfsParam;
  326. try {
  327. const parsedObfs = JSON.parse(transportHost);
  328. const parsedHost = parsedObfs?.Host;
  329. if (parsedHost) {
  330. transportHost = parsedHost;
  331. }
  332. } catch (e) {
  333. // ignore JSON parse errors
  334. }
  335. let transportPath = params.path;
  336. if (proxy.network === "http") {
  337. if (transportHost) {
  338. transportHost = Array.isArray(transportHost)
  339. ? transportHost[0]
  340. : transportHost;
  341. }
  342. if (transportPath) {
  343. transportPath = Array.isArray(transportPath)
  344. ? transportPath[0]
  345. : transportPath;
  346. } else {
  347. transportPath = "/";
  348. }
  349. }
  350. if (transportPath || transportHost) {
  351. if (["grpc"].includes(proxy.network)) {
  352. proxy[`grpc-opts`] = {
  353. "grpc-service-name": getIfNotBlank(transportPath),
  354. };
  355. } else {
  356. const opts: Record<string, any> = {
  357. path: getIfNotBlank(transportPath),
  358. headers: { Host: getIfNotBlank(transportHost) },
  359. };
  360. if (httpupgrade) {
  361. opts["v2ray-http-upgrade"] = true;
  362. opts["v2ray-http-upgrade-fast-open"] = true;
  363. }
  364. proxy[`${proxy.network}-opts`] = opts;
  365. }
  366. } else {
  367. delete proxy.network;
  368. }
  369. if (proxy.tls && !proxy.servername && transportHost) {
  370. proxy.servername = transportHost;
  371. }
  372. }
  373. return proxy;
  374. }
  375. }
  376. function URI_VLESS(line: string): IProxyVlessConfig {
  377. line = line.split("vless://")[1];
  378. let isShadowrocket;
  379. let parsed = /^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!;
  380. if (!parsed) {
  381. let [_, base64, other] = /^(.*?)(\?.*?$)/.exec(line)!;
  382. line = `${atob(base64)}${other}`;
  383. parsed = /^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!;
  384. isShadowrocket = true;
  385. }
  386. let [__, uuid, server, portStr, ___, addons = "", name] = parsed;
  387. if (isShadowrocket) {
  388. uuid = uuid.replace(/^.*?:/g, "");
  389. }
  390. const port = parseInt(portStr, 10);
  391. uuid = decodeURIComponent(uuid);
  392. name = decodeURIComponent(name);
  393. const proxy: IProxyVlessConfig = {
  394. type: "vless",
  395. name: "",
  396. server,
  397. port,
  398. uuid,
  399. };
  400. const params: Record<string, string> = {};
  401. for (const addon of addons.split("&")) {
  402. const [key, valueRaw] = addon.split("=");
  403. const value = decodeURIComponent(valueRaw);
  404. params[key] = value;
  405. }
  406. proxy.name =
  407. trimStr(name) ??
  408. trimStr(params.remarks) ??
  409. trimStr(params.remark) ??
  410. `VLESS ${server}:${port}`;
  411. proxy.tls = (params.security && params.security !== "none") || undefined;
  412. if (isShadowrocket && /TRUE|1/i.test(params.tls)) {
  413. proxy.tls = true;
  414. params.security = params.security ?? "reality";
  415. }
  416. proxy.servername = params.sni || params.peer;
  417. proxy.flow = params.flow ? "xtls-rprx-vision" : undefined;
  418. proxy["client-fingerprint"] = params.fp as
  419. | "chrome"
  420. | "firefox"
  421. | "safari"
  422. | "iOS"
  423. | "android"
  424. | "edge"
  425. | "360"
  426. | "qq"
  427. | "random";
  428. proxy.alpn = params.alpn ? params.alpn.split(",") : undefined;
  429. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(params.allowInsecure);
  430. if (["reality"].includes(params.security)) {
  431. const opts: IProxyVlessConfig["reality-opts"] = {};
  432. if (params.pbk) {
  433. opts["public-key"] = params.pbk;
  434. }
  435. if (params.sid) {
  436. opts["short-id"] = params.sid;
  437. }
  438. if (Object.keys(opts).length > 0) {
  439. proxy["reality-opts"] = opts;
  440. }
  441. }
  442. let httpupgrade = false;
  443. proxy["ws-opts"] = {
  444. headers: undefined,
  445. path: undefined,
  446. };
  447. proxy["http-opts"] = {
  448. headers: undefined,
  449. path: undefined,
  450. };
  451. proxy["grpc-opts"] = {};
  452. if (params.headerType === "http") {
  453. proxy.network = "http";
  454. } else {
  455. proxy.network = "ws";
  456. httpupgrade = true;
  457. }
  458. if (!proxy.network && isShadowrocket && params.obfs) {
  459. switch (params.type) {
  460. case "sw":
  461. proxy.network = "ws";
  462. break;
  463. case "http":
  464. proxy.network = "http";
  465. break;
  466. case "h2":
  467. proxy.network = "h2";
  468. break;
  469. case "grpc":
  470. proxy.network = "grpc";
  471. break;
  472. default: {
  473. }
  474. }
  475. }
  476. if (["websocket"].includes(proxy.network)) {
  477. proxy.network = "ws";
  478. }
  479. if (proxy.network && !["tcp", "none"].includes(proxy.network)) {
  480. const opts: Record<string, any> = {};
  481. const host = params.host ?? params.obfsParam;
  482. if (host) {
  483. if (params.obfsParam) {
  484. try {
  485. const parsed = JSON.parse(host);
  486. opts.headers = parsed;
  487. } catch (e) {
  488. opts.headers = { Host: host };
  489. }
  490. } else {
  491. opts.headers = { Host: host };
  492. }
  493. }
  494. if (params.path) {
  495. opts.path = params.path;
  496. }
  497. if (httpupgrade) {
  498. opts["v2ray-http-upgrade"] = true;
  499. opts["v2ray-http-upgrade-fast-open"] = true;
  500. }
  501. if (Object.keys(opts).length > 0) {
  502. proxy[`${proxy.network}-opts`] = opts;
  503. }
  504. }
  505. if (proxy.tls && !proxy.servername) {
  506. if (proxy.network === "ws") {
  507. proxy.servername = proxy["ws-opts"]?.headers?.Host;
  508. } else if (proxy.network === "http") {
  509. let httpHost = proxy["http-opts"]?.headers?.Host;
  510. proxy.servername = Array.isArray(httpHost) ? httpHost[0] : httpHost;
  511. }
  512. }
  513. return proxy;
  514. }
  515. function URI_Trojan(line: string): IProxyTrojanConfig {
  516. let [newLine, name] = line.split(/#(.+)/, 2);
  517. const parser = getTrojanURIParser();
  518. const proxy: IProxyTrojanConfig = parser.parse(newLine);
  519. if (isNotBlank(name)) {
  520. try {
  521. proxy.name = decodeURIComponent(name).trim();
  522. } catch (e) {
  523. throw Error("Can not get proxy name");
  524. }
  525. }
  526. return proxy;
  527. }
  528. function URI_Hysteria2(line: string): IProxyHysteria2Config {
  529. line = line.split(/(hysteria2|hy2):\/\//)[2];
  530. // eslint-disable-next-line no-unused-vars
  531. let [__, password, server, ___, port, ____, addons = "", name] =
  532. /^(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line) || [];
  533. let portNum = parseInt(`${port}`, 10);
  534. if (isNaN(portNum)) {
  535. portNum = 443;
  536. }
  537. password = decodeURIComponent(password);
  538. let decodedName = trimStr(decodeURIComponent(name));
  539. name = decodedName ?? `Hysteria2 ${server}:${port}`;
  540. const proxy: IProxyHysteria2Config = {
  541. type: "hysteria2",
  542. name,
  543. server,
  544. port: portNum,
  545. password,
  546. };
  547. const params: Record<string, string> = {};
  548. for (const addon of addons.split("&")) {
  549. const [key, valueRaw] = addon.split("=");
  550. let value = valueRaw;
  551. value = decodeURIComponent(valueRaw);
  552. params[key] = value;
  553. }
  554. proxy.sni = params.sni;
  555. if (!proxy.sni && params.peer) {
  556. proxy.sni = params.peer;
  557. }
  558. if (params.obfs && params.obfs !== "none") {
  559. proxy.obfs = params.obfs;
  560. }
  561. proxy.ports = params.mport;
  562. proxy["obfs-password"] = params["obfs-password"];
  563. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(params.insecure);
  564. proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
  565. proxy.fingerprint = params.pinSHA256;
  566. return proxy;
  567. }
  568. function URI_Hysteria(line: string): IProxyHysteriaConfig {
  569. line = line.split(/(hysteria|hy):\/\//)[2];
  570. let [__, server, ___, port, ____, addons = "", name] =
  571. /^(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!;
  572. let portNum = parseInt(`${port}`, 10);
  573. if (isNaN(portNum)) {
  574. portNum = 443;
  575. }
  576. let decodedName = trimStr(decodeURIComponent(name));
  577. name = decodedName ?? `Hysteria ${server}:${port}`;
  578. const proxy: IProxyHysteriaConfig = {
  579. type: "hysteria",
  580. name,
  581. server,
  582. port: portNum,
  583. };
  584. const params: Record<string, string> = {};
  585. for (const addon of addons.split("&")) {
  586. let [key, value] = addon.split("=");
  587. key = key.replace(/_/, "-");
  588. value = decodeURIComponent(value);
  589. switch (key) {
  590. case "alpn":
  591. proxy["alpn"] = value ? value.split(",") : undefined;
  592. break;
  593. case "insecure":
  594. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value);
  595. break;
  596. case "auth":
  597. proxy["auth-str"] = value;
  598. break;
  599. case "mport":
  600. proxy["ports"] = value;
  601. break;
  602. case "obfsParam":
  603. proxy["obfs"] = value;
  604. break;
  605. case "upmbps":
  606. proxy["up"] = value;
  607. break;
  608. case "downmbps":
  609. proxy["down"] = value;
  610. break;
  611. case "obfs":
  612. proxy["obfs"] = value || "";
  613. break;
  614. case "fast-open":
  615. proxy["fast-open"] = /(TRUE)|1/i.test(value);
  616. break;
  617. case "peer":
  618. proxy["fast-open"] = /(TRUE)|1/i.test(value);
  619. break;
  620. case "recv-window-conn":
  621. proxy["recv-window-conn"] = parseInt(value);
  622. break;
  623. case "recv-window":
  624. proxy["recv-window"] = parseInt(value);
  625. break;
  626. case "ca":
  627. proxy["ca"] = value;
  628. break;
  629. case "ca-str":
  630. proxy["ca-str"] = value;
  631. break;
  632. case "disable-mtu-discovery":
  633. proxy["disable-mtu-discovery"] = /(TRUE)|1/i.test(value);
  634. break;
  635. case "fingerprint":
  636. proxy["fingerprint"] = value;
  637. break;
  638. case "protocol":
  639. proxy["protocol"] = value;
  640. case "sni":
  641. proxy["sni"] = value;
  642. default:
  643. break;
  644. }
  645. }
  646. if (!proxy.sni && params.peer) {
  647. proxy.sni = params.peer;
  648. }
  649. if (!proxy["fast-open"] && params["fast-open"]) {
  650. proxy["fast-open"] = true;
  651. }
  652. if (!proxy.protocol) {
  653. proxy.protocol = "udp";
  654. }
  655. return proxy;
  656. }
  657. function URI_TUIC(line: string): IProxyTuicConfig {
  658. line = line.split(/tuic:\/\//)[1];
  659. let [__, uuid, password, server, ___, port, ____, addons = "", name] =
  660. /^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line) || [];
  661. let portNum = parseInt(`${port}`, 10);
  662. if (isNaN(portNum)) {
  663. portNum = 443;
  664. }
  665. password = decodeURIComponent(password);
  666. let decodedName = trimStr(decodeURIComponent(name));
  667. name = decodedName ?? `TUIC ${server}:${port}`;
  668. const proxy: IProxyTuicConfig = {
  669. type: "tuic",
  670. name,
  671. server,
  672. port: portNum,
  673. password,
  674. uuid,
  675. };
  676. for (const addon of addons.split("&")) {
  677. let [key, value] = addon.split("=");
  678. key = key.replace(/_/, "-");
  679. value = decodeURIComponent(value);
  680. switch (key) {
  681. case "token":
  682. proxy["token"] = value;
  683. break;
  684. case "ip":
  685. proxy["ip"] = value;
  686. break;
  687. case "heartbeat-interval":
  688. proxy["heartbeat-interval"] = parseInt(value);
  689. break;
  690. case "alpn":
  691. proxy["alpn"] = value ? value.split(",") : undefined;
  692. break;
  693. case "disable-sni":
  694. proxy["disable-sni"] = /(TRUE)|1/i.test(value);
  695. break;
  696. case "reduce-rtt":
  697. proxy["reduce-rtt"] = /(TRUE)|1/i.test(value);
  698. break;
  699. case "request-timeout":
  700. proxy["request-timeout"] = parseInt(value);
  701. break;
  702. case "udp-relay-mode":
  703. proxy["udp-relay-mode"] = value;
  704. break;
  705. case "congestion-controller":
  706. proxy["congestion-controller"] = value;
  707. break;
  708. case "max-udp-relay-packet-size":
  709. proxy["max-udp-relay-packet-size"] = parseInt(value);
  710. break;
  711. case "fast-open":
  712. proxy["fast-open"] = /(TRUE)|1/i.test(value);
  713. break;
  714. case "skip-cert-verify":
  715. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value);
  716. break;
  717. case "max-open-streams":
  718. proxy["max-open-streams"] = parseInt(value);
  719. break;
  720. case "sni":
  721. proxy["sni"] = value;
  722. break;
  723. case "allow-insecure":
  724. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value);
  725. break;
  726. }
  727. }
  728. return proxy;
  729. }
  730. function URI_Wireguard(line: string): IProxyWireguardConfig {
  731. line = line.split(/(wireguard|wg):\/\//)[2];
  732. let [__, ___, privateKey, server, ____, port, _____, addons = "", name] =
  733. /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!;
  734. let portNum = parseInt(`${port}`, 10);
  735. if (isNaN(portNum)) {
  736. portNum = 443;
  737. }
  738. privateKey = decodeURIComponent(privateKey);
  739. let decodedName = trimStr(decodeURIComponent(name));
  740. name = decodedName ?? `WireGuard ${server}:${port}`;
  741. const proxy: IProxyWireguardConfig = {
  742. type: "wireguard",
  743. name,
  744. server,
  745. port: portNum,
  746. "private-key": privateKey,
  747. udp: true,
  748. };
  749. for (const addon of addons.split("&")) {
  750. let [key, value] = addon.split("=");
  751. key = key.replace(/_/, "-");
  752. value = decodeURIComponent(value);
  753. switch (key) {
  754. case "address":
  755. case "ip":
  756. value.split(",").map((i) => {
  757. const ip = i
  758. .trim()
  759. .replace(/\/\d+$/, "")
  760. .replace(/^\[/, "")
  761. .replace(/\]$/, "");
  762. if (isIPv4(ip)) {
  763. proxy.ip = ip;
  764. } else if (isIPv6(ip)) {
  765. proxy.ipv6 = ip;
  766. }
  767. });
  768. break;
  769. case "publickey":
  770. proxy["public-key"] = value;
  771. break;
  772. case "allowed-ips":
  773. proxy["allowed-ips"] = value.split(",");
  774. break;
  775. case "pre-shared-key":
  776. proxy["pre-shared-key"] = value;
  777. break;
  778. case "reserved":
  779. const parsed = value
  780. .split(",")
  781. .map((i) => parseInt(i.trim(), 10))
  782. .filter((i) => Number.isInteger(i));
  783. if (parsed.length === 3) {
  784. proxy["reserved"] = parsed;
  785. }
  786. break;
  787. case "udp":
  788. proxy["udp"] = /(TRUE)|1/i.test(value);
  789. break;
  790. case "mtu":
  791. proxy.mtu = parseInt(value.trim(), 10);
  792. break;
  793. case "dialer-proxy":
  794. proxy["dialer-proxy"] = value;
  795. break;
  796. case "remote-dns-resolve":
  797. proxy["remote-dns-resolve"] = /(TRUE)|1/i.test(value);
  798. break;
  799. case "dns":
  800. proxy.dns = value.split(",");
  801. break;
  802. default:
  803. break;
  804. }
  805. }
  806. return proxy;
  807. }
  808. function URI_HTTP(line: string): IProxyHttpConfig {
  809. line = line.split(/(http|https):\/\//)[2];
  810. let [__, ___, auth, server, ____, port, _____, addons = "", name] =
  811. /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!;
  812. let portNum = parseInt(`${port}`, 10);
  813. if (isNaN(portNum)) {
  814. portNum = 443;
  815. }
  816. if (auth) {
  817. auth = decodeURIComponent(auth);
  818. }
  819. let decodedName = trimStr(decodeURIComponent(name));
  820. name = decodedName ?? `HTTP ${server}:${portNum}`;
  821. const proxy: IProxyHttpConfig = {
  822. type: "http",
  823. name,
  824. server,
  825. port: portNum,
  826. };
  827. if (auth) {
  828. const [username, password] = auth.split(":");
  829. proxy.username = username;
  830. proxy.password = password;
  831. }
  832. for (const addon of addons.split("&")) {
  833. let [key, value] = addon.split("=");
  834. key = key.replace(/_/, "-");
  835. value = decodeURIComponent(value);
  836. switch (key) {
  837. case "tls":
  838. proxy.tls = /(TRUE)|1/i.test(value);
  839. break;
  840. case "fingerprint":
  841. proxy["fingerprint"] = value;
  842. break;
  843. case "skip-cert-verify":
  844. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value);
  845. break;
  846. case "ip-version":
  847. if (
  848. ["dual", "ipv4", "ipv6", "ipv4-prefer", "ipv6-prefer"].includes(value)
  849. ) {
  850. proxy["ip-version"] = value as
  851. | "dual"
  852. | "ipv4"
  853. | "ipv6"
  854. | "ipv4-prefer"
  855. | "ipv6-prefer";
  856. } else {
  857. proxy["ip-version"] = "dual";
  858. }
  859. break;
  860. default:
  861. break;
  862. }
  863. }
  864. return proxy;
  865. }
  866. function URI_SOCKS(line: string): IProxySocks5Config {
  867. line = line.split(/socks5:\/\//)[1];
  868. let [__, ___, auth, server, ____, port, _____, addons = "", name] =
  869. /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!;
  870. let portNum = parseInt(`${port}`, 10);
  871. if (isNaN(portNum)) {
  872. portNum = 443;
  873. }
  874. if (auth) {
  875. auth = decodeURIComponent(auth);
  876. }
  877. let decodedName = trimStr(decodeURIComponent(name));
  878. name = decodedName ?? `SOCKS5 ${server}:${portNum}`;
  879. const proxy: IProxySocks5Config = {
  880. type: "socks5",
  881. name,
  882. server,
  883. port: portNum,
  884. };
  885. if (auth) {
  886. const [username, password] = auth.split(":");
  887. proxy.username = username;
  888. proxy.password = password;
  889. }
  890. for (const addon of addons.split("&")) {
  891. let [key, value] = addon.split("=");
  892. key = key.replace(/_/, "-");
  893. value = decodeURIComponent(value);
  894. switch (key) {
  895. case "tls":
  896. proxy.tls = /(TRUE)|1/i.test(value);
  897. break;
  898. case "fingerprint":
  899. proxy["fingerprint"] = value;
  900. break;
  901. case "skip-cert-verify":
  902. proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value);
  903. break;
  904. case "udp":
  905. proxy["udp"] = /(TRUE)|1/i.test(value);
  906. break;
  907. case "ip-version":
  908. if (
  909. ["dual", "ipv4", "ipv6", "ipv4-prefer", "ipv6-prefer"].includes(value)
  910. ) {
  911. proxy["ip-version"] = value as
  912. | "dual"
  913. | "ipv4"
  914. | "ipv6"
  915. | "ipv4-prefer"
  916. | "ipv6-prefer";
  917. } else {
  918. proxy["ip-version"] = "dual";
  919. }
  920. break;
  921. default:
  922. break;
  923. }
  924. }
  925. return proxy;
  926. }