verdaccio
Version:
A lightweight private npm proxy registry
624 lines (597 loc) • 78.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _request = _interopRequireDefault(require("@cypress/request"));
var _JSONStream = _interopRequireDefault(require("JSONStream"));
var _debug = _interopRequireDefault(require("debug"));
var _lodash = _interopRequireDefault(require("lodash"));
var _stream = _interopRequireDefault(require("stream"));
var _url = _interopRequireDefault(require("url"));
var _zlib = _interopRequireDefault(require("zlib"));
var _streams = require("@verdaccio/streams");
var _utils = require("@verdaccio/utils");
var _constants = require("./constants");
var _logger = require("./logger");
var _utils2 = require("./utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
const debug = (0, _debug.default)('verdaccio:proxy');
const encode = function (thing) {
return encodeURIComponent(thing).replace(/^%40/, '@');
};
const jsonContentType = _constants.HEADERS.JSON;
const contentTypeAccept = `${jsonContentType};`;
/**
* Just a helper (`config[key] || default` doesn't work because of zeroes)
*/
const setConfig = (config, key, def) => {
return _lodash.default.isNil(config[key]) === false ? config[key] : def;
};
/**
* Implements Storage interface
* (same for storage.js, local-storage.js, up-storage.js)
*/
class ProxyStorage {
/**
* Constructor
* @param {*} config
* @param {*} mainConfig
*/
constructor(config, mainConfig) {
var _mainConfig$user_agen;
_defineProperty(this, "config", void 0);
_defineProperty(this, "failed_requests", void 0);
_defineProperty(this, "userAgent", void 0);
_defineProperty(this, "ca", void 0);
_defineProperty(this, "logger", void 0);
_defineProperty(this, "server_id", void 0);
_defineProperty(this, "url", void 0);
_defineProperty(this, "maxage", void 0);
_defineProperty(this, "timeout", void 0);
_defineProperty(this, "max_fails", void 0);
_defineProperty(this, "fail_timeout", void 0);
_defineProperty(this, "agent_options", void 0);
// FIXME: upname is assigned to each instance
// @ts-ignore
_defineProperty(this, "upname", void 0);
// FIXME: proxy can be boolean or object, something smells here
// @ts-ignore
_defineProperty(this, "proxy", void 0);
// @ts-ignore
_defineProperty(this, "last_request_time", void 0);
_defineProperty(this, "strict_ssl", void 0);
this.config = config;
this.failed_requests = 0;
// @ts-ignore
this.userAgent = (_mainConfig$user_agen = mainConfig.user_agent) !== null && _mainConfig$user_agen !== void 0 ? _mainConfig$user_agen : 'hidden';
this.ca = config.ca;
this.logger = _logger.logger;
this.server_id = mainConfig.server_id;
this.url = _url.default.parse(this.config.url);
this._setupProxy(this.url.hostname, config, mainConfig, this.url.protocol === 'https:');
this.config.url = this.config.url.replace(/\/$/, '');
if (this.config.timeout && Number(this.config.timeout) >= 1000) {
this.logger.warn(['Too big timeout value: ' + this.config.timeout, 'We changed time format to nginx-like one', '(see http://nginx.org/en/docs/syntax.html)', 'so please update your config accordingly'].join('\n'));
}
// a bunch of different configurable timers
this.maxage = (0, _utils2.parseInterval)(setConfig(this.config, 'maxage', '2m'));
this.timeout = (0, _utils2.parseInterval)(setConfig(this.config, 'timeout', '30s'));
this.max_fails = Number(setConfig(this.config, 'max_fails', 2));
this.fail_timeout = (0, _utils2.parseInterval)(setConfig(this.config, 'fail_timeout', '5m'));
this.strict_ssl = Boolean(setConfig(this.config, 'strict_ssl', true));
this.agent_options = setConfig(this.config, 'agent_options', {
keepAlive: true,
maxSockets: 40,
maxFreeSockets: 10
});
}
/**
* Fetch an asset.
* @param {*} options
* @param {*} cb
* @return {Request}
*/
request(options, cb) {
let json;
if (this._statusCheck() === false) {
const streamRead = new _stream.default.Readable();
process.nextTick(function () {
if (cb) {
cb(_utils2.ErrorCode.getInternalError(_constants.API_ERROR.UPLINK_OFFLINE));
}
streamRead.emit('error', _utils2.ErrorCode.getInternalError(_constants.API_ERROR.UPLINK_OFFLINE));
});
streamRead._read = function () {};
// preventing 'Uncaught, unspecified "error" event'
streamRead.on('error', function () {});
return streamRead;
}
const self = this;
const headers = this._setHeaders(options);
this._addProxyHeaders(options.req, headers);
this._overrideWithUpLinkConfigHeaders(headers);
const method = options.method || 'GET';
const uri = options.uri_full || this.config.url + options.uri;
self.logger.info({
method: method,
uri: uri
}, "making request: '@{method} @{uri}'");
if ((0, _utils2.isObject)(options.json)) {
json = JSON.stringify(options.json);
headers['Content-Type'] = headers['Content-Type'] || _constants.HEADERS.JSON;
}
const requestCallback = cb ? function (err, res, body) {
let error;
const responseLength = err ? 0 : body.length;
processBody();
logActivity();
cb(err, res, body);
/**
* Perform a decode.
*/
function processBody() {
if (err) {
error = err.message;
return;
}
if (options.json && res.statusCode < 300) {
try {
body = JSON.parse(body.toString(_constants.CHARACTER_ENCODING.UTF8));
} catch (_err) {
body = {};
err = _err;
error = err.message;
}
}
if (!err && (0, _utils2.isObject)(body)) {
if (_lodash.default.isString(body.error)) {
error = body.error;
}
}
}
/**
* Perform a log.
*/
function logActivity() {
let message = "@{!status}, req: '@{request.method} @{request.url}'";
message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.http({
err: err || undefined,
// if error is null/false change this to undefined so it wont log
request: {
method: method,
url: uri
},
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: responseLength || 0
}
}, message);
}
} : undefined;
let requestOptions = {
url: uri,
method: method,
headers: headers,
body: json,
proxy: this.proxy,
encoding: null,
gzip: true,
timeout: this.timeout,
strictSSL: this.strict_ssl,
agentOptions: this.agent_options
};
if (this.ca) {
requestOptions = Object.assign({}, requestOptions, {
ca: this.ca
});
}
const req = (0, _request.default)(requestOptions, requestCallback);
let statusCalled = false;
req.on('response', function (res) {
// FIXME: _verdaccio_aborted seems not used
// @ts-ignore
if (!req._verdaccio_aborted && !statusCalled) {
statusCalled = true;
self._statusCheck(true);
}
if (_lodash.default.isNil(requestCallback) === false) {
(function do_log() {
const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
self.logger.http({
request: {
method: method,
url: uri
},
status: _lodash.default.isNull(res) === false ? res.statusCode : 'ERR'
}, message);
})();
}
});
req.on('error', function (_err) {
// FIXME: _verdaccio_aborted seems not used
// @ts-ignore
if (!req._verdaccio_aborted && !statusCalled) {
statusCalled = true;
self._statusCheck(false);
}
});
// @ts-ignore
return req;
}
/**
* Set default headers.
* @param {Object} options
* @return {Object}
* @private
*/
_setHeaders(options) {
var _options$req;
const headers = options.headers || {};
const accept = _constants.HEADERS.ACCEPT;
const acceptEncoding = _constants.HEADERS.ACCEPT_ENCODING;
const userAgent = _constants.HEADERS.USER_AGENT;
headers[accept] = headers[accept] || contentTypeAccept;
headers[acceptEncoding] = headers[acceptEncoding] || 'gzip';
// registry.npmjs.org will only return search result if user-agent include string 'npm'
headers[userAgent] = this.userAgent ? `npm (${this.userAgent})` : (_options$req = options.req) === null || _options$req === void 0 ? void 0 : _options$req.get('user-agent');
return this._setAuth(headers);
}
/**
* Validate configuration auth and assign Header authorization
* @param {Object} headers
* @return {Object}
* @private
*/
_setAuth(headers) {
const {
auth
} = this.config;
if (_lodash.default.isNil(auth) || headers[_constants.HEADERS.AUTHORIZATION]) {
return headers;
}
if (_lodash.default.isObject(auth) === false && _lodash.default.isObject(auth.token) === false) {
this._throwErrorAuth('Auth invalid');
}
// get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules
// or get other variable export in env
// https://github.com/verdaccio/verdaccio/releases/tag/v2.5.0
let token;
const tokenConf = auth;
if (_lodash.default.isNil(tokenConf.token) === false && _lodash.default.isString(tokenConf.token)) {
token = tokenConf.token;
} else if (_lodash.default.isNil(tokenConf.token_env) === false) {
if (_lodash.default.isString(tokenConf.token_env)) {
token = process.env[tokenConf.token_env];
} else if (_lodash.default.isBoolean(tokenConf.token_env) && tokenConf.token_env) {
token = process.env.NPM_TOKEN;
} else {
this.logger.error(_constants.ERROR_CODE.token_required);
this._throwErrorAuth(_constants.ERROR_CODE.token_required);
}
} else {
token = process.env.NPM_TOKEN;
}
if (_lodash.default.isNil(token)) {
this._throwErrorAuth(_constants.ERROR_CODE.token_required);
}
// define type Auth allow basic and bearer
const type = tokenConf.type || _constants.TOKEN_BASIC;
this._setHeaderAuthorization(headers, type, token);
return headers;
}
/**
* @param {string} message
* @throws {Error}
* @private
*/
_throwErrorAuth(message) {
this.logger.error(message);
throw new Error(message);
}
/**
* Assign Header authorization with type authentication
* @param {Object} headers
* @param {string} type
* @param {string} token
* @private
*/
_setHeaderAuthorization(headers, type, token) {
const _type = type.toLowerCase();
if ([_constants.TOKEN_BEARER.toLowerCase(), _constants.TOKEN_BASIC.toLowerCase()].includes(_type) === false) {
this._throwErrorAuth(`Auth type '${_type}' not allowed`);
}
type = _lodash.default.upperFirst(type);
headers[_constants.HEADERS.AUTHORIZATION] = (0, _utils.buildToken)(type, token);
}
/**
* It will add or override specified headers from config file.
*
* Eg:
*
* uplinks:
npmjs:
url: https://registry.npmjs.org/
headers:
Accept: "application/vnd.npm.install-v2+json; q=1.0"
verdaccio-staging:
url: https://mycompany.com/npm
headers:
Accept: "application/json"
authorization: "Basic YourBase64EncodedCredentials=="
* @param {Object} headers
* @private
*/
_overrideWithUpLinkConfigHeaders(headers) {
if (!this.config.headers) {
return headers;
}
// add/override headers specified in the config
/* eslint guard-for-in: 0 */
for (const key in this.config.headers) {
headers[key] = this.config.headers[key];
}
}
/**
* Determine whether can fetch from the provided URL
* @param {*} url
* @return {Boolean}
*/
isUplinkValid(url) {
const urlParsed = _url.default.parse(url);
const isHTTPS = urlDomainParsed => urlDomainParsed.protocol === 'https:' && (urlParsed.port === null || urlParsed.port === '443');
const getHost = urlDomainParsed => isHTTPS(urlDomainParsed) ? urlDomainParsed.hostname : urlDomainParsed.host;
const isMatchProtocol = urlParsed.protocol === this.url.protocol;
const isMatchHost = getHost(urlParsed) === getHost(this.url);
// @ts-ignore
const isMatchPath = urlParsed.path.indexOf(this.url.path) === 0;
return isMatchProtocol && isMatchHost && isMatchPath;
}
/**
* Get a remote package metadata
* @param {*} name package name
* @param {*} options request options, eg: eTag.
* @param {*} callback
*/
getRemoteMetadata(name, options, callback) {
const headers = {};
if (_lodash.default.isNil(options.etag) === false) {
headers['If-None-Match'] = options.etag;
headers[_constants.HEADERS.ACCEPT] = contentTypeAccept;
}
this.request({
uri: `/${encode(name)}`,
json: true,
headers: headers,
req: options.req
}, (err, res, body) => {
if (err) {
return callback(err);
}
if (res.statusCode === _constants.HTTP_STATUS.NOT_FOUND) {
return callback(_utils2.ErrorCode.getNotFound(_constants.API_ERROR.NOT_PACKAGE_UPLINK));
}
if (!(res.statusCode >= _constants.HTTP_STATUS.OK && res.statusCode < _constants.HTTP_STATUS.MULTIPLE_CHOICES)) {
const error = _utils2.ErrorCode.getInternalError(`${_constants.API_ERROR.BAD_STATUS_CODE}: ${res.statusCode}`);
error.remoteStatus = res.statusCode;
return callback(error);
}
callback(null, body, res.headers.etag);
});
}
/**
* Fetch a tarball from the uplink.
* @param {String} url
* @return {Stream}
*/
fetchTarball(url) {
const stream = new _streams.ReadTarball({});
let current_length = 0;
let expected_length;
stream.abort = () => {};
const readStream = this.request({
uri_full: url,
encoding: null,
headers: {
Accept: contentTypeAccept
}
});
readStream.on('response', function (res) {
if (res.statusCode === _constants.HTTP_STATUS.NOT_FOUND) {
return stream.emit('error', _utils2.ErrorCode.getNotFound(_constants.API_ERROR.NOT_FILE_UPLINK));
}
if (!(res.statusCode >= _constants.HTTP_STATUS.OK && res.statusCode < _constants.HTTP_STATUS.MULTIPLE_CHOICES)) {
return stream.emit('error', _utils2.ErrorCode.getInternalError(`bad uplink status code: ${res.statusCode}`));
}
if (res.headers[_constants.HEADER_TYPE.CONTENT_LENGTH]) {
expected_length = res.headers[_constants.HEADER_TYPE.CONTENT_LENGTH];
stream.emit(_constants.HEADER_TYPE.CONTENT_LENGTH, res.headers[_constants.HEADER_TYPE.CONTENT_LENGTH]);
}
readStream.pipe(stream);
});
readStream.on('error', function (err) {
stream.emit('error', err);
});
readStream.on('data', function (data) {
current_length += data.length;
});
readStream.on('end', function (data) {
if (data) {
current_length += data.length;
}
if (expected_length && current_length != expected_length) {
stream.emit('error', _utils2.ErrorCode.getInternalError(_constants.API_ERROR.CONTENT_MISMATCH));
}
});
return stream;
}
/**
* Perform a stream search.
* @param {*} options request options
* @return {Stream}
*/
search(options) {
const transformStream = new _stream.default.PassThrough({
objectMode: true
});
const requestStream = this.request({
uri: options.req.url,
req: options.req,
headers: {
// query for search
referer: options.req.get('referer')
}
});
const parsePackage = pkg => {
if ((0, _utils2.isObjectOrArray)(pkg)) {
transformStream.emit('data', pkg);
}
};
requestStream.on('response', res => {
if (!String(res.statusCode).match(/^2\d\d$/)) {
return transformStream.emit('error', _utils2.ErrorCode.getInternalError(`bad status code ${res.statusCode} from uplink`));
}
// See https://github.com/request/request#requestoptions-callback
// Request library will not decode gzip stream.
let jsonStream;
if (res.headers[_constants.HEADER_TYPE.CONTENT_ENCODING] === _constants.HEADERS.GZIP) {
jsonStream = res.pipe(_zlib.default.createUnzip());
} else {
jsonStream = res;
}
jsonStream.pipe(_JSONStream.default.parse('*')).on('data', parsePackage);
jsonStream.on('end', () => {
transformStream.emit('end');
});
});
requestStream.on('error', err => {
transformStream.emit('error', err);
});
transformStream.abort = () => {
// FIXME: this is clearly a potential issue
// there is no abort method on Stream.Readable
// @ts-ignore
requestStream.abort();
transformStream.emit('end');
};
return transformStream;
}
/**
* Add proxy headers.
* FIXME: object mutations, it should return an new object
* @param {*} req the http request
* @param {*} headers the request headers
*/
_addProxyHeaders(req, headers) {
if (req) {
// Only submit X-Forwarded-For field if we don't have a proxy selected
// in the config file.
//
// Otherwise misconfigured proxy could return 407:
// https://github.com/rlidwka/sinopia/issues/254
//
// FIXME: proxy logic is odd, something is wrong here.
// @ts-ignore
if (!this.proxy) {
headers['x-forwarded-for'] = (req.get('x-forwarded-for') ? req.get('x-forwarded-for') + ', ' : '') + req.connection.remoteAddress;
}
}
// always attach Via header to avoid loops, even if we're not proxying
headers['via'] = req && req.get('via') ? req.get('via') + ', ' : '';
headers['via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
}
/**
* Check whether the remote host is available.
* @param {*} alive
* @return {Boolean}
*/
_statusCheck(alive) {
if (arguments.length === 0) {
return this._ifRequestFailure() === false;
}
if (alive) {
if (this.failed_requests >= this.max_fails) {
this.logger.warn({
host: this.url.host
}, 'host @{host} is back online');
}
this.failed_requests = 0;
} else {
this.failed_requests++;
if (this.failed_requests === this.max_fails) {
this.logger.warn({
host: this.url.host
}, 'host @{host} is now offline');
}
}
this.last_request_time = Date.now();
}
/**
* If the request failure.
* @return {boolean}
* @private
*/
_ifRequestFailure() {
return this.failed_requests >= this.max_fails && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout;
}
/**
* Set up a proxy.
* @param {*} hostname
* @param {*} config
* @param {*} mainconfig
* @param {*} isHTTPS
*/
_setupProxy(hostname, config, mainconfig, isHTTPS) {
let noProxyList;
const proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy';
// get http_proxy and no_proxy configs
if (proxy_key in config) {
this.proxy = config[proxy_key];
} else if (proxy_key in mainconfig) {
this.proxy = mainconfig[proxy_key];
}
if ('no_proxy' in config) {
noProxyList = config.no_proxy;
} else if ('no_proxy' in mainconfig) {
noProxyList = mainconfig.no_proxy;
}
// use wget-like algorithm to determine if proxy shouldn't be used
if (hostname[0] !== '.') {
hostname = '.' + hostname;
}
if (_lodash.default.isString(noProxyList) && noProxyList.length) {
noProxyList = noProxyList.split(',');
}
if (_lodash.default.isArray(noProxyList)) {
for (let i = 0; i < noProxyList.length; i++) {
let noProxyItem = noProxyList[i];
if (noProxyItem[0] !== '.') {
noProxyItem = '.' + noProxyItem;
}
if (hostname.lastIndexOf(noProxyItem) === hostname.length - noProxyItem.length) {
if (this.proxy) {
debug('not using proxy for %o, excluded by %o rule', this.url.href, noProxyItem);
// @ts-ignore
this.proxy = false;
}
break;
}
}
}
// if it's non-string (i.e. "false"), don't use it
if (_lodash.default.isString(this.proxy) === false) {
// @ts-ignore
delete this.proxy;
} else {
debug('using proxy %o for %o', this.url.href, this.proxy);
}
}
}
var _default = exports.default = ProxyStorage;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVxdWVzdCIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJyZXF1aXJlIiwiX0pTT05TdHJlYW0iLCJfZGVidWciLCJfbG9kYXNoIiwiX3N0cmVhbSIsIl91cmwiLCJfemxpYiIsIl9zdHJlYW1zIiwiX3V0aWxzIiwiX2NvbnN0YW50cyIsIl9sb2dnZXIiLCJfdXRpbHMyIiwiZSIsIl9fZXNNb2R1bGUiLCJkZWZhdWx0IiwiX2RlZmluZVByb3BlcnR5IiwiciIsInQiLCJfdG9Qcm9wZXJ0eUtleSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwidmFsdWUiLCJlbnVtZXJhYmxlIiwiY29uZmlndXJhYmxlIiwid3JpdGFibGUiLCJpIiwiX3RvUHJpbWl0aXZlIiwiU3ltYm9sIiwidG9QcmltaXRpdmUiLCJjYWxsIiwiVHlwZUVycm9yIiwiU3RyaW5nIiwiTnVtYmVyIiwiZGVidWciLCJidWlsZERlYnVnIiwiZW5jb2RlIiwidGhpbmciLCJlbmNvZGVVUklDb21wb25lbnQiLCJyZXBsYWNlIiwianNvbkNvbnRlbnRUeXBlIiwiSEVBREVSUyIsIkpTT04iLCJjb250ZW50VHlwZUFjY2VwdCIsInNldENvbmZpZyIsImNvbmZpZyIsImtleSIsImRlZiIsIl8iLCJpc05pbCIsIlByb3h5U3RvcmFnZSIsImNvbnN0cnVjdG9yIiwibWFpbkNvbmZpZyIsIl9tYWluQ29uZmlnJHVzZXJfYWdlbiIsImZhaWxlZF9yZXF1ZXN0cyIsInVzZXJBZ2VudCIsInVzZXJfYWdlbnQiLCJjYSIsImxvZ2dlciIsInNlcnZlcl9pZCIsInVybCIsIlVSTCIsInBhcnNlIiwiX3NldHVwUHJveHkiLCJob3N0bmFtZSIsInByb3RvY29sIiwidGltZW91dCIsIndhcm4iLCJqb2luIiwibWF4YWdlIiwicGFyc2VJbnRlcnZhbCIsIm1heF9mYWlscyIsImZhaWxfdGltZW91dCIsInN0cmljdF9zc2wiLCJCb29sZWFuIiwiYWdlbnRfb3B0aW9ucyIsImtlZXBBbGl2ZSIsIm1heFNvY2tldHMiLCJtYXhGcmVlU29ja2V0cyIsInJlcXVlc3QiLCJvcHRpb25zIiwiY2IiLCJqc29uIiwiX3N0YXR1c0NoZWNrIiwic3RyZWFtUmVhZCIsIlN0cmVhbSIsIlJlYWRhYmxlIiwicHJvY2VzcyIsIm5leHRUaWNrIiwiRXJyb3JDb2RlIiwiZ2V0SW50ZXJuYWxFcnJvciIsIkFQSV9FUlJPUiIsIlVQTElOS19PRkZMSU5FIiwiZW1pdCIsIl9yZWFkIiwib24iLCJzZWxmIiwiaGVhZGVycyIsIl9zZXRIZWFkZXJzIiwiX2FkZFByb3h5SGVhZGVycyIsInJlcSIsIl9vdmVycmlkZVdpdGhVcExpbmtDb25maWdIZWFkZXJzIiwibWV0aG9kIiwidXJpIiwidXJpX2Z1bGwiLCJpbmZvIiwiaXNPYmplY3QiLCJzdHJpbmdpZnkiLCJyZXF1ZXN0Q2FsbGJhY2siLCJlcnIiLCJyZXMiLCJib2R5IiwiZXJyb3IiLCJyZXNwb25zZUxlbmd0aCIsImxlbmd0aCIsInByb2Nlc3NCb2R5IiwibG9nQWN0aXZpdHkiLCJtZXNzYWdlIiwic3RhdHVzQ29kZSIsInRvU3RyaW5nIiwiQ0hBUkFDVEVSX0VOQ09ESU5HIiwiVVRGOCIsIl9lcnIiLCJpc1N0cmluZyIsImh0dHAiLCJ1bmRlZmluZWQiLCJzdGF0dXMiLCJieXRlcyIsImluIiwib3V0IiwicmVxdWVzdE9wdGlvbnMiLCJwcm94eSIsImVuY29kaW5nIiwiZ3ppcCIsInN0cmljdFNTTCIsImFnZW50T3B0aW9ucyIsImFzc2lnbiIsInN0YXR1c0NhbGxlZCIsIl92ZXJkYWNjaW9fYWJvcnRlZCIsImRvX2xvZyIsImlzTnVsbCIsIl9vcHRpb25zJHJlcSIsImFjY2VwdCIsIkFDQ0VQVCIsImFjY2VwdEVuY29kaW5nIiwiQUNDRVBUX0VOQ09ESU5HIiwiVVNFUl9BR0VOVCIsImdldCIsIl9zZXRBdXRoIiwiYXV0aCIsIkFVVEhPUklaQVRJT04iLCJ0b2tlbiIsIl90aHJvd0Vycm9yQXV0aCIsInRva2VuQ29uZiIsInRva2VuX2VudiIsImVudiIsImlzQm9vbGVhbiIsIk5QTV9UT0tFTiIsIkVSUk9SX0NPREUiLCJ0b2tlbl9yZXF1aXJlZCIsInR5cGUiLCJUT0tFTl9CQVNJQyIsIl9zZXRIZWFkZXJBdXRob3JpemF0aW9uIiwiRXJyb3IiLCJfdHlwZSIsInRvTG93ZXJDYXNlIiwiVE9LRU5fQkVBUkVSIiwiaW5jbHVkZXMiLCJ1cHBlckZpcnN0IiwiYnVpbGRUb2tlbiIsImlzVXBsaW5rVmFsaWQiLCJ1cmxQYXJzZWQiLCJpc0hUVFBTIiwidXJsRG9tYWluUGFyc2VkIiwicG9ydCIsImdldEhvc3QiLCJob3N0IiwiaXNNYXRjaFByb3RvY29sIiwiaXNNYXRjaEhvc3QiLCJpc01hdGNoUGF0aCIsInBhdGgiLCJpbmRleE9mIiwiZ2V0UmVtb3RlTWV0YWRhdGEiLCJuYW1lIiwiY2FsbGJhY2siLCJldGFnIiwiSFRUUF9TVEFUVVMiLCJOT1RfRk9VTkQiLCJnZXROb3RGb3VuZCIsIk5PVF9QQUNLQUdFX1VQTElOSyIsIk9LIiwiTVVMVElQTEVfQ0hPSUNFUyIsIkJBRF9TVEFUVVNfQ09ERSIsInJlbW90ZVN0YXR1cyIsImZldGNoVGFyYmFsbCIsInN0cmVhbSIsIlJlYWRUYXJiYWxsIiwiY3VycmVudF9sZW5ndGgiLCJleHBlY3RlZF9sZW5ndGgiLCJhYm9ydCIsInJlYWRTdHJlYW0iLCJBY2NlcHQiLCJOT1RfRklMRV9VUExJTksiLCJIRUFERVJfVFlQRSIsIkNPTlRFTlRfTEVOR1RIIiwicGlwZSIsImRhdGEiLCJDT05URU5UX01JU01BVENIIiwic2VhcmNoIiwidHJhbnNmb3JtU3RyZWFtIiwiUGFzc1Rocm91Z2giLCJvYmplY3RNb2RlIiwicmVxdWVzdFN0cmVhbSIsInJlZmVyZXIiLCJwYXJzZVBhY2thZ2UiLCJwa2ciLCJpc09iamVjdE9yQXJyYXkiLCJtYXRjaCIsImpzb25TdHJlYW0iLCJDT05URU5UX0VOQ09ESU5HIiwiR1pJUCIsInpsaWIiLCJjcmVhdGVVbnppcCIsIkpTT05TdHJlYW0iLCJjb25uZWN0aW9uIiwicmVtb3RlQWRkcmVzcyIsImFsaXZlIiwiYXJndW1lbnRzIiwiX2lmUmVxdWVzdEZhaWx1cmUiLCJsYXN0X3JlcXVlc3RfdGltZSIsIkRhdGUiLCJub3ciLCJNYXRoIiwiYWJzIiwibWFpbmNvbmZpZyIsIm5vUHJveHlMaXN0IiwicHJveHlfa2V5Iiwibm9fcHJveHkiLCJzcGxpdCIsImlzQXJyYXkiLCJub1Byb3h5SXRlbSIsImxhc3RJbmRleE9mIiwiaHJlZiIsIl9kZWZhdWx0IiwiZXhwb3J0cyJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvdXAtc3RvcmFnZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgcmVxdWVzdCBmcm9tICdAY3lwcmVzcy9yZXF1ZXN0JztcbmltcG9ydCBKU09OU3RyZWFtIGZyb20gJ0pTT05TdHJlYW0nO1xuaW1wb3J0IGJ1aWxkRGVidWcgZnJvbSAnZGVidWcnO1xuaW1wb3J0IF8gZnJvbSAnbG9kYXNoJztcbmltcG9ydCBTdHJlYW0gZnJvbSAnc3RyZWFtJztcbmltcG9ydCBVUkwsIHsgVXJsV2l0aFN0cmluZ1F1ZXJ5IH0gZnJvbSAndXJsJztcbmltcG9ydCB6bGliIGZyb20gJ3psaWInO1xuXG5pbXBvcnQgeyBSZWFkVGFyYmFsbCB9IGZyb20gJ0B2ZXJkYWNjaW8vc3RyZWFtcyc7XG5pbXBvcnQgeyBDYWxsYmFjaywgQ29uZmlnLCBIZWFkZXJzLCBMb2dnZXIsIFBhY2thZ2UsIFVwTGlua0NvbmYgfSBmcm9tICdAdmVyZGFjY2lvL3R5cGVzJztcbmltcG9ydCB7IGJ1aWxkVG9rZW4gfSBmcm9tICdAdmVyZGFjY2lvL3V0aWxzJztcblxuaW1wb3J0IHtcbiAgQVBJX0VSUk9SLFxuICBDSEFSQUNURVJfRU5DT0RJTkcsXG4gIEVSUk9SX0NPREUsXG4gIEhFQURFUlMsXG4gIEhFQURFUl9UWVBFLFxuICBIVFRQX1NUQVRVUyxcbiAgVE9LRU5fQkFTSUMsXG4gIFRPS0VOX0JFQVJFUixcbn0gZnJvbSAnLi9jb25zdGFudHMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi9sb2dnZXInO1xuaW1wb3J0IHsgRXJyb3JDb2RlLCBpc09iamVjdCwgaXNPYmplY3RPckFycmF5LCBwYXJzZUludGVydmFsIH0gZnJvbSAnLi91dGlscyc7XG5cbmNvbnN0IGRlYnVnID0gYnVpbGREZWJ1ZygndmVyZGFjY2lvOnByb3h5Jyk7XG5cbmNvbnN0IGVuY29kZSA9IGZ1bmN0aW9uICh0aGluZyk6IHN0cmluZyB7XG4gIHJldHVybiBlbmNvZGVVUklDb21wb25lbnQodGhpbmcpLnJlcGxhY2UoL14lNDAvLCAnQCcpO1xufTtcblxuY29uc3QganNvbkNvbnRlbnRUeXBlID0gSEVBREVSUy5KU09OO1xuY29uc3QgY29udGVudFR5cGVBY2NlcHQgPSBgJHtqc29uQ29udGVudFR5cGV9O2A7XG5cbi8qKlxuICogSnVzdCBhIGhlbHBlciAoYGNvbmZpZ1trZXldIHx8IGRlZmF1bHRgIGRvZXNuJ3Qgd29yayBiZWNhdXNlIG9mIHplcm9lcylcbiAqL1xuY29uc3Qgc2V0Q29uZmlnID0gKGNvbmZpZywga2V5LCBkZWYpOiBzdHJpbmcgPT4ge1xuICByZXR1cm4gXy5pc05pbChjb25maWdba2V5XSkgPT09IGZhbHNlID8gY29uZmlnW2tleV0gOiBkZWY7XG59O1xuXG4vKipcbiAqIEltcGxlbWVudHMgU3RvcmFnZSBpbnRlcmZhY2VcbiAqIChzYW1lIGZvciBzdG9yYWdlLmpzLCBsb2NhbC1zdG9yYWdlLmpzLCB1cC1zdG9yYWdlLmpzKVxuICovXG5jbGFzcyBQcm94eVN0b3JhZ2Uge1xuICBwdWJsaWMgY29uZmlnOiBVcExpbmtDb25mO1xuICBwdWJsaWMgZmFpbGVkX3JlcXVlc3RzOiBudW1iZXI7XG4gIHB1YmxpYyB1c2VyQWdlbnQ6IHN0cmluZztcbiAgcHVibGljIGNhOiBzdHJpbmcgfCB2b2lkO1xuICBwdWJsaWMgbG9nZ2VyOiBMb2dnZXI7XG4gIHB1YmxpYyBzZXJ2ZXJfaWQ6IHN0cmluZztcbiAgcHVibGljIHVybDogYW55O1xuICBwdWJsaWMgbWF4YWdlOiBudW1iZXI7XG4gIHB1YmxpYyB0aW1lb3V0OiBudW1iZXI7XG4gIHB1YmxpYyBtYXhfZmFpbHM6IG51bWJlcjtcbiAgcHVibGljIGZhaWxfdGltZW91dDogbnVtYmVyO1xuICBwdWJsaWMgYWdlbnRfb3B0aW9uczogYW55O1xuICAvLyBGSVhNRTogdXBuYW1lIGlzIGFzc2lnbmVkIHRvIGVhY2ggaW5zdGFuY2VcbiAgLy8gQHRzLWlnbm9yZVxuICBwdWJsaWMgdXBuYW1lOiBzdHJpbmc7XG4gIC8vIEZJWE1FOiBwcm94eSBjYW4gYmUgYm9vbGVhbiBvciBvYmplY3QsIHNvbWV0aGluZyBzbWVsbHMgaGVyZVxuICAvLyBAdHMtaWdub3JlXG4gIHB1YmxpYyBwcm94eTogc3RyaW5nIHwgdm9pZDtcbiAgLy8gQHRzLWlnbm9yZVxuICBwdWJsaWMgbGFzdF9yZXF1ZXN0X3RpbWU6IG51bWJlciB8IG51bGw7XG4gIHB1YmxpYyBzdHJpY3Rfc3NsOiBib29sZWFuO1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RvclxuICAgKiBAcGFyYW0geyp9IGNvbmZpZ1xuICAgKiBAcGFyYW0geyp9IG1haW5Db25maWdcbiAgICovXG4gIHB1YmxpYyBjb25zdHJ1Y3Rvcihjb25maWc6IFVwTGlua0NvbmYsIG1haW5Db25maWc6IENvbmZpZykge1xuICAgIHRoaXMuY29uZmlnID0gY29uZmlnO1xuICAgIHRoaXMuZmFpbGVkX3JlcXVlc3RzID0gMDtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdGhpcy51c2VyQWdlbnQgPSBtYWluQ29uZmlnLnVzZXJfYWdlbnQgPz8gJ2hpZGRlbic7XG4gICAgdGhpcy5jYSA9IGNvbmZpZy5jYTtcbiAgICB0aGlzLmxvZ2dlciA9IGxvZ2dlcjtcbiAgICB0aGlzLnNlcnZlcl9pZCA9IG1haW5Db25maWcuc2VydmVyX2lkO1xuXG4gICAgdGhpcy51cmwgPSBVUkwucGFyc2UodGhpcy5jb25maWcudXJsKTtcblxuICAgIHRoaXMuX3NldHVwUHJveHkodGhpcy51cmwuaG9zdG5hbWUsIGNvbmZpZywgbWFpbkNvbmZpZywgdGhpcy51cmwucHJvdG9jb2wgPT09ICdodHRwczonKTtcblxuICAgIHRoaXMuY29uZmlnLnVybCA9IHRoaXMuY29uZmlnLnVybC5yZXBsYWNlKC9cXC8kLywgJycpO1xuXG4gICAgaWYgKHRoaXMuY29uZmlnLnRpbWVvdXQgJiYgTnVtYmVyKHRoaXMuY29uZmlnLnRpbWVvdXQpID49IDEwMDApIHtcbiAgICAgIHRoaXMubG9nZ2VyLndhcm4oXG4gICAgICAgIFtcbiAgICAgICAgICAnVG9vIGJpZyB0aW1lb3V0IHZhbHVlOiAnICsgdGhpcy5jb25maWcudGltZW91dCxcbiAgICAgICAgICAnV2UgY2hhbmdlZCB0aW1lIGZvcm1hdCB0byBuZ2lueC1saWtlIG9uZScsXG4gICAgICAgICAgJyhzZWUgaHR0cDovL25naW54Lm9yZy9lbi9kb2NzL3N5bnRheC5odG1sKScsXG4gICAgICAgICAgJ3NvIHBsZWFzZSB1cGRhdGUgeW91ciBjb25maWcgYWNjb3JkaW5nbHknLFxuICAgICAgICBdLmpvaW4oJ1xcbicpXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIGEgYnVuY2ggb2YgZGlmZmVyZW50IGNvbmZpZ3VyYWJsZSB0aW1lcnNcbiAgICB0aGlzLm1heGFnZSA9IHBhcnNlSW50ZXJ2YWwoc2V0Q29uZmlnKHRoaXMuY29uZmlnLCAnbWF4YWdlJywgJzJtJykpO1xuICAgIHRoaXMudGltZW91dCA9IHBhcnNlSW50ZXJ2YWwoc2V0Q29uZmlnKHRoaXMuY29uZmlnLCAndGltZW91dCcsICczMHMnKSk7XG4gICAgdGhpcy5tYXhfZmFpbHMgPSBOdW1iZXIoc2V0Q29uZmlnKHRoaXMuY29uZmlnLCAnbWF4X2ZhaWxzJywgMikpO1xuICAgIHRoaXMuZmFpbF90aW1lb3V0ID0gcGFyc2VJbnRlcnZhbChzZXRDb25maWcodGhpcy5jb25maWcsICdmYWlsX3RpbWVvdXQnLCAnNW0nKSk7XG4gICAgdGhpcy5zdHJpY3Rfc3NsID0gQm9vbGVhbihzZXRDb25maWcodGhpcy5jb25maWcsICdzdHJpY3Rfc3NsJywgdHJ1ZSkpO1xuICAgIHRoaXMuYWdlbnRfb3B0aW9ucyA9IHNldENvbmZpZyh0aGlzLmNvbmZpZywgJ2FnZW50X29wdGlvbnMnLCB7XG4gICAgICBrZWVwQWxpdmU6IHRydWUsXG4gICAgICBtYXhTb2NrZXRzOiA0MCxcbiAgICAgIG1heEZyZWVTb2NrZXRzOiAxMCxcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBGZXRjaCBhbiBhc3NldC5cbiAgICogQHBhcmFtIHsqfSBvcHRpb25zXG4gICAqIEBwYXJhbSB7Kn0gY2JcbiAgICogQHJldHVybiB7UmVxdWVzdH1cbiAgICovXG4gIHByaXZhdGUgcmVxdWVzdChvcHRpb25zOiBhbnksIGNiPzogQ2FsbGJhY2spOiBTdHJlYW0uUmVhZGFibGUge1xuICAgIGxldCBqc29uO1xuXG4gICAgaWYgKHRoaXMuX3N0YXR1c0NoZWNrKCkgPT09IGZhbHNlKSB7XG4gICAgICBjb25zdCBzdHJlYW1SZWFkID0gbmV3IFN0cmVhbS5SZWFkYWJsZSgpO1xuXG4gICAgICBwcm9jZXNzLm5leHRUaWNrKGZ1bmN0aW9uICgpOiB2b2lkIHtcbiAgICAgICAgaWYgKGNiKSB7XG4gICAgICAgICAgY2IoRXJyb3JDb2RlLmdldEludGVybmFsRXJyb3IoQVBJX0VSUk9SLlVQTElOS19PRkZMSU5FKSk7XG4gICAgICAgIH1cbiAgICAgICAgc3RyZWFtUmVhZC5lbWl0KCdlcnJvcicsIEVycm9yQ29kZS5nZXRJbnRlcm5hbEVycm9yKEFQSV9FUlJPUi5VUExJTktfT0ZGTElORSkpO1xuICAgICAgfSk7XG5cbiAgICAgIHN0cmVhbVJlYWQuX3JlYWQgPSBmdW5jdGlvbiAoKTogdm9pZCB7fTtcbiAgICAgIC8vIHByZXZlbnRpbmcgJ1VuY2F1Z2h0LCB1bnNwZWNpZmllZCBcImVycm9yXCIgZXZlbnQnXG4gICAgICBzdHJlYW1SZWFkLm9uKCdlcnJvcicsIGZ1bmN0aW9uICgpOiB2b2lkIHt9KTtcbiAgICAgIHJldHVybiBzdHJlYW1SZWFkO1xuICAgIH1cblxuICAgIGNvbnN0IHNlbGYgPSB0aGlzO1xuICAgIGNvbnN0IGhlYWRlcnM6IEhlYWRlcnMgPSB0aGlzLl9zZXRIZWFkZXJzKG9wdGlvbnMpO1xuXG4gICAgdGhpcy5fYWRkUHJveHlIZWFkZXJzKG9wdGlvbnMucmVxLCBoZWFkZXJzKTtcbiAgICB0aGlzLl9vdmVycmlkZVdpdGhVcExpbmtDb25maWdIZWFkZXJzKGhlYWRlcnMpO1xuXG4gICAgY29uc3QgbWV0aG9kID0gb3B0aW9ucy5tZXRob2QgfHwgJ0dFVCc7XG4gICAgY29uc3QgdXJpID0gb3B0aW9ucy51cmlfZnVsbCB8fCB0aGlzLmNvbmZpZy51cmwgKyBvcHRpb25zLnVyaTtcblxuICAgIHNlbGYubG9nZ2VyLmluZm8oXG4gICAgICB7XG4gICAgICAgIG1ldGhvZDogbWV0aG9kLFxuICAgICAgICB1cmk6IHVyaSxcbiAgICAgIH0sXG4gICAgICBcIm1ha2luZyByZXF1ZXN0OiAnQHttZXRob2R9IEB7dXJpfSdcIlxuICAgICk7XG5cbiAgICBpZiAoaXNPYmplY3Qob3B0aW9ucy5qc29uKSkge1xuICAgICAganNvbiA9IEpTT04uc3RyaW5naWZ5KG9wdGlvbnMuanNvbik7XG4gICAgICBoZWFkZXJzWydDb250ZW50LVR5cGUnXSA9IGhlYWRlcnNbJ0NvbnRlbnQtVHlwZSddIHx8IEhFQURFUlMuSlNPTjtcbiAgICB9XG5cbiAgICBjb25zdCByZXF1ZXN0Q2FsbGJhY2sgPSBjYlxuICAgICAgPyBmdW5jdGlvbiAoZXJyLCByZXMsIGJvZHkpOiB2b2lkIHtcbiAgICAgICAgICBsZXQgZXJyb3I7XG4gICAgICAgICAgY29uc3QgcmVzcG9uc2VMZW5ndGggPSBlcnIgPyAwIDogYm9keS5sZW5ndGg7XG5cbiAgICAgICAgICBwcm9jZXNzQm9keSgpO1xuICAgICAgICAgIGxvZ0FjdGl2aXR5KCk7XG5cbiAgICAgICAgICBjYihlcnIsIHJlcywgYm9keSk7XG5cbiAgICAgICAgICAvKipcbiAgICAgICAgICAgKiBQZXJmb3JtIGEgZGVjb2RlLlxuICAgICAgICAgICAqL1xuICAgICAgICAgIGZ1bmN0aW9uIHByb2Nlc3NCb2R5KCk6IHZvaWQge1xuICAgICAgICAgICAgaWYgKGVycikge1xuICAgICAgICAgICAgICBlcnJvciA9IGVyci5tZXNzYWdlO1xuICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChvcHRpb25zLmpzb24gJiYgcmVzLnN0YXR1c0NvZGUgPCAzMDApIHtcbiAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBib2R5ID0gSlNPTi5wYXJzZShib2R5LnRvU3RyaW5nKENIQVJBQ1RFUl9FTkNPRElORy5VVEY4KSk7XG4gICAgICAgICAgICAgIH0gY2F0Y2ggKF9lcnIpIHtcbiAgICAgICAgICAgICAgICBib2R5ID0ge307XG4gICAgICAgICAgICAgICAgZXJyID0gX2VycjtcbiAgICAgICAgICAgICAgICBlcnJvciA9IGVyci5tZXNzYWdlO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghZXJyICYmIGlzT2JqZWN0KGJvZHkpKSB7XG4gICAgICAgICAgICAgIGlmIChfLmlzU3RyaW5nKGJvZHkuZXJyb3IpKSB7XG4gICAgICAgICAgICAgICAgZXJyb3IgPSBib2R5LmVycm9yO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIC8qKlxuICAgICAgICAgICAqIFBlcmZvcm0gYSBsb2cuXG4gICAgICAgICAgICovXG4gICAgICAgICAgZnVuY3Rpb24gbG9nQWN0aXZpdHkoKTogdm9pZCB7XG4gICAgICAgICAgICBsZXQgbWVzc2FnZSA9IFwiQHshc3RhdHVzfSwgcmVxOiAnQHtyZXF1ZXN0Lm1ldGhvZH0gQHtyZXF1ZXN0LnVybH0nXCI7XG4gICAgICAgICAgICBtZXNzYWdlICs9IGVycm9yID8gJywgZXJyb3I6IEB7IWVycm9yfScgOiAnLCBieXRlczogQHtieXRlcy5pbn0vQHtieXRlcy5vdXR9JztcbiAgICAgICAgICAgIHNlbGYubG9nZ2VyLmh0dHAoXG4gICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBlcnI6IGVyciB8fCB1bmRlZmluZWQsIC8vIGlmIGVycm9yIGlzIG51bGwvZmFsc2UgY2hhbmdlIHRoaXMgdG8gdW5kZWZpbmVkIHNvIGl0IHdvbnQgbG9nXG4gICAgICAgICAgICAgICAgcmVxdWVzdDogeyBtZXRob2Q6IG1ldGhvZCwgdXJsOiB1cmkgfSxcbiAgICAgICAgICAgICAgICBzdGF0dXM6IHJlcyAhPSBudWxsID8gcmVzLnN0YXR1c0NvZGUgOiAnRVJSJyxcbiAgICAgICAgICAgICAgICBlcnJvcjogZXJyb3IsXG4gICAgICAgICAgICAgICAgYnl0ZXM6IHtcbiAgICAgICAgICAgICAgICAgIGluOiBqc29uID8ganNvbi5sZW5ndGggOiAwLFxuICAgICAgICAgICAgICAgICAgb3V0OiByZXNwb25zZUxlbmd0aCB8fCAwLFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIG1lc3NhZ2VcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICA6IHVuZGVmaW5lZDtcbiAgICBsZXQgcmVxdWVzdE9wdGlvbnM6IHJlcXVlc3QuT3B0aW9uc1dpdGhVcmwgPSB7XG4gICAgICB1cmw6IHVyaSxcbiAgICAgIG1ldGhvZDogbWV0aG9kLFxuICAgICAgaGVhZGVyczogaGVhZGVycyxcbiAgICAgIGJvZHk6IGpzb24sXG4gICAgICBwcm94eTogdGhpcy5wcm94eSxcbiAgICAgIGVuY29kaW5nOiBudWxsLFxuICAgICAgZ3ppcDogdHJ1ZSxcbiAgICAgIHRpbWVvdXQ6IHRoaXMudGltZW91dCxcbiAgICAgIHN0cmljdFNTTDogdGhpcy5zdHJpY3Rfc3NsLFxuICAgICAgYWdlbnRPcHRpb25zOiB0aGlzLmFnZW50X29wdGlvbnMsXG4gICAgfTtcblxuICAgIGlmICh0aGlzLmNhKSB7XG4gICAgICByZXF1ZXN0T3B0aW9ucyA9IE9iamVjdC5hc3NpZ24oe30sIHJlcXVlc3RPcHRpb25zLCB7XG4gICAgICAgIGNhOiB0aGlzLmNhLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgY29uc3QgcmVxID0gcmVxdWVzdChyZXF1ZXN0T3B0aW9ucywgcmVxdWVzdENhbGxiYWNrKTtcblxuICAgIGxldCBzdGF0dXNDYWxsZWQgPSBmYWxzZTtcbiAgICByZXEub24oJ3Jlc3BvbnNlJywgZnVuY3Rpb24gKHJlcyk6IHZvaWQge1xuICAgICAgLy8gRklYTUU6IF92ZXJkYWNjaW9fYWJvcnRlZCBzZWVtcyBub3QgdXNlZFxuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgaWYgKCFyZXEuX3ZlcmRhY2Npb19hYm9ydGVkICYmICFzdGF0dXNDYWxsZWQpIHtcbiAgICAgICAgc3RhdHVzQ2FsbGVkID0gdHJ1ZTtcbiAgICAgICAgc2VsZi5fc3RhdHVzQ2hlY2sodHJ1ZSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChfLmlzTmlsKHJlcXVlc3RDYWxsYmFjaykgPT09IGZhbHNlKSB7XG4gICAgICAgIChmdW5jdGlvbiBkb19sb2coKTogdm9pZCB7XG4gICAgICAgICAgY29uc3QgbWVzc2FnZSA9IFwiQHshc3RhdHVzfSwgcmVxOiAnQHtyZXF1ZXN0Lm1ldGhvZH0gQHtyZXF1ZXN0LnVybH0nIChzdHJlYW1pbmcpXCI7XG4gICAgICAgICAgc2VsZi5sb2dnZXIuaHR0cChcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgcmVxdWVzdDoge1xuICAgICAgICAgICAgICAgIG1ldGhvZDogbWV0aG9kLFxuICAgICAgICAgICAgICAgIHVybDogdXJpLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICBzdGF0dXM6IF8uaXNOdWxsKHJlcykgPT09IGZhbHNlID8gcmVzLnN0YXR1c0NvZGUgOiAnRVJSJyxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBtZXNzYWdlXG4gICAgICAgICAgKTtcbiAgICAgICAgfSkoKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICByZXEub24oJ2Vycm9yJywgZnVuY3Rpb24gKF9lcnIpOiB2b2lkIHtcbiAgICAgIC8vIEZJWE1FOiBfdmVyZGFjY2lvX2Fib3J0ZWQgc2VlbXMgbm90IHVzZWRcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGlmICghcmVxLl92ZXJkYWNjaW9fYWJvcnRlZCAmJiAhc3RhdHVzQ2FsbGVkKSB7XG4gICAgICAgIHN0YXR1c0NhbGxlZCA9IHRydWU7XG4gICAgICAgIHNlbGYuX3N0YXR1c0NoZWNrKGZhbHNlKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgcmV0dXJuIHJlcTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZXQgZGVmYXVsdCBoZWFkZXJzLlxuICAgKiBAcGFyYW0ge09iamVjdH0gb3B0aW9uc1xuICAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIF9zZXRIZWFkZXJzKG9wdGlvbnM6IGFueSk6IEhlYWRlcnMge1xuICAgIGNvbnN0IGhlYWRlcnMgPSBvcHRpb25zLmhlYWRlcnMgfHwge307XG4gICAgY29uc3QgYWNjZXB0ID0gSEVBREVSUy5BQ0NFUFQ7XG4gICAgY29uc3QgYWNjZXB0RW5jb2RpbmcgPSBIRUFERVJTLkFDQ0VQVF9FTkNPRElORztcbiAgICBjb25zdCB1c2VyQWdlbnQgPSBIRUFERVJTLlVTRVJfQUdFTlQ7XG5cbiAgICBoZWFkZXJzW2FjY2VwdF0gPSBoZWFkZXJzW2FjY2VwdF0gfHwgY29udGVudFR5cGVBY2NlcHQ7XG4gICAgaGVhZGVyc1thY2NlcHRFbmNvZGluZ10gPSBoZWFkZXJzW2FjY2VwdEVuY29kaW5nXSB8fCAnZ3ppcCc7XG4gICAgLy8gcmVnaXN0cnkubnBtanMub3JnIHdpbGwgb25seSByZXR1cm4gc2VhcmNoIHJlc3VsdCBpZiB1c2VyLWFnZW50IGluY2x1ZGUgc3RyaW5nICducG0nXG4gICAgaGVhZGVyc1t1c2VyQWdlbnRdID0gdGhpcy51c2VyQWdlbnRcbiAgICAgID8gYG5wbSAoJHt0aGlzLnVzZXJBZ2VudH0pYFxuICAgICAgOiBvcHRpb25zLnJlcT8uZ2V0KCd1c2VyLWFnZW50Jyk7XG5cbiAgICByZXR1cm4gdGhpcy5fc2V0QXV0aChoZWFkZXJzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZSBjb25maWd1cmF0aW9uIGF1dGggYW5kIGFzc2lnbiBIZWFkZXIgYXV0aG9yaXphdGlvblxuICAgKiBAcGFyYW0ge09iamVjdH0gaGVhZGVyc1xuICAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIF9zZXRBdXRoKGhlYWRlcnM6IGFueSk6IEhlYWRlcnMge1xuICAgIGNvbnN0IHsgYXV0aCB9ID0gdGhpcy5jb25maWc7XG5cbiAgICBpZiAoXy5pc05pbChhdXRoKSB8fCBoZWFkZXJzW0hFQURFUlMuQVVUSE9SSVpBVElPTl0pIHtcbiAgICAgIHJldHVybiBoZWFkZXJzO1xuICAgIH1cblxuICAgIGlmIChfLmlzT2JqZWN0KGF1dGgpID09PSBmYWxzZSAmJiBfLmlzT2JqZWN0KGF1dGgudG9rZW4pID09PSBmYWxzZSkge1xuICAgICAgdGhpcy5fdGhyb3dFcnJvckF1dGgoJ0F1dGggaW52YWxpZCcpO1xuICAgIH1cblxuICAgIC8vIGdldCBOUE1fVE9LRU4gaHR0cDovL2Jsb2cubnBtanMub3JnL3Bvc3QvMTE4MzkzMzY4NTU1L2RlcGxveWluZy13aXRoLW5wbS1wcml2YXRlLW1vZHVsZXNcbiAgICAvLyBvciBnZXQgb3RoZXIgdmFyaWFibGUgZXhwb3J0IGluIGVudlxuICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS92ZXJkYWNjaW8vdmVyZGFjY2lvL3JlbGVhc2VzL3RhZy92Mi41LjBcbiAgICBsZXQgdG9rZW46IGFueTtcbiAgICBjb25zdCB0b2tlbkNvbmY6IGFueSA9IGF1dGg7XG5cbiAgICBpZiAoXy5pc05pbCh0b2tlbkNvbmYudG9rZW4pID09PSBmYWxzZSAmJiBfLmlzU3RyaW5nKHRva2VuQ29uZi50b2tlbikpIHtcbiAgICAgIHRva2VuID0gdG9rZW5Db25mLnRva2VuO1xuICAgIH0gZWxzZSBpZiAoXy5pc05pbCh0b2tlbkNvbmYudG9rZW5fZW52KSA9PT0gZmFsc2UpIHtcbiAgICAgIGlmIChfLmlzU3RyaW5nKHRva2VuQ29uZi50b2tlbl9lbnYpKSB7XG4gICAgICAgIHRva2VuID0gcHJvY2Vzcy5lbnZbdG9rZW5Db25mLnRva2VuX2Vudl07XG4gICAgICB9IGVsc2UgaWYgKF8uaXNCb29sZWFuKHRva2VuQ29uZi50b2tlbl9lbnYpICYmIHRva2VuQ29uZi50b2tlbl9lbnYpIHtcbiAgICAgICAgdG9rZW4gPSBwcm9jZXNzLmVudi5OUE1fVE9LRU47XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcihFUlJPUl9DT0RFLnRva2VuX3JlcXVpcmVkKTtcbiAgICAgICAgdGhpcy5fdGhyb3dFcnJvckF1dGgoRVJST1JfQ09ERS50b2tlbl9yZXF1aXJlZCk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHRva2VuID0gcHJvY2Vzcy5lbnYuTlBNX1RPS0VOO1xuICAgIH1cblxuICAgIGlmIChfLmlzTmlsKHRva2VuKSkge1xuICAgICAgdGhpcy5fdGhyb3dFcnJvckF1dGgoRVJST1JfQ09ERS50b2tlbl9yZXF1aXJlZCk7XG4gICAgfVxuXG4gICAgLy8gZGVmaW5lIHR5cGUgQXV0aCBhbGxvdyBiYXNpYyBhbmQgYmVhcmVyXG4gICAgY29uc3QgdHlwZSA9IHRva2VuQ29uZi50eXBlIHx8IFRPS0VOX0JBU0lDO1xuICAgIHRoaXMuX3NldEhlYWRlckF1dGhvcml6YXRpb24oaGVhZGVycywgdHlwZSwgdG9rZW4pO1xuXG4gICAgcmV0dXJuIGhlYWRlcnM7XG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAgICogQHRocm93cyB7RXJyb3J9XG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIF90aHJvd0Vycm9yQXV0aChtZXNzYWdlOiBzdHJpbmcpOiBFcnJvciB7XG4gICAgdGhpcy5sb2dnZXIuZXJyb3IobWVzc2FnZSk7XG4gICAgdGhyb3cgbmV3IEVycm9yKG1lc3NhZ2UpO1xuICB9XG5cbiAgLyoqXG4gICAqIEFzc2lnbiBIZWFkZXIgYXV0aG9yaXphdGlvbiB3aXRoIHR5cGUgYXV0aGVudGljYXRpb25cbiAgICogQHBhcmFtIHtPYmplY3R9IGhlYWRlcnNcbiAgICogQHBhcmFtIHtzdHJpbmd9IHR5cGVcbiAgICogQHBhcmFtIHtzdHJpbmd9IHRva2VuXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIF9zZXRIZWFkZXJBdXRob3JpemF0aW9uKGhlYWRlcnM6IGFueSwgdHlwZTogc3RyaW5nLCB0b2tlbjogYW55KTogdm9pZCB7XG4gICAgY29uc3QgX3R5cGU6IHN0cmluZyA9IHR5cGUudG9Mb3dlckNhc2UoKTtcblxuICAgIGlmIChbVE9LRU5fQkVBUkVSLnRvTG93ZXJDYXNlKCksIFRPS0VOX0JBU0lDLnRvTG93ZXJDYXNlKCldLmluY2x1ZGVzKF90eXBlKSA9PT0gZmFsc2UpIHtcbiAgICAgIHRoaXMuX3Rocm93RXJyb3JBdXRoKGBBdXRoIHR5cGUgJyR7X3R5cGV9JyBub3QgYWxsb3dlZGApO1xuICAgIH1cblxuICAgIHR5cGUgPSBfLnVwcGVyRmlyc3QodHlwZSk7XG4gICAgaGVhZGVyc1tIRUFERVJTLkFVVEhPUklaQVRJT05dID0gYnVpbGRUb2tlbih0eXBlLCB0b2tlbik7XG4gIH1cblxuICAvKipcbiAgICogSXQgd2lsbCBhZGQgb3Igb3ZlcnJpZGUgc3BlY2lmaWVkIGhlYWRlcnMgZnJvbSBjb25maWcgZmlsZS5cbiAgICpcbiAgICogRWc6XG4gICAqXG4gICAqIHVwbGlua3M6XG4gICBucG1qczpcbiAgIHVybDogaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmcvXG4gICBoZWFkZXJzOlxuICAgQWNjZXB0OiBcImFwcGxpY2F0aW9uL3ZuZC5ucG0uaW5zdGFsbC12Mitqc29uOyBxPTEuMFwiXG4gICB2ZXJkYWNjaW8tc3RhZ2luZzpcbiAgIHVybDogaHR0cHM6Ly9teWNvbXBhbnkuY29tL25wbVxuICAgaGVhZGVyczpcbiAgIEFjY2VwdDogXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgIGF1dGhvcml6YXRpb246IFwiQmFzaWMgWW91ckJhc2U2NEVuY29kZWRDcmVkZW50aWFscz09XCJcblxuICAgKiBAcGFyYW0ge09iamVjdH0gaGVhZGVyc1xuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgcHJpdmF0ZSBfb3ZlcnJpZGVXaXRoVXBMaW5rQ29uZmlnSGVhZGVycyhoZWFkZXJzOiBIZWFkZXJzKTogYW55IHtcbiAgICBpZiAoIXRoaXMuY29uZmlnLmhlYWRlcnMpIHtcbiAgICAgIHJldHVybiBoZWFkZXJzO1xuICAgIH1cblxuICAgIC8vIGFkZC9vdmVycmlkZSBoZWFkZXJzIHNwZWNpZmllZCBpbiB0aGUgY29uZmlnXG4gICAgLyogZXNsaW50IGd1YXJkLWZvci1pbjogMCAqL1xuICAgIGZvciAoY29uc3Qga2V5IGluIHRoaXMuY29uZmlnLmhlYWRlcnMpIHtcbiAgICAgIGhlYWRlcnNba2V5XSA9IHRoaXMuY29uZmlnLmhlYWRlcnNba2V5XTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRGV0ZXJtaW5lIHdoZXRoZXIgY2FuIGZldGNoIGZyb20gdGhlIHByb3ZpZGVkIFVSTFxuICAgKiBAcGFyYW0geyp9IHVybFxuICAgKiBAcmV0dXJuIHtCb29sZWFufVxuICAgKi9cbiAgcHVibGljIGlzVXBsaW5rVmFsaWQodXJsOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICBjb25zdCB1cmxQYXJzZWQ6IFVybFdpdGhTdHJpbmdRdWVyeSA9IFVSTC5wYXJzZSh1cmwpO1xuICAgIGNvbnN0IGlzSFRUUFMgPSAodXJsRG9tYWluUGFyc2VkOiBVUkwpOiBib29sZWFuID0+XG4gICAgICB1cmxEb21haW5QYXJzZWQucHJvdG9jb2wgPT09ICdodHRwczonICYmXG4gICAgICAodXJsUGFyc2VkLnBvcnQgPT09IG51bGwgfHwgdXJsUGFyc2VkLnBvcnQgPT09ICc0NDMnKTtcbiAgICBjb25zdCBnZXRIb3N0ID0gKHVybERvbWFpblBhcnNlZCk6IGJvb2xlYW4gPT5cbiAgICAgIGlzSFRUUFModXJsRG9tYWluUGFyc2VkKSA/IHVybERvbWFpblBhcnNlZC5ob3N0bmFtZSA6IHVybERvbWFpblBhcnNlZC5ob3N0O1xuICAgIGNvbnN0IGlzTWF0Y2hQcm90b2NvbDogYm9vbGVhbiA9IHVybFBhcnNlZC5wcm90b2NvbCA9PT0gdGhpcy51cmwucHJvdG9jb2w7XG4gICAgY29uc3QgaXNNYXRjaEhvc3Q6IGJvb2xlYW4gPSBnZXRIb3N0KHVybFBhcnNlZCkgPT09IGdldEhvc3QodGhpcy51cmwpO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBjb25zdCBpc01hdGNoUGF0aDogYm9vbGVhbiA9IHVybFBhcnNlZC5wYXRoLmluZGV4T2YodGhpcy51cmwucGF0aCkgPT09IDA7XG5cbiAgICByZXR1cm4gaXNNYXRjaFByb3RvY29sICYmIGlzTWF0Y2hIb3N0ICYmIGlzTWF0Y2hQYXRoO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBhIHJlbW90ZSBwYWNrYWdlIG1ldGFkYXRhXG4gICAqIEBwYXJhbSB7Kn0gbmFtZSBwYWNrYWdlIG5hbWVcbiAgICogQHBhcmFtIHsqfSBvcHRpb25zIHJlcXVlc3Qgb3B0aW9ucywgZWc6IGVUYWcuXG4gICAqIEBwYXJhbSB7Kn0gY2FsbGJhY2tcbiAgICovXG4gIHB1YmxpYyBnZXRSZW1vdGVNZXRhZGF0YShuYW1lOiBzdHJpbmcsIG9wdGlvbnM6IGFueSwgY2FsbGJhY2s6IENhbGxiYWNrKTogdm9pZCB7XG4gICAgY29uc3QgaGVhZGVycyA9IHt9O1xuICAgIGlmIChfLmlzTmlsKG9wdGlvbnMuZXRhZykgPT09IGZhbHNlKSB7XG4gICAgICBoZWFkZXJzWydJZi1Ob25lLU1hdGNoJ10gPSBvcHRpb25zLmV0YWc7XG4gICAgICBoZWFkZXJzW0hFQURFUlMuQUNDRVBUXSA9IGNvbnRlbnRUeXBlQWNjZXB0O1xuICAgIH1cblxuICAgIHRoaXMucmVxdWVzdChcbiAgICAgIHtcbiAgICAgICAgdXJpOiBgLyR7ZW5jb2RlKG5hbWUpfWAsXG4gICAgICAgIGpzb246IHRydWUsXG4gICAgICAgIGhlYWRlcnM6IGhlYWRlcnMsXG4gICAgICAgIHJlcTogb3B0aW9ucy5yZXEsXG4gICAgICB9LFxuICAgICAgKGVyciwgcmVzLCBib2R5KTogdm9pZCA9PiB7XG4gICAgICAgIGlmIChlcnIpIHtcbiAgICAgICAgICByZXR1cm4gY2FsbGJhY2soZXJyKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocmVzLnN0YXR1c0NvZGUgPT09IEhUVFBfU1RBVFVTLk5PVF9GT1VORCkge1xuICAgICAgICAgIHJldHVybiBjYWxsYmFjayhFcnJvckNvZGUuZ2V0Tm90Rm91bmQoQVBJX0VSUk9SLk5PVF9QQUNLQUdFX1VQTElOSykpO1xuICAgICAgICB9XG4gICAgICAgIGlmICghKHJlcy5zdGF0dXNDb2RlID49IEhUVFBfU1RBVFVTLk9LICYmIHJlcy5zdGF0dXNDb2RlIDwgSFRUUF9TVEFUVVMuTVVMVElQTEVfQ0hPSUNFUykpIHtcbiAgICAgICAgICBjb25zdCBlcnJvciA9IEVycm9yQ29kZS5nZXRJbnRlcm5hbEVycm9yKFxuICAgICAgICAgICAgYCR7QVBJX0VSUk9SLkJBRF9TVEFUVVNfQ09ERX06ICR7cmVzLnN0YXR1c0NvZGV9YFxuICAgICAgICAgICk7XG5cbiAgICAgICAgICBlcnJvci5yZW1vdGVTdGF0dXMgPSByZXMuc3RhdHVzQ29kZTtcbiAgICAgICAgICByZXR1cm4gY2FsbGJhY2soZXJyb3IpO1xuICAgICAgICB9XG4gICAgICAgIGNhbGxiYWNrKG51bGwsIGJvZHksIHJlcy5oZWFkZXJzLmV0YWcpO1xuICAgICAgfVxuICAgICk7XG4gIH1cblxuICAvKipcbiAgICogRmV0Y2ggYSB0YXJiYWxsIGZyb20gdGhlIHVwbGluay5cbiAgICogQHBhcmFtIHtTdHJpbmd9IHVybFxuICAgKiBAcmV0dXJuIHtTdHJlYW19XG4gICAqL1xuICBwdWJsaWMgZmV0Y2hUYXJiYWxsKHVybDogc3RyaW5nKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gbmV3IFJlYWRUYXJiYWxsKHt9KTtcbiAgICBsZXQgY3VycmVudF9sZW5ndGggPSAwO1xuICAgIGxldCBleHBlY3RlZF9sZW5ndGg7XG5cbiAgICBzdHJlYW0uYWJvcnQgPSAoKSA9PiB7fTtcbiAgICBjb25zdCByZWFkU3RyZWFtID0gdGhpcy5yZXF1ZXN0KHtcbiAgICAgIHVyaV9mdWxsOiB1cmwsXG4gICAgICBlbmNvZGluZzogbnVsbCxcbiAgICAgIGhlYWRlcnM6IHtcbiAgICAgICAgQWNjZXB0OiBjb250ZW50VHlwZUFjY2VwdCxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICByZWFkU3RyZWFtLm9uKCdyZXNwb25zZScsIGZ1bmN0aW9uIChyZXM6IGFueSkge1xuICAgICAgaWYgKHJlcy5zdGF0dXNDb2RlID09PSBIVFRQX1NUQVRVUy5OT1RfRk9VTkQpIHtcbiAgICAgICAgcmV0dXJuIHN0cmVhbS5lbWl0KCdlcnJvcicsIEVycm9yQ29kZS5nZXROb3RGb3VuZChBUElfRVJST1IuTk9UX0ZJTEVfVVBMSU5LKSk7XG4gICAgICB9XG4gICAgICBpZiAoIShyZXMuc3RhdHVzQ29kZSA+PSBIVFRQX1NUQVRVUy5PSyAmJiByZXMuc3RhdHVzQ29kZSA8IEhUVFBfU1RBVFVTLk1VTFRJUExFX0NIT0lDRVMpKSB7XG4gICAgICAgIHJldHVybiBzdHJlYW0uZW1pdChcbiAgICAgICAgICAnZXJyb3InLFxuICAgICAgICAgIEVycm9yQ29kZS5nZXRJbnRlcm5hbEVycm9yKGBiYWQgdXBsaW5rIHN0YXR1cyBjb2RlOiAke3Jlcy5zdGF0dXNDb2RlfWApXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgICBpZiAocmVzLmhlYWRlcnNbSEVBREVSX1RZUEUuQ09OVEVOVF9MRU5HVEhdKSB7XG4gICAgICAgIGV4cGVjdGVkX2xlbmd0aCA9IHJlcy5oZWFkZXJzW0hFQURFUl9UWVBFLkNPTlRFTlRfTEVOR1RIXTtcbiAgICAgICAgc3RyZWFtLmVtaXQoSEVBREVSX1RZUEUuQ09OVEVOVF9MRU5HVEgsIHJlcy5oZWFkZXJzW0hFQURFUl9UWVBFLkNPTlRFTlRfTEVOR1RIXSk7XG4gICAgICB9XG5cbiAgICAgIHJlYWRTdHJlYW0ucGlwZShzdHJlYW0pO1xuICAgIH0pO1xuXG4gICAgcmVhZFN0cmVhbS5vbignZXJyb3InLCBmdW5jdGlvbiAoZXJyKSB7XG4gICAgICBzdHJlYW0uZW1pdCgnZXJyb3InLCBlcnIpO1xuICAgIH0pO1xuICAgIHJlYWRTdHJlYW0ub24oJ2RhdGEnLCBmdW5jdGlvbiAoZGF0YSkge1xuICAgICAgY3VycmVudF9sZW5ndGggKz0gZGF0YS5sZW5ndGg7XG4gICAgfSk7XG4gICAgcmVhZFN0cmVhbS5vbignZW5kJywgZnVuY3Rpb24gKGRhdGEpIHtcbiAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGRhdGEubGVuZ3RoO1xuICAgICAgfVxuICAgICAgaWYgKGV4cGVjdGVkX2xlbmd0aCAmJiBjdXJyZW50X2xlbmd0aCAhPSBleHBlY3RlZF9sZW5ndGgpIHtcbiAgICAgICAgc3RyZWFtLmVtaXQoJ2Vycm9yJywgRXJyb3JDb2RlLmdldEludGVybmFsRXJyb3IoQVBJX0VSUk9SLkNPTlRFTlRfTUlTTUFUQ0gpKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICByZXR1cm4gc3RyZWFtO1xuICB9XG5cbiAgLyoqXG4gICAqIFBlcmZvcm0gYSBzdHJlYW0gc2VhcmNoLlxuICAgKiBAcGFyYW0geyp9IG9wdGlvbnMgcmVxdWVzdCBvcHRpb25zXG4gICAqIEByZXR1cm4ge1N0cmVhbX1cbiAgICovXG4gIHB1YmxpYyBzZWFyY2gob3B0aW9uczogYW55KSB7XG4gICAgY29uc3QgdHJhbnNmb3JtU3RyZWFtOiBhbnkgPSBuZXcgU3RyZWFtLlBhc3NUaHJvdWdoKHsgb2JqZWN0TW9kZTogdHJ1ZSB9KTtcbiAgICBjb25zdCByZXF1ZXN0U3RyZWFtOiBTdHJlYW0uUmVhZGFibGUgPSB0aGlzLnJlcXVlc3Qoe1xuICAgICAgdXJpOiBvcHRpb25zLnJlcS51cmwsXG4gICAgICByZXE6IG9wdGlvbnMucmVxLFxuICAgICAgaGVhZGVyczoge1xuICAgICAgICAvLyBxdWVyeSBmb3Igc2VhcmNoXG4gICAgICAgIHJlZmVyZXI6IG9wdGlvbnMucmVxLmdldCgncmVmZXJlcicpLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIGNvbnN0IHBhcnNlUGFja2FnZSA9IChwa2c6IFBhY2thZ2UpOiB2b2lkID0+IHtcbiAgICAgIGlmIChpc09iamVjdE9yQXJyYXkocGtnKSkge1xuICAgICAgICB0cmFuc2Zvcm1TdHJlYW0uZW1pdCgnZGF0YScsIHBrZyk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIHJlcXVlc3RTdHJlYW0ub24oJ3Jlc3BvbnNlJywgKHJlcyk6IHZvaWQgPT4ge1xuICAgICAgaWYgKCFTdHJpbmcocmVzLnN0YXR1c0NvZGUpLm1hdGNoKC9eMlxcZFxcZCQvKSkge1xuICAgICAgICByZXR1cm4gdHJhbnNmb3JtU3RyZWFtLmVtaXQoXG4gICAgICAgICAgJ2Vycm9yJyxcbiAgICAgICAgICBFcnJvckNvZGUuZ2V0SW50ZXJuYWxFcnJvcihgYmFkIHN0YXR1cyBjb2RlICR7cmVzLnN0YXR1c0NvZGV9IGZyb20gdXBsaW5rYClcbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgLy8gU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9yZXF1ZXN0L3JlcXVlc3QjcmVxdWVzdG9wdGlvbnMtY2FsbGJhY2tcbiAgICAgIC8vIFJlcXVlc3QgbGlicmFyeSB3aWxsIG5vdCBkZWNvZGUgZ3ppcCBzdHJlYW0uXG4gICAgICBsZXQganNvblN0cmVhbTtcbiAgICAgIGlmIChyZXMuaGVhZGVyc1tIRUFERVJfVFlQRS5DT05URU5UX0VOQ09ESU5HXSA9PT0gSEVBREVSUy5HWklQKSB7XG4gICAgICAgIGpzb25TdHJlYW0gPSByZXMucGlwZSh6bGliLmNyZWF0ZVVuemlwKCkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAganNvblN0cmVhbSA9IHJlcztcbiAgICAgIH1cbiAgICAgIGpzb25TdHJlYW0ucGlwZShKU09OU3RyZWFtLnBhcnNlKCcqJykpLm9uKCdkYXRhJywgcGFyc2VQYWNrYWdlKTtcbiAgICAgIGpzb25TdHJlYW0ub24oJ2VuZCcsICgpOiB2b2lkID0+IHtcbiAgICAgICAgdHJhbnNmb3JtU3RyZWFtLmVtaXQoJ2VuZCcpO1xuICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICByZXF1ZXN0U3RyZWFtLm9uKCdlcnJvcicsIChlcnI6IEVycm9yKTogdm9pZCA9PiB7XG4gICAgICB0cmFuc2Zvcm1TdHJlYW0uZW1pdCgnZXJyb3InLCBlcnIpO1xuICAgIH0pO1xuXG4gICAgdHJhbnNmb3JtU3RyZWFtLmFib3J0ID0gKCk6IHZvaWQgPT4ge1xuICAgICAgLy8gRklYTUU6IHRoaXMgaXMgY2xlYXJseSBhIHBvdGVudGlhbCBpc3N1ZVxuICAgICAgLy8gdGhlcmUgaXMgbm8gYWJvcnQgbWV0aG9kIG9uIFN0cmVhbS5SZWFkYWJsZVxuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgcmVxdWVzdFN0cmVhbS5hYm9ydCgpO1xuICAgICAgdHJhbnNmb3JtU3RyZWFtLmVtaXQoJ2VuZCcpO1xuICAgIH07XG5cbiAgICByZXR1cm4gdHJhbnNmb3JtU3RyZWFtO1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZCBwcm94eSBoZWFkZXJzLlxuICAgKiBGSVhNRTogb2JqZWN0IG11dGF0aW9ucywgaXQgc2hvdWxkIHJldHVybiBhbiBuZXcgb2JqZWN0XG4gICAqIEBwYXJhbSB7Kn0gcmVxIHRoZSBodHRwIHJlcXVlc3RcbiAgICogQHBhcmFtIHsqfSBoZWFkZXJzIHRoZSByZXF1ZXN0IGhlYWRlcnNcbiAgICovXG4gIHByaXZhdGUgX2FkZFByb3h5SGVhZGVycyhyZXE6IGFueSwgaGVhZGVyczogYW55KTogdm9pZCB7XG4gICAgaWYgKHJlcSkge1xuICAgICAgLy8gT25seSBzdWJtaXQgWC1Gb3J3YXJkZWQtRm9yIGZpZWxkIGlmIHdlIGRvbid0IGhhdmUgYSBwcm94eSBzZWxlY3RlZFxuICAgICAgLy8gaW4gdGhlIGNvbmZpZyBmaWxlLlxuICAgICAgLy9cbiAgICAgIC8vIE90aGVyd2lzZSBtaXNjb25maWd1cmVkIHByb3h5IGNvdWxkIHJldHVybiA0MDc6XG4gICAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vcmxpZHdrYS9zaW5vcGlhL2lzc3Vlcy8yNTRcbiAgICAgIC8vXG4gICAgICAvLyBGSVhNRTogcHJveHkgbG9naWMgaXMgb2RkLCBzb21ldGhpbmcgaXMgd3JvbmcgaGVyZS5cbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGlmICghdGhpcy5wcm94eSkge1xuICAgICAgICBoZWFkZXJzWyd4LWZvcndhcmRlZC1mb3InXSA9XG4gICAgICAgICAgKHJlcS5nZXQoJ3gtZm9yd2FyZGVkLWZvcicpID8gcmVxLmdldCgneC1mb3J3YXJkZWQtZm9yJykgKyAnLCAnIDogJycpICtcbiAgICAgICAgICByZXEuY29ubmVjdGlvbi5yZW1vdGVBZGRyZXNzO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIGFsd2F5cyBhdHRhY2ggVmlhIGhlYWRlciB0byBhdm9pZCBsb29wcywgZXZlbiBpZiB3ZSdyZSBub3QgcHJveHlpbmdcbiAgICBoZWFkZXJzWyd2aWEnXSA9IHJlcSAmJiByZXEuZ2V0KCd2aWEnKSA/IHJlcS5nZXQoJ3ZpYScpICsgJywgJyA6ICcnO1xuXG4gICAgaGVhZGVyc1sndmlhJ10gKz0gJzEuMSAnICsgdGhpcy5zZXJ