From 819e1aad26ccc9f31d2846742cb6c39ce817f09e Mon Sep 17 00:00:00 2001 From: Nafees Nehar Date: Mon, 13 May 2024 13:22:12 +0530 Subject: [PATCH] [ENGG-1696] MV3: support for rq_request_initiator_origin() predefined function (#1677) * MV3: Expose onBeforeAjaxRequest hook for service worker to register new DNR rules * added request processor * fix: tabService cleanup * fix: cache the rules * added todo comment * update timeout * cleanup AJAX interception * cleanup request processor * remove logs * fix: excluded * fix: excluded domains * added comment * moved to psmh * refactor: methods * remove session rules on extension status toggle * do no create dnr for predefined function * added predefined function session rule * fix:type * null safe * refactor: request processor * fix: headers match * added filters check * fix: request type * fix: regex substitution * fix: moved to file * fix: initiator domain function * fix: parseHeaders rule * fix: matchedRule info * fixed match rule getter * fix: destructure * fix: matched rule * fix: import changes * remove log * fix: functions --------- Co-authored-by: Vaibhav Nigam --- .../extension/ruleParser/parseHeadersRule.ts | 72 +++++++++------- .../handleInitiatorDomainFunction.ts | 83 +++++++++++++++++++ .../services/requestProcessor/index.ts | 5 ++ .../services/requestProcessor/types.ts | 1 + .../service-worker/services/ruleMatcher.ts | 6 +- 5 files changed, 135 insertions(+), 32 deletions(-) create mode 100644 browser-extension/mv3/src/service-worker/services/requestProcessor/handleInitiatorDomainFunction.ts diff --git a/app/src/modules/extension/ruleParser/parseHeadersRule.ts b/app/src/modules/extension/ruleParser/parseHeadersRule.ts index 2f1476ce01..5a430982f5 100644 --- a/app/src/modules/extension/ruleParser/parseHeadersRule.ts +++ b/app/src/modules/extension/ruleParser/parseHeadersRule.ts @@ -3,41 +3,53 @@ import { ExtensionRule, ExtensionRuleAction, HeadersRuleOperation, ModifyHeaderI import { parseConditionFromSource } from "./utils"; const parseHeaders = (headers: HeadersRuleModificationData[]): ModifyHeaderInfo[] => { - return headers.map((header) => { - if (header.type === HeaderRuleActionType.REMOVE) { - return { - header: header.header, - operation: "remove" as HeadersRuleOperation, - }; - } else { - return { - header: header.header, - value: header.value, - operation: "set" as HeadersRuleOperation, - }; - } - }); + return headers + .map((header) => { + if (header.value === "rq_request_initiator_origin()") { + return null; + } + + if (header.type === HeaderRuleActionType.REMOVE) { + return { + header: header.header, + operation: "remove" as HeadersRuleOperation, + }; + } else { + return { + header: header.header, + value: header.value, + operation: "set" as HeadersRuleOperation, + }; + } + }) + .filter(Boolean); }; const parseHeadersRule = (rule: HeadersRule): ExtensionRule[] => { - return rule.pairs.map( - (rulePair): ExtensionRule => { - const condition = parseConditionFromSource(rulePair.source); - const action: ExtensionRuleAction = { - type: RuleActionType.MODIFY_HEADERS, - }; - - if (rulePair.modifications?.Request?.length) { - action.requestHeaders = parseHeaders(rulePair.modifications?.Request); - } + return rule.pairs + .map( + (rulePair): ExtensionRule => { + const condition = parseConditionFromSource(rulePair.source); + const action: ExtensionRuleAction = { + type: RuleActionType.MODIFY_HEADERS, + }; - if (rulePair.modifications?.Response?.length) { - action.responseHeaders = parseHeaders(rulePair.modifications?.Response); - } + if (rulePair.modifications?.Request?.length) { + action.requestHeaders = parseHeaders(rulePair.modifications?.Request); + } + + if (rulePair.modifications?.Response?.length) { + action.responseHeaders = parseHeaders(rulePair.modifications?.Response); + } - return { action, condition }; - } - ); + if (!(action.requestHeaders?.length || action.responseHeaders?.length)) { + return null; + } + + return { action, condition }; + } + ) + .filter(Boolean); }; export default parseHeadersRule; diff --git a/browser-extension/mv3/src/service-worker/services/requestProcessor/handleInitiatorDomainFunction.ts b/browser-extension/mv3/src/service-worker/services/requestProcessor/handleInitiatorDomainFunction.ts new file mode 100644 index 0000000000..e6a3eca109 --- /dev/null +++ b/browser-extension/mv3/src/service-worker/services/requestProcessor/handleInitiatorDomainFunction.ts @@ -0,0 +1,83 @@ +import { Rule } from "common/types"; +import { findMatchingRule } from "../ruleMatcher"; +import { updateRequestSpecificRules } from "../rulesManager"; +import { AJAXRequestDetails, SessionRuleType } from "./types"; + +const INITIATOR_DOMAIN_FUNCTION = "rq_request_initiator_origin()"; + +export const handleInitiatorDomainFunction = async ( + tabId: number, + requestDetails: AJAXRequestDetails, + rules: Rule[] +) => { + const { isApplied, matchedPair } = findMatchingRule(rules, requestDetails) ?? {}; + + if (!isApplied) { + return; + } + + const headerKeyValueMap: Record<"Response" | "Request", Record> = { + Request: {}, + Response: {}, + }; + + if (matchedPair.modifications?.Request?.length) { + matchedPair.modifications.Request.forEach((header: { header: string; type: string; value: string }) => { + if (header.value === INITIATOR_DOMAIN_FUNCTION) { + headerKeyValueMap.Request[header.header] = requestDetails.initiatorDomain; + } + }); + } + + if (matchedPair.modifications?.Response?.length) { + matchedPair.modifications.Response.forEach((header: { header: string; type: string; value: string }) => { + if (header.value === INITIATOR_DOMAIN_FUNCTION) { + headerKeyValueMap.Response[header.header] = requestDetails.initiatorDomain; + } + }); + } + + const ruleAction: { + requestHeaders?: chrome.declarativeNetRequest.RuleAction["requestHeaders"]; + responseHeaders?: chrome.declarativeNetRequest.RuleAction["responseHeaders"]; + } = {}; + + if (Object.keys(headerKeyValueMap.Request).length) { + ruleAction.requestHeaders = Object.entries(headerKeyValueMap.Request).map(([header, value]) => ({ + header, + value, + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + })); + } + + if (Object.keys(headerKeyValueMap.Response).length) { + ruleAction.responseHeaders = Object.entries(headerKeyValueMap.Response).map(([header, value]) => ({ + header, + value, + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + })); + } + + if (!Object.keys(ruleAction).length) { + return; + } + + await updateRequestSpecificRules( + tabId, + requestDetails.url, + { + action: { + ...ruleAction, + type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, + }, + condition: { + urlFilter: `|${requestDetails.url}|`, + resourceTypes: [chrome.declarativeNetRequest.ResourceType.XMLHTTPREQUEST], + tabIds: [tabId], + requestMethods: [requestDetails.method.toLowerCase() as chrome.declarativeNetRequest.RequestMethod], + excludedInitiatorDomains: ["requestly.io", "requestly.com"], + }, + }, + SessionRuleType.INITIATOR_DOMAIN + ); +}; diff --git a/browser-extension/mv3/src/service-worker/services/requestProcessor/index.ts b/browser-extension/mv3/src/service-worker/services/requestProcessor/index.ts index 5997c0bd07..7bb0f6e418 100644 --- a/browser-extension/mv3/src/service-worker/services/requestProcessor/index.ts +++ b/browser-extension/mv3/src/service-worker/services/requestProcessor/index.ts @@ -2,16 +2,19 @@ import { getEnabledRules, onRuleOrGroupChange } from "common/rulesStore"; import { Rule, RuleType } from "common/types"; import { AJAXRequestDetails } from "./types"; import { forwardHeadersOnRedirect } from "./handleHeadersOnRedirect"; +import { handleInitiatorDomainFunction } from "./handleInitiatorDomainFunction"; class RequestProcessor { cachedRules: Record = { redirectRules: [], replaceRules: [], + headerRules: [], }; private updateCachedRules = async () => { this.cachedRules.redirectRules = await getEnabledRules(RuleType.REDIRECT); this.cachedRules.replaceRules = await getEnabledRules(RuleType.REPLACE); + this.cachedRules.headerRules = await getEnabledRules(RuleType.HEADERS); }; constructor() { @@ -28,6 +31,8 @@ class RequestProcessor { ...requestProcessor.cachedRules.redirectRules, ...requestProcessor.cachedRules.replaceRules, ]); + + await handleInitiatorDomainFunction(tabId, requestDetails, requestProcessor.cachedRules.headerRules); }; } diff --git a/browser-extension/mv3/src/service-worker/services/requestProcessor/types.ts b/browser-extension/mv3/src/service-worker/services/requestProcessor/types.ts index d34b5e416a..8f8a820a27 100644 --- a/browser-extension/mv3/src/service-worker/services/requestProcessor/types.ts +++ b/browser-extension/mv3/src/service-worker/services/requestProcessor/types.ts @@ -8,4 +8,5 @@ export interface AJAXRequestDetails { export enum SessionRuleType { FORWARD_IGNORED_HEADERS = "forwardIgnoredHeaders", + INITIATOR_DOMAIN = "initiatorDomain", } diff --git a/browser-extension/mv3/src/service-worker/services/ruleMatcher.ts b/browser-extension/mv3/src/service-worker/services/ruleMatcher.ts index 70387462db..fbdcdb78a8 100644 --- a/browser-extension/mv3/src/service-worker/services/ruleMatcher.ts +++ b/browser-extension/mv3/src/service-worker/services/ruleMatcher.ts @@ -138,7 +138,9 @@ export const matchRuleWithRequest = function (rule: Rule, requestDetails: AJAXRe ); if (!matchedPair) { - return {}; + return { + isApplied: false, + }; } const destinationUrl = populateRedirectedUrl(matchedPair, rule.ruleType, requestDetails); @@ -217,7 +219,7 @@ export const populateRedirectedUrl = (rulePair: RulePair, ruleType: RuleType, re export const findMatchingRule = (rules: Rule[], requestDetails: AJAXRequestDetails) => { for (const rule of rules) { const matchedRule = matchRuleWithRequest(rule, requestDetails); - if (matchedRule) { + if (matchedRule.isApplied) { return matchedRule; } }