mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-21 22:57:59 -04:00
feat: enable Url in pre-request scripting [INS-3379] (#7123)
* chore: enable Headers in pre-request scripting * feat: add Variables and VariableList to the collection * feat(hidden-window): enable url and UrlMatchPattern in pre-request scripting --------- Co-authored-by: Jack Kavanagh <jackkav@gmail.com>
This commit is contained in:
@@ -72,10 +72,16 @@ test.describe('pre-request UI tests', async () => {
|
||||
{
|
||||
name: 'require / require classes from insomnia-collection module',
|
||||
preReqScript: `
|
||||
const { Property, Header, Variable } = require('insomnia-collection');
|
||||
const { Property, Header, Variable, QueryParam, Url } = require('insomnia-collection');
|
||||
const prop = new Property('pid', 'pname');
|
||||
const header = new Header({ key: 'headerKey', value: 'headerValue' });
|
||||
const variable = new Variable({ key: 'headerKey', value: 'headerValue' });
|
||||
const qParam = new QueryParam({ key: 'queryKey', value: 'queryValue' });
|
||||
const url = new Url({
|
||||
host: ['insomnia', 'rest'],
|
||||
path: ['path1', 'path2'],
|
||||
protocol: 'https',
|
||||
});
|
||||
// set part of values
|
||||
insomnia.environment.set('propJson', JSON.stringify(prop.toJSON()));
|
||||
insomnia.environment.set('headerJson', JSON.stringify(header.toJSON()));
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { Header } from '../headers';
|
||||
// import { QueryParam, setUrlParser, Url, UrlMatchPattern } from '../urls';
|
||||
|
||||
describe('test Header object', () => {
|
||||
it('test basic operations', () => {
|
||||
// const header = new Header('Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n');
|
||||
const headerStr = 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n';
|
||||
const headerObjs = [
|
||||
{ key: 'Content-Type', value: 'application/json' },
|
||||
|
||||
171
packages/insomnia/src/sdk/objects/__tests__/urls.test.ts
Normal file
171
packages/insomnia/src/sdk/objects/__tests__/urls.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import url from 'node:url';
|
||||
|
||||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { QueryParam, setUrlParser, Url, UrlMatchPattern } from '../urls';
|
||||
import { Variable } from '../variables';
|
||||
|
||||
describe('test Url object', () => {
|
||||
setUrlParser(url.URL);
|
||||
|
||||
it('test QueryParam', () => {
|
||||
const queryParam = new QueryParam({
|
||||
key: 'uname',
|
||||
value: 'patrick star',
|
||||
});
|
||||
|
||||
expect(queryParam.toString()).toEqual('uname=patrick+star');
|
||||
|
||||
queryParam.update('uname=peter+parker');
|
||||
expect(queryParam.toString()).toEqual('uname=peter+parker');
|
||||
|
||||
expect(
|
||||
QueryParam.unparseSingle({ key: 'uname', value: 'patrick star' })
|
||||
).toEqual('uname=patrick+star');
|
||||
|
||||
expect(
|
||||
QueryParam.unparse({ uname: 'patrick star', password: '123' })
|
||||
).toEqual('uname=patrick+star&password=123');
|
||||
|
||||
expect(
|
||||
QueryParam.parseSingle('uname=patrick+star')
|
||||
).toEqual({ key: 'uname', value: 'patrick star' });
|
||||
|
||||
expect(
|
||||
QueryParam.parse('uname=patrick+star&password=123')
|
||||
).toEqual([{ 'key': 'uname', 'value': 'patrick star' }, { 'key': 'password', 'value': '123' }]);
|
||||
});
|
||||
|
||||
it('test Url methods', () => {
|
||||
const url = new Url({
|
||||
auth: {
|
||||
username: 'usernameValue',
|
||||
password: 'passwordValue',
|
||||
},
|
||||
hash: 'hashValue',
|
||||
host: ['hostValue', 'com'],
|
||||
path: ['pathLevel1', 'pathLevel2'],
|
||||
port: '777',
|
||||
protocol: 'https:',
|
||||
query: [
|
||||
new QueryParam({ key: 'key1', value: 'value1' }),
|
||||
new QueryParam({ key: 'key2', value: 'value2' }),
|
||||
],
|
||||
variables: [
|
||||
new Variable({ key: 'varKey', value: 'varValue' }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(url.getHost()).toEqual('hostvalue.com');
|
||||
expect(url.getPath()).toEqual('/pathLevel1/pathLevel2');
|
||||
expect(url.getQueryString()).toEqual('key1=value1&key2=value2');
|
||||
expect(url.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2');
|
||||
expect(url.getRemote(true)).toEqual('hostvalue.com:777');
|
||||
expect(url.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases
|
||||
|
||||
url.removeQueryParams([
|
||||
new QueryParam({ key: 'key1', value: 'value1' }),
|
||||
]);
|
||||
expect(url.getQueryString()).toEqual('key2=value2');
|
||||
expect(url.toString()).toEqual('https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue');
|
||||
});
|
||||
|
||||
it('test Url static methods', () => {
|
||||
// static methods
|
||||
const urlStr = 'https://myhost.com/path1/path2';
|
||||
const urlOptions = Url.parse(urlStr);
|
||||
const urlObj = new Url(urlOptions || '');
|
||||
|
||||
expect(urlObj.toString()).toEqual(urlStr);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test Url Match Pattern', () => {
|
||||
it('test UrlMatchPattern', () => {
|
||||
const pattern = 'http+https+custom://*.insomnia.com:80/p1/*';
|
||||
const matchPattern = new UrlMatchPattern(pattern);
|
||||
|
||||
expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']);
|
||||
expect(matchPattern.testProtocol('http')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('https')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('custom')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('unmatched')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy();
|
||||
expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy();
|
||||
expect(matchPattern.testHost('insomnia.com')).toBeFalsy();
|
||||
expect(matchPattern.testHost('com')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testPath('/p1/abc')).toBeTruthy();
|
||||
expect(matchPattern.testPath('/p1/')).toBeTruthy();
|
||||
expect(matchPattern.testPath('/p1')).toBeFalsy();
|
||||
expect(matchPattern.testPath('/')).toBeFalsy();
|
||||
expect(matchPattern.testPath('')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testPort('80', 'https')).toBeTruthy();
|
||||
expect(matchPattern.testPort('443', 'https')).toBeFalsy();
|
||||
expect(matchPattern.testPort('80', 'http')).toBeTruthy();
|
||||
expect(matchPattern.testPort('80', 'unmatched')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('test UrlMatchPattern with no protocol', () => {
|
||||
const pattern = '*.insomnia.com/p1/*';
|
||||
try {
|
||||
const matchPattern = new UrlMatchPattern(pattern);
|
||||
matchPattern.testProtocol('http');
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('UrlMatchPattern: protocol is not specified');
|
||||
}
|
||||
});
|
||||
|
||||
it('test UrlMatchPattern with no port', () => {
|
||||
const pattern = 'http+https+custom://*.insomnia.com/p1/*';
|
||||
const matchPattern = new UrlMatchPattern(pattern);
|
||||
|
||||
expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']);
|
||||
expect(matchPattern.testProtocol('http')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('https')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('custom')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('unmatched')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy();
|
||||
expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy();
|
||||
expect(matchPattern.testHost('insomnia.com')).toBeFalsy();
|
||||
expect(matchPattern.testHost('com')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testPath('/p1/abc')).toBeTruthy();
|
||||
expect(matchPattern.testPath('/p1/')).toBeTruthy();
|
||||
expect(matchPattern.testPath('/p1')).toBeFalsy();
|
||||
expect(matchPattern.testPath('/')).toBeFalsy();
|
||||
expect(matchPattern.testPath('')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testPort('443', 'https')).toBeTruthy();
|
||||
expect(matchPattern.testPort('80', 'http')).toBeTruthy();
|
||||
expect(matchPattern.testPort('443', 'http')).toBeFalsy();
|
||||
expect(matchPattern.testPort('80', 'https')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('test UrlMatchPattern with no path', () => {
|
||||
const pattern = 'http+https+custom://*.insomnia.com';
|
||||
const matchPattern = new UrlMatchPattern(pattern);
|
||||
|
||||
expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']);
|
||||
expect(matchPattern.testProtocol('http')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('https')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('custom')).toBeTruthy();
|
||||
expect(matchPattern.testProtocol('unmatched')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy();
|
||||
expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy();
|
||||
expect(matchPattern.testHost('insomnia.com')).toBeFalsy();
|
||||
expect(matchPattern.testHost('com')).toBeFalsy();
|
||||
|
||||
expect(matchPattern.testPath('')).toBeTruthy();
|
||||
expect(matchPattern.testPath('/')).toBeFalsy(); // it is not handled temporarily
|
||||
|
||||
expect(matchPattern.testPort('443', 'https')).toBeTruthy();
|
||||
expect(matchPattern.testPort('80', 'http')).toBeTruthy();
|
||||
expect(matchPattern.testPort('443', 'http')).toBeFalsy();
|
||||
expect(matchPattern.testPort('80', 'https')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
export { PropertyBase, Property, PropertyList } from './properties';
|
||||
export { Header, HeaderList } from './headers';
|
||||
export { Variable, VariableList } from './variables';
|
||||
|
||||
export { QueryParam, Url, UrlMatchPattern, UrlMatchPatternList } from './urls';
|
||||
|
||||
555
packages/insomnia/src/sdk/objects/urls.ts
Normal file
555
packages/insomnia/src/sdk/objects/urls.ts
Normal file
@@ -0,0 +1,555 @@
|
||||
import { Property, PropertyBase, PropertyList } from './properties';
|
||||
import { Variable, VariableList } from './variables';
|
||||
|
||||
// TODO: make it also work with node.js
|
||||
let UrlParser = URL;
|
||||
let UrlSearchParams = URLSearchParams;
|
||||
export function setUrlParser(provider: any) {
|
||||
UrlParser = provider;
|
||||
}
|
||||
export function setUrlSearchParams(provider: any) {
|
||||
UrlSearchParams = provider;
|
||||
}
|
||||
|
||||
export interface QueryParamOptions {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class QueryParam extends Property {
|
||||
_kind: string = 'QueryParam';
|
||||
|
||||
key: string;
|
||||
value: string;
|
||||
|
||||
constructor(options: { key: string; value: string } | string) {
|
||||
super();
|
||||
|
||||
if (typeof options === 'string') {
|
||||
try {
|
||||
const optionsObj = JSON.parse(options);
|
||||
this.key = optionsObj.key;
|
||||
this.value = optionsObj.value;
|
||||
} catch (e) {
|
||||
throw Error(`invalid QueryParam options ${e}`);
|
||||
}
|
||||
} else if (typeof options === 'object' && ('key' in options) && ('value' in options)) {
|
||||
this.key = options.key;
|
||||
this.value = options.value;
|
||||
} else {
|
||||
throw Error('unknown options for new QueryParam');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// (static) _postman_propertyAllowsMultipleValues :Boolean
|
||||
// (static) _postman_propertyIndexKey :String
|
||||
|
||||
static parse(queryStr: string) {
|
||||
const params = new UrlSearchParams(queryStr);
|
||||
return Array.from(params.entries())
|
||||
.map(entry => ({ key: entry[0], value: entry[1] }));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static parseSingle(paramStr: string, _idx?: number, _all?: string[]) {
|
||||
const pairs = QueryParam.parse(paramStr);
|
||||
if (pairs.length === 0) {
|
||||
throw Error('invalid search query string');
|
||||
}
|
||||
|
||||
return pairs[0];
|
||||
}
|
||||
|
||||
static unparse(params: QueryParamOptions[] | Record<string, string>) {
|
||||
const searchParams = new UrlSearchParams();
|
||||
|
||||
if (Array.isArray(params)) {
|
||||
params.forEach((entry: QueryParamOptions) => searchParams.append(entry.key, entry.value));
|
||||
} else {
|
||||
Object.entries(params)
|
||||
.forEach(entry => searchParams.append(entry[0], entry[1]));
|
||||
}
|
||||
|
||||
return searchParams.toString();
|
||||
}
|
||||
|
||||
static unparseSingle(obj: { key: string; value: string }) {
|
||||
if ('key' in obj && 'value' in obj) {
|
||||
const params = new UrlSearchParams();
|
||||
params.append(obj.key, obj.value);
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
toString() {
|
||||
const params = new UrlSearchParams();
|
||||
params.append(this.key, this.value);
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
update(param: string | { key: string; value: string }) {
|
||||
if (typeof param === 'string') {
|
||||
const paramObj = QueryParam.parseSingle(param);
|
||||
this.key = typeof paramObj.key === 'string' ? paramObj.key : '';
|
||||
this.value = typeof paramObj.value === 'string' ? paramObj.value : '';
|
||||
} else if ('key' in param && 'value' in param) {
|
||||
this.key = param.key;
|
||||
this.value = param.value;
|
||||
} else {
|
||||
throw Error('the param for update must be: string | { key: string; value: string }');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface UrlOptions {
|
||||
auth?: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
hash?: string;
|
||||
host: string[];
|
||||
path?: string[];
|
||||
port?: string;
|
||||
protocol: string;
|
||||
query: { key: string; value: string }[];
|
||||
variables: { key: string; value: string }[];
|
||||
}
|
||||
|
||||
export class Url extends PropertyBase {
|
||||
_kind: string = 'Url';
|
||||
|
||||
// TODO: should be related to RequestAuth
|
||||
// but the implementation seems only supports username + password
|
||||
auth?: { username: string; password: string };
|
||||
hash?: string;
|
||||
host: string[] = [];
|
||||
path?: string[] = [];
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
query: PropertyList<QueryParam> = new PropertyList<QueryParam>(QueryParam, undefined, []);
|
||||
variables: VariableList<Variable> = new VariableList<Variable>(undefined, []);
|
||||
|
||||
constructor(
|
||||
def: UrlOptions | string
|
||||
) {
|
||||
super();
|
||||
this.setFields(def);
|
||||
}
|
||||
|
||||
private setFields(def: UrlOptions | string) {
|
||||
const urlObj = typeof def === 'string' ? Url.parse(def) : def;
|
||||
|
||||
if (urlObj) {
|
||||
this.auth = urlObj.auth;
|
||||
this.hash = urlObj.hash;
|
||||
this.host = urlObj.host;
|
||||
this.path = urlObj.path;
|
||||
this.port = urlObj.port;
|
||||
this.protocol = urlObj.protocol;
|
||||
|
||||
const queryList = urlObj.query ?
|
||||
urlObj.query.map(kvObj => new QueryParam(kvObj), {}) :
|
||||
[];
|
||||
this.query = new PropertyList<QueryParam>(
|
||||
QueryParam,
|
||||
undefined,
|
||||
queryList
|
||||
);
|
||||
|
||||
// TODO: variable is always empty in this way
|
||||
const varList = urlObj.variables ?
|
||||
urlObj.variables
|
||||
.map(
|
||||
(kvObj: { key: string; value: string }) => new Variable(kvObj),
|
||||
{},
|
||||
) :
|
||||
[];
|
||||
|
||||
this.variables = new VariableList(undefined, varList);
|
||||
|
||||
} else {
|
||||
throw Error(`url is invalid: ${def}`); // TODO:
|
||||
}
|
||||
}
|
||||
|
||||
static isUrl(obj: object) {
|
||||
return '_kind' in obj && obj._kind === 'Url';
|
||||
}
|
||||
|
||||
static parse(urlStr: string): UrlOptions | undefined {
|
||||
// TODO: enable validation
|
||||
// if (!UrlParser.canParse(urlStr)) {
|
||||
// console.error(`invalid URL string ${urlStr}`);
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
const url = new UrlParser(urlStr);
|
||||
const query = Array.from(url.searchParams.entries())
|
||||
.map(kv => {
|
||||
const kvArray = kv as [string, string];
|
||||
return { key: kvArray[0], value: kvArray[1] };
|
||||
});
|
||||
|
||||
return {
|
||||
auth: url.username !== '' ? { // TODO: make it compatible with RequestAuth
|
||||
username: url.username,
|
||||
password: url.password,
|
||||
} : undefined,
|
||||
hash: url.hash,
|
||||
host: url.hostname.split('/'),
|
||||
path: url.pathname.split('/'),
|
||||
port: url.port,
|
||||
protocol: url.protocol, // e.g. https:
|
||||
query,
|
||||
variables: [],
|
||||
};
|
||||
}
|
||||
|
||||
addQueryParams(params: { key: string; value: string }[] | string) {
|
||||
let queryParams: { key: string; value: string }[];
|
||||
|
||||
if (typeof params === 'string') {
|
||||
queryParams = QueryParam.parse(params);
|
||||
} else {
|
||||
queryParams = params;
|
||||
}
|
||||
|
||||
queryParams.forEach((param: { key: string; value: string }) => {
|
||||
this.query.append(new QueryParam({ key: param.key, value: param.value }));
|
||||
});
|
||||
}
|
||||
|
||||
getHost() {
|
||||
return this.host.join('.').toLowerCase();
|
||||
}
|
||||
|
||||
getPath(unresolved?: boolean) {
|
||||
const pathStr = this.path ? this.path.join('/') : '/';
|
||||
const finalPath = pathStr.startsWith('/') ? pathStr : '/' + pathStr;
|
||||
|
||||
if (unresolved) {
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
// TODO: should it support rendering variables here?
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
getPathWithQuery() {
|
||||
return `${this.getPath(true)}?${this.getQueryString()}`;
|
||||
}
|
||||
|
||||
getQueryString() {
|
||||
const params = new UrlSearchParams();
|
||||
this.query.each(param => params.append(param.key, param.value), {});
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
getRemote(forcePort?: boolean) {
|
||||
const host = this.getHost();
|
||||
|
||||
if (forcePort) {
|
||||
// TODO: it does not support GQL, gRPC and so on
|
||||
const port = this.port ? this.port :
|
||||
this.protocol && (this.protocol === 'https:') ? 443 : 80;
|
||||
return `${host}:${port}`;
|
||||
}
|
||||
|
||||
// TODO: it does not support GQL, gRPC and so on
|
||||
const portWithColon = this.port ? `:${this.port}` : '';
|
||||
return `${host}${portWithColon}`;
|
||||
}
|
||||
|
||||
removeQueryParams(params: QueryParam[] | string[] | string) {
|
||||
if (typeof params === 'string') {
|
||||
// it is a string
|
||||
this.query = new PropertyList(
|
||||
QueryParam,
|
||||
undefined,
|
||||
this.query.filter(queryParam => queryParam.key === params, {})
|
||||
);
|
||||
} else if (params.length > 0) {
|
||||
let toBeRemoved: Set<string>;
|
||||
|
||||
if (typeof params[0] === 'string') {
|
||||
// it is a string[]
|
||||
toBeRemoved = new Set(params as string[]);
|
||||
} else {
|
||||
// it is a QueryParam[]
|
||||
toBeRemoved = new Set(
|
||||
(params as QueryParam[])
|
||||
.map(param => param.key)
|
||||
);
|
||||
}
|
||||
|
||||
this.query = new PropertyList(
|
||||
QueryParam,
|
||||
undefined,
|
||||
this.query.filter(queryParam => !toBeRemoved.has(queryParam.key), {})
|
||||
);
|
||||
} else {
|
||||
console.error('failed to remove query params: unknown params type, only supports QueryParam[], string[] or string');
|
||||
}
|
||||
}
|
||||
|
||||
toString(forceProtocol?: boolean) {
|
||||
const protocol = forceProtocol ?
|
||||
(this.protocol ? this.protocol : 'https:') :
|
||||
(this.protocol ? this.protocol : '');
|
||||
|
||||
const parser = new UrlParser(`${protocol}//` + this.getHost());
|
||||
parser.username = this.auth?.username || '';
|
||||
parser.password = this.auth?.password || '';
|
||||
parser.port = this.port || '';
|
||||
parser.pathname = this.getPath();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
parser.search = this.getQueryString();
|
||||
parser.hash = this.hash || '';
|
||||
|
||||
return parser.toString();
|
||||
}
|
||||
|
||||
update(url: UrlOptions | string) {
|
||||
this.setFields(url);
|
||||
}
|
||||
}
|
||||
|
||||
// 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:';
|
||||
|
||||
// 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 (/).
|
||||
|
||||
// path
|
||||
// Must contain at least a forward slash
|
||||
// The slash by itself matches any path.
|
||||
|
||||
// Special cases: https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns#special
|
||||
// "<all_urls>"
|
||||
// "file:///"
|
||||
// "http://localhost/*"
|
||||
// It doesn't support match patterns for top Level domains (TLD).
|
||||
|
||||
private pattern: string;
|
||||
|
||||
constructor(pattern: string) {
|
||||
super();
|
||||
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
static readonly MATCH_ALL_URLS: string = '<all_urls>';
|
||||
static pattern: string | undefined = undefined; // TODO: its usage is unknown
|
||||
static readonly PROTOCOL_DELIMITER: string = '+';
|
||||
|
||||
// TODO: the url can not start with -
|
||||
|
||||
getProtocols(): string[] {
|
||||
const protocolEndPos = this.pattern.indexOf('://');
|
||||
if (protocolEndPos < 0) {
|
||||
throw Error('UrlMatchPattern: protocol is not specified');
|
||||
}
|
||||
|
||||
const protocolPattern = this.pattern.slice(0, protocolEndPos);
|
||||
const protocols = protocolPattern.split(UrlMatchPattern.PROTOCOL_DELIMITER);
|
||||
|
||||
return protocols.map(protocol => protocol.replace(':', ''));
|
||||
}
|
||||
|
||||
test(urlStr: string) {
|
||||
const protoEndPos = urlStr.indexOf(':');
|
||||
const protoStr = urlStr.slice(0, protoEndPos);
|
||||
const hostStr = this.getHost(urlStr);
|
||||
const pathStr = this.getPath(this.pattern);
|
||||
const portStr = this.getPort(urlStr);
|
||||
|
||||
return this.testProtocol(protoStr) &&
|
||||
this.testHost(hostStr) &&
|
||||
this.testPath(pathStr) &&
|
||||
this.testPort(portStr, protoStr);
|
||||
}
|
||||
|
||||
private getHost(urlStr: string) {
|
||||
const protocolEndPos = urlStr.indexOf('://') + 3;
|
||||
const hostBegPos = protocolEndPos;
|
||||
|
||||
const portBegPos = urlStr.indexOf(':', protocolEndPos);
|
||||
const pathBegPos = urlStr.indexOf('/', protocolEndPos);
|
||||
const queryBegPos = urlStr.indexOf('?', protocolEndPos);
|
||||
const hashBegPos = urlStr.indexOf('?', protocolEndPos);
|
||||
|
||||
let hostEndPos = urlStr.length;
|
||||
if (portBegPos >= 0) {
|
||||
hostEndPos = portBegPos;
|
||||
} else if (pathBegPos >= 0) {
|
||||
hostEndPos = pathBegPos;
|
||||
} else if (queryBegPos >= 0) {
|
||||
hostEndPos = queryBegPos;
|
||||
} else if (hashBegPos >= 0) {
|
||||
hostEndPos = hashBegPos;
|
||||
}
|
||||
|
||||
return urlStr.slice(hostBegPos, hostEndPos);
|
||||
}
|
||||
|
||||
testHost(hostStr: string) {
|
||||
const patternSegments = this.getHost(this.pattern).split('.');
|
||||
console.log(patternSegments);
|
||||
|
||||
const inputHostSegments = hostStr.split('.');
|
||||
|
||||
if (patternSegments.length !== inputHostSegments.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < patternSegments.length; i++) {
|
||||
if (patternSegments[i] === '*') {
|
||||
continue;
|
||||
} else if (patternSegments[i] !== inputHostSegments[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private getPath(urlStr: string) {
|
||||
const protocolEndPos = urlStr.indexOf('://') + 3;
|
||||
const hostBegPos = protocolEndPos;
|
||||
const pathBegPos = urlStr.indexOf('/', hostBegPos);
|
||||
if (pathBegPos < 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const queryBegPos = urlStr.indexOf('?');
|
||||
const hashBegPos = urlStr.indexOf('#');
|
||||
let pathEndPos = urlStr.length;
|
||||
if (queryBegPos >= 0) {
|
||||
pathEndPos = queryBegPos;
|
||||
} else if (hashBegPos >= 0) {
|
||||
pathEndPos = hashBegPos;
|
||||
}
|
||||
|
||||
return urlStr.slice(pathBegPos, pathEndPos);
|
||||
}
|
||||
|
||||
testPath(pathStr: string) {
|
||||
const patternSegments = this.getPath(this.pattern).split('/');
|
||||
const inputSegments = pathStr.split('/');
|
||||
|
||||
if (patternSegments.length !== inputSegments.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < patternSegments.length; i++) {
|
||||
if (patternSegments[i] === '*') {
|
||||
continue;
|
||||
} else if (patternSegments[i] !== inputSegments[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private getPort(urlStr: string) {
|
||||
const protocolEndPos = urlStr.indexOf('/') + 2;
|
||||
const hostBegPos = protocolEndPos;
|
||||
|
||||
const portBegPos = this.pattern.indexOf(':', protocolEndPos) + 1;
|
||||
if (portBegPos <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let portEndPos = this.pattern.length;
|
||||
const pathBegPos = urlStr.indexOf('/', hostBegPos);
|
||||
const queryBegPos = urlStr.indexOf('?');
|
||||
const hashBegPos = urlStr.indexOf('#');
|
||||
|
||||
if (pathBegPos >= 0) {
|
||||
portEndPos = pathBegPos;
|
||||
} else if (queryBegPos >= 0) {
|
||||
portEndPos = queryBegPos;
|
||||
} else if (hashBegPos >= 0) {
|
||||
portEndPos = hashBegPos;
|
||||
}
|
||||
|
||||
return urlStr.slice(portBegPos, portEndPos);
|
||||
}
|
||||
|
||||
testPort(port: string, protocol: string) {
|
||||
if (!this.testProtocol(protocol)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const portPattern = this.getPort(this.pattern);
|
||||
if (portPattern === '*') {
|
||||
return true;
|
||||
} else if (portPattern === '') {
|
||||
const protos = this.getProtocols();
|
||||
|
||||
if (protos.includes('https') && port === '443' && protocol === 'https') {
|
||||
return true;
|
||||
} else if (protos.includes('http') && port === '80' && protocol === 'http') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return portPattern === port;
|
||||
}
|
||||
|
||||
testProtocol(protocol: string) {
|
||||
const protoPatterns = this.getProtocols();
|
||||
|
||||
for (let i = 0; i < protoPatterns.length; i++) {
|
||||
if (protoPatterns[i] === '*') {
|
||||
return true;
|
||||
} else if (protoPatterns[i] === protocol) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
update(pattern: string) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
export class UrlMatchPatternList<T extends UrlMatchPattern> extends PropertyList<T> {
|
||||
_kind: string = 'UrlMatchPatternList';
|
||||
|
||||
constructor(parent: PropertyList<T> | undefined, populate: T[]) {
|
||||
super(UrlMatchPattern, undefined, populate);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
static isUrlMatchPatternList(obj: any) {
|
||||
return '_kind' in obj && obj._kind === 'UrlMatchPatternList';
|
||||
}
|
||||
|
||||
// TODO: unsupported yet
|
||||
// toObject(excludeDisabledopt, nullable, caseSensitiveopt, nullable, multiValueopt, nullable, sanitizeKeysopt) → {Object}
|
||||
|
||||
test(urlStr: string) {
|
||||
return this
|
||||
.filter(matchPattern => matchPattern.test(urlStr), {})
|
||||
.length > 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user