349 lines
11 KiB
JavaScript
349 lines
11 KiB
JavaScript
var _ = require('../util').lodash,
|
|
Property = require('../collection/property').Property,
|
|
Url = require('../collection/url').Url,
|
|
|
|
STRING = 'string',
|
|
UNDEFINED = 'undefined',
|
|
|
|
MATCH_ALL = '*',
|
|
PREFIX_DELIMITER = '^',
|
|
PROTOCOL_DELIMITER = '+',
|
|
POSTFIX_DELIMITER = '$',
|
|
MATCH_ALL_URLS = '<all_urls>',
|
|
ALLOWED_PROTOCOLS = ['http', 'https', 'file', 'ftp'],
|
|
ALLOWED_PROTOCOLS_REGEX = ALLOWED_PROTOCOLS.join('|'),
|
|
|
|
// @todo initialize this and ALLOWED_PROTOCOLS via UrlMatchPattern options
|
|
DEFAULT_PROTOCOL_PORT = {
|
|
ftp: '21',
|
|
http: '80',
|
|
https: '443'
|
|
},
|
|
|
|
regexes = {
|
|
escapeMatcher: /[.+^${}()|[\]\\]/g,
|
|
escapeMatchReplacement: '\\$&',
|
|
questionmarkMatcher: /\?/g,
|
|
questionmarkReplacment: '.',
|
|
starMatcher: '*',
|
|
starReplacement: '.*',
|
|
// @todo match valid HOST name
|
|
// @note PATH is required(can be empty '/' or '/*') i.e, {PROTOCOL}://{HOST}/
|
|
patternSplit: '^((' + ALLOWED_PROTOCOLS_REGEX + '|\\*)(\\+(' + ALLOWED_PROTOCOLS_REGEX +
|
|
'))*)://(\\*|\\*\\.[^*/:]+|[^*/:]+)(:\\*|:\\d+)?(/.*)$'
|
|
},
|
|
|
|
UrlMatchPattern;
|
|
|
|
/**
|
|
* @typedef UrlMatchPattern.definition
|
|
* @property {String} pattern The url match pattern string
|
|
*/
|
|
_.inherit((
|
|
|
|
/**
|
|
* UrlMatchPattern allows to create rules to define Urls to match for.
|
|
* It is based on Google's Match Pattern - https://developer.chrome.com/extensions/match_patterns
|
|
*
|
|
* @constructor
|
|
* @extends {Property}
|
|
* @param {UrlMatchPattern.definition} options -
|
|
*
|
|
* @example <caption>An example UrlMatchPattern</caption>
|
|
* var matchPattern = new UrlMatchPattern('https://*.google.com/*');
|
|
*/
|
|
UrlMatchPattern = function UrlMatchPattern (options) {
|
|
// called as new UrlMatchPattern('https+http://*.example.com/*')
|
|
if (_.isString(options)) {
|
|
options = { pattern: options };
|
|
}
|
|
|
|
// this constructor is intended to inherit and as such the super constructor is required to be executed
|
|
UrlMatchPattern.super_.apply(this, arguments);
|
|
|
|
// Assign defaults before proceeding
|
|
_.assign(this, /** @lends UrlMatchPattern */ {
|
|
/**
|
|
* The url match pattern string
|
|
*
|
|
* @type {String}
|
|
*/
|
|
pattern: MATCH_ALL_URLS
|
|
});
|
|
|
|
this.update(options);
|
|
}), Property);
|
|
|
|
_.assign(UrlMatchPattern.prototype, /** @lends UrlMatchPattern.prototype */ {
|
|
/**
|
|
* Assigns the given properties to the UrlMatchPattern.
|
|
*
|
|
* @param {{ pattern: (string) }} options -
|
|
*/
|
|
update (options) {
|
|
_.has(options, 'pattern') && (_.isString(options.pattern) && !_.isEmpty(options.pattern)) &&
|
|
(this.pattern = options.pattern);
|
|
|
|
// create a match pattern and store it on cache
|
|
this._matchPatternObject = this.createMatchPattern();
|
|
},
|
|
|
|
/**
|
|
* Used to generate the match regex object from the match string we have.
|
|
*
|
|
* @private
|
|
* @returns {*} Match regex object
|
|
*/
|
|
createMatchPattern () {
|
|
var matchPattern = this.pattern,
|
|
// Check the match pattern of sanity and split it into protocol, host and path
|
|
match = matchPattern.match(regexes.patternSplit);
|
|
|
|
if (!match) {
|
|
// This ensures it is a invalid match pattern
|
|
return;
|
|
}
|
|
|
|
return {
|
|
protocols: _.uniq(match[1].split(PROTOCOL_DELIMITER)),
|
|
host: match[5],
|
|
port: match[6] && match[6].substr(1), // remove leading `:`
|
|
path: this.globPatternToRegexp(match[7])
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Converts a given glob pattern into a regular expression.
|
|
*
|
|
* @private
|
|
* @param {String} pattern Glob pattern string
|
|
* @returns {RegExp=}
|
|
*/
|
|
globPatternToRegexp (pattern) {
|
|
// Escape everything except ? and *.
|
|
pattern = pattern.replace(regexes.escapeMatcher, regexes.escapeMatchReplacement);
|
|
pattern = pattern.replace(regexes.questionmarkMatcher, regexes.questionmarkReplacment);
|
|
pattern = pattern.replace(regexes.starMatcher, regexes.starReplacement);
|
|
|
|
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
return new RegExp(PREFIX_DELIMITER + pattern + POSTFIX_DELIMITER);
|
|
},
|
|
|
|
/**
|
|
* Tests if the given protocol string, is allowed by the pattern.
|
|
*
|
|
* @param {String=} protocol The protocol to be checked if the pattern allows.
|
|
* @returns {Boolean=}
|
|
*/
|
|
testProtocol (protocol) {
|
|
var matchRegexObject = this._matchPatternObject;
|
|
|
|
return _.includes(ALLOWED_PROTOCOLS, protocol) &&
|
|
(_.includes(matchRegexObject.protocols, MATCH_ALL) || _.includes(matchRegexObject.protocols, protocol));
|
|
},
|
|
|
|
/**
|
|
* Returns the protocols supported
|
|
*
|
|
* @returns {Array.<String>}
|
|
*/
|
|
getProtocols () {
|
|
return _.get(this, '_matchPatternObject.protocols') || [];
|
|
},
|
|
|
|
/**
|
|
* Tests if the given host string, is allowed by the pattern.
|
|
*
|
|
* @param {String=} host The host to be checked if the pattern allows.
|
|
* @returns {Boolean=}
|
|
*/
|
|
testHost (host) {
|
|
/*
|
|
* For Host match, we are considering the port with the host, hence we are using getRemote() instead of getHost()
|
|
* We need to address three cases for the host urlStr
|
|
* 1. * It matches all the host + protocol, hence we are not having any parsing logic for it.
|
|
* 2. *.foo.bar.com Here the prefix could be anything but it should end with foo.bar.com
|
|
* 3. foo.bar.com This is the absolute matching needs to done.
|
|
*/
|
|
var matchRegexObject = this._matchPatternObject;
|
|
|
|
return (
|
|
this.matchAnyHost(matchRegexObject) ||
|
|
this.matchAbsoluteHostPattern(matchRegexObject, host) ||
|
|
this.matchSuffixHostPattern(matchRegexObject, host)
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Checks whether the matchRegexObject has the MATCH_ALL host.
|
|
*
|
|
* @private
|
|
* @param {Object=} matchRegexObject The regex object generated by the createMatchPattern function.
|
|
* @returns {Boolean}
|
|
*/
|
|
matchAnyHost (matchRegexObject) {
|
|
return matchRegexObject.host === MATCH_ALL;
|
|
},
|
|
|
|
|
|
/**
|
|
* Check for the (*.foo.bar.com) kind of matches with the remote provided.
|
|
*
|
|
* @private
|
|
* @param {Object=} matchRegexObject The regex object generated by the createMatchPattern function.
|
|
* @param {String=} remote The remote url (host+port) of the url for which the hostpattern needs to checked
|
|
* @returns {Boolean}
|
|
*/
|
|
matchSuffixHostPattern (matchRegexObject, remote) {
|
|
var hostSuffix = matchRegexObject.host.substr(2);
|
|
|
|
return matchRegexObject.host[0] === MATCH_ALL && (remote === hostSuffix || remote.endsWith('.' + hostSuffix));
|
|
},
|
|
|
|
/**
|
|
* Check for the absolute host match.
|
|
*
|
|
* @private
|
|
* @param {Object=} matchRegexObject The regex object generated by the createMatchPattern function.
|
|
* @param {String=} remote The remote url, host+port of the url for which the hostpattern needs to checked
|
|
* @returns {Boolean}
|
|
*/
|
|
matchAbsoluteHostPattern (matchRegexObject, remote) {
|
|
return matchRegexObject.host === remote;
|
|
},
|
|
|
|
/**
|
|
* Tests if the current pattern allows the given port.
|
|
*
|
|
* @param {String} port The port to be checked if the pattern allows.
|
|
* @param {String} protocol Protocol to refer default port.
|
|
* @returns {Boolean}
|
|
*/
|
|
testPort (port, protocol) {
|
|
var portRegex = this._matchPatternObject.port,
|
|
|
|
// default port for given protocol
|
|
defaultPort = protocol && DEFAULT_PROTOCOL_PORT[protocol];
|
|
|
|
// return true if both given port and match pattern are absent
|
|
if (typeof port === UNDEFINED && typeof portRegex === UNDEFINED) {
|
|
return true;
|
|
}
|
|
|
|
// convert integer port to string
|
|
(port && typeof port !== STRING) && (port = String(port));
|
|
|
|
// assign default port or portRegex
|
|
!port && (port = defaultPort);
|
|
!portRegex && (portRegex = defaultPort);
|
|
|
|
// matches * or specific port
|
|
return (
|
|
portRegex === MATCH_ALL ||
|
|
portRegex === port
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Tests if the current pattern allows the given path.
|
|
*
|
|
* @param {String=} path The path to be checked if the pattern allows.
|
|
* @returns {Boolean=}
|
|
*/
|
|
testPath (path) {
|
|
var matchRegexObject = this._matchPatternObject;
|
|
|
|
return !_.isEmpty(path.match(matchRegexObject.path));
|
|
},
|
|
|
|
/**
|
|
* Tests the url string with the match pattern provided.
|
|
* Follows the https://developer.chrome.com/extensions/match_patterns pattern for pattern validation and matching
|
|
*
|
|
* @param {String=} urlStr The url string for which the proxy match needs to be done.
|
|
* @returns {Boolean=}
|
|
*/
|
|
test (urlStr) {
|
|
/*
|
|
* This function executes the code in the following sequence for early return avoiding the costly regex matches.
|
|
* To avoid most of the memory consuming code.
|
|
* 1. It check whether the match string is <all_urls> in that case, it return immediately without any further
|
|
* processing.
|
|
* 2. Checks whether the matchPattern follows the rules, https://developer.chrome.com/extensions/match_patterns,
|
|
* If not then, don't process it.
|
|
* 3. Check for the protocol, as it is a normal array check.
|
|
* 4. Checks the host, as it doesn't involve regex match and has only string comparisons.
|
|
* 5. Finally, checks for the path, which actually involves the Regex matching, the slow process.
|
|
*/
|
|
// If the matchPattern is <all_urls> then there is no need for any validations.
|
|
if (this.pattern === MATCH_ALL_URLS) {
|
|
return true;
|
|
}
|
|
|
|
// Empty _matchPatternObject represents the match is INVALID match
|
|
if (_.isEmpty(this._matchPatternObject)) {
|
|
return false;
|
|
}
|
|
|
|
const url = new Url(urlStr);
|
|
|
|
return (this.testProtocol(url.protocol) &&
|
|
this.testHost(url.getHost()) &&
|
|
this.testPort(url.port, url.protocol) &&
|
|
this.testPath(url.getPath()));
|
|
},
|
|
|
|
/**
|
|
* Returns a string representation of the match pattern
|
|
*
|
|
* @returns {String} pattern
|
|
*/
|
|
toString () {
|
|
return String(this.pattern);
|
|
},
|
|
|
|
/**
|
|
* Returns the JSON representation.
|
|
*
|
|
* @returns {{ pattern: (String) }}
|
|
*/
|
|
toJSON () {
|
|
var pattern;
|
|
|
|
pattern = this.toString();
|
|
|
|
return { pattern };
|
|
}
|
|
});
|
|
|
|
_.assign(UrlMatchPattern, /** @lends UrlMatchPattern */ {
|
|
/**
|
|
* Defines the name of this property for internal use
|
|
*
|
|
* @private
|
|
* @readOnly
|
|
* @type {String}
|
|
*/
|
|
_postman_propertyName: 'UrlMatchPattern',
|
|
|
|
/**
|
|
* Multiple protocols in the match pattern should be separated by this string
|
|
*
|
|
* @readOnly
|
|
* @type {String}
|
|
*/
|
|
PROTOCOL_DELIMITER: PROTOCOL_DELIMITER,
|
|
|
|
/**
|
|
* String representation for matching all urls - <all_urls>
|
|
*
|
|
* @readOnly
|
|
* @type {String}
|
|
*/
|
|
MATCH_ALL_URLS: MATCH_ALL_URLS
|
|
});
|
|
|
|
module.exports = {
|
|
UrlMatchPattern
|
|
};
|