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 JSON definition of an example proxy object * { * "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 Create a new ProxyConfig * 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.} 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.} */ 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 };