var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
Url = require('./url').Url,
UrlMatchPattern = require('../url-pattern/url-match-pattern').UrlMatchPattern,
UrlMatchPatternList = require('../url-pattern/url-match-pattern-list').UrlMatchPatternList,
ProxyConfig,
PROTOCOL_DELIMITER = UrlMatchPattern.PROTOCOL_DELIMITER,
E = '',
COLON = ':',
DEFAULT_PORT = 8080,
PROTOCOL_HOST_SEPARATOR = '://',
MATCH_ALL_HOST_AND_PATH = '*:*/*',
AUTH_CREDENTIALS_SEPARATOR = '@',
DEFAULT_PROTOCOL = 'http',
ALLOWED_PROTOCOLS = ['http', 'https'],
// 'http+https://*:*/*'
DEFAULT_PATTERN = ALLOWED_PROTOCOLS.join(PROTOCOL_DELIMITER) + PROTOCOL_HOST_SEPARATOR + MATCH_ALL_HOST_AND_PATH;
/**
* The following is the object structure accepted as constructor parameter while calling `new ProxyConfig(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Proxy instance.
*
* @typedef ProxyConfig.definition
*
* @property {String=} [match = 'http+https://*\/*'] The match for which the proxy needs to be configured.
* @property {String=} [host = ''] The proxy server url.
* @property {Number=} [port = 8080] The proxy server port number.
* @property {Boolean=} [tunnel = false] The tunneling option for the proxy request.
* @property {Boolean=} [disabled = false] To override the proxy for the particular url, you need to provide true.
* @property {Boolean=} [authenticate = false] To enable authentication for the proxy, you need to provide true.
* @property {String=} [username] The proxy authentication username
* @property {String=} [password] The proxy authentication password
*
* @example <caption>JSON definition of an example proxy object</caption>
* {
* "match": "http+https://example.com/*",
* "host": "proxy.com",
* "port": "8080",
* "tunnel": true,
* "disabled": false,
* "authenticate": true,
* "username": "proxy_username",
* "password": "proxy_password"
* }
*/
_.inherit((
/**
* A ProxyConfig definition that represents the proxy configuration for an url match.
* Properties can then use the `.toObjectResolved` function to procure an object representation of the property with
* all the variable references replaced by corresponding values.
*
* @constructor
* @extends {Property}
* @param {ProxyConfig.definition=} [options] - Specifies object with props matches, server and tunnel.
*
* @example <caption>Create a new ProxyConfig</caption>
* var ProxyConfig = require('postman-collection').ProxyConfig,
* myProxyConfig = new ProxyConfig({
* host: 'proxy.com',
* match: 'http+https://example.com/*',
* port: 8080,
* tunnel: true,
* disabled: false,
* authenticate: true,
* username: 'proxy_username',
* password: 'proxy_password'
* });
*/
ProxyConfig = function ProxyConfig (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ProxyConfig.super_.call(this, options);
// Assign defaults before proceeding
_.assign(this, /** @lends ProxyConfig */ {
/**
* The proxy server host or ip
*
* @type {String}
*/
host: E,
/**
* The url mach for which the proxy has been associated with.
*
* @type {String}
*/
match: new UrlMatchPattern(DEFAULT_PATTERN),
/**
* The proxy server port number
*
* @type {Number}
*/
port: DEFAULT_PORT,
/**
* This represents whether the tunneling needs to done while proxying this request.
*
* @type Boolean
*/
tunnel: false,
/**
* Proxy bypass list
*
* @type {UrlMatchPatternList}
*/
bypass: undefined,
/**
* Enable proxy authentication
*
* @type {Boolean}
*/
authenticate: false,
/**
* Proxy auth username
*
* @type {String}
*/
username: undefined,
/**
* Proxy auth password
*
* @type {String}
*/
password: undefined
});
this.update(options);
}), Property);
_.assign(ProxyConfig.prototype, /** @lends ProxyConfig.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Updates the properties of the proxy object based on the options provided.
*
* @param {ProxyConfig.definition} options The proxy object structure.
*/
update: function (options) {
if (!_.isObject(options)) {
return;
}
var parsedUrl,
port = _.get(options, 'port') >> 0;
if (_.isString(options.host)) {
// strip the protocol from given host
parsedUrl = new Url(options.host);
this.host = parsedUrl.getHost();
}
_.isString(options.match) && (this.match = new UrlMatchPattern(options.match));
_.isString(_.get(options, 'match.pattern')) && (this.match = new UrlMatchPattern(options.match.pattern));
port && (this.port = port);
_.isBoolean(options.tunnel) && (this.tunnel = options.tunnel);
// todo: Add update method in parent class Property and call that here
_.isBoolean(options.disabled) && (this.disabled = options.disabled);
_.isBoolean(options.authenticate) && (this.authenticate = options.authenticate);
_.isString(options.username) && (this.username = options.username);
_.isString(options.password) && (this.password = options.password);
// init bypass list from the given array
if (Array.isArray(options.bypass)) {
this.bypass = new UrlMatchPatternList(null, options.bypass);
}
// or, convert existing PropertyList or UrlMatchPatternList
else if (PropertyList.isPropertyList(options.bypass)) {
this.bypass = new UrlMatchPatternList(null, options.bypass.all());
}
},
/**
* Updates the protocols in the match pattern
*
* @param {Array.<String>} protocols The array of protocols
*/
updateProtocols: function (protocols) {
if (!protocols) {
return;
}
var updatedProtocols,
hostAndPath = _.split(this.match.pattern, PROTOCOL_HOST_SEPARATOR)[1];
if (!hostAndPath) {
return;
}
updatedProtocols = _.intersection(ALLOWED_PROTOCOLS, _.castArray(protocols));
_.isEmpty(updatedProtocols) && (updatedProtocols = ALLOWED_PROTOCOLS);
this.match.update({
pattern: updatedProtocols.join(PROTOCOL_DELIMITER) + PROTOCOL_HOST_SEPARATOR + hostAndPath
});
},
/**
* Tests the url string with the match 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.
*/
test: function (urlStr) {
var protocol = Url.isUrl(urlStr) ? urlStr.protocol : (Url.parse(urlStr || E).protocol || E);
// to allow target URLs without any protocol. e.g.: 'foo.com/bar'
if (_.isEmpty(protocol)) {
protocol = DEFAULT_PROTOCOL;
urlStr = protocol + PROTOCOL_HOST_SEPARATOR + urlStr;
}
// this ensures we don't proceed any further for any non-supported protocol
if (!_.includes(ALLOWED_PROTOCOLS, protocol)) {
return false;
}
// don't proceed if the given URL should skip use of a proxy all together
if (this.bypass && this.bypass.test(urlStr)) {
return false;
}
return this.match.test(urlStr);
},
/**
* Returns the proxy server url.
*
* @returns {String}
*/
getProxyUrl: function () {
var auth = E;
// Add authentication method to URL if the same is requested. We do it this way because
// this is how `postman-request` library accepts auth credentials in its proxy configuration.
if (this.authenticate) {
auth = encodeURIComponent(this.username || E);
if (this.password) {
auth += COLON + encodeURIComponent(this.password);
}
if (auth) {
auth += AUTH_CREDENTIALS_SEPARATOR;
}
}
return DEFAULT_PROTOCOL + PROTOCOL_HOST_SEPARATOR + auth + this.host + COLON + this.port;
},
/**
* Returns the protocols supported.
*
* @returns {Array.<String>}
*/
getProtocols: function () {
return this.match.getProtocols();
}
});
_.assign(ProxyConfig, /** @lends ProxyConfig */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'ProxyConfig',
/**
* Check whether an object is an instance of PostmanItem.
*
* @param {*} obj -
* @returns {Boolean}
*/
isProxyConfig: function (obj) {
return Boolean(obj) && ((obj instanceof ProxyConfig) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ProxyConfig._postman_propertyName));
}
});
module.exports = {
ProxyConfig,
ALLOWED_PROTOCOLS,
DEFAULT_PATTERN
};