import getTrojanURIParser from "@/utils/trojan-uri"; export default function parseUri(uri: string): IProxyConfig { const head = uri.split("://")[0]; switch (head) { case "ss": return URI_SS(uri); case "ssr": return URI_SSR(uri); case "vmess": return URI_VMESS(uri); case "vless": return URI_VLESS(uri); case "trojan": return URI_Trojan(uri); case "hysteria2": return URI_Hysteria2(uri); case "hy2": return URI_Hysteria2(uri); case "hysteria": return URI_Hysteria(uri); case "hy": return URI_Hysteria(uri); case "tuic": return URI_TUIC(uri); case "wireguard": return URI_Wireguard(uri); case "wg": return URI_Wireguard(uri); case "http": return URI_HTTP(uri); case "socks5": return URI_SOCKS(uri); default: throw Error(`Unknown uri type: ${head}`); } } function getIfNotBlank( value: string | undefined, dft?: string ): string | undefined { return value && value.trim() !== "" ? value : dft; } function getIfPresent(value: any, dft?: any): any { return value ? value : dft; } function isPresent(value: any): boolean { return value !== null && value !== undefined; } function trimStr(str: string | undefined): string | undefined { return str ? str.trim() : str; } function isNotBlank(name: string) { return name.trim().length !== 0; } function isIPv4(address: string): boolean { // Check if the address is IPv4 const ipv4Regex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/; return ipv4Regex.test(address); } function isIPv6(address: string): boolean { // Check if the address is IPv6 const ipv6Regex = /^((?=.*(::))(?!.*\3.+)(::)?)([0-9A-Fa-f]{1,4}(\3|:\b)|\3){7}[0-9A-Fa-f]{1,4}$/; return ipv6Regex.test(address); } function decodeBase64OrOriginal(str: string): string { try { return atob(str); } catch { return str; } } function URI_SS(line: string): IProxyShadowsocksConfig { // parse url let content = line.split("ss://")[1]; const proxy: IProxyShadowsocksConfig = { name: decodeURIComponent(line.split("#")[1]).trim(), type: "ss", server: "", port: 0, }; content = content.split("#")[0]; // strip proxy name // handle IPV4 and IPV6 let serverAndPortArray = content.match(/@([^/]*)(\/|$)/); let userInfoStr = decodeBase64OrOriginal(content.split("@")[0]); let query = ""; if (!serverAndPortArray) { if (content.includes("?")) { const parsed = content.match(/^(.*)(\?.*)$/); content = parsed?.[1] ?? ""; query = parsed?.[2] ?? ""; } content = decodeBase64OrOriginal(content); if (query) { if (/(&|\?)v2ray-plugin=/.test(query)) { const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/); let v2rayPlugin = parsed![2]; if (v2rayPlugin) { proxy.plugin = "v2ray-plugin"; proxy["plugin-opts"] = JSON.parse( decodeBase64OrOriginal(v2rayPlugin) ); } } content = `${content}${query}`; } userInfoStr = content.split("@")[0]; serverAndPortArray = content.match(/@([^/]*)(\/|$)/); } const serverAndPort = serverAndPortArray?.[1]; const portIdx = serverAndPort?.lastIndexOf(":") ?? 0; proxy.server = serverAndPort?.substring(0, portIdx) ?? ""; proxy.port = parseInt( `${serverAndPort?.substring(portIdx + 1)}`.match(/\d+/)?.[0] ?? "" ); const userInfo = userInfoStr.match(/(^.*?):(.*$)/); proxy.cipher = userInfo?.[1]; proxy.password = userInfo?.[2]; // handle obfs const idx = content.indexOf("?plugin="); if (idx !== -1) { const pluginInfo = ( "plugin=" + decodeURIComponent(content.split("?plugin=")[1].split("&")[0]) ).split(";"); const params: Record = {}; for (const item of pluginInfo) { const [key, val] = item.split("="); if (key) params[key] = val || true; // some options like "tls" will not have value } switch (params.plugin) { case "obfs-local": case "simple-obfs": proxy.plugin = "obfs"; proxy["plugin-opts"] = { mode: params.obfs, host: getIfNotBlank(params["obfs-host"]), }; break; case "v2ray-plugin": proxy.plugin = "v2ray-plugin"; proxy["plugin-opts"] = { mode: "websocket", host: getIfNotBlank(params["obfs-host"]), path: getIfNotBlank(params.path), tls: getIfPresent(params.tls), }; break; default: throw new Error(`Unsupported plugin option: ${params.plugin}`); } } if (/(&|\?)uot=(1|true)/i.test(query)) { proxy["udp-over-tcp"] = true; } if (/(&|\?)tfo=(1|true)/i.test(query)) { proxy.tfo = true; } return proxy; } function URI_SSR(line: string): IProxyshadowsocksRConfig { line = decodeBase64OrOriginal(line.split("ssr://")[1]); // handle IPV6 & IPV4 format let splitIdx = line.indexOf(":origin"); if (splitIdx === -1) { splitIdx = line.indexOf(":auth_"); } const serverAndPort = line.substring(0, splitIdx); const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":")); const port = parseInt( serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1) ); let params = line .substring(splitIdx + 1) .split("/?")[0] .split(":"); let proxy: IProxyshadowsocksRConfig = { name: "SSR", type: "ssr", server, port, protocol: params[0], cipher: params[1], obfs: params[2], password: decodeBase64OrOriginal(params[3]), }; // get other params const other_params: Record = {}; const paramsArray = line.split("/?")[1]?.split("&") || []; for (const item of paramsArray) { const [key, val] = item.split("="); if (val?.trim().length > 0) { other_params[key] = val.trim(); } } proxy = { ...proxy, name: other_params.remarks ? decodeBase64OrOriginal(other_params.remarks).trim() : proxy.server ?? "", "protocol-param": getIfNotBlank( decodeBase64OrOriginal(other_params.protoparam || "").replace(/\s/g, "") ), "obfs-param": getIfNotBlank( decodeBase64OrOriginal(other_params.obfsparam || "").replace(/\s/g, "") ), }; return proxy; } function URI_VMESS(line: string): IProxyVmessConfig { line = line.split("vmess://")[1]; let content = decodeBase64OrOriginal(line); if (/=\s*vmess/.test(content)) { // Quantumult VMess URI format const partitions = content.split(",").map((p) => p.trim()); const params: Record = {}; for (const part of partitions) { if (part.indexOf("=") !== -1) { const [key, val] = part.split("="); params[key.trim()] = val.trim(); } } const proxy: IProxyVmessConfig = { name: partitions[0].split("=")[0].trim(), type: "vmess", server: partitions[1], port: parseInt(partitions[2], 10), cipher: getIfNotBlank(partitions[3], "auto"), uuid: partitions[4].match(/^"(.*)"$/)?.[1] || "", tls: params.obfs === "wss", udp: getIfPresent(params["udp-relay"]), tfo: getIfPresent(params["fast-open"]), "skip-cert-verify": isPresent(params["tls-verification"]) ? !params["tls-verification"] : undefined, }; if (isPresent(params.obfs)) { if (params.obfs === "ws" || params.obfs === "wss") { proxy.network = "ws"; proxy["ws-opts"] = { path: (getIfNotBlank(params["obfs-path"]) || '"/"').match( /^"(.*)"$/ )?.[1] || "/", headers: { Host: params["obfs-header"]?.match(/Host:\s*([a-zA-Z0-9-.]*)/)?.[1] || "", }, }; } else { throw new Error(`Unsupported obfs: ${params.obfs}`); } } return proxy; } else { let params: Record = {}; try { // V2rayN URI format params = JSON.parse(content); } catch (e) { // Shadowrocket URI format const match = /(^[^?]+?)\/?\?(.*)$/.exec(line); if (match) { let [_, base64Line, qs] = match; content = decodeBase64OrOriginal(base64Line); for (const addon of qs.split("&")) { const [key, valueRaw] = addon.split("="); const value = decodeURIComponent(valueRaw); if (value.indexOf(",") === -1) { params[key] = value; } else { params[key] = value.split(","); } } const contentMatch = /(^[^:]+?):([^:]+?)@(.*):(\d+)$/.exec(content); if (contentMatch) { let [__, cipher, uuid, server, port] = contentMatch; params.scy = cipher; params.id = uuid; params.port = port; params.add = server; } } } const server = params.add; const port = parseInt(getIfPresent(params.port), 10); const proxy: IProxyVmessConfig = { name: trimStr(params.ps) ?? trimStr(params.remarks) ?? trimStr(params.remark) ?? `VMess ${server}:${port}`, type: "vmess", server, port, cipher: getIfPresent(params.scy, "auto"), uuid: params.id, tls: ["tls", true, 1, "1"].includes(params.tls), "skip-cert-verify": isPresent(params.verify_cert) ? !params.verify_cert : undefined, }; proxy.alterId = parseInt(getIfPresent(params.aid ?? params.alterId, 0), 10); if (proxy.tls && params.sni) { proxy.servername = params.sni; } let httpupgrade = false; if (params.net === "ws" || params.obfs === "websocket") { proxy.network = "ws"; } else if ( ["http"].includes(params.net) || ["http"].includes(params.obfs) || ["http"].includes(params.type) ) { proxy.network = "http"; } else if (["grpc"].includes(params.net)) { proxy.network = "grpc"; } else if (params.net === "httpupgrade") { proxy.network = "ws"; httpupgrade = true; } else if (params.net === "h2" || proxy.network === "h2") { proxy.network = "h2"; } if (proxy.network) { let transportHost = params.host ?? params.obfsParam; try { const parsedObfs = JSON.parse(transportHost); const parsedHost = parsedObfs?.Host; if (parsedHost) { transportHost = parsedHost; } } catch (e) { // ignore JSON parse errors } let transportPath = params.path; if (proxy.network === "http") { if (transportHost) { transportHost = Array.isArray(transportHost) ? transportHost[0] : transportHost; } if (transportPath) { transportPath = Array.isArray(transportPath) ? transportPath[0] : transportPath; } else { transportPath = "/"; } } if (transportPath || transportHost) { if (["grpc"].includes(proxy.network)) { proxy[`grpc-opts`] = { "grpc-service-name": getIfNotBlank(transportPath), }; } else { const opts: Record = { path: getIfNotBlank(transportPath), headers: { Host: getIfNotBlank(transportHost) }, }; if (httpupgrade) { opts["v2ray-http-upgrade"] = true; opts["v2ray-http-upgrade-fast-open"] = true; } proxy[`${proxy.network}-opts`] = opts; } } else { delete proxy.network; } if (proxy.tls && !proxy.servername && transportHost) { proxy.servername = transportHost; } } return proxy; } } function URI_VLESS(line: string): IProxyVlessConfig { line = line.split("vless://")[1]; let isShadowrocket; let parsed = /^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!; if (!parsed) { let [_, base64, other] = /^(.*?)(\?.*?$)/.exec(line)!; line = `${atob(base64)}${other}`; parsed = /^(.*?)@(.*?):(\d+)\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!; isShadowrocket = true; } let [__, uuid, server, portStr, ___, addons = "", name] = parsed; if (isShadowrocket) { uuid = uuid.replace(/^.*?:/g, ""); } const port = parseInt(portStr, 10); uuid = decodeURIComponent(uuid); name = decodeURIComponent(name); const proxy: IProxyVlessConfig = { type: "vless", name: "", server, port, uuid, }; const params: Record = {}; for (const addon of addons.split("&")) { const [key, valueRaw] = addon.split("="); const value = decodeURIComponent(valueRaw); params[key] = value; } proxy.name = trimStr(name) ?? trimStr(params.remarks) ?? trimStr(params.remark) ?? `VLESS ${server}:${port}`; proxy.tls = (params.security && params.security !== "none") || undefined; if (isShadowrocket && /TRUE|1/i.test(params.tls)) { proxy.tls = true; params.security = params.security ?? "reality"; } 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.alpn = params.alpn ? params.alpn.split(",") : undefined; proxy["skip-cert-verify"] = /(TRUE)|1/i.test(params.allowInsecure); if (["reality"].includes(params.security)) { const opts: IProxyVlessConfig["reality-opts"] = {}; if (params.pbk) { opts["public-key"] = params.pbk; } if (params.sid) { opts["short-id"] = params.sid; } if (Object.keys(opts).length > 0) { proxy["reality-opts"] = opts; } } let httpupgrade = false; proxy["ws-opts"] = { headers: undefined, path: undefined, }; proxy["http-opts"] = { headers: undefined, path: undefined, }; proxy["grpc-opts"] = {}; if (params.headerType === "http") { proxy.network = "http"; } else { proxy.network = "ws"; httpupgrade = true; } if (!proxy.network && isShadowrocket && params.obfs) { switch (params.type) { case "sw": proxy.network = "ws"; break; case "http": proxy.network = "http"; break; case "h2": proxy.network = "h2"; break; case "grpc": proxy.network = "grpc"; break; default: { } } } if (["websocket"].includes(proxy.network)) { proxy.network = "ws"; } if (proxy.network && !["tcp", "none"].includes(proxy.network)) { const opts: Record = {}; const host = params.host ?? params.obfsParam; if (host) { if (params.obfsParam) { try { const parsed = JSON.parse(host); opts.headers = parsed; } catch (e) { opts.headers = { Host: host }; } } else { opts.headers = { Host: host }; } } if (params.path) { opts.path = params.path; } if (httpupgrade) { opts["v2ray-http-upgrade"] = true; opts["v2ray-http-upgrade-fast-open"] = true; } if (Object.keys(opts).length > 0) { proxy[`${proxy.network}-opts`] = opts; } } if (proxy.tls && !proxy.servername) { if (proxy.network === "ws") { proxy.servername = proxy["ws-opts"]?.headers?.Host; } else if (proxy.network === "http") { let httpHost = proxy["http-opts"]?.headers?.Host; proxy.servername = Array.isArray(httpHost) ? httpHost[0] : httpHost; } } return proxy; } 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"); } } return proxy; } function URI_Hysteria2(line: string): IProxyHysteria2Config { line = line.split(/(hysteria2|hy2):\/\//)[2]; // eslint-disable-next-line no-unused-vars 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 ?? `Hysteria2 ${server}:${port}`; const proxy: IProxyHysteria2Config = { type: "hysteria2", name, server, port: portNum, password, }; const params: Record = {}; for (const addon of addons.split("&")) { const [key, valueRaw] = addon.split("="); let value = valueRaw; value = decodeURIComponent(valueRaw); params[key] = value; } proxy.sni = params.sni; if (!proxy.sni && params.peer) { proxy.sni = params.peer; } if (params.obfs && params.obfs !== "none") { proxy.obfs = params.obfs; } proxy.ports = params.mport; proxy["obfs-password"] = params["obfs-password"]; proxy["skip-cert-verify"] = /(TRUE)|1/i.test(params.insecure); proxy.tfo = /(TRUE)|1/i.test(params.fastopen); proxy.fingerprint = params.pinSHA256; return proxy; } function URI_Hysteria(line: string): IProxyHysteriaConfig { line = line.split(/(hysteria|hy):\/\//)[2]; let [__, server, ___, port, ____, addons = "", name] = /^(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!; let portNum = parseInt(`${port}`, 10); if (isNaN(portNum)) { portNum = 443; } let decodedName = trimStr(decodeURIComponent(name)); name = decodedName ?? `Hysteria ${server}:${port}`; const proxy: IProxyHysteriaConfig = { type: "hysteria", name, server, port: portNum, }; const params: Record = {}; for (const addon of addons.split("&")) { let [key, value] = addon.split("="); key = key.replace(/_/, "-"); value = decodeURIComponent(value); switch (key) { case "alpn": proxy["alpn"] = value ? value.split(",") : undefined; break; case "insecure": proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value); break; case "auth": proxy["auth-str"] = value; break; case "mport": proxy["ports"] = value; break; case "obfsParam": proxy["obfs"] = value; break; case "upmbps": proxy["up"] = value; break; case "downmbps": proxy["down"] = value; break; case "obfs": proxy["obfs"] = value || ""; break; case "fast-open": proxy["fast-open"] = /(TRUE)|1/i.test(value); break; case "peer": proxy["fast-open"] = /(TRUE)|1/i.test(value); break; case "recv-window-conn": proxy["recv-window-conn"] = parseInt(value); break; case "recv-window": proxy["recv-window"] = parseInt(value); break; case "ca": proxy["ca"] = value; break; case "ca-str": proxy["ca-str"] = value; break; case "disable-mtu-discovery": proxy["disable-mtu-discovery"] = /(TRUE)|1/i.test(value); break; case "fingerprint": proxy["fingerprint"] = value; break; case "protocol": proxy["protocol"] = value; case "sni": proxy["sni"] = value; default: break; } } if (!proxy.sni && params.peer) { proxy.sni = params.peer; } if (!proxy["fast-open"] && params["fast-open"]) { proxy["fast-open"] = true; } if (!proxy.protocol) { proxy.protocol = "udp"; } return proxy; } function URI_TUIC(line: string): IProxyTuicConfig { line = line.split(/tuic:\/\//)[1]; let [__, uuid, 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 ?? `TUIC ${server}:${port}`; const proxy: IProxyTuicConfig = { type: "tuic", name, server, port: portNum, password, uuid, }; for (const addon of addons.split("&")) { let [key, value] = addon.split("="); key = key.replace(/_/, "-"); value = decodeURIComponent(value); switch (key) { case "token": proxy["token"] = value; break; case "ip": proxy["ip"] = value; break; case "heartbeat-interval": proxy["heartbeat-interval"] = parseInt(value); break; case "alpn": proxy["alpn"] = value ? value.split(",") : undefined; break; case "disable-sni": proxy["disable-sni"] = /(TRUE)|1/i.test(value); break; case "reduce-rtt": proxy["reduce-rtt"] = /(TRUE)|1/i.test(value); break; case "request-timeout": proxy["request-timeout"] = parseInt(value); break; case "udp-relay-mode": proxy["udp-relay-mode"] = value; break; case "congestion-controller": proxy["congestion-controller"] = value; break; case "max-udp-relay-packet-size": proxy["max-udp-relay-packet-size"] = parseInt(value); break; case "fast-open": proxy["fast-open"] = /(TRUE)|1/i.test(value); break; case "skip-cert-verify": proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value); break; case "max-open-streams": proxy["max-open-streams"] = parseInt(value); break; case "sni": proxy["sni"] = value; break; case "allow-insecure": proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value); break; } } return proxy; } function URI_Wireguard(line: string): IProxyWireguardConfig { line = line.split(/(wireguard|wg):\/\//)[2]; let [__, ___, privateKey, server, ____, port, _____, addons = "", name] = /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!; let portNum = parseInt(`${port}`, 10); if (isNaN(portNum)) { portNum = 443; } privateKey = decodeURIComponent(privateKey); let decodedName = trimStr(decodeURIComponent(name)); name = decodedName ?? `WireGuard ${server}:${port}`; const proxy: IProxyWireguardConfig = { type: "wireguard", name, server, port: portNum, "private-key": privateKey, udp: true, }; for (const addon of addons.split("&")) { let [key, value] = addon.split("="); key = key.replace(/_/, "-"); value = decodeURIComponent(value); switch (key) { case "address": case "ip": value.split(",").map((i) => { const ip = i .trim() .replace(/\/\d+$/, "") .replace(/^\[/, "") .replace(/\]$/, ""); if (isIPv4(ip)) { proxy.ip = ip; } else if (isIPv6(ip)) { proxy.ipv6 = ip; } }); break; case "publickey": proxy["public-key"] = value; break; case "allowed-ips": proxy["allowed-ips"] = value.split(","); break; case "pre-shared-key": proxy["pre-shared-key"] = value; break; case "reserved": const parsed = value .split(",") .map((i) => parseInt(i.trim(), 10)) .filter((i) => Number.isInteger(i)); if (parsed.length === 3) { proxy["reserved"] = parsed; } break; case "udp": proxy["udp"] = /(TRUE)|1/i.test(value); break; case "mtu": proxy.mtu = parseInt(value.trim(), 10); break; case "dialer-proxy": proxy["dialer-proxy"] = value; break; case "remote-dns-resolve": proxy["remote-dns-resolve"] = /(TRUE)|1/i.test(value); break; case "dns": proxy.dns = value.split(","); break; default: break; } } return proxy; } function URI_HTTP(line: string): IProxyHttpConfig { line = line.split(/(http|https):\/\//)[2]; let [__, ___, auth, server, ____, port, _____, addons = "", name] = /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!; let portNum = parseInt(`${port}`, 10); if (isNaN(portNum)) { portNum = 443; } if (auth) { auth = decodeURIComponent(auth); } let decodedName = trimStr(decodeURIComponent(name)); name = decodedName ?? `HTTP ${server}:${portNum}`; const proxy: IProxyHttpConfig = { type: "http", name, server, port: portNum, }; if (auth) { const [username, password] = auth.split(":"); proxy.username = username; proxy.password = password; } for (const addon of addons.split("&")) { let [key, value] = addon.split("="); key = key.replace(/_/, "-"); value = decodeURIComponent(value); switch (key) { case "tls": proxy.tls = /(TRUE)|1/i.test(value); break; case "fingerprint": proxy["fingerprint"] = value; break; case "skip-cert-verify": proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value); break; case "ip-version": if ( ["dual", "ipv4", "ipv6", "ipv4-prefer", "ipv6-prefer"].includes(value) ) { proxy["ip-version"] = value as | "dual" | "ipv4" | "ipv6" | "ipv4-prefer" | "ipv6-prefer"; } else { proxy["ip-version"] = "dual"; } break; default: break; } } return proxy; } function URI_SOCKS(line: string): IProxySocks5Config { line = line.split(/socks5:\/\//)[1]; let [__, ___, auth, server, ____, port, _____, addons = "", name] = /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line)!; let portNum = parseInt(`${port}`, 10); if (isNaN(portNum)) { portNum = 443; } if (auth) { auth = decodeURIComponent(auth); } let decodedName = trimStr(decodeURIComponent(name)); name = decodedName ?? `SOCKS5 ${server}:${portNum}`; const proxy: IProxySocks5Config = { type: "socks5", name, server, port: portNum, }; if (auth) { const [username, password] = auth.split(":"); proxy.username = username; proxy.password = password; } for (const addon of addons.split("&")) { let [key, value] = addon.split("="); key = key.replace(/_/, "-"); value = decodeURIComponent(value); switch (key) { case "tls": proxy.tls = /(TRUE)|1/i.test(value); break; case "fingerprint": proxy["fingerprint"] = value; break; case "skip-cert-verify": proxy["skip-cert-verify"] = /(TRUE)|1/i.test(value); break; case "udp": proxy["udp"] = /(TRUE)|1/i.test(value); break; case "ip-version": if ( ["dual", "ipv4", "ipv6", "ipv4-prefer", "ipv6-prefer"].includes(value) ) { proxy["ip-version"] = value as | "dual" | "ipv4" | "ipv6" | "ipv4-prefer" | "ipv6-prefer"; } else { proxy["ip-version"] = "dual"; } break; default: break; } } return proxy; }