feat: add DoH blocking, subscription keyword filtering, bulk proxy text inputs, and insecure TLS flag
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -590,7 +590,9 @@ function createProfileContent(section) {
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean);
|
||||
for (const line of lines) {
|
||||
const clean = line.replace(/^(full:|keyword:|regex:)/, "").replace(/^\*?\./, "");
|
||||
const clean = line
|
||||
.replace(/^(full:|keyword:|regex:)/, "")
|
||||
.replace(/^\*?\./, "");
|
||||
if (/^(full:|keyword:|regex:)/.test(line) && !clean) {
|
||||
return _("Invalid domain: empty prefix value");
|
||||
}
|
||||
@@ -673,7 +675,9 @@ function createProfileContent(section) {
|
||||
style: "width:100%;min-height:100px;box-sizing:border-box;",
|
||||
placeholder: "tiktok.com\nroblox.com",
|
||||
});
|
||||
const strVal = Array.isArray(cfgvalue) ? cfgvalue.join("\n") : (cfgvalue || "");
|
||||
const strVal = Array.isArray(cfgvalue)
|
||||
? cfgvalue.join("\n")
|
||||
: cfgvalue || "";
|
||||
if (strVal) textarea.value = strVal;
|
||||
container.appendChild(textarea);
|
||||
return container;
|
||||
@@ -1229,7 +1233,9 @@ function createParentalContent(section) {
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean);
|
||||
for (const line of lines) {
|
||||
const clean = line.replace(/^(full:|keyword:|regex:)/, "").replace(/^\*?\./, "");
|
||||
const clean = line
|
||||
.replace(/^(full:|keyword:|regex:)/, "")
|
||||
.replace(/^\*?\./, "");
|
||||
if (/^(full:|keyword:|regex:)/.test(line) && !clean) {
|
||||
return _("Invalid domain: empty prefix value");
|
||||
}
|
||||
@@ -1314,7 +1320,9 @@ function createParentalContent(section) {
|
||||
style: "width:100%;min-height:110px;box-sizing:border-box;",
|
||||
placeholder: "youtube.com\ngooglevideo.com",
|
||||
});
|
||||
const strVal = Array.isArray(cfgvalue) ? cfgvalue.join("\n") : (cfgvalue || "");
|
||||
const strVal = Array.isArray(cfgvalue)
|
||||
? cfgvalue.join("\n")
|
||||
: cfgvalue || "";
|
||||
if (strVal) textarea.value = strVal;
|
||||
container.appendChild(textarea);
|
||||
return container;
|
||||
|
||||
@@ -2447,6 +2447,7 @@ function subscriptionUrlSettingsKeys() {
|
||||
"include_urltest_groups",
|
||||
"hide_urltest_group_outbounds",
|
||||
"hide_detour_outbounds",
|
||||
"subscription_insecure",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2473,6 +2474,7 @@ function defaultSubscriptionUrlSettings() {
|
||||
include_urltest_groups: "1",
|
||||
hide_urltest_group_outbounds: "1",
|
||||
hide_detour_outbounds: "1",
|
||||
subscription_insecure: "0",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2872,6 +2874,17 @@ function addSubscriptionUrlItemOptions(itemSection, options = {}) {
|
||||
);
|
||||
o.default = "1";
|
||||
o.rmempty = false;
|
||||
|
||||
o = itemSection.option(
|
||||
form.Flag,
|
||||
"subscription_insecure",
|
||||
_("Allow insecure TLS"),
|
||||
_(
|
||||
"Download this subscription without TLS certificate verification (for panels with self-signed or mismatched certificates)",
|
||||
),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
}
|
||||
|
||||
function addInterfaceItemOptions(itemSection) {
|
||||
@@ -5521,7 +5534,10 @@ function parseCommentAwareListTokens(value) {
|
||||
let offset = 0;
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
const cleanLine = line.replace(/^(full|keyword|regex):[ \t]*(\/\/|#).*$/, "");
|
||||
const cleanLine = line.replace(
|
||||
/^(full|keyword|regex):[ \t]*(\/\/|#).*$/,
|
||||
"",
|
||||
);
|
||||
const hashIndex = cleanLine.indexOf("#");
|
||||
const slashIndex = cleanLine.indexOf("//");
|
||||
let commentIndex = -1;
|
||||
@@ -5534,7 +5550,8 @@ function parseCommentAwareListTokens(value) {
|
||||
commentIndex = slashIndex;
|
||||
}
|
||||
|
||||
const source = commentIndex >= 0 ? cleanLine.slice(0, commentIndex) : cleanLine;
|
||||
const source =
|
||||
commentIndex >= 0 ? cleanLine.slice(0, commentIndex) : cleanLine;
|
||||
const matcher = /[^,\s]+/g;
|
||||
let match;
|
||||
|
||||
@@ -9944,6 +9961,32 @@ function createSectionContent(section) {
|
||||
};
|
||||
outboundNameSourceOptions.set("selector_proxy_links", o);
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.TextValue,
|
||||
"selector_proxy_links_text",
|
||||
_("Connection links (text)"),
|
||||
_("Paste proxy links one per line; they are added to the connection list"),
|
||||
);
|
||||
o.depends("action", "connection");
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.rows = 5;
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.TextValue,
|
||||
"urltest_proxy_links_text",
|
||||
_("URLTest links (text)"),
|
||||
_(
|
||||
"Paste proxy links one per line; they form a URLTest group with automatic fastest selection",
|
||||
),
|
||||
);
|
||||
o.depends("action", "connection");
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.rows = 5;
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
SettingsDynamicList,
|
||||
@@ -9992,6 +10035,34 @@ function createSectionContent(section) {
|
||||
);
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.DynamicList,
|
||||
"subscription_filter_include_keywords",
|
||||
_("Subscription include keywords"),
|
||||
_(
|
||||
"Keep only subscription nodes whose name contains any of these keywords (empty keeps all). Case-insensitive, emoji supported.",
|
||||
),
|
||||
);
|
||||
o.depends("action", "connection");
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.placeholder = _("Add include keyword");
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.DynamicList,
|
||||
"subscription_filter_exclude_keywords",
|
||||
_("Subscription exclude keywords"),
|
||||
_(
|
||||
"Drop subscription nodes whose name contains any of these keywords. Case-insensitive, emoji supported.",
|
||||
),
|
||||
);
|
||||
o.depends("action", "connection");
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.placeholder = _("Add exclude keyword");
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
InterfaceSettingsDynamicList,
|
||||
|
||||
@@ -1468,8 +1468,10 @@ function buildAwgClientConfig(sectionId) {
|
||||
|
||||
const awgVer = uci.get(UCI_PACKAGE, sectionId, "awg_version") || "2.0";
|
||||
if (awgVer === "3.0" || awgVer === "3.1") {
|
||||
const hpk = uci.get(UCI_PACKAGE, sectionId, "awg_header_protection_key") || "";
|
||||
const cpa = uci.get(UCI_PACKAGE, sectionId, "awg_content_padding_addition") || "";
|
||||
const hpk =
|
||||
uci.get(UCI_PACKAGE, sectionId, "awg_header_protection_key") || "";
|
||||
const cpa =
|
||||
uci.get(UCI_PACKAGE, sectionId, "awg_content_padding_addition") || "";
|
||||
if (hpk) conf += `HeaderProtectionKey = ${hpk}\n`;
|
||||
if (cpa) conf += `ContentPaddingAddition = ${cpa}\n`;
|
||||
}
|
||||
@@ -1478,7 +1480,8 @@ function buildAwgClientConfig(sectionId) {
|
||||
const rkt = uci.get(UCI_PACKAGE, sectionId, "awg_rekey_timeout") || "";
|
||||
const rja = uci.get(UCI_PACKAGE, sectionId, "awg_reject_after_time") || "";
|
||||
const kpt = uci.get(UCI_PACKAGE, sectionId, "awg_keepalive_timeout") || "";
|
||||
const mha = uci.get(UCI_PACKAGE, sectionId, "awg_max_handshake_attempts") || "";
|
||||
const mha =
|
||||
uci.get(UCI_PACKAGE, sectionId, "awg_max_handshake_attempts") || "";
|
||||
if (rka) conf += `RekeyAfterTime = ${rka}\n`;
|
||||
if (rkt) conf += `RekeyTimeout = ${rkt}\n`;
|
||||
if (rja) conf += `RejectAfterTime = ${rja}\n`;
|
||||
@@ -3470,7 +3473,9 @@ function createServerContent(section, options = {}) {
|
||||
form.ListValue,
|
||||
"awg_version",
|
||||
_("AmneziaWG Version"),
|
||||
_("Protocol version: 2.0 (standard integer headers) or 3.1 (header protection key, padding ranges, advanced timers)"),
|
||||
_(
|
||||
"Protocol version: 2.0 (standard integer headers) or 3.1 (header protection key, padding ranges, advanced timers)",
|
||||
),
|
||||
);
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
|
||||
@@ -1440,9 +1440,13 @@ function createSettingsContent(section, capabilities) {
|
||||
// path-style DoH (e.g. "cloudflare-dns.com/dns-query"). This mirrors
|
||||
// the backend validator.uc check so the user gets immediate feedback.
|
||||
if (normalized.includes("://"))
|
||||
return _("Fallback DNS servers must be plain UDP (IP or hostname). DoH/DoT URLs are not allowed here.");
|
||||
return _(
|
||||
"Fallback DNS servers must be plain UDP (IP or hostname). DoH/DoT URLs are not allowed here.",
|
||||
);
|
||||
if (normalized.includes("/"))
|
||||
return _("Fallback DNS servers must be plain UDP (IP or hostname). Path-style DoH (e.g. domain/path) is not allowed here.");
|
||||
return _(
|
||||
"Fallback DNS servers must be plain UDP (IP or hostname). Path-style DoH (e.g. domain/path) is not allowed here.",
|
||||
);
|
||||
const validation = main.validateDNS(normalized);
|
||||
return validation.valid ? true : validation.message;
|
||||
};
|
||||
@@ -1848,6 +1852,18 @@ function createSettingsContent(section, capabilities) {
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.taboption(
|
||||
"services",
|
||||
form.Flag,
|
||||
"block_doh",
|
||||
_("Block DoH"),
|
||||
_(
|
||||
"Block well-known DNS-over-HTTPS resolver IPs so LAN clients cannot bypass the router DNS",
|
||||
),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.taboption(
|
||||
"services",
|
||||
form.Flag,
|
||||
|
||||
Reference in New Issue
Block a user