diff --git a/package-lock.json b/package-lock.json index 6b2c037fed..b5f4eebac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9362,6 +9362,14 @@ "node": ">=8" } }, + "node_modules/browser-extension-url-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-extension-url-match/-/browser-extension-url-match-1.0.0.tgz", + "integrity": "sha512-LfIs9SYgPjYksjxkgOVYZhxMIroR56isQB3YHTAmzunWuT9qrH6Fxt7TD9/s9MoKo7GP37JZbLlZhL9vwQAk3w==", + "dependencies": { + "fancy-regex": "^0.5.4" + } + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -13443,6 +13451,11 @@ "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", "dev": true }, + "node_modules/fancy-regex": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/fancy-regex/-/fancy-regex-0.5.4.tgz", + "integrity": "sha512-O6qfjtMnrPRs+3XOavCxGQDFaMS9K1vEsQMhPowqx2P/h1fDCvK5RUyeWeyDusMH2FkSHAsRE3IbSBMMg53fmw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -24969,6 +24982,7 @@ "apiconnect-wsdl": "1.8.31", "aws4": "^1.12.0", "axios": "^1.4.0", + "browser-extension-url-match": "^1.0.0", "chai": "^4.3.4", "chai-json-schema": "1.5.1", "change-case": "^4.1.2", diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json index 84ddd7867a..9e82282059 100644 --- a/packages/insomnia/package.json +++ b/packages/insomnia/package.json @@ -49,6 +49,7 @@ "apiconnect-wsdl": "1.8.31", "aws4": "^1.12.0", "axios": "^1.4.0", + "browser-extension-url-match": "^1.0.0", "chai": "^4.3.4", "chai-json-schema": "1.5.1", "change-case": "^4.1.2", diff --git a/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/__tests__/urls.test.ts b/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/__tests__/urls.test.ts index e4211df972..b44787c3de 100644 --- a/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/__tests__/urls.test.ts +++ b/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/__tests__/urls.test.ts @@ -3,7 +3,7 @@ import url from 'node:url'; import { describe, expect, it } from '@jest/globals'; import { PropertyList } from '../base'; -import { QueryParam, setUrlParser, Url } from '../urls'; +import { QueryParam, setUrlParser, Url, UrlMatchPattern } from '../urls'; import { Variable } from '../variables'; describe('test Certificate object', () => { @@ -75,12 +75,17 @@ describe('test Certificate object', () => { // static methods const urlStr = 'https://myhost.com/path1/path2'; const urlOptions = Url.parse(urlStr); - const urlOpt = new Url(urlOptions || ''); - - expect(urlOpt.toString()).toEqual(urlStr); + const urlObj = new Url(urlOptions || ''); + expect(urlObj.toString()).toEqual(urlStr); }); it('test UrlMatchPattern', () => { + const pattern = 'https://*.insomnia.com/*'; + const matchPattern = new UrlMatchPattern(pattern); + + expect(matchPattern.getProtocols()).toEqual(['https']); + expect(matchPattern.test('https://*.insomnia.com')).toBeTruthy(); + expect(matchPattern.toString()).toEqual(pattern); }); }); diff --git a/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/urls.ts b/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/urls.ts index 68fc30ca76..548db81d93 100644 --- a/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/urls.ts +++ b/packages/insomnia/src/renderers/hidden-browser-window/sdk-objects/urls.ts @@ -1,3 +1,5 @@ +import { MatcherOrInvalid, matchPattern } from 'browser-extension-url-match'; + import { Property, PropertyBase, PropertyList } from './base'; import { Variable, VariableList } from './variables'; @@ -363,22 +365,26 @@ export class Url extends PropertyBase { } } +// interface Matcher { +// match(pattern: string): boolean; +// } + // UrlMatchPattern implements chrome extension match patterns: // https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns export class UrlMatchPattern extends Property { // scheme - scheme: 'http:' | 'https:' | '*' | 'file:'; + // scheme: 'http:' | 'https:' | '*' | 'file:'; // host // About wildcard: // If you use a wildcard in the host pattern // it must be the first or only character, and it must be followed by a period (.) or forward slash (/). - host: string; + // host: string; // path: // Must contain at least a forward slash // The slash by itself matches any path. - path: string; + // path: string; - private port: string; + // private port: string; // Special cases: https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns#special // "" @@ -386,125 +392,137 @@ export class UrlMatchPattern extends Property { // "http://localhost/*" // It doesn't support match patterns for top Level domains (TLD). + private matcher: MatcherOrInvalid; + private pattern: string; + constructor(pattern: string) { super(); - const patternObj = UrlMatchPattern.parseAndValidate(pattern); - this.scheme = patternObj.scheme; - this.host = patternObj.host; - this.path = patternObj.path; - this.port = patternObj.port; + // const patternObj = UrlMatchPattern.parse(pattern); + this.pattern = pattern; + this.matcher = matchPattern(pattern).assertValid(); + // this.scheme = patternObj.scheme; + // this.host = patternObj.host; + // this.path = patternObj.path; + // this.port = patternObj.port; } - static parseAndValidate(pattern: string): { - scheme: 'http:' | 'https:' | '*' | 'file:'; - host: string; - path: string; - port: string; - } { - // TODO: validate the pattern - const urlObj = new Url(pattern); + // private static parse(pattern: string): { + // scheme: 'http:' | 'https:' | '*' | 'file:'; + // host: string; + // path: string; + // port: string; + // } { + // const urlObj = new Url(pattern); - if (!urlObj || urlObj.host.length === 0) { - throw Error(`match pattern (${pattern}) is invalid and failed to parse`); - } + // if (!urlObj || urlObj.host.length === 0) { + // throw Error(`match pattern (${pattern}) is invalid and failed to parse`); + // } - if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:' && urlObj.protocol !== '*' && urlObj.protocol !== 'file:') { - throw Error(`scheme (${urlObj.protocol}) is invalid and failed to parse`); - } + // if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:' && urlObj.protocol !== '*' && urlObj.protocol !== 'file:') { + // throw Error(`scheme (${urlObj.protocol}) is invalid and failed to parse`); + // } - return { - scheme: urlObj.protocol, - host: urlObj.getHost(), - path: urlObj.getPath() || '/', - port: urlObj.port || '', - }; - } + // return { + // scheme: urlObj.protocol, + // host: urlObj.getHost(), + // path: urlObj.getPath() || '/', + // port: urlObj.port || '', + // }; + // } static readonly MATCH_ALL_URLS: string = ''; static pattern: string | undefined = undefined; // TODO: its usage is unknown static readonly PROTOCOL_DELIMITER: string = '+'; // TODO: the url can not start with - - private readonly starRegPattern = '[a-zA-Z0-9\-]*'; + // private readonly starRegPattern = '[a-zA-Z0-9\-]*'; getProtocols(): string[] { - switch (this.scheme) { - case 'http:': - return ['http']; - case 'https:': - return ['https']; - case '*': - return ['http', 'https']; - case 'file:': - return ['file']; - default: - throw `invalid scheme ${this.scheme}`; - } + const protocolEndPos = this.pattern.indexOf(':'); + const protocolPattern = this.pattern.slice(0, protocolEndPos); + const protocols = protocolPattern.split(UrlMatchPattern.PROTOCOL_DELIMITER); + + return protocols.map(protocol => protocol.replace(':', '')); } test(urlStr: string) { - const urlObj = Url.parse(urlStr); - if (!urlObj) { - return false; - } - - return this.testProtocol(urlObj.protocol) - && this.testHost(urlObj.host.join('/')) - && this.testPort(urlObj.port || '', urlObj.protocol) - && urlObj?.path && this.testPath(urlObj.path.join('/')); + return this.matcher.match(urlStr); } - testHost(host: string) { - const hostRegPattern = new RegExp(this.host.replace('*', this.starRegPattern), 'ig'); - return hostRegPattern.test(host); - } + // testHost(hostStr: string) { + // const protocolEndPos = this.pattern.indexOf(':'); + // const pathBegPos = this.pattern.indexOf('/', 3); + // const hostOnlyPattern = '*' + this.pattern.slice(protocolEndPos + 3, pathBegPos) + '*'; + // const hostOnlyMatcher = matchPattern(hostOnlyPattern); - testPath(path: string) { - const pathRegPattern = new RegExp(this.path.replace('*', this.starRegPattern), 'ig'); - return pathRegPattern.test(path); - } + // return hostOnlyMatcher.match(hostStr); + // } - // TODO: it is confused to verify both port and protocol - // testPort verifies both port and protocol, but not the relationship between them - testPort(port: string, protocol: string) { - if (!this.testProtocol(protocol)) { - return false; - } + // testPath(pathStr: string) { + // const pathBegPos = this.pattern.indexOf('/', 3); + // const pathOnlyPattern = '*' + '*://*' + this.pattern.slice(pathBegPos); + // const pathOnlyMatcher = matchPattern(pathOnlyPattern); - const portRegPattern = new RegExp(this.port.replace('*', this.starRegPattern), 'ig'); - if (!portRegPattern.test(port)) { - return false; - } + // return pathOnlyMatcher.match(pathStr); + // } - return true; - } + // // TODO: it is confusing to verify both port and protocol + // testPort(port: string, protocol: string) { + // const protocolEndPos = this.pattern.indexOf(':'); + // const protocolPattern = this.pattern.slice(0, protocolEndPos); + // if (protocolPattern === '*') { + // return true; + // } + // const protocolMatcher = matchPattern(protocolPattern + '://*/*'); + // if (protocolMatcher.match(p)) - testProtocol(protocol: string) { - switch (protocol) { - case 'http:': - return this.scheme === 'http:' || this.scheme === '*'; - case 'https:': - return this.scheme === 'https:' || this.scheme === '*'; - case '*': - return this.scheme === 'http:' || this.scheme === 'https:' || this.scheme === '*'; - case 'file:': - return this.scheme === 'file:'; - default: - throw `invalid scheme ${protocol}`; - } - } + // const portBeg = this.pattern.indexOf(':', 2); + // const portEnd = this.pattern.indexOf('/', 3); + // const portPattern = this.pattern.slice(portBeg, portEnd); + // if (portPattern === '') { + + // } + + // const pathOnlyPattern = '*' + '*://*' + this.pattern.slice(pathBegPos); + // const pathOnlyMatcher = matchPattern(pathOnlyPattern); + + // // return pathOnlyMatcher.match(pathStr); + + // // if (!this.testProtocol(protocol)) { + // // return false; + // // } + + // // const portRegPattern = new RegExp(this.port.replace('*', this.starRegPattern), 'ig'); + // // if (!portRegPattern.test(port)) { + // // return false; + // // } + + // // return true; + // } + + // testProtocol(protocol: string) { + // switch (protocol) { + // case 'http:': + // return this.scheme === 'http:' || this.scheme === '*'; + // case 'https:': + // return this.scheme === 'https:' || this.scheme === '*'; + // case '*': + // return this.scheme === 'http:' || this.scheme === 'https:' || this.scheme === '*'; + // case 'file:': + // return this.scheme === 'file:'; + // default: + // throw `invalid scheme ${protocol}`; + // } + // } toString() { - return `${this.scheme}//${this.host}${this.path}`; + return this.pattern; } update(pattern: string) { - const patternObj = UrlMatchPattern.parseAndValidate(pattern); - this.scheme = patternObj.scheme; - this.host = patternObj.host.join('/'); - this.path = patternObj.path.join('/'); - this.port = patternObj.port; + this.pattern = pattern; + this.matcher = matchPattern(pattern); } }