-
Notifications
You must be signed in to change notification settings - Fork 325
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Showing
13 changed files
with
409 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
export interface IRegexFilter { | ||
originUrl: string | ||
redirectUrl: string | ||
} | ||
|
||
export interface IFilter { | ||
regexFilter: string | ||
regexSubstitution: string | ||
} | ||
|
||
/** | ||
* Base class for all regex filters. | ||
*/ | ||
export class RegexFilter { | ||
readonly _redirectUrl!: string | ||
readonly _originUrl!: string | ||
readonly originURL: URL | ||
readonly redirectURL: URL | ||
readonly originNS: string | ||
readonly redirectNS: string | ||
// by default we cannot handle the request. | ||
private _canHandle = false | ||
regexFilter!: string | ||
regexSubstitution!: string | ||
|
||
constructor ({ originUrl, redirectUrl }: IRegexFilter) { | ||
this._originUrl = originUrl | ||
this._redirectUrl = redirectUrl | ||
this.originURL = new URL(this._originUrl) | ||
this.redirectURL = new URL(this._redirectUrl) | ||
this.redirectNS = this.computeNamespaceFromUrl(this.redirectURL) | ||
this.originNS = this.computeNamespaceFromUrl(this.originURL) | ||
this.computeFilter() | ||
this.normalizeRegexFilter() | ||
} | ||
|
||
/** | ||
* Getter for the originUrl provided at construction. | ||
*/ | ||
get originUrl (): string { | ||
return this._originUrl | ||
} | ||
|
||
/** | ||
* Getter for the redirectUrl provided at construction. | ||
*/ | ||
get redirectUrl (): string { | ||
return this._redirectUrl | ||
} | ||
|
||
/** | ||
* Getter for the canHandle flag. | ||
*/ | ||
get canHandle (): boolean { | ||
return this._canHandle | ||
} | ||
|
||
/** | ||
* Setter for the canHandle flag. | ||
*/ | ||
set canHandle (value: boolean) { | ||
this._canHandle = value | ||
} | ||
|
||
/** | ||
* Getter for the filter. This is the regex filter and substitution. | ||
*/ | ||
get filter (): IFilter { | ||
if (!this.canHandle) { | ||
throw new Error('Cannot handle this request') | ||
} | ||
|
||
return { | ||
regexFilter: this.regexFilter, | ||
regexSubstitution: this.regexSubstitution | ||
} | ||
} | ||
|
||
/** | ||
* Compute the regex filter and substitution. | ||
* This is the main method that needs to be implemented by subclasses. | ||
*/ | ||
computeFilter (): void { | ||
throw new Error('Method not implemented.') | ||
} | ||
|
||
/** | ||
* Normalize the regex filter. This is a helper method that can be used by subclasses. | ||
*/ | ||
normalizeRegexFilter (): void { | ||
this.regexFilter = this.regexFilter.replace(/https?\??/ig, 'https?') | ||
} | ||
|
||
/** | ||
* Compute the namespace from the URL. This finds the first path segment. | ||
* e.g. http://<gateway>/<namespace>/path/to/file/or/cid | ||
* | ||
* @param url URL | ||
*/ | ||
computeNamespaceFromUrl ({ pathname }: URL): string { | ||
// regex to match the first path segment. | ||
return (/\/([^/]+)\//i.exec(pathname)?.[1] ?? '').toLowerCase() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
add-on/src/lib/redirect-handler/commonPatternRedirectRegexFilter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { RegexFilter } from './baseRegexFilter.js' | ||
import { RULE_REGEX_ENDING, escapeURLRegex } from './blockOrObserve.js' | ||
|
||
/** | ||
* Handles redirects like: | ||
* origin: '^https?\\:\\/\\/awesome\\.ipfs\\.io\\/(.*)' | ||
* destination: 'http://localhost:8081/ipns/awesome.ipfs.io/$1' | ||
*/ | ||
export class CommonPatternRedirectRegexFilter extends RegexFilter { | ||
computeFilter (): void { | ||
// this filter is the worst case scenario, we can handle any redirect. | ||
this.canHandle = true | ||
// We can traverse the URL from the end, and find the first character that is different. | ||
let commonIdx = 1 | ||
const leastLength = Math.min(this.originUrl.length, this.redirectUrl.length) | ||
while (commonIdx < leastLength) { | ||
if (this.originUrl[this.originUrl.length - commonIdx] !== this.redirectUrl[this.redirectUrl.length - commonIdx]) { | ||
break | ||
} | ||
commonIdx += 1 | ||
} | ||
|
||
// We can now construct the regex filter and substitution. | ||
this.regexSubstitution = this.redirectUrl.slice(0, this.redirectUrl.length - commonIdx + 1) + '\\1' | ||
// We need to escape the characters that are allowed in the URL, but not in the regex. | ||
const regexFilterFirst = escapeURLRegex(this.originUrl.slice(0, this.originUrl.length - commonIdx + 1)) | ||
this.regexFilter = `^${regexFilterFirst}${RULE_REGEX_ENDING}` | ||
// calling normalize should add the protocol in the regexFilter. | ||
this.normalizeRegexFilter() | ||
|
||
// This method does not parse: | ||
// originUrl: "https://awesome.ipfs.io/" | ||
// redirectUrl: "http://localhost:8081/ipns/awesome.ipfs.io/" | ||
// that ends up with capturing all urls which we do not want. | ||
if (this.regexFilter === `^https?\\:\\/${RULE_REGEX_ENDING}`) { | ||
const subdomain = new URL(this.originUrl).hostname | ||
this.regexFilter = `^https?\\:\\/\\/${escapeURLRegex(subdomain)}${RULE_REGEX_ENDING}` | ||
this.regexSubstitution = this.regexSubstitution.replace('\\1', `/${subdomain}\\1`) | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
add-on/src/lib/redirect-handler/namespaceRedirectRegexFilter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { RegexFilter } from './baseRegexFilter.js' | ||
import { DEFAULT_NAMESPACES, RULE_REGEX_ENDING, defaultNSRegexStr, escapeURLRegex } from './blockOrObserve.js' | ||
|
||
/** | ||
* Handles namespace redirects like: | ||
* origin: '^https?\\:\\/\\/ipfs\\.io\\/(ipfs|ipns)\\/(.*)' | ||
* destination: 'http://localhost:8080/$1/$2' | ||
*/ | ||
export class NamespaceRedirectRegexFilter extends RegexFilter { | ||
computeFilter (): void { | ||
this.canHandle = DEFAULT_NAMESPACES.has(this.originNS) && | ||
DEFAULT_NAMESPACES.has(this.redirectNS) && | ||
this.originNS === this.redirectNS && | ||
this.originURL.searchParams.get('uri') == null | ||
// if the namespaces are the same, we can generate simpler regex. | ||
// The only value that needs special handling is the `uri` param. | ||
// A redirect like | ||
// https://ipfs.io/ipfs/QmZMxU -> http://localhost:8080/ipfs/QmZMxU | ||
const [originFirst, originLast] = this.originUrl.split(`/${this.originNS}/`) | ||
this.regexFilter = `^${escapeURLRegex(originFirst)}\\/${defaultNSRegexStr}\\/${RULE_REGEX_ENDING}` | ||
this.regexSubstitution = this.redirectUrl | ||
.replace(`/${this.redirectNS}/`, '/\\1/') | ||
.replace(originLast, '\\2') | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
add-on/src/lib/redirect-handler/subdomainRedirectRegexFilter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { IRegexFilter, RegexFilter } from './baseRegexFilter.js' | ||
import { DEFAULT_NAMESPACES, RULE_REGEX_ENDING, defaultNSRegexStr, escapeURLRegex } from './blockOrObserve.js' | ||
|
||
/** | ||
* Handles subdomain redirects like: | ||
* origin: '^https?\\:\\/\\/bafybeigfejjsuq5im5c3w3t3krsiytszhfdc4v5myltcg4myv2n2w6jumy\\.ipfs\\.dweb\\.link' | ||
* destination: 'http://localhost:8080/ipfs/bafybeigfejjsuq5im5c3w3t3krsiytszhfdc4v5myltcg4myv2n2w6jumy' | ||
*/ | ||
export class SubdomainRedirectRegexFilter extends RegexFilter { | ||
constructor ({ originUrl, redirectUrl }: IRegexFilter) { | ||
super({ originUrl, redirectUrl }) | ||
} | ||
|
||
computeFilter (): void { | ||
this.regexSubstitution = this.redirectUrl | ||
this.regexFilter = this.originUrl | ||
if (!DEFAULT_NAMESPACES.has(this.originNS) && DEFAULT_NAMESPACES.has(this.redirectNS)) { | ||
// We'll use this to match the origin URL later. | ||
this.regexFilter = `^${escapeURLRegex(this.regexFilter)}` | ||
this.normalizeRegexFilter() | ||
const origRegexFilter = this.regexFilter | ||
// tld and root are known, we are just interested in the remainder of URL. | ||
const [tld, root, ...urlParts] = this.originURL.hostname.split('.').reverse() | ||
// can use the staticUrlParts to match the origin URL later. | ||
const staticUrlParts = [root, tld] | ||
// regex to match the start of the URL, this remains common. | ||
const commonStaticUrlStart = escapeURLRegex(`^${this.originURL.protocol}//`) | ||
// going though the subdomains to find a namespace or CID. | ||
while (urlParts.length > 0) { | ||
// get the urlPart at the 0th index and remove it from the array. | ||
const subdomainPart = urlParts.shift() as string | ||
// this needs to be computed for every iteration as the staticUrlParts changes | ||
const commonStaticUrlEnd = `\\.${escapeURLRegex(staticUrlParts.join('.'))}\\/${RULE_REGEX_ENDING}` | ||
|
||
// this does not work for subdomains where namespace is not provided. | ||
// e.g. https://helia-identify.on.fleek.co/ | ||
// e.g. https://bafybeib3bzis4mejzsnzsb65od3rnv5ffit7vsllratddjkgfgq4wiamqu.on.fleek.co/ | ||
// check if the subdomainPart is a namespace. | ||
if (DEFAULT_NAMESPACES.has(subdomainPart)) { | ||
// We found a namespace, this is going to match group 2, i.e. namespace. | ||
// e.g https://bafybeib3bzis4mejzsnzsb65od3rnv5ffit7vsllratddjkgfgq4wiamqu.ipfs.dweb.link | ||
this.regexFilter = `${commonStaticUrlStart}(.*?)\\.${defaultNSRegexStr}${commonStaticUrlEnd}` | ||
|
||
this.regexSubstitution = this._redirectUrl | ||
.replace(urlParts.reverse().join('.'), '\\1') // replace urlParts or CID. | ||
.replace(`/${subdomainPart}/`, '/\\2/') // replace namespace dynamically. | ||
|
||
const pathWithSearch = this.originURL.pathname + this.originURL.search | ||
if (pathWithSearch !== '/') { | ||
this.regexSubstitution = this.regexSubstitution.replace(pathWithSearch, '/\\3') // replace path | ||
} else { | ||
this.regexSubstitution += '\\3' | ||
} | ||
|
||
// no need to continue, we found a namespace. | ||
break | ||
} | ||
|
||
// till we find a namespace or CID, we keep adding subdomains to the staticUrlParts. | ||
staticUrlParts.unshift(subdomainPart) | ||
} | ||
|
||
if (this.regexFilter !== origRegexFilter) { | ||
// this means we constructed a regexFilter with dynamic parts, instead of the original regexFilter which was | ||
// static. There might be other suited regexFilters in that case. | ||
this.canHandle = true | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
declare module 'is-ipfs' { | ||
function cid (value: string): boolean | ||
} |
Oops, something went wrong.