From 65e18bbec354f94d7bf7e4b2117b89d05bcce2df Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:01:12 +0900 Subject: [PATCH 1/4] test --- src/app/service/service_worker/script.ts | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 227be28d1..34c7a623b 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -300,6 +300,54 @@ export class ScriptService { } } ); + + { + type Config = Record; + const REMOVE_HEADERS = [ + `content-security-policy`, + `content-security-policy-report-only`, + `x-webkit-csp`, + `x-content-security-policy`, + `x-frame-options`, + ]; + + const { RuleActionType, HeaderOperation, ResourceType } = chrome.declarativeNetRequest; + + const rules: chrome.declarativeNetRequest.Rule[] = [ + { + id: 2001, + action: { + type: RuleActionType.MODIFY_HEADERS, + responseHeaders: REMOVE_HEADERS.map((header) => ({ + operation: HeaderOperation.REMOVE, + header, + })), + }, + condition: { + urlFilter: `|http*`, + resourceTypes: [ResourceType.MAIN_FRAME, ResourceType.SUB_FRAME], + }, + }, + ]; + + const updateRules = (newConfig: Config, oldConfig?: Config) => { + if (oldConfig && newConfig.csp_http_disabled === oldConfig?.csp_http_disabled) { + return; + } + if (newConfig.csp_http_disabled) { + chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: rules.map((rule) => rule.id), + addRules: rules, + }); + } else { + chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: rules.map((rule) => rule.id), + }); + } + }; + + updateRules({ csp_http_disabled: true }); + } } public async openInstallPageByUrl( From 18953c3419496a8673ea30fd851ba9dea79981f1 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:13:21 +0900 Subject: [PATCH 2/4] Create dnr.ts --- src/pkg/utils/dnr.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/pkg/utils/dnr.ts diff --git a/src/pkg/utils/dnr.ts b/src/pkg/utils/dnr.ts new file mode 100644 index 000000000..32f011b54 --- /dev/null +++ b/src/pkg/utils/dnr.ts @@ -0,0 +1,46 @@ +export const isValidDNRUrlFilter = (text: string) => { + // https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest?hl=en#property-RuleCondition-urlFilter + + const domainNameAnchor = text.startsWith("||"); + + const leftAnchor = !domainNameAnchor && text.startsWith("|"); + + const rightAnchor = text.endsWith("|"); + + try { + let s = text; + + if (domainNameAnchor) s = s.slice(2); + if (leftAnchor) s = s.slice(1); + if (rightAnchor) s = s.slice(0, -1); + + const t = s.replace(/\*/g, "").replace(/^/g, "_"); + + // eslint-disable-next-line no-control-regex + if (/[^\x00-\xFF]/.test(t)) return false; + + new URL(t); + + return true; + } catch { + return false; + } +}; + +export const convertDomainToDNRUrlFilter = (text: string) => { + // will match its domain and subdomain + let ret; + text = text.toLowerCase(); + try { + if (text.startsWith("http") && /^http[s*]?:\/\//.test(text)) { + const u = new URL(`${text.replace("http*://", "http-wildcard://")}`); + ret = `|${u.origin.replace("http-wildcard://", "http://")}`; + } else { + const u = new URL(`https://${text}/`); + ret = `||${u.hostname}`; + } + } catch { + throw new Error("invalid domain"); + } + return ret; +}; From f1d2c4e05e1a8adfbb67d70b3575c28cd1407f21 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 22 Mar 2026 04:54:21 +0900 Subject: [PATCH 3/4] demo code --- src/app/service/service_worker/script.ts | 28 ++------------------- src/pkg/utils/dnr.ts | 31 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 406ee96dd..0c0cf7b35 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -47,6 +47,7 @@ import { getSimilarityScore, ScriptUpdateCheck } from "./script_update_check"; import { LocalStorageDAO } from "@App/app/repo/localStorage"; import { CompiledResourceDAO } from "@App/app/repo/resource"; import { initRegularUpdateCheck } from "./regular_updatecheck"; +import { createNoCSPRules } from "@App/pkg/utils/dnr"; export type TCheckScriptUpdateOption = Partial< { checkType: "user"; noUpdateCheck?: number } | ({ checkType: "system" } & Record) @@ -316,32 +317,7 @@ export class ScriptService { { type Config = Record; - const REMOVE_HEADERS = [ - `content-security-policy`, - `content-security-policy-report-only`, - `x-webkit-csp`, - `x-content-security-policy`, - `x-frame-options`, - ]; - - const { RuleActionType, HeaderOperation, ResourceType } = chrome.declarativeNetRequest; - - const rules: chrome.declarativeNetRequest.Rule[] = [ - { - id: 2001, - action: { - type: RuleActionType.MODIFY_HEADERS, - responseHeaders: REMOVE_HEADERS.map((header) => ({ - operation: HeaderOperation.REMOVE, - header, - })), - }, - condition: { - urlFilter: `|http*`, - resourceTypes: [ResourceType.MAIN_FRAME, ResourceType.SUB_FRAME], - }, - }, - ]; + const rules = createNoCSPRules([`|http*`]); const updateRules = (newConfig: Config, oldConfig?: Config) => { if (oldConfig && newConfig.csp_http_disabled === oldConfig?.csp_http_disabled) { diff --git a/src/pkg/utils/dnr.ts b/src/pkg/utils/dnr.ts index 32f011b54..c6dd6b13e 100644 --- a/src/pkg/utils/dnr.ts +++ b/src/pkg/utils/dnr.ts @@ -44,3 +44,34 @@ export const convertDomainToDNRUrlFilter = (text: string) => { } return ret; }; + +export const createNoCSPRules = (urlFilters: string[]) => { + const REMOVE_HEADERS = [ + `content-security-policy`, + `content-security-policy-report-only`, + `x-webkit-csp`, + `x-content-security-policy`, + `x-frame-options`, + ]; + const { RuleActionType, HeaderOperation, ResourceType } = chrome.declarativeNetRequest; + if (urlFilters.length > 512) { + throw new Error(`Too many URL patterns (${urlFilters.length}). Max is 512.`); + } + const rules: chrome.declarativeNetRequest.Rule[] = urlFilters.map((urlFilter, index) => { + return { + id: 2001 + index, + action: { + type: RuleActionType.MODIFY_HEADERS, + responseHeaders: REMOVE_HEADERS.map((header) => ({ + operation: HeaderOperation.REMOVE, + header, + })), + }, + condition: { + urlFilter: urlFilter, + resourceTypes: [ResourceType.MAIN_FRAME, ResourceType.SUB_FRAME], + }, + } satisfies chrome.declarativeNetRequest.Rule; + }); + return rules; +}; From 24385e32482520a9d84f3a8dd0e6af0abd0029a4 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 11 Apr 2026 14:16:09 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20NOCSP=20DNR=20->=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=97=E6=9C=80=E5=A4=9A=2032768=20domains=20?= =?UTF-8?q?=E5=8F=8A=20512=20url=20filters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/script.ts | 14 ++---- src/pkg/utils/dnr.ts | 60 ++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 3904c0ae5..0079fbe4f 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -47,7 +47,7 @@ import { getSimilarityScore, ScriptUpdateCheck } from "./script_update_check"; import { LocalStorageDAO } from "@App/app/repo/localStorage"; import { CompiledResourceDAO } from "@App/app/repo/resource"; import { initRegularUpdateCheck } from "./regular_updatecheck"; -import { createNoCSPRules } from "@App/pkg/utils/dnr"; +import { createNoCSPRules, removeDynamicRulesInRange } from "@App/pkg/utils/dnr"; export type TCheckScriptUpdateOption = Partial< { checkType: "user"; noUpdateCheck?: number } | ({ checkType: "system" } & Record) @@ -317,21 +317,17 @@ export class ScriptService { { type Config = Record; - const rules = createNoCSPRules([`|http*`]); + const rules = createNoCSPRules(["github.com", "facebook.com"], [`|http*`]); - const updateRules = (newConfig: Config, oldConfig?: Config) => { + const updateRules = async (newConfig: Config, oldConfig?: Config) => { if (oldConfig && newConfig.csp_http_disabled === oldConfig?.csp_http_disabled) { return; } + await removeDynamicRulesInRange(2001, 2520); if (newConfig.csp_http_disabled) { - chrome.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: rules.map((rule) => rule.id), + await chrome.declarativeNetRequest.updateDynamicRules({ addRules: rules, }); - } else { - chrome.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: rules.map((rule) => rule.id), - }); } }; diff --git a/src/pkg/utils/dnr.ts b/src/pkg/utils/dnr.ts index c6dd6b13e..2eb4b82f1 100644 --- a/src/pkg/utils/dnr.ts +++ b/src/pkg/utils/dnr.ts @@ -1,3 +1,6 @@ +const NOCSP_RULES_DOMAIN_MAX_COUNT = 32768; +const NOCSP_RULES_URLFILTER_MAX_COUNT = 512; + export const isValidDNRUrlFilter = (text: string) => { // https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest?hl=en#property-RuleCondition-urlFilter @@ -45,7 +48,19 @@ export const convertDomainToDNRUrlFilter = (text: string) => { return ret; }; -export const createNoCSPRules = (urlFilters: string[]) => { +export const createNoCSPRules = (domains: string[], urlFilters: string[]) => { + // domains = max 32768 domains + // urlFilters = max 512 + if (domains.length > NOCSP_RULES_DOMAIN_MAX_COUNT) { + throw new Error( + `createNoCSPRules: The number of domains = ${domains.length} exceeding ${NOCSP_RULES_DOMAIN_MAX_COUNT}` + ); + } + if (urlFilters.length > NOCSP_RULES_URLFILTER_MAX_COUNT) { + throw new Error( + `createNoCSPRules: The number of urlFilters = ${urlFilters.length} exceeding ${NOCSP_RULES_URLFILTER_MAX_COUNT}` + ); + } const REMOVE_HEADERS = [ `content-security-policy`, `content-security-policy-report-only`, @@ -54,12 +69,9 @@ export const createNoCSPRules = (urlFilters: string[]) => { `x-frame-options`, ]; const { RuleActionType, HeaderOperation, ResourceType } = chrome.declarativeNetRequest; - if (urlFilters.length > 512) { - throw new Error(`Too many URL patterns (${urlFilters.length}). Max is 512.`); - } const rules: chrome.declarativeNetRequest.Rule[] = urlFilters.map((urlFilter, index) => { return { - id: 2001 + index, + id: 2002 + index, action: { type: RuleActionType.MODIFY_HEADERS, responseHeaders: REMOVE_HEADERS.map((header) => ({ @@ -73,5 +85,43 @@ export const createNoCSPRules = (urlFilters: string[]) => { }, } satisfies chrome.declarativeNetRequest.Rule; }); + const requestDomains = domains + .map((s) => { + try { + const u = new URL(`https://${s}/`); // 取编码后的 hostname + return u.hostname; + } catch { + // ingored + } + }) + .filter(Boolean) as string[]; // 去除错误或空字串 + if (domains.length > 0) { + rules.push({ + id: 2001, + action: { + type: RuleActionType.MODIFY_HEADERS, + responseHeaders: REMOVE_HEADERS.map((header) => ({ + operation: HeaderOperation.REMOVE, + header, + })), + }, + condition: { + requestDomains: requestDomains, + resourceTypes: [ResourceType.MAIN_FRAME, ResourceType.SUB_FRAME], + }, + } satisfies chrome.declarativeNetRequest.Rule); + } return rules; }; + +export const removeDynamicRulesInRange = async (minId: number, maxId: number) => { + const existingRules = await chrome.declarativeNetRequest.getDynamicRules(); + + const idsToRemove = existingRules.filter((rule) => rule.id >= minId && rule.id <= maxId).map((rule) => rule.id); + + if (idsToRemove.length === 0) return; + + await chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: idsToRemove, + }); +};