-
Notifications
You must be signed in to change notification settings - Fork 325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(rules): Better Redirect Rules #1256
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
7f68ab4
fix(mv3): :wrench: Modifying the default local redirect behaviour.
whizzzkid d94b9bb
Merge branch 'rc/3.0-mv3' into fix/default-rules
whizzzkid a817045
Merge branch 'rc/3.0-mv3' into fix/default-rules
whizzzkid 045e660
fix(mv3): :wrench: Modifying the default local redirect behaviour.
whizzzkid f6561b9
Merge remote-tracking branch 'refs/remotes/origin/fix/default-rules' …
whizzzkid c020638
Merge branch 'feat/redirection-tests' into fix/default-rules
whizzzkid fc085b5
fix(mv3): :bug: Making rules less greedy
whizzzkid 007f41f
fix(mv3): :sparkles: Dynamic Rules for subdomain gateways.
whizzzkid f18579b
fix(types): Adding ambient types for is-ipfs.
whizzzkid 3e72d36
fix(test):
whizzzkid d36a282
fix(test): helper
whizzzkid 6ee2d31
feat(mv3): less greedy rules
whizzzkid e592755
feat: Adding simpler regex for redirects from similar namespaces.
whizzzkid 5c85d84
fix(lint): :rotating_light: Warnings
whizzzkid fca5fe2
feat(mv3): Better Default Rules (#1260)
whizzzkid 832679d
Update add-on/src/lib/redirect-handler/blockOrObserve.ts
whizzzkid 5e22cea
fix(docs): :pencil2: Adding comments
whizzzkid dbc672c
refactor(regexfilters): Better Structure and Readability (#1261)
whizzzkid 381f63c
fix(mv3): no blanket redirect for subdomains without namespaces.
whizzzkid 4020796
fix(lint): unused import
whizzzkid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created ipfs-shipyard/is-ipfs#88