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( 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; +};