81 lines
2.6 KiB
JavaScript
81 lines
2.6 KiB
JavaScript
// URL 匹配 + HTML 转义,浏览器与 Node 通用
|
||
(function (root, factory) {
|
||
if (typeof module === "object" && module.exports) {
|
||
module.exports = factory();
|
||
} else {
|
||
const api = factory();
|
||
root.isDomainMatch = api.isDomainMatch;
|
||
root.escapeHtml = api.escapeHtml;
|
||
}
|
||
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
|
||
"use strict";
|
||
|
||
/**
|
||
* 判断 URL 是否匹配规则。
|
||
* 纯 hostname:example.com / *.example.com
|
||
* hostname+path:example.com/login/*,路径部分支持 * 通配
|
||
*/
|
||
function isDomainMatch(url, pattern) {
|
||
if (!url || !pattern) return false;
|
||
try {
|
||
const u = new URL(url);
|
||
|
||
const rawPattern = String(pattern).trim().replace(/^https?:\/\//i, "");
|
||
|
||
if (rawPattern.includes("/")) {
|
||
const slashIdx = rawPattern.indexOf("/");
|
||
const hostPart = rawPattern.slice(0, slashIdx);
|
||
const pathPart = rawPattern.slice(slashIdx + 1) || "";
|
||
const hostForMatch = hostPart.includes(":") ? u.host : u.hostname;
|
||
if (!matchHost(hostForMatch, hostPart)) return false;
|
||
return matchPath(u.pathname, pathPart);
|
||
}
|
||
|
||
const hostForMatch = rawPattern.includes(":") ? u.host : u.hostname;
|
||
return matchHost(hostForMatch, rawPattern);
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function matchHost(hostname, hostPart) {
|
||
if (hostPart.startsWith("*.")) {
|
||
const suffix = hostPart.slice(2);
|
||
return hostname === suffix || hostname.endsWith("." + suffix);
|
||
}
|
||
if (hostPart.includes("*")) {
|
||
const regexStr = hostPart.replace(/\./g, "\\.").replace(/\*/g, ".*");
|
||
return new RegExp("^" + regexStr + "$").test(hostname);
|
||
}
|
||
return hostname === hostPart;
|
||
}
|
||
|
||
function matchPath(pathname, pathPart) {
|
||
const pagePath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
||
if (!pathPart) return true;
|
||
if (pathPart === "*") return true;
|
||
if (pathPart.endsWith("/*")) {
|
||
const prefix = pathPart.slice(0, -2);
|
||
return pagePath === prefix || pagePath.startsWith(prefix + "/");
|
||
}
|
||
if (pathPart.includes("*")) {
|
||
const regexStr = pathPart.replace(/\//g, "\\/").replace(/\*/g, ".*");
|
||
return new RegExp("^" + regexStr + "$").test(pagePath);
|
||
}
|
||
return pagePath === pathPart;
|
||
}
|
||
|
||
/** HTML 转义,防止配置中的特殊字符破坏页面 */
|
||
function escapeHtml(str) {
|
||
if (str == null) return "";
|
||
return String(str)
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
return { isDomainMatch, escapeHtml };
|
||
});
|