Skip to content

Commit 7be2c23

Browse files
whizzzkidSgtPookilidel
authored
feat(mv3): Redirection Tests (#1236)
* feat(mv3): ✨ Patching countly-sdk-web Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(mv3): ✨ Implementing Custom Async Store. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * chore(mv3): 🩹 Hooking everything up together. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): Countly Patching + ignite-metrics@2.0.0 Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): 🩹 Patching the Patch Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: tests Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: lint Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): ♻️ Refactoring `supportsBlock` Checks. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): Regex Bug Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat: Migrating blocking redirection test to observing redirection test Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): 🔧 Fixing the mocha-setup. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): ♻️ Moving Setup Files. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): gateway-redirect tests now fixed. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: 🩹 Patching error messages Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(patch): countly-web-sdk * fix(patch): :pin: Pinning countly-web-sdk to 23.2.2 * fix(mv3): 💄 Fixing Lint Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat: protocol-handler-redirection-tests Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat: more tests fixed Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: More tests Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: lint fix Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * test: merge mocha-setup files (#1246) * test: merge mocha-setup files * test(helper): add mv3-test-enabled helper * test(setup): remove duplicate setup for mv3/mv2 * test(mv3): merge mv3 tests into mv2 test files (#1247) * test(mv3): merge mv3 tests into mv2 test files * chore(lint): fix lint failures * test(supportsBlock): cleanup test * test: migrating some tests * test(redirect): mv3 tests use same code as mv2 * test(redirect): mv3 test cleanup * test(protohandler): mv3 tests use same code as mv2 * test(helper): make isMv3 flag a boolean * test: fix after merge * test: fix after merge * fix: typerrors for localstorage Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: Updating test:functional_MV3 command. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: setup Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): Fixing tests Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(lint): Becuase Ofcourse * feat(test): scaffolding mv3 + mv2 calls in a single check. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(test): unskipping and upgrading dnslink tests to mv3 Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(test): Upgrading workaround tests to MV3 Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): removing all skips with better checks. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): ♻️ Refactoring tests and removing redundant calls. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): More Dryer Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): one more Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): ✏️ Renaming isMv3TestingEnabled -> isManifestV3 Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): refactor expectNoRedirect Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): ♻️ Refactoring more. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: replacing checks to undefined Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: renaming expectNoRedirect -> ensureNoRedirect Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): Adding missing JSDoc Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): 🤷 how did this get removed. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): 🗑️ removed. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): Suggestion * fix(test): 🩹 Bad Merge Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): sequential expectNoRedirect * Update add-on/src/lib/redirect-handler/blockOrObserve.ts Co-authored-by: Marcin Rataj <lidel@lidel.org> * fix(rules): Better Redirect Rules (#1256) * fix(mv3): 🔧 Modifying the default local redirect behaviour. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): 🔧 Modifying the default local redirect behaviour. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): 🐛 Making rules less greedy Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): ✨ Dynamic Rules for subdomain gateways. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(types): Adding ambient types for is-ipfs. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(test): helper Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(mv3): less greedy rules Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat: Adding simpler regex for redirects from similar namespaces. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(lint): 🚨 Warnings Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(mv3): Better Default Rules (#1260) * refactor(mv3): blockOrRequest code Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * refactor(mv3): Port Logic for Default Rules is more robust. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(test): Adding tests for default rule logic. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> --------- Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * Update add-on/src/lib/redirect-handler/blockOrObserve.ts * fix(docs): ✏️ Adding comments Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * refactor(regexfilters): Better Structure and Readability (#1261) * refactor(regexFilters): ✨ Adding a base class for regexFilters. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * refactor(regexFilters): ♻️ Moving subdomain filter to a subclass Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * refactor(regexFilters): ♻️ Moving namespace filter to a subclass Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * refactor(regexFilters): ♻️ Moving common filter to a subclass Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * feat(regexFilters): ✨ Hooking Up All together Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(lint): ✏️ Lint Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(regexFilters): ✏️ Updating message. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(rename): ✏️ CommonPatterRedirectRegexFilter -> CommonPatternRedirectRegexFilter Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(regexFilters): ♻️ Refactor to remove call to super Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: make _canHandle private Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix: ⚡ Fix math.min on every loop. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> --------- Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(mv3): no blanket redirect for subdomains without namespaces. Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> * fix(lint): unused import Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> --------- Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> --------- Signed-off-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Co-authored-by: Marcin Rataj <lidel@lidel.org>
1 parent 2d65822 commit 7be2c23

21 files changed

+1149
-335
lines changed

add-on/src/lib/ipfs-request.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
188188
}
189189
// poor-mans protocol handlers - https://github.com/ipfs/ipfs-companion/issues/164#issuecomment-328374052
190190
if (state.catchUnhandledProtocols && mayContainUnhandledIpfsProtocol(request)) {
191-
const fix = normalizedUnhandledIpfsProtocol(request, state.pubGwURLString)
191+
const fix = await normalizedUnhandledIpfsProtocol(request, state.pubGwURLString)
192192
if (fix) {
193193
return fix
194194
}
@@ -485,15 +485,15 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
485485
* @param {object} input contains originUrl and redirectUrl.
486486
* @returns
487487
*/
488-
function handleRedirection ({ originUrl, redirectUrl, request }) {
488+
async function handleRedirection ({ originUrl, redirectUrl, request }) {
489489
if (redirectUrl !== '' && originUrl !== '' && redirectUrl !== originUrl) {
490490
resolvedRequestTracker.track(request)
491-
if (supportsBlock) {
491+
if (supportsBlock()) {
492492
return { redirectUrl }
493493
}
494494

495495
// Let browser handle redirection MV3 style.
496-
addRuleToDynamicRuleSet({ originUrl, redirectUrl })
496+
await addRuleToDynamicRuleSet({ originUrl, redirectUrl })
497497
}
498498
}
499499

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
export interface IRegexFilter {
2+
originUrl: string
3+
redirectUrl: string
4+
}
5+
6+
export interface IFilter {
7+
regexFilter: string
8+
regexSubstitution: string
9+
}
10+
11+
/**
12+
* Base class for all regex filters.
13+
*/
14+
export class RegexFilter {
15+
readonly _redirectUrl!: string
16+
readonly _originUrl!: string
17+
readonly originURL: URL
18+
readonly redirectURL: URL
19+
readonly originNS: string
20+
readonly redirectNS: string
21+
// by default we cannot handle the request.
22+
private _canHandle = false
23+
regexFilter!: string
24+
regexSubstitution!: string
25+
26+
constructor ({ originUrl, redirectUrl }: IRegexFilter) {
27+
this._originUrl = originUrl
28+
this._redirectUrl = redirectUrl
29+
this.originURL = new URL(this._originUrl)
30+
this.redirectURL = new URL(this._redirectUrl)
31+
this.redirectNS = this.computeNamespaceFromUrl(this.redirectURL)
32+
this.originNS = this.computeNamespaceFromUrl(this.originURL)
33+
this.computeFilter()
34+
this.normalizeRegexFilter()
35+
}
36+
37+
/**
38+
* Getter for the originUrl provided at construction.
39+
*/
40+
get originUrl (): string {
41+
return this._originUrl
42+
}
43+
44+
/**
45+
* Getter for the redirectUrl provided at construction.
46+
*/
47+
get redirectUrl (): string {
48+
return this._redirectUrl
49+
}
50+
51+
/**
52+
* Getter for the canHandle flag.
53+
*/
54+
get canHandle (): boolean {
55+
return this._canHandle
56+
}
57+
58+
/**
59+
* Setter for the canHandle flag.
60+
*/
61+
set canHandle (value: boolean) {
62+
this._canHandle = value
63+
}
64+
65+
/**
66+
* Getter for the filter. This is the regex filter and substitution.
67+
*/
68+
get filter (): IFilter {
69+
if (!this.canHandle) {
70+
throw new Error('Cannot handle this request')
71+
}
72+
73+
return {
74+
regexFilter: this.regexFilter,
75+
regexSubstitution: this.regexSubstitution
76+
}
77+
}
78+
79+
/**
80+
* Compute the regex filter and substitution.
81+
* This is the main method that needs to be implemented by subclasses.
82+
*/
83+
computeFilter (): void {
84+
throw new Error('Method not implemented.')
85+
}
86+
87+
/**
88+
* Normalize the regex filter. This is a helper method that can be used by subclasses.
89+
*/
90+
normalizeRegexFilter (): void {
91+
this.regexFilter = this.regexFilter.replace(/https?\??/ig, 'https?')
92+
}
93+
94+
/**
95+
* Compute the namespace from the URL. This finds the first path segment.
96+
* e.g. http://<gateway>/<namespace>/path/to/file/or/cid
97+
*
98+
* @param url URL
99+
*/
100+
computeNamespaceFromUrl ({ pathname }: URL): string {
101+
// regex to match the first path segment.
102+
return (/\/([^/]+)\//i.exec(pathname)?.[1] ?? '').toLowerCase()
103+
}
104+
}

add-on/src/lib/redirect-handler/blockOrObserve.ts

+53-45
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import debug from 'debug'
22
import browser from 'webextension-polyfill'
33
import { CompanionState } from '../../types/companion.js'
4+
import { IFilter, IRegexFilter, RegexFilter } from './baseRegexFilter.js'
5+
import { CommonPatternRedirectRegexFilter } from './commonPatternRedirectRegexFilter.js'
6+
import { NamespaceRedirectRegexFilter } from './namespaceRedirectRegexFilter.js'
7+
import { SubdomainRedirectRegexFilter } from './subdomainRedirectRegexFilter.js'
48

59
// this won't work in webworker context. Needs to be enabled manually
610
// https://github.com/debug-js/debug/issues/916
711
const log = debug('ipfs-companion:redirect-handler:blockOrObserve')
812
log.error = debug('ipfs-companion:redirect-handler:blockOrObserve:error')
913

14+
export const DEFAULT_NAMESPACES = new Set(['ipfs', 'ipns'])
15+
1016
export const GLOBAL_STATE_CHANGE = 'GLOBAL_STATE_CHANGE'
1117
export const GLOBAL_STATE_OPTION_CHANGE = 'GLOBAL_STATE_OPTION_CHANGE'
1218
export const DELETE_RULE_REQUEST = 'DELETE_RULE_REQUEST'
1319
export const DELETE_RULE_REQUEST_SUCCESS = 'DELETE_RULE_REQUEST_SUCCESS'
20+
21+
// We need to match the rest of the URL, so we can use a wildcard.
1422
export const RULE_REGEX_ENDING = '((?:[^\\.]|$).*)$'
1523

1624
interface regexFilterMap {
@@ -21,6 +29,7 @@ interface regexFilterMap {
2129
interface redirectHandlerInput {
2230
originUrl: string
2331
redirectUrl: string
32+
getPort: (state: CompanionState) => string
2433
}
2534

2635
type messageToSelfType = typeof GLOBAL_STATE_CHANGE | typeof GLOBAL_STATE_OPTION_CHANGE | typeof DELETE_RULE_REQUEST
@@ -29,10 +38,16 @@ interface messageToSelf {
2938
value?: string | Record<string, unknown>
3039
}
3140

41+
export const defaultNSRegexStr = `(${[...DEFAULT_NAMESPACES].join('|')})`
42+
3243
// We need to check if the browser supports the declarativeNetRequest API.
3344
// TODO: replace with check for `Blocking` in `chrome.webRequest.OnBeforeRequestOptions`
3445
// which is currently a bug https://bugs.chromium.org/p/chromium/issues/detail?id=1427952
35-
export const supportsBlock = !(browser.declarativeNetRequest?.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES === 5000)
46+
// this needs to be a function call, because in tests we mock browser.declarativeNetRequest
47+
// the way sinon ends up stubbing it, it's not directly available in the global scope on import
48+
// rather it gets replaced dynamically when the module is imported. Which means, we can't
49+
// just check for the existence of the property, we need to call the browser instance at that point.
50+
export const supportsBlock = (): boolean => !(browser.declarativeNetRequest?.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES > 0)
3651

3752
/**
3853
* Notify self about state change.
@@ -61,21 +76,28 @@ export async function notifyDeleteRule (id: number): Promise<void> {
6176
*/
6277
async function sendMessageToSelf (msg: messageToSelfType, value?: any): Promise<void> {
6378
// this check ensures we don't send messages to ourselves if blocking mode is enabled.
64-
if (!supportsBlock) {
65-
const message: messageToSelf = { type: msg, value }
66-
await browser.runtime.sendMessage({ message })
79+
if (!supportsBlock()) {
80+
const message: messageToSelf = { type: msg }
81+
await browser.runtime.sendMessage(message)
6782
}
6883
}
6984

7085
const savedRegexFilters: Map<string, regexFilterMap> = new Map()
7186
const DEFAULT_LOCAL_RULES: redirectHandlerInput[] = [
7287
{
7388
originUrl: 'http://127.0.0.1',
74-
redirectUrl: 'http://localhost'
89+
redirectUrl: 'http://localhost',
90+
getPort: ({ gwURLString }): string => new URL(gwURLString).port
7591
},
7692
{
7793
originUrl: 'http://[::1]',
78-
redirectUrl: 'http://localhost'
94+
redirectUrl: 'http://localhost',
95+
getPort: ({ gwURLString }): string => new URL(gwURLString).port
96+
},
97+
{
98+
originUrl: 'http://localhost',
99+
redirectUrl: 'http://127.0.0.1',
100+
getPort: ({ apiURL }): string => new URL(apiURL).port
79101
}
80102
]
81103

@@ -97,10 +119,10 @@ export function isLocalHost (url: string): boolean {
97119
* @param str URL string to escape
98120
* @returns
99121
*/
100-
function escapeURLRegex (str: string): string {
122+
export function escapeURLRegex (str: string): string {
101123
// these characters are allowed in the URL, but not in the regex.
102124
// eslint-disable-next-line no-useless-escape
103-
const ALLOWED_CHARS_URL_REGEX = /([:\/\?#\[\]@!$&'\(\ )\*\+,;=-_\.~])/g
125+
const ALLOWED_CHARS_URL_REGEX = /([:\/\?#\[\]@!$&'\(\ )\*\+,;=\-_\.~])/g
104126
return str.replace(ALLOWED_CHARS_URL_REGEX, '\\$1')
105127
}
106128

@@ -111,43 +133,29 @@ function escapeURLRegex (str: string): string {
111133
* @param redirectUrl
112134
* @returns
113135
*/
114-
function constructRegexFilter ({ originUrl, redirectUrl }: redirectHandlerInput): {
115-
regexSubstitution: string
116-
regexFilter: string
117-
} {
118-
// We can traverse the URL from the end, and find the first character that is different.
119-
let commonIdx = 1
120-
while (commonIdx < Math.min(originUrl.length, redirectUrl.length)) {
121-
if (originUrl[originUrl.length - commonIdx] !== redirectUrl[redirectUrl.length - commonIdx]) {
122-
break
136+
function constructRegexFilter ({ originUrl, redirectUrl }: IRegexFilter): IFilter {
137+
// the order is very important here, because we want to match the best possible filter.
138+
const filtersToTryInOrder: Array<typeof RegexFilter> = [
139+
SubdomainRedirectRegexFilter,
140+
NamespaceRedirectRegexFilter,
141+
CommonPatternRedirectRegexFilter
142+
]
143+
144+
for (const Filter of filtersToTryInOrder) {
145+
const filter = new Filter({ originUrl, redirectUrl })
146+
if (filter.canHandle) {
147+
return filter.filter
123148
}
124-
commonIdx += 1
125-
}
126-
127-
// We can now construct the regex filter and substitution.
128-
let regexSubstitution = redirectUrl.slice(0, redirectUrl.length - commonIdx + 1) + '\\1'
129-
// We need to escape the characters that are allowed in the URL, but not in the regex.
130-
const regexFilterFirst = escapeURLRegex(originUrl.slice(0, originUrl.length - commonIdx + 1))
131-
// We need to match the rest of the URL, so we can use a wildcard.
132-
const RULE_REGEX_ENDING = '((?:[^\\.]|$).*)$'
133-
let regexFilter = `^${regexFilterFirst}${RULE_REGEX_ENDING}`.replace(/https?/ig, 'https?')
134-
135-
// This method does not parse:
136-
// originUrl: "https://awesome.ipfs.io/"
137-
// redirectUrl: "http://localhost:8081/ipns/awesome.ipfs.io/"
138-
// that ends up with capturing all urls which we do not want.
139-
if (regexFilter === `^https?\\:\\/${RULE_REGEX_ENDING}`) {
140-
const subdomain = new URL(originUrl).hostname
141-
regexFilter = `^https?\\:\\/\\/${escapeURLRegex(subdomain)}${RULE_REGEX_ENDING}`
142-
regexSubstitution = regexSubstitution.replace('\\1', `/${subdomain}\\1`)
143149
}
144150

145-
return { regexSubstitution, regexFilter }
151+
// this is just to satisfy the compiler, this should never happen. Because CommonPatternRedirectRegexFilter can always
152+
// handle.
153+
return new CommonPatternRedirectRegexFilter({ originUrl, redirectUrl }).filter
146154
}
147155

148156
// If the browser supports the declarativeNetRequest API, we can block the request.
149157
export function getExtraInfoSpec<T> (additionalParams: T[] = []): T[] {
150-
if (supportsBlock) {
158+
if (supportsBlock()) {
151159
return ['blocking' as T, ...additionalParams]
152160
}
153161
return additionalParams
@@ -239,15 +247,15 @@ async function reconcileRulesAndRemoveOld (state: CompanionState): Promise<void>
239247
if (rules.length === 0) {
240248
// we need to populate old rules.
241249
for (const [regexFilter, { regexSubstitution, id }] of savedRegexFilters.entries()) {
242-
addRules.push(generateRule(id, regexFilter, regexSubstitution))
250+
addRules.push(generateAddRule(id, regexFilter, regexSubstitution))
243251
}
244252
}
245253

246254
// make sure that the default rules are added.
247-
for (const { originUrl, redirectUrl } of DEFAULT_LOCAL_RULES) {
248-
const { port } = new URL(state.gwURLString)
249-
const regexFilter = `^${escapeURLRegex(`${originUrl}:${port}`)}(.*)$`
250-
const regexSubstitution = `${redirectUrl}:${port}\\1`
255+
for (const { originUrl, redirectUrl, getPort } of DEFAULT_LOCAL_RULES) {
256+
const port = getPort(state)
257+
const regexFilter = `^${escapeURLRegex(`${originUrl}:${port}`)}\\/${defaultNSRegexStr}\\/${RULE_REGEX_ENDING}`
258+
const regexSubstitution = `${redirectUrl}:${port}/\\1/\\2`
251259

252260
if (!savedRegexFilters.has(regexFilter)) {
253261
// We need to add the new rule.
@@ -276,7 +284,7 @@ function saveAndGenerateRule (
276284
const id = Math.floor(Math.random() * 29999)
277285
// We need to save the regex filter and ID to check if the rule already exists later.
278286
savedRegexFilters.set(regexFilter, { id, regexSubstitution })
279-
return generateRule(id, regexFilter, regexSubstitution, excludedInitiatorDomains)
287+
return generateAddRule(id, regexFilter, regexSubstitution, excludedInitiatorDomains)
280288
}
281289

282290
/**
@@ -287,7 +295,7 @@ function saveAndGenerateRule (
287295
* @param excludedInitiatorDomains - The domains that are excluded from the rule.
288296
* @returns
289297
*/
290-
function generateRule (
298+
export function generateAddRule (
291299
id: number,
292300
regexFilter: string,
293301
regexSubstitution: string,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { RegexFilter } from './baseRegexFilter.js'
2+
import { RULE_REGEX_ENDING, escapeURLRegex } from './blockOrObserve.js'
3+
4+
/**
5+
* Handles redirects like:
6+
* origin: '^https?\\:\\/\\/awesome\\.ipfs\\.io\\/(.*)'
7+
* destination: 'http://localhost:8081/ipns/awesome.ipfs.io/$1'
8+
*/
9+
export class CommonPatternRedirectRegexFilter extends RegexFilter {
10+
computeFilter (): void {
11+
// this filter is the worst case scenario, we can handle any redirect.
12+
this.canHandle = true
13+
// We can traverse the URL from the end, and find the first character that is different.
14+
let commonIdx = 1
15+
const leastLength = Math.min(this.originUrl.length, this.redirectUrl.length)
16+
while (commonIdx < leastLength) {
17+
if (this.originUrl[this.originUrl.length - commonIdx] !== this.redirectUrl[this.redirectUrl.length - commonIdx]) {
18+
break
19+
}
20+
commonIdx += 1
21+
}
22+
23+
// We can now construct the regex filter and substitution.
24+
this.regexSubstitution = this.redirectUrl.slice(0, this.redirectUrl.length - commonIdx + 1) + '\\1'
25+
// We need to escape the characters that are allowed in the URL, but not in the regex.
26+
const regexFilterFirst = escapeURLRegex(this.originUrl.slice(0, this.originUrl.length - commonIdx + 1))
27+
this.regexFilter = `^${regexFilterFirst}${RULE_REGEX_ENDING}`
28+
// calling normalize should add the protocol in the regexFilter.
29+
this.normalizeRegexFilter()
30+
31+
// This method does not parse:
32+
// originUrl: "https://awesome.ipfs.io/"
33+
// redirectUrl: "http://localhost:8081/ipns/awesome.ipfs.io/"
34+
// that ends up with capturing all urls which we do not want.
35+
if (this.regexFilter === `^https?\\:\\/${RULE_REGEX_ENDING}`) {
36+
const subdomain = new URL(this.originUrl).hostname
37+
this.regexFilter = `^https?\\:\\/\\/${escapeURLRegex(subdomain)}${RULE_REGEX_ENDING}`
38+
this.regexSubstitution = this.regexSubstitution.replace('\\1', `/${subdomain}\\1`)
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { RegexFilter } from './baseRegexFilter.js'
2+
import { DEFAULT_NAMESPACES, RULE_REGEX_ENDING, defaultNSRegexStr, escapeURLRegex } from './blockOrObserve.js'
3+
4+
/**
5+
* Handles namespace redirects like:
6+
* origin: '^https?\\:\\/\\/ipfs\\.io\\/(ipfs|ipns)\\/(.*)'
7+
* destination: 'http://localhost:8080/$1/$2'
8+
*/
9+
export class NamespaceRedirectRegexFilter extends RegexFilter {
10+
computeFilter (): void {
11+
this.canHandle = DEFAULT_NAMESPACES.has(this.originNS) &&
12+
DEFAULT_NAMESPACES.has(this.redirectNS) &&
13+
this.originNS === this.redirectNS &&
14+
this.originURL.searchParams.get('uri') == null
15+
// if the namespaces are the same, we can generate simpler regex.
16+
// The only value that needs special handling is the `uri` param.
17+
// A redirect like
18+
// https://ipfs.io/ipfs/QmZMxU -> http://localhost:8080/ipfs/QmZMxU
19+
const [originFirst, originLast] = this.originUrl.split(`/${this.originNS}/`)
20+
this.regexFilter = `^${escapeURLRegex(originFirst)}\\/${defaultNSRegexStr}\\/${RULE_REGEX_ENDING}`
21+
this.regexSubstitution = this.redirectUrl
22+
.replace(`/${this.redirectNS}/`, '/\\1/')
23+
.replace(originLast, '\\2')
24+
}
25+
}

0 commit comments

Comments
 (0)