Files
clash-verge-rev/src/utils/helper.ts
T
b17dd39f31 feat(tunnels): add tunnels viewer UI with add/delete support (#6052)
* feat(settings): add tunnels viewer UI and management logic

* docs: Changelog.md

* refactor(notice): remove redundant t() call

* refactor(tunnels): make proxy optional and follow current configuration by default

* refactor(tunnels): save after close dialog

* feat(tunnel): update check

* refactor(tunnels): merge import

* refactor(tunnels): use ipaddr.js

* docs: Changelog.md

* refactor(tunnels): enhance validation

* feat: add tunnels ti PATCH_CONFIG_INNER

* fix: sanitize invalid proxy references in tunnels

* refactor: use smartstring alias String

* docs: Changelog.md

* perf: optimize tunnels proxy validation and collection logic

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2026-01-29 09:32:37 +00:00

113 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import ipaddr from "ipaddr.js";
import { debugLog } from "@/utils/debug";
export type HostKind = "localhost" | "domain" | "ipv4" | "ipv6";
export type ParsedHost = {
kind: HostKind;
host: string;
};
export const isValidUrl = (url: string) => {
try {
new URL(url);
return true;
} catch (e) {
debugLog(e);
return false;
}
};
export const isValidPort = (port: string) => {
const portNumber = Number(port);
return Number.isInteger(portNumber) && portNumber > 0 && portNumber < 65536;
};
export const isValidDomain = (domain: string) => {
try {
const url = new URL(`http://${domain}`);
return url.hostname.toLowerCase() === domain.toLowerCase();
} catch {
return false;
}
};
const isLocalhostString = (host: string) => {
const lowerHost = host.toLowerCase();
return lowerHost === "localhost";
};
const stripBrackets = (host: string): string =>
host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
export const parseHost = (host: string): ParsedHost | null => {
// 去除前后空白
host = host.trim();
// 优先检测 localhost
if (isLocalhostString(host)) {
return { kind: "localhost", host: "localhost" };
}
// 除去方括号后检测 IP
const strippedHost = stripBrackets(host);
// IPv4
if (ipaddr.IPv4.isValidFourPartDecimal(strippedHost)) {
return { kind: "ipv4", host: strippedHost };
}
// IPv6
if (ipaddr.IPv6.isValid(strippedHost)) {
return { kind: "ipv6", host: strippedHost };
}
// 再检测 domain, 防止像 [::1] 这样的合法 IPv6 地址被误判为域名
if (isValidDomain(host)) {
return { kind: "domain", host };
}
// 都不是
return null;
};
const LISTEN_CIDRS_V4 = [
"0.0.0.0/32", // any
"127.0.0.0/8", // loopback
"10.0.0.0/8", // RFC1918
"172.16.0.0/12", // RFC1918
"192.168.0.0/16", // RFC1918
];
const LISTEN_CIDRS_V6 = [
"::/128", // any
"::1/128", // loopback
"fc00::/7", // ULA(LAN)
"fe80::/10", // link-local
];
export const parsedLocalhost = (host: string): ParsedHost | null => {
// 先 parse 一下
const parsed = parseHost(host);
if (!parsed) return null;
// localhost 直接通过
if (parsed.kind === "localhost") {
return parsed;
}
// IP 则检查是否在允许的 CIDR 列表内
if (parsed.kind === "ipv4") {
// IPv4
const addr = ipaddr.IPv4.parse(parsed.host);
for (const cidr of LISTEN_CIDRS_V4) {
if (addr.match(ipaddr.IPv4.parseCIDR(cidr))) {
return parsed;
}
}
} else if (parsed.kind === "ipv6") {
// IPv6
const addr = ipaddr.IPv6.parse(parsed.host);
for (const cidr of LISTEN_CIDRS_V6) {
if (addr.match(ipaddr.IPv6.parseCIDR(cidr))) {
return parsed;
}
}
}
// domain和都不符合则返回 null
return null;
};