matrix-react-sdk
Version:
SDK for matrix.org using React
542 lines (522 loc) • 72.4 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EMOJI_REGEX = void 0;
Object.defineProperty(exports, "Linkify", {
enumerable: true,
get: function () {
return _Linkify.Linkify;
}
});
exports.bodyToDiv = bodyToDiv;
exports.bodyToHtml = bodyToHtml;
exports.bodyToSpan = bodyToSpan;
exports.checkBlockNode = checkBlockNode;
exports.formatEmojis = formatEmojis;
exports.getHtmlText = getHtmlText;
exports.isUrlPermitted = isUrlPermitted;
Object.defineProperty(exports, "linkifyAndSanitizeHtml", {
enumerable: true,
get: function () {
return _Linkify.linkifyAndSanitizeHtml;
}
});
Object.defineProperty(exports, "linkifyElement", {
enumerable: true,
get: function () {
return _Linkify.linkifyElement;
}
});
exports.sanitizedHtmlNode = sanitizedHtmlNode;
exports.topicToHtml = topicToHtml;
exports.unicodeToShortcode = unicodeToShortcode;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _sanitizeHtml = _interopRequireDefault(require("sanitize-html"));
var _classnames = _interopRequireDefault(require("classnames"));
var _katex = _interopRequireDefault(require("katex"));
var _htmlEntities = require("html-entities");
var _escapeHtml = _interopRequireDefault(require("escape-html"));
var _emojibaseBindings = require("@matrix-org/emojibase-bindings");
var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore"));
var _Reply = require("./utils/Reply");
var _UrlUtils = require("./utils/UrlUtils");
var _Linkify = require("./Linkify");
var _strings = require("./utils/strings");
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /*
Copyright 2024 New Vector Ltd.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2017, 2018 New Vector Ltd
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
// Anything outside the basic multilingual plane will be a surrogate pair
const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/;
// And there a bunch more symbol characters that emojibase has within the
// BMP, so this includes the ranges from 'letterlike symbols' to
// 'miscellaneous symbols and arrows' which should catch all of them
// (with plenty of false positives, but that's OK)
const SYMBOL_PATTERN = /([\u2100-\u2bff])/;
// Regex pattern for non-emoji characters that can appear in an "all-emoji" message
// (Zero-Width Space, other whitespace)
const EMOJI_SEPARATOR_REGEX = /[\u200B\s]/g;
// Regex for emoji. This includes any RGI_Emoji sequence followed by an optional
// emoji presentation VS (U+FE0F), but not those sequences that are followed by
// a text presentation VS (U+FE0E). We also count lone regional indicators
// (U+1F1E6-U+1F1FF). Technically this regex produces false negatives for emoji
// followed by U+FE0E when the emoji doesn't have a text variant, but in
// practice this doesn't matter.
const EMOJI_REGEX = exports.EMOJI_REGEX = (() => {
try {
// Per our support policy, v mode is available to us, but we still don't
// want the app to completely crash on older platforms. We use the
// constructor here to avoid a syntax error on such platforms.
return new RegExp("\\p{RGI_Emoji}(?!\\uFE0E)(?:(?<!\\uFE0F)\\uFE0F)?|[\\u{1f1e6}-\\u{1f1ff}]", "v");
} catch (_e) {
// v mode not supported; fall back to matching nothing
return /(?!)/;
}
})();
const BIGEMOJI_REGEX = (() => {
try {
return new RegExp(`^(${EMOJI_REGEX.source})+$`, "iv");
} catch (_e) {
// Fall back, just like for EMOJI_REGEX
return /(?!)/;
}
})();
/*
* Return true if the given string contains emoji
* Uses a much, much simpler regex than emojibase's so will give false
* positives, but useful for fast-path testing strings to see if they
* need emojification.
*/
function mightContainEmoji(str) {
return !!str && (SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str));
}
/**
* Returns the shortcode for an emoji character.
*
* @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:)
*/
function unicodeToShortcode(char) {
const shortcodes = (0, _emojibaseBindings.getEmojiFromUnicode)(char)?.shortcodes;
return shortcodes?.length ? `:${shortcodes[0]}:` : "";
}
/*
* Given an untrusted HTML string, return a React node with an sanitized version
* of that HTML.
*/
function sanitizedHtmlNode(insaneHtml) {
const saneHtml = (0, _sanitizeHtml.default)(insaneHtml, _Linkify.sanitizeHtmlParams);
return /*#__PURE__*/_react.default.createElement("div", {
dangerouslySetInnerHTML: {
__html: saneHtml
},
dir: "auto"
});
}
function getHtmlText(insaneHtml) {
return (0, _sanitizeHtml.default)(insaneHtml, {
allowedTags: [],
allowedAttributes: {},
selfClosing: [],
allowedSchemes: [],
disallowedTagsMode: "discard"
});
}
/**
* Tests if a URL from an untrusted source may be safely put into the DOM
* The biggest threat here is javascript: URIs.
* Note that the HTML sanitiser library has its own internal logic for
* doing this, to which we pass the same list of schemes. This is used in
* other places we need to sanitise URLs.
* @return true if permitted, otherwise false
*/
function isUrlPermitted(inputUrl) {
try {
// URL parser protocol includes the trailing colon
return _UrlUtils.PERMITTED_URL_SCHEMES.includes(new URL(inputUrl).protocol.slice(0, -1));
} catch (e) {
return false;
}
}
// this is the same as the above except with less rewriting
const composerSanitizeHtmlParams = _objectSpread(_objectSpread({}, _Linkify.sanitizeHtmlParams), {}, {
transformTags: {
"code": _Linkify.transformTags["code"],
"*": _Linkify.transformTags["*"]
}
});
// reduced set of allowed tags to avoid turning topics into Myspace
const topicSanitizeHtmlParams = _objectSpread(_objectSpread({}, _Linkify.sanitizeHtmlParams), {}, {
allowedTags: ["font",
// custom to matrix for IRC-style font coloring
"del",
// for markdown
"s", "a", "sup", "sub", "b", "i", "u", "strong", "em", "strike", "br", "div", "span"]
});
class BaseHighlighter {
constructor(highlightClass, highlightLink) {
this.highlightClass = highlightClass;
this.highlightLink = highlightLink;
}
/**
* Apply the highlights to a section of text
*
* @param {string} safeSnippet The snippet of text to apply the highlights
* to. This input must be sanitised as it will be treated as HTML.
* @param {string[]} safeHighlights A list of substrings to highlight,
* sorted by descending length.
*
* returns a list of results (strings for HtmlHighligher, react nodes for
* TextHighlighter).
*/
applyHighlights(safeSnippet, safeHighlights) {
let lastOffset = 0;
let offset;
let nodes = [];
const safeHighlight = safeHighlights[0];
while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) {
// handle preamble
if (offset > lastOffset) {
const subSnippet = safeSnippet.substring(lastOffset, offset);
nodes = nodes.concat(this.applySubHighlights(subSnippet, safeHighlights));
}
// do highlight. use the original string rather than safeHighlight
// to preserve the original casing.
const endOffset = offset + safeHighlight.length;
nodes.push(this.processSnippet(safeSnippet.substring(offset, endOffset), true));
lastOffset = endOffset;
}
// handle postamble
if (lastOffset !== safeSnippet.length) {
const subSnippet = safeSnippet.substring(lastOffset, undefined);
nodes = nodes.concat(this.applySubHighlights(subSnippet, safeHighlights));
}
return nodes;
}
applySubHighlights(safeSnippet, safeHighlights) {
if (safeHighlights[1]) {
// recurse into this range to check for the next set of highlight matches
return this.applyHighlights(safeSnippet, safeHighlights.slice(1));
} else {
// no more highlights to be found, just return the unhighlighted string
return [this.processSnippet(safeSnippet, false)];
}
}
}
class HtmlHighlighter extends BaseHighlighter {
/* highlight the given snippet if required
*
* snippet: content of the span; must have been sanitised
* highlight: true to highlight as a search match
*
* returns an HTML string
*/
processSnippet(snippet, highlight) {
if (!highlight) {
// nothing required here
return snippet;
}
let span = `<span class="${this.highlightClass}">${snippet}</span>`;
if (this.highlightLink) {
span = `<a href="${encodeURI(this.highlightLink)}">${span}</a>`;
}
return span;
}
}
const emojiToHtmlSpan = emoji => `<span class='mx_Emoji' title='${unicodeToShortcode(emoji)}'>${emoji}</span>`;
const emojiToJsxSpan = (emoji, key) => /*#__PURE__*/_react.default.createElement("span", {
key: key,
className: "mx_Emoji",
title: unicodeToShortcode(emoji)
}, emoji);
/**
* Wraps emojis in <span> to style them separately from the rest of message. Consecutive emojis (and modifiers) are wrapped
* in the same <span>.
* @param {string} message the text to format
* @param {boolean} isHtmlMessage whether the message contains HTML
* @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis
* and plain text for everything else
*/
function formatEmojis(message, isHtmlMessage) {
const emojiToSpan = isHtmlMessage ? emojiToHtmlSpan : emojiToJsxSpan;
const result = [];
if (!message) return result;
let text = "";
let key = 0;
for (const data of _strings.graphemeSegmenter.segment(message)) {
if (EMOJI_REGEX.test(data.segment)) {
if (text) {
result.push(text);
text = "";
}
result.push(emojiToSpan(data.segment, key));
key++;
} else {
text += data.segment;
}
}
if (text) {
result.push(text);
}
return result;
}
function analyseEvent(content, highlights, opts = {}) {
let sanitizeParams = _Linkify.sanitizeHtmlParams;
if (opts.forComposerQuote) {
sanitizeParams = composerSanitizeHtmlParams;
}
try {
const isFormattedBody = content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
let bodyHasEmoji = false;
let isHtmlMessage = false;
let safeBody; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
// A search for `<foo` will make the browser crash an alternative would be to escape HTML special characters
// but that would bring no additional benefit as the highlighter does not work with those special chars
const safeHighlights = highlights?.filter(highlight => !highlight.includes("<")).map(highlight => (0, _sanitizeHtml.default)(highlight, sanitizeParams));
let formattedBody = typeof content.formatted_body === "string" ? content.formatted_body : null;
const plainBody = typeof content.body === "string" ? content.body : "";
if (opts.stripReplyFallback && formattedBody) formattedBody = (0, _Reply.stripHTMLReply)(formattedBody);
const strippedBody = opts.stripReplyFallback ? (0, _Reply.stripPlainReply)(plainBody) : plainBody;
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
const highlighter = safeHighlights?.length ? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink) : null;
if (isFormattedBody) {
if (highlighter) {
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeParams.textFilter = function (safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join("");
};
}
safeBody = (0, _sanitizeHtml.default)(formattedBody, sanitizeParams);
const phtml = new DOMParser().parseFromString(safeBody, "text/html");
const isPlainText = phtml.body.innerHTML === phtml.body.textContent;
isHtmlMessage = !isPlainText;
if (isHtmlMessage && _SettingsStore.default.getValue("feature_latex_maths")) {
[...phtml.querySelectorAll("div[data-mx-maths], span[data-mx-maths]")].forEach(e => {
e.outerHTML = _katex.default.renderToString((0, _htmlEntities.decode)(e.getAttribute("data-mx-maths")), {
throwOnError: false,
displayMode: e.tagName == "DIV",
output: "htmlAndMathml"
});
});
safeBody = phtml.body.innerHTML;
}
} else if (highlighter) {
safeBody = highlighter.applyHighlights((0, _escapeHtml.default)(plainBody), safeHighlights).join("");
}
return {
bodyHasEmoji,
isHtmlMessage,
strippedBody,
safeBody,
isFormattedBody
};
} finally {
delete sanitizeParams.textFilter;
}
}
function bodyToDiv(content, highlights, opts = {}, ref) {
const {
strippedBody,
formattedBody,
emojiBodyElements,
className
} = bodyToNode(content, highlights, opts);
return formattedBody ? /*#__PURE__*/_react.default.createElement("div", {
key: "body",
ref: ref,
className: className,
dangerouslySetInnerHTML: {
__html: formattedBody
},
dir: "auto"
}) : /*#__PURE__*/_react.default.createElement("div", {
key: "body",
ref: ref,
className: className,
dir: "auto"
}, emojiBodyElements || strippedBody);
}
function bodyToSpan(content, highlights, opts = {}, ref, includeDir = true) {
const {
strippedBody,
formattedBody,
emojiBodyElements,
className
} = bodyToNode(content, highlights, opts);
return formattedBody ? /*#__PURE__*/_react.default.createElement("span", {
key: "body",
ref: ref,
className: className,
dangerouslySetInnerHTML: {
__html: formattedBody
},
dir: includeDir ? "auto" : undefined
}) : /*#__PURE__*/_react.default.createElement("span", {
key: "body",
ref: ref,
className: className,
dir: includeDir ? "auto" : undefined
}, emojiBodyElements || strippedBody);
}
function bodyToNode(content, highlights, opts = {}) {
const eventInfo = analyseEvent(content, highlights, opts);
let emojiBody = false;
if (!opts.disableBigEmoji && eventInfo.bodyHasEmoji) {
const contentBody = eventInfo.safeBody ?? eventInfo.strippedBody;
let contentBodyTrimmed = contentBody !== undefined ? contentBody.trim() : "";
// Remove zero width joiner, zero width spaces and other spaces in body
// text. This ensures that emojis with spaces in between or that are made
// up of multiple unicode characters are still counted as purely emoji
// messages.
contentBodyTrimmed = contentBodyTrimmed.replace(EMOJI_SEPARATOR_REGEX, "");
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
emojiBody = match?.[0]?.length === contentBodyTrimmed.length && (
// Prevent user pills expanding for users with only emoji in
// their username. Permalinks (links in pills) can be any URL
// now, so we just check for an HTTP-looking thing.
eventInfo.strippedBody === eventInfo.safeBody ||
// replies have the html fallbacks, account for that here
content.formatted_body === undefined || !content.formatted_body.includes("http:") && !content.formatted_body.includes("https:"));
}
const className = (0, _classnames.default)({
"mx_EventTile_body": true,
"mx_EventTile_bigEmoji": emojiBody,
"markdown-body": eventInfo.isHtmlMessage && !emojiBody,
// Override the global `notranslate` class set by the top-level `matrixchat` div.
"translate": true
});
let formattedBody = eventInfo.safeBody;
if (eventInfo.isFormattedBody && eventInfo.bodyHasEmoji && eventInfo.safeBody) {
// This has to be done after the emojiBody check as to not break big emoji on replies
formattedBody = formatEmojis(eventInfo.safeBody, true).join("");
}
let emojiBodyElements;
if (!eventInfo.safeBody && eventInfo.bodyHasEmoji) {
emojiBodyElements = formatEmojis(eventInfo.strippedBody, false);
}
return {
strippedBody: eventInfo.strippedBody,
formattedBody,
emojiBodyElements,
className
};
}
/**
* Turn a matrix event body into html
*
* content: 'content' of the MatrixEvent
*
* highlights: optional list of words to highlight, ordered by longest word first
*
* opts.highlightLink: optional href to add to highlighted words
* opts.disableBigEmoji: optional argument to disable the big emoji class.
* opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing
* opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer
* opts.ref: React ref to attach to any React components returned (not compatible with opts.returnString)
*/
function bodyToHtml(content, highlights, opts = {}) {
const eventInfo = analyseEvent(content, highlights, opts);
let formattedBody = eventInfo.safeBody;
if (eventInfo.isFormattedBody && eventInfo.bodyHasEmoji && formattedBody) {
// This has to be done after the emojiBody check above as to not break big emoji on replies
formattedBody = formatEmojis(eventInfo.safeBody, true).join("");
}
return formattedBody ?? eventInfo.strippedBody;
}
/**
* Turn a room topic into html
* @param topic plain text topic
* @param htmlTopic optional html topic
* @param ref React ref to attach to any React components returned
* @param allowExtendedHtml whether to allow extended HTML tags such as headings and lists
* @return The HTML-ified node.
*/
function topicToHtml(topic, htmlTopic, ref, allowExtendedHtml = false) {
if (!_SettingsStore.default.getValue("feature_html_topic")) {
htmlTopic = undefined;
}
let isFormattedTopic = !!htmlTopic;
let topicHasEmoji = false;
let safeTopic = "";
try {
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic : topic);
if (isFormattedTopic) {
safeTopic = (0, _sanitizeHtml.default)(htmlTopic, allowExtendedHtml ? _Linkify.sanitizeHtmlParams : topicSanitizeHtmlParams);
if (topicHasEmoji) {
safeTopic = formatEmojis(safeTopic, true).join("");
}
}
} catch {
isFormattedTopic = false; // Fall back to plain-text topic
}
let emojiBodyElements;
if (!isFormattedTopic && topicHasEmoji) {
emojiBodyElements = formatEmojis(topic, false);
}
if (isFormattedTopic) {
if (!safeTopic) return null;
return /*#__PURE__*/_react.default.createElement("span", {
ref: ref,
dangerouslySetInnerHTML: {
__html: safeTopic
},
dir: "auto"
});
}
if (!emojiBodyElements && !topic) return null;
return /*#__PURE__*/_react.default.createElement("span", {
ref: ref,
dir: "auto"
}, emojiBodyElements || topic);
}
/**
* Returns if a node is a block element or not.
* Only takes html nodes into account that are allowed in matrix messages.
*
* @param {Node} node
* @returns {bool}
*/
function checkBlockNode(node) {
switch (node.nodeName) {
case "H1":
case "H2":
case "H3":
case "H4":
case "H5":
case "H6":
case "PRE":
case "BLOCKQUOTE":
case "P":
case "UL":
case "OL":
case "LI":
case "HR":
case "TABLE":
case "THEAD":
case "TBODY":
case "TR":
case "TH":
case "TD":
return true;
case "DIV":
// don't treat math nodes as block nodes for deserializing
return !node.hasAttribute("data-mx-maths");
default:
return false;
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwicmVxdWlyZSIsIl9zYW5pdGl6ZUh0bWwiLCJfY2xhc3NuYW1lcyIsIl9rYXRleCIsIl9odG1sRW50aXRpZXMiLCJfZXNjYXBlSHRtbCIsIl9lbW9qaWJhc2VCaW5kaW5ncyIsIl9TZXR0aW5nc1N0b3JlIiwiX1JlcGx5IiwiX1VybFV0aWxzIiwiX0xpbmtpZnkiLCJfc3RyaW5ncyIsIm93bktleXMiLCJlIiwiciIsInQiLCJPYmplY3QiLCJrZXlzIiwiZ2V0T3duUHJvcGVydHlTeW1ib2xzIiwibyIsImZpbHRlciIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsImVudW1lcmFibGUiLCJwdXNoIiwiYXBwbHkiLCJfb2JqZWN0U3ByZWFkIiwiYXJndW1lbnRzIiwibGVuZ3RoIiwiZm9yRWFjaCIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJkZWZhdWx0IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJkZWZpbmVQcm9wZXJ0eSIsIlNVUlJPR0FURV9QQUlSX1BBVFRFUk4iLCJTWU1CT0xfUEFUVEVSTiIsIkVNT0pJX1NFUEFSQVRPUl9SRUdFWCIsIkVNT0pJX1JFR0VYIiwiZXhwb3J0cyIsIlJlZ0V4cCIsIl9lIiwiQklHRU1PSklfUkVHRVgiLCJzb3VyY2UiLCJtaWdodENvbnRhaW5FbW9qaSIsInN0ciIsInRlc3QiLCJ1bmljb2RlVG9TaG9ydGNvZGUiLCJjaGFyIiwic2hvcnRjb2RlcyIsImdldEVtb2ppRnJvbVVuaWNvZGUiLCJzYW5pdGl6ZWRIdG1sTm9kZSIsImluc2FuZUh0bWwiLCJzYW5lSHRtbCIsInNhbml0aXplSHRtbCIsInNhbml0aXplSHRtbFBhcmFtcyIsImNyZWF0ZUVsZW1lbnQiLCJkYW5nZXJvdXNseVNldElubmVySFRNTCIsIl9faHRtbCIsImRpciIsImdldEh0bWxUZXh0IiwiYWxsb3dlZFRhZ3MiLCJhbGxvd2VkQXR0cmlidXRlcyIsInNlbGZDbG9zaW5nIiwiYWxsb3dlZFNjaGVtZXMiLCJkaXNhbGxvd2VkVGFnc01vZGUiLCJpc1VybFBlcm1pdHRlZCIsImlucHV0VXJsIiwiUEVSTUlUVEVEX1VSTF9TQ0hFTUVTIiwiaW5jbHVkZXMiLCJVUkwiLCJwcm90b2NvbCIsInNsaWNlIiwiY29tcG9zZXJTYW5pdGl6ZUh0bWxQYXJhbXMiLCJ0cmFuc2Zvcm1UYWdzIiwidG9waWNTYW5pdGl6ZUh0bWxQYXJhbXMiLCJCYXNlSGlnaGxpZ2h0ZXIiLCJjb25zdHJ1Y3RvciIsImhpZ2hsaWdodENsYXNzIiwiaGlnaGxpZ2h0TGluayIsImFwcGx5SGlnaGxpZ2h0cyIsInNhZmVTbmlwcGV0Iiwic2FmZUhpZ2hsaWdodHMiLCJsYXN0T2Zmc2V0Iiwib2Zmc2V0Iiwibm9kZXMiLCJzYWZlSGlnaGxpZ2h0IiwidG9Mb3dlckNhc2UiLCJpbmRleE9mIiwic3ViU25pcHBldCIsInN1YnN0cmluZyIsImNvbmNhdCIsImFwcGx5U3ViSGlnaGxpZ2h0cyIsImVuZE9mZnNldCIsInByb2Nlc3NTbmlwcGV0IiwidW5kZWZpbmVkIiwiSHRtbEhpZ2hsaWdodGVyIiwic25pcHBldCIsImhpZ2hsaWdodCIsInNwYW4iLCJlbmNvZGVVUkkiLCJlbW9qaVRvSHRtbFNwYW4iLCJlbW9qaSIsImVtb2ppVG9Kc3hTcGFuIiwia2V5IiwiY2xhc3NOYW1lIiwidGl0bGUiLCJmb3JtYXRFbW9qaXMiLCJtZXNzYWdlIiwiaXNIdG1sTWVzc2FnZSIsImVtb2ppVG9TcGFuIiwicmVzdWx0IiwidGV4dCIsImRhdGEiLCJncmFwaGVtZVNlZ21lbnRlciIsInNlZ21lbnQiLCJhbmFseXNlRXZlbnQiLCJjb250ZW50IiwiaGlnaGxpZ2h0cyIsIm9wdHMiLCJzYW5pdGl6ZVBhcmFtcyIsImZvckNvbXBvc2VyUXVvdGUiLCJpc0Zvcm1hdHRlZEJvZHkiLCJmb3JtYXQiLCJmb3JtYXR0ZWRfYm9keSIsImJvZHlIYXNFbW9qaSIsInNhZmVCb2R5IiwibWFwIiwiZm9ybWF0dGVkQm9keSIsInBsYWluQm9keSIsImJvZHkiLCJzdHJpcFJlcGx5RmFsbGJhY2siLCJzdHJpcEhUTUxSZXBseSIsInN0cmlwcGVkQm9keSIsInN0cmlwUGxhaW5SZXBseSIsImhpZ2hsaWdodGVyIiwidGV4dEZpbHRlciIsInNhZmVUZXh0Iiwiam9pbiIsInBodG1sIiwiRE9NUGFyc2VyIiwicGFyc2VGcm9tU3RyaW5nIiwiaXNQbGFpblRleHQiLCJpbm5lckhUTUwiLCJ0ZXh0Q29udGVudCIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsInF1ZXJ5U2VsZWN0b3JBbGwiLCJvdXRlckhUTUwiLCJrYXRleCIsInJlbmRlclRvU3RyaW5nIiwiZGVjb2RlIiwiZ2V0QXR0cmlidXRlIiwidGhyb3dPbkVycm9yIiwiZGlzcGxheU1vZGUiLCJ0YWdOYW1lIiwib3V0cHV0IiwiZXNjYXBlSHRtbCIsImJvZHlUb0RpdiIsInJlZiIsImVtb2ppQm9keUVsZW1lbnRzIiwiYm9keVRvTm9kZSIsImJvZHlUb1NwYW4iLCJpbmNsdWRlRGlyIiwiZXZlbnRJbmZvIiwiZW1vamlCb2R5IiwiZGlzYWJsZUJpZ0Vtb2ppIiwiY29udGVudEJvZHkiLCJjb250ZW50Qm9keVRyaW1tZWQiLCJ0cmltIiwicmVwbGFjZSIsIm1hdGNoIiwiZXhlYyIsImNsYXNzTmFtZXMiLCJib2R5VG9IdG1sIiwidG9waWNUb0h0bWwiLCJ0b3BpYyIsImh0bWxUb3BpYyIsImFsbG93RXh0ZW5kZWRIdG1sIiwiaXNGb3JtYXR0ZWRUb3BpYyIsInRvcGljSGFzRW1vamkiLCJzYWZlVG9waWMiLCJjaGVja0Jsb2NrTm9kZSIsIm5vZGUiLCJub2RlTmFtZSIsImhhc0F0dHJpYnV0ZSJdLCJzb3VyY2VzIjpbIi4uL3NyYy9IdG1sVXRpbHMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIi8qXG5Db3B5cmlnaHQgMjAyNCBOZXcgVmVjdG9yIEx0ZC5cbkNvcHlyaWdodCAyMDE5IE1pY2hhZWwgVGVsYXR5bnNraSA8N3QzY2hndXlAZ21haWwuY29tPlxuQ29weXJpZ2h0IDIwMTkgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cbkNvcHlyaWdodCAyMDE3LCAyMDE4IE5ldyBWZWN0b3IgTHRkXG5Db3B5cmlnaHQgMjAxNSwgMjAxNiBPcGVuTWFya2V0IEx0ZFxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgUmVhY3QsIHsgTGVnYWN5UmVmLCBSZWFjdE5vZGUgfSBmcm9tIFwicmVhY3RcIjtcbmltcG9ydCBzYW5pdGl6ZUh0bWwgZnJvbSBcInNhbml0aXplLWh0bWxcIjtcbmltcG9ydCBjbGFzc05hbWVzIGZyb20gXCJjbGFzc25hbWVzXCI7XG5pbXBvcnQga2F0ZXggZnJvbSBcImthdGV4XCI7XG5pbXBvcnQgeyBkZWNvZGUgfSBmcm9tIFwiaHRtbC1lbnRpdGllc1wiO1xuaW1wb3J0IHsgSUNvbnRlbnQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbWF0cml4XCI7XG5pbXBvcnQgeyBPcHRpb25hbCB9IGZyb20gXCJtYXRyaXgtZXZlbnRzLXNka1wiO1xuaW1wb3J0IGVzY2FwZUh0bWwgZnJvbSBcImVzY2FwZS1odG1sXCI7XG5pbXBvcnQgeyBnZXRFbW9qaUZyb21Vbmljb2RlIH0gZnJvbSBcIkBtYXRyaXgtb3JnL2Vtb2ppYmFzZS1iaW5kaW5nc1wiO1xuXG5pbXBvcnQgeyBJRXh0ZW5kZWRTYW5pdGl6ZU9wdGlvbnMgfSBmcm9tIFwiLi9AdHlwZXMvc2FuaXRpemUtaHRtbFwiO1xuaW1wb3J0IFNldHRpbmdzU3RvcmUgZnJvbSBcIi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuaW1wb3J0IHsgc3RyaXBIVE1MUmVwbHksIHN0cmlwUGxhaW5SZXBseSB9IGZyb20gXCIuL3V0aWxzL1JlcGx5XCI7XG5pbXBvcnQgeyBQRVJNSVRURURfVVJMX1NDSEVNRVMgfSBmcm9tIFwiLi91dGlscy9VcmxVdGlsc1wiO1xuaW1wb3J0IHsgc2FuaXRpemVIdG1sUGFyYW1zLCB0cmFuc2Zvcm1UYWdzIH0gZnJvbSBcIi4vTGlua2lmeVwiO1xuaW1wb3J0IHsgZ3JhcGhlbWVTZWdtZW50ZXIgfSBmcm9tIFwiLi91dGlscy9zdHJpbmdzXCI7XG5cbmV4cG9ydCB7IExpbmtpZnksIGxpbmtpZnlFbGVtZW50LCBsaW5raWZ5QW5kU2FuaXRpemVIdG1sIH0gZnJvbSBcIi4vTGlua2lmeVwiO1xuXG4vLyBBbnl0aGluZyBvdXRzaWRlIHRoZSBiYXNpYyBtdWx0aWxpbmd1YWwgcGxhbmUgd2lsbCBiZSBhIHN1cnJvZ2F0ZSBwYWlyXG5jb25zdCBTVVJST0dBVEVfUEFJUl9QQVRURVJOID0gLyhbXFx1ZDgwMC1cXHVkYmZmXSkoW1xcdWRjMDAtXFx1ZGZmZl0pLztcbi8vIEFuZCB0aGVyZSBhIGJ1bmNoIG1vcmUgc3ltYm9sIGNoYXJhY3RlcnMgdGhhdCBlbW9qaWJhc2UgaGFzIHdpdGhpbiB0aGVcbi8vIEJNUCwgc28gdGhpcyBpbmNsdWRlcyB0aGUgcmFuZ2VzIGZyb20gJ2xldHRlcmxpa2Ugc3ltYm9scycgdG9cbi8vICdtaXNjZWxsYW5lb3VzIHN5bWJvbHMgYW5kIGFycm93cycgd2hpY2ggc2hvdWxkIGNhdGNoIGFsbCBvZiB0aGVtXG4vLyAod2l0aCBwbGVudHkgb2YgZmFsc2UgcG9zaXRpdmVzLCBidXQgdGhhdCdzIE9LKVxuY29uc3QgU1lNQk9MX1BBVFRFUk4gPSAvKFtcXHUyMTAwLVxcdTJiZmZdKS87XG5cbi8vIFJlZ2V4IHBhdHRlcm4gZm9yIG5vbi1lbW9qaSBjaGFyYWN0ZXJzIHRoYXQgY2FuIGFwcGVhciBpbiBhbiBcImFsbC1lbW9qaVwiIG1lc3NhZ2Vcbi8vIChaZXJvLVdpZHRoIFNwYWNlLCBvdGhlciB3aGl0ZXNwYWNlKVxuY29uc3QgRU1PSklfU0VQQVJBVE9SX1JFR0VYID0gL1tcXHUyMDBCXFxzXS9nO1xuXG4vLyBSZWdleCBmb3IgZW1vamkuIFRoaXMgaW5jbHVkZXMgYW55IFJHSV9FbW9qaSBzZXF1ZW5jZSBmb2xsb3dlZCBieSBhbiBvcHRpb25hbFxuLy8gZW1vamkgcHJlc2VudGF0aW9uIFZTIChVK0ZFMEYpLCBidXQgbm90IHRob3NlIHNlcXVlbmNlcyB0aGF0IGFyZSBmb2xsb3dlZCBieVxuLy8gYSB0ZXh0IHByZXNlbnRhdGlvbiBWUyAoVStGRTBFKS4gV2UgYWxzbyBjb3VudCBsb25lIHJlZ2lvbmFsIGluZGljYXRvcnNcbi8vIChVKzFGMUU2LVUrMUYxRkYpLiBUZWNobmljYWxseSB0aGlzIHJlZ2V4IHByb2R1Y2VzIGZhbHNlIG5lZ2F0aXZlcyBmb3IgZW1vamlcbi8vIGZvbGxvd2VkIGJ5IFUrRkUwRSB3aGVuIHRoZSBlbW9qaSBkb2Vzbid0IGhhdmUgYSB0ZXh0IHZhcmlhbnQsIGJ1dCBpblxuLy8gcHJhY3RpY2UgdGhpcyBkb2Vzbid0IG1hdHRlci5cbmV4cG9ydCBjb25zdCBFTU9KSV9SRUdFWCA9ICgoKSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgICAgLy8gUGVyIG91ciBzdXBwb3J0IHBvbGljeSwgdiBtb2RlIGlzIGF2YWlsYWJsZSB0byB1cywgYnV0IHdlIHN0aWxsIGRvbid0XG4gICAgICAgIC8vIHdhbnQgdGhlIGFwcCB0byBjb21wbGV0ZWx5IGNyYXNoIG9uIG9sZGVyIHBsYXRmb3Jtcy4gV2UgdXNlIHRoZVxuICAgICAgICAvLyBjb25zdHJ1Y3RvciBoZXJlIHRvIGF2b2lkIGEgc3ludGF4IGVycm9yIG9uIHN1Y2ggcGxhdGZvcm1zLlxuICAgICAgICByZXR1cm4gbmV3IFJlZ0V4cChcIlxcXFxwe1JHSV9FbW9qaX0oPyFcXFxcdUZFMEUpKD86KD88IVxcXFx1RkUwRilcXFxcdUZFMEYpP3xbXFxcXHV7MWYxZTZ9LVxcXFx1ezFmMWZmfV1cIiwgXCJ2XCIpO1xuICAgIH0gY2F0Y2ggKF9lKSB7XG4gICAgICAgIC8vIHYgbW9kZSBub3Qgc3VwcG9ydGVkOyBmYWxsIGJhY2sgdG8gbWF0Y2hpbmcgbm90aGluZ1xuICAgICAgICByZXR1cm4gLyg/ISkvO1xuICAgIH1cbn0pKCk7XG5cbmNvbnN0IEJJR0VNT0pJX1JFR0VYID0gKCgpID0+IHtcbiAgICB0cnkge1xuICAgICAgICByZXR1cm4gbmV3IFJlZ0V4cChgXigke0VNT0pJX1JFR0VYLnNvdXJjZX0pKyRgLCBcIml2XCIpO1xuICAgIH0gY2F0Y2ggKF9lKSB7XG4gICAgICAgIC8vIEZhbGwgYmFjaywganVzdCBsaWtlIGZvciBFTU9KSV9SRUdFWFxuICAgICAgICByZXR1cm4gLyg/ISkvO1xuICAgIH1cbn0pKCk7XG5cbi8qXG4gKiBSZXR1cm4gdHJ1ZSBpZiB0aGUgZ2l2ZW4gc3RyaW5nIGNvbnRhaW5zIGVtb2ppXG4gKiBVc2VzIGEgbXVjaCwgbXVjaCBzaW1wbGVyIHJlZ2V4IHRoYW4gZW1vamliYXNlJ3Mgc28gd2lsbCBnaXZlIGZhbHNlXG4gKiBwb3NpdGl2ZXMsIGJ1dCB1c2VmdWwgZm9yIGZhc3QtcGF0aCB0ZXN0aW5nIHN0cmluZ3MgdG8gc2VlIGlmIHRoZXlcbiAqIG5lZWQgZW1vamlmaWNhdGlvbi5cbiAqL1xuZnVuY3Rpb24gbWlnaHRDb250YWluRW1vamkoc3RyPzogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICEhc3RyICYmIChTVVJST0dBVEVfUEFJUl9QQVRURVJOLnRlc3Qoc3RyKSB8fCBTWU1CT0xfUEFUVEVSTi50ZXN0KHN0cikpO1xufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIHNob3J0Y29kZSBmb3IgYW4gZW1vamkgY2hhcmFjdGVyLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBjaGFyIFRoZSBlbW9qaSBjaGFyYWN0ZXJcbiAqIEByZXR1cm4ge1N0cmluZ30gVGhlIHNob3J0Y29kZSAoc3VjaCBhcyA6dGh1bWJ1cDopXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB1bmljb2RlVG9TaG9ydGNvZGUoY2hhcjogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBjb25zdCBzaG9ydGNvZGVzID0gZ2V0RW1vamlGcm9tVW5pY29kZShjaGFyKT8uc2hvcnRjb2RlcztcbiAgICByZXR1cm4gc2hvcnRjb2Rlcz8ubGVuZ3RoID8gYDoke3Nob3J0Y29kZXNbMF19OmAgOiBcIlwiO1xufVxuXG4vKlxuICogR2l2ZW4gYW4gdW50cnVzdGVkIEhUTUwgc3RyaW5nLCByZXR1cm4gYSBSZWFjdCBub2RlIHdpdGggYW4gc2FuaXRpemVkIHZlcnNpb25cbiAqIG9mIHRoYXQgSFRNTC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNhbml0aXplZEh0bWxOb2RlKGluc2FuZUh0bWw6IHN0cmluZyk6IFJlYWN0Tm9kZSB7XG4gICAgY29uc3Qgc2FuZUh0bWwgPSBzYW5pdGl6ZUh0bWwoaW5zYW5lSHRtbCwgc2FuaXRpemVIdG1sUGFyYW1zKTtcblxuICAgIHJldHVybiA8ZGl2IGRhbmdlcm91c2x5U2V0SW5uZXJIVE1MPXt7IF9faHRtbDogc2FuZUh0bWwgfX0gZGlyPVwiYXV0b1wiIC8+O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0SHRtbFRleHQoaW5zYW5lSHRtbDogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gc2FuaXRpemVIdG1sKGluc2FuZUh0bWwsIHtcbiAgICAgICAgYWxsb3dlZFRhZ3M6IFtdLFxuICAgICAgICBhbGxvd2VkQXR0cmlidXRlczoge30sXG4gICAgICAgIHNlbGZDbG9zaW5nOiBbXSxcbiAgICAgICAgYWxsb3dlZFNjaGVtZXM6IFtdLFxuICAgICAgICBkaXNhbGxvd2VkVGFnc01vZGU6IFwiZGlzY2FyZFwiLFxuICAgIH0pO1xufVxuXG4vKipcbiAqIFRlc3RzIGlmIGEgVVJMIGZyb20gYW4gdW50cnVzdGVkIHNvdXJjZSBtYXkgYmUgc2FmZWx5IHB1dCBpbnRvIHRoZSBET01cbiAqIFRoZSBiaWdnZXN0IHRocmVhdCBoZXJlIGlzIGphdmFzY3JpcHQ6IFVSSXMuXG4gKiBOb3RlIHRoYXQgdGhlIEhUTUwgc2FuaXRpc2VyIGxpYnJhcnkgaGFzIGl0cyBvd24gaW50ZXJuYWwgbG9naWMgZm9yXG4gKiBkb2luZyB0aGlzLCB0byB3aGljaCB3ZSBwYXNzIHRoZSBzYW1lIGxpc3Qgb2Ygc2NoZW1lcy4gVGhpcyBpcyB1c2VkIGluXG4gKiBvdGhlciBwbGFjZXMgd2UgbmVlZCB0byBzYW5pdGlzZSBVUkxzLlxuICogQHJldHVybiB0cnVlIGlmIHBlcm1pdHRlZCwgb3RoZXJ3aXNlIGZhbHNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc1VybFBlcm1pdHRlZChpbnB1dFVybDogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgdHJ5IHtcbiAgICAgICAgLy8gVVJMIHBhcnNlciBwcm90b2NvbCBpbmNsdWRlcyB0aGUgdHJhaWxpbmcgY29sb25cbiAgICAgICAgcmV0dXJuIFBFUk1JVFRFRF9VUkxfU0NIRU1FUy5pbmNsdWRlcyhuZXcgVVJMKGlucHV0VXJsKS5wcm90b2NvbC5zbGljZSgwLCAtMSkpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbn1cblxuLy8gdGhpcyBpcyB0aGUgc2FtZSBhcyB0aGUgYWJvdmUgZXhjZXB0IHdpdGggbGVzcyByZXdyaXRpbmdcbmNvbnN0IGNvbXBvc2VyU2FuaXRpemVIdG1sUGFyYW1zOiBJRXh0ZW5kZWRTYW5pdGl6ZU9wdGlvbnMgPSB7XG4gICAgLi4uc2FuaXRpemVIdG1sUGFyYW1zLFxuICAgIHRyYW5zZm9ybVRhZ3M6IHtcbiAgICAgICAgXCJjb2RlXCI6IHRyYW5zZm9ybVRhZ3NbXCJjb2RlXCJdLFxuICAgICAgICBcIipcIjogdHJhbnNmb3JtVGFnc1tcIipcIl0sXG4gICAgfSxcbn07XG5cbi8vIHJlZHVjZWQgc2V0IG9mIGFsbG93ZWQgdGFncyB0byBhdm9pZCB0dXJuaW5nIHRvcGljcyBpbnRvIE15c3BhY2VcbmNvbnN0IHRvcGljU2FuaXRpemVIdG1sUGFyYW1zOiBJRXh0ZW5kZWRTYW5pdGl6ZU9wdGlvbnMgPSB7XG4gICAgLi4uc2FuaXRpemVIdG1sUGFyYW1zLFxuICAgIGFsbG93ZWRUYWdzOiBbXG4gICAgICAgIFwiZm9udFwiLCAvLyBjdXN0b20gdG8gbWF0cml4IGZvciBJUkMtc3R5bGUgZm9udCBjb2xvcmluZ1xuICAgICAgICBcImRlbFwiLCAvLyBmb3IgbWFya2Rvd25cbiAgICAgICAgXCJzXCIsXG4gICAgICAgIFwiYVwiLFxuICAgICAgICBcInN1cFwiLFxuICAgICAgICBcInN1YlwiLFxuICAgICAgICBcImJcIixcbiAgICAgICAgXCJpXCIsXG4gICAgICAgIFwidVwiLFxuICAgICAgICBcInN0cm9uZ1wiLFxuICAgICAgICBcImVtXCIsXG4gICAgICAgIFwic3RyaWtlXCIsXG4gICAgICAgIFwiYnJcIixcbiAgICAgICAgXCJkaXZcIixcbiAgICAgICAgXCJzcGFuXCIsXG4gICAgXSxcbn07XG5cbmFic3RyYWN0IGNsYXNzIEJhc2VIaWdobGlnaHRlcjxUIGV4dGVuZHMgUmVhY3QuUmVhY3ROb2RlPiB7XG4gICAgcHVibGljIGNvbnN0cnVjdG9yKFxuICAgICAgICBwdWJsaWMgaGlnaGxpZ2h0Q2xhc3M6IHN0cmluZyxcbiAgICAgICAgcHVibGljIGhpZ2hsaWdodExpbms/OiBzdHJpbmcsXG4gICAgKSB7fVxuXG4gICAgLyoqXG4gICAgICogQXBwbHkgdGhlIGhpZ2hsaWdodHMgdG8gYSBzZWN0aW9uIG9mIHRleHRcbiAgICAgKlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBzYWZlU25pcHBldCBUaGUgc25pcHBldCBvZiB0ZXh0IHRvIGFwcGx5IHRoZSBoaWdobGlnaHRzXG4gICAgICogICAgIHRvLiBUaGlzIGlucHV0IG11c3QgYmUgc2FuaXRpc2VkIGFzIGl0IHdpbGwgYmUgdHJlYXRlZCBhcyBIVE1MLlxuICAgICAqIEBwYXJhbSB7c3RyaW5nW119IHNhZmVIaWdobGlnaHRzIEEgbGlzdCBvZiBzdWJzdHJpbmdzIHRvIGhpZ2hsaWdodCxcbiAgICAgKiAgICAgc29ydGVkIGJ5IGRlc2NlbmRpbmcgbGVuZ3RoLlxuICAgICAqXG4gICAgICogcmV0dXJucyBhIGxpc3Qgb2YgcmVzdWx0cyAoc3RyaW5ncyBmb3IgSHRtbEhpZ2hsaWdoZXIsIHJlYWN0IG5vZGVzIGZvclxuICAgICAqIFRleHRIaWdobGlnaHRlcikuXG4gICAgICovXG4gICAgcHVibGljIGFwcGx5SGlnaGxpZ2h0cyhzYWZlU25pcHBldDogc3RyaW5nLCBzYWZlSGlnaGxpZ2h0czogc3RyaW5nW10pOiBUW10ge1xuICAgICAgICBsZXQgbGFzdE9mZnNldCA9IDA7XG4gICAgICAgIGxldCBvZmZzZXQ6IG51bWJlcjtcbiAgICAgICAgbGV0IG5vZGVzOiBUW10gPSBbXTtcblxuICAgICAgICBjb25zdCBzYWZlSGlnaGxpZ2h0ID0gc2FmZUhpZ2hsaWdodHNbMF07XG4gICAgICAgIHdoaWxlICgob2Zmc2V0ID0gc2FmZVNuaXBwZXQudG9Mb3dlckNhc2UoKS5pbmRleE9mKHNhZmVIaWdobGlnaHQudG9Mb3dlckNhc2UoKSwgbGFzdE9mZnNldCkpID49IDApIHtcbiAgICAgICAgICAgIC8vIGhhbmRsZSBwcmVhbWJsZVxuICAgICAgICAgICAgaWYgKG9mZnNldCA+IGxhc3RPZmZzZXQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBzdWJTbmlwcGV0ID0gc2FmZVNuaXBwZXQuc3Vic3RyaW5nKGxhc3RPZmZzZXQsIG9mZnNldCk7XG4gICAgICAgICAgICAgICAgbm9kZXMgPSBub2Rlcy5jb25jYXQodGhpcy5hcHBseVN1YkhpZ2hsaWdodHMoc3ViU25pcHBldCwgc2FmZUhpZ2hsaWdodHMpKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gZG8gaGlnaGxpZ2h0LiB1c2UgdGhlIG9yaWdpbmFsIHN0cmluZyByYXRoZXIgdGhhbiBzYWZlSGlnaGxpZ2h0XG4gICAgICAgICAgICAvLyB0byBwcmVzZXJ2ZSB0aGUgb3JpZ2luYWwgY2FzaW5nLlxuICAgICAgICAgICAgY29uc3QgZW5kT2Zmc2V0ID0gb2Zmc2V0ICsgc2FmZUhpZ2hsaWdodC5sZW5ndGg7XG4gICAgICAgICAgICBub2Rlcy5wdXNoKHRoaXMucHJvY2Vzc1NuaXBwZXQoc2FmZVNuaXBwZXQuc3Vic3RyaW5nKG9mZnNldCwgZW5kT2Zmc2V0KSwgdHJ1ZSkpO1xuXG4gICAgICAgICAgICBsYXN0T2Zmc2V0ID0gZW5kT2Zmc2V0O1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gaGFuZGxlIHBvc3RhbWJsZVxuICAgICAgICBpZiAobGFzdE9mZnNldCAhPT0gc2FmZVNuaXBwZXQubGVuZ3RoKSB7XG4gICAgICAgICAgICBjb25zdCBzdWJTbmlwcGV0ID0gc2FmZVNuaXBwZXQuc3Vic3RyaW5nKGxhc3RPZmZzZXQsIHVuZGVmaW5lZCk7XG4gICAgICAgICAgICBub2RlcyA9IG5vZGVzLmNvbmNhdCh0aGlzLmFwcGx5U3ViSGlnaGxpZ2h0cyhzdWJTbmlwcGV0LCBzYWZlSGlnaGxpZ2h0cykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBub2RlcztcbiAgICB9XG5cbiAgICBwcml2YXRlIGFwcGx5U3ViSGlnaGxpZ2h0cyhzYWZlU25pcHBldDogc3RyaW5nLCBzYWZlSGlnaGxpZ2h0czogc3RyaW5nW10pOiBUW10ge1xuICAgICAgICBpZiAoc2FmZUhpZ2hsaWdodHNbMV0pIHtcbiAgICAgICAgICAgIC8vIHJlY3Vyc2UgaW50byB0aGlzIHJhbmdlIHRvIGNoZWNrIGZvciB0aGUgbmV4dCBzZXQgb2YgaGlnaGxpZ2h0IG1hdGNoZXNcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmFwcGx5SGlnaGxpZ2h0cyhzYWZlU25pcHBldCwgc2FmZUhpZ2hsaWdodHMuc2xpY2UoMSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gbm8gbW9yZSBoaWdobGlnaHRzIHRvIGJlIGZvdW5kLCBqdXN0IHJldHVybiB0aGUgdW5oaWdobGlnaHRlZCBzdHJpbmdcbiAgICAgICAgICAgIHJldHVybiBbdGhpcy5wcm9jZXNzU25pcHBldChzYWZlU25pcHBldCwgZmFsc2UpXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHByb3RlY3RlZCBhYnN0cmFjdCBwcm9jZXNzU25pcHBldChzbmlwcGV0OiBzdHJpbmcsIGhpZ2hsaWdodDogYm9vbGVhbik6IFQ7XG59XG5cbmNsYXNzIEh0bWxIaWdobGlnaHRlciBleHRlbmRzIEJhc2VIaWdobGlnaHRlcjxzdHJpbmc+IHtcbiAgICAvKiBoaWdobGlnaHQgdGhlIGdpdmVuIHNuaXBwZXQgaWYgcmVxdWlyZWRcbiAgICAgKlxuICAgICAqIHNuaXBwZXQ6IGNvbnRlbnQgb2YgdGhlIHNwYW47IG11c3QgaGF2ZSBiZWVuIHNhbml0aXNlZFxuICAgICAqIGhpZ2hsaWdodDogdHJ1ZSB0byBoaWdobGlnaHQgYXMgYSBzZWFyY2ggbWF0Y2hcbiAgICAgKlxuICAgICAqIHJldHVybnMgYW4gSFRNTCBzdHJpbmdcbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgcHJvY2Vzc1NuaXBwZXQoc25pcHBldDogc3RyaW5nLCBoaWdobGlnaHQ6IGJvb2xlYW4pOiBzdHJpbmcge1xuICAgICAgICBpZiAoIWhpZ2hsaWdodCkge1xuICAgICAgICAgICAgLy8gbm90aGluZyByZXF1aXJlZCBoZXJlXG4gICAgICAgICAgICByZXR1cm4gc25pcHBldDtcbiAgICAgICAgfVxuXG4gICAgICAgIGxldCBzcGFuID0gYDxzcGFuIGNsYXNzPVwiJHt0aGlzLmhpZ2hsaWdodENsYXNzfVwiPiR7c25pcHBldH08L3NwYW4+YDtcblxuICAgICAgICBpZiAodGhpcy5oaWdobGlnaHRMaW5rKSB7XG4gICAgICAgICAgICBzcGFuID0gYDxhIGhyZWY9XCIke2VuY29kZVVSSSh0aGlzLmhpZ2hsaWdodExpbmspfVwiPiR7c3Bhbn08L2E+YDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc3BhbjtcbiAgICB9XG59XG5cbmNvbnN0IGVtb2ppVG9IdG1sU3BhbiA9IChlbW9qaTogc3RyaW5nKTogc3RyaW5nID0+XG4gICAgYDxzcGFuIGNsYXNzPSdteF9FbW9qaScgdGl0bGU9JyR7dW5pY29kZVRvU2hvcnRjb2RlKGVtb2ppKX0nPiR7ZW1vaml9PC9zcGFuPmA7XG5jb25zdCBlbW9qaVRvSnN4U3BhbiA9IChlbW9qaTogc3RyaW5nLCBrZXk6IG51bWJlcik6IEpTWC5FbGVtZW50ID0+IChcbiAgICA8c3BhbiBrZXk9e2tleX0gY2xhc3NOYW1lPVwibXhfRW1vamlcIiB0aXRsZT17dW5pY29kZVRvU2hvcnRjb2RlKGVtb2ppKX0+XG4gICAgICAgIHtlbW9qaX1cbiAgICA8L3NwYW4+XG4pO1xuXG4vKipcbiAqIFdyYXBzIGVtb2ppcyBpbiA8c3Bhbj4gdG8gc3R5bGUgdGhlbSBzZXBhcmF0ZWx5IGZyb20gdGhlIHJlc3Qgb2YgbWVzc2FnZS4gQ29uc2VjdXRpdmUgZW1vamlzIChhbmQgbW9kaWZpZXJzKSBhcmUgd3JhcHBlZFxuICogaW4gdGhlIHNhbWUgPHNwYW4+LlxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2UgdGhlIHRleHQgdG8gZm9ybWF0XG4gKiBAcGFyYW0ge2Jvb2xlYW59IGlzSHRtbE1lc3NhZ2Ugd2hldGhlciB0aGUgbWVzc2FnZSBjb250YWlucyBIVE1MXG4gKiBAcmV0dXJucyBpZiBpc0h0bWxNZXNzYWdlIGlzIHRydWUsIHJldHVybnMgYW4gYXJyYXkgb2Ygc3RyaW5ncywgb3RoZXJ3aXNlIHJldHVybiBhbiBhcnJheSBvZiBSZWFjdCBFbGVtZW50cyBmb3IgZW1vamlzXG4gKiBhbmQgcGxhaW4gdGV4dCBmb3IgZXZlcnl0aGluZyBlbHNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRFbW9qaXMobWVzc2FnZTogc3RyaW5nIHwgdW5kZWZpbmVkLCBpc0h0bWxNZXNzYWdlPzogZmFsc2UpOiBKU1guRWxlbWVudFtdO1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEVtb2ppcyhtZXNzYWdlOiBzdHJpbmcgfCB1bmRlZmluZWQsIGlzSHRtbE1lc3NhZ2U6IHRydWUpOiBzdHJpbmdbXTtcbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRFbW9qaXMobWVzc2FnZTogc3RyaW5nIHwgdW5kZWZpbmVkLCBpc0h0bWxNZXNzYWdlPzogYm9vbGVhbik6IChKU1guRWxlbWVudCB8IHN0cmluZylbXSB7XG4gICAgY29uc3QgZW1vamlUb1NwYW4gPSBpc0h0bWxNZXNzYWdlID8gZW1vamlUb0h0bWxTcGFuIDogZW1vamlUb0pzeFNwYW47XG4gICAgY29uc3QgcmVzdWx0OiAoSlNYLkVsZW1lbnQgfCBzdHJpbmcpW10gPSBbXTtcbiAgICBpZiAoIW1lc3NhZ2UpIHJldHVybiByZXN1bHQ7XG5cbiAgICBsZXQgdGV4dCA9IFwiXCI7XG4gICAgbGV0IGtleSA9IDA7XG5cbiAgICBmb3IgKGNvbnN0IGRhdGEgb2YgZ3JhcGhlbWVTZWdtZW50ZXIuc2VnbWVudChtZXNzYWdlKSkge1xuICAgICAgICBpZiAoRU1PSklfUkVHRVgudGVzdChkYXRhLnNlZ21lbnQpKSB7XG4gICAgICAgICAgICBpZiAodGV4dCkge1xuICAgICAgICAgICAgICAgIHJlc3VsdC5wdXNoKHRleHQpO1xuICAgICAgICAgICAgICAgIHRleHQgPSBcIlwiO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmVzdWx0LnB1c2goZW1vamlUb1NwYW4oZGF0YS5zZWdtZW50LCBrZXkpKTtcbiAgICAgICAgICAgIGtleSsrO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGV4dCArPSBkYXRhLnNlZ21lbnQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKHRleHQpIHtcbiAgICAgICAgcmVzdWx0LnB1c2godGV4dCk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbmludGVyZmFjZSBFdmVudEFuYWx5c2lzIHtcbiAgICBib2R5SGFzRW1vamk6IGJvb2xlYW47XG4gICAgaXNIdG1sTWVzc2FnZTogYm9vbGVhbjtcbiAgICBzdHJpcHBlZEJvZHk6IHN0cmluZztcbiAgICBzYWZlQm9keT86IHN0cmluZzsgLy8gc2FmZSwgc2FuaXRpc2VkIEhUTUwsIHByZWZlcnJlZCBvdmVyIGBzdHJpcHBlZEJvZHlgIHdoaWNoIGlzIGZ1bGx5IHBsYWludGV4dFxuICAgIGlzRm9ybWF0dGVkQm9keTogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBFdmVudFJlbmRlck9wdHMge1xuICAgIGhpZ2hsaWdodExpbms/OiBzdHJpbmc7XG4gICAgZGlzYWJsZUJpZ0Vtb2ppPzogYm9vbGVhbjtcbiAgICBzdHJpcFJlcGx5RmFsbGJhY2s/OiBib29sZWFuO1xuICAgIGZvckNvbXBvc2VyUXVvdGU/OiBib29sZWFuO1xufVxuXG5mdW5jdGlvbiBhbmFseXNlRXZlbnQoY29udGVudDogSUNvbnRlbnQsIGhpZ2hsaWdodHM6IE9wdGlvbmFsPHN0cmluZ1tdPiwgb3B0czogRXZlbnRSZW5kZXJPcHRzID0ge30pOiBFdmVudEFuYWx5c2lzIHtcbiAgICBsZXQgc2FuaXRpemVQYXJhbXMgPSBzYW5pdGl6ZUh0bWxQYXJhbXM7XG4gICAgaWYgKG9wdHMuZm9yQ29tcG9zZXJRdW90ZSkge1xuICAgICAgICBzYW5pdGl6ZVBhcmFtcyA9IGNvbXBvc2VyU2FuaXRpemVIdG1sUGFyYW1zO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGlzRm9ybWF0dGVkQm9keSA9XG4gICAgICAgICAgICBjb250ZW50LmZvcm1hdCA9PT0gXCJvcmcubWF0cml4LmN1c3RvbS5odG1sXCIgJiYgdHlwZW9mIGNvbnRlbnQuZm9ybWF0dGVkX2JvZHkgPT09IFwic3RyaW5nXCI7XG4gICAgICAgIGxldCBib2R5SGFzRW1vamkgPSBmYWxzZTtcbiAgICAgICAgbGV0IGlzSHRtbE1lc3NhZ2UgPSBmYWxzZTtcblxuICAgICAgICBsZXQgc2FmZUJvZHk6IHN0cmluZyB8IHVuZGVmaW5lZDsgLy8gc2FmZSwgc2FuaXRpc2VkIEhUTUwsIHByZWZlcnJlZCBvdmVyIGBzdHJpcHBlZEJvZHlgIHdoaWNoIGlzIGZ1bGx5IHBsYWludGV4dFxuXG4gICAgICAgIC8vIHNhbml0aXplSHRtbCBjYW4gaGFuZyBpZiBhbiB1bmNsb3NlZCBIVE1MIHRhZyBpcyB0aHJvd24gYXQgaXRcbiAgICAgICAgLy8gQSBzZWFyY2ggZm9yIGA8Zm9vYCB3aWxsIG1ha2UgdGhlIGJyb3dzZXIgY3Jhc2ggYW4gYWx0ZXJuYXRpdmUgd291bGQgYmUgdG8gZXNjYXBlIEhUTUwgc3BlY2lhbCBjaGFyYWN0ZXJzXG4gICAgICAgIC8vIGJ1dCB0aGF0IHdvdWxkIGJyaW5nIG5vIGFkZGl0aW9uYWwgYmVuZWZpdCBhcyB0aGUgaGlnaGxpZ2h0ZXIgZG9lcyBub3Qgd29yayB3aXRoIHRob3NlIHNwZWNpYWwgY2hhcnNcbiAgICAgICAgY29uc3Qgc2FmZUhpZ2hsaWdodHMgPSBoaWdobGlnaHRzXG4gICAgICAgICAgICA/LmZpbHRlcigoaGlnaGxpZ2h0OiBzdHJpbmcpOiBib29sZWFuID0+ICFoaWdobGlnaHQuaW5jbHVkZXMoXCI8XCIpKVxuICAgICAgICAgICAgLm1hcCgoaGlnaGxpZ2h0OiBzdHJpbmcpOiBzdHJpbmcgPT4gc2FuaXRpemVIdG1sKGhpZ2hsaWdodCwgc2FuaXRpemVQYXJhbXMpKTtcblxuICAgICAgICBsZXQgZm9ybWF0dGVkQm9keSA9IHR5cGVvZiBjb250ZW50LmZvcm1hdHRlZF9ib2R5ID09PSBcInN0cmluZ1wiID8gY29udGVudC5mb3JtYXR0ZWRfYm9keSA6IG51bGw7XG4gICAgICAgIGNvbnN0IHBsYWluQm9keSA9IHR5cGVvZiBjb250ZW50LmJvZHkgPT09IFwic3RyaW5nXCIgPyBjb250ZW50LmJvZHkgOiBcIlwiO1xuXG4gICAgICAgIGlmIChvcHRzLnN0cmlwUmVwbHlGYWxsYmFjayAmJiBmb3JtYXR0ZWRCb2R5KSBmb3JtYXR0ZWRCb2R5ID0gc3RyaXBIVE1MUmVwbHkoZm9ybWF0dGVkQm9keSk7XG4gICAgICAgIGNvbnN0IHN0cmlwcGVkQm9keSA9IG9wdHMuc3RyaXBSZXBseUZhbGxiYWNrID8gc3RyaXBQbGFpblJlcGx5KHBsYWluQm9keSkgOiBwbGFpbkJvZHk7XG4gICAgICAgIGJvZHlIYXNFbW9qaSA9IG1pZ2h0Q29udGFpbkVtb2ppKGlzRm9ybWF0dGVkQm9keSA/IGZvcm1hdHRlZEJvZHkhIDogcGxhaW5Cb2R5KTtcblxuICAgICAgICBjb25zdCBoaWdobGlnaHRlciA9IHNhZmVIaWdobGlnaHRzPy5sZW5ndGhcbiAgICAgICAgICAgID8gbmV3IEh0bWxIaWdobGlnaHRlcihcIm14X0V2ZW50VGlsZV9zZWFyY2hIaWdobGlnaHRcIiwgb3B0cy5oaWdobGlnaHRMaW5rKVxuICAgICAgICAgICAgOiBudWxsO1xuXG4gICAgICAgIGlmIChpc0Zvcm1hdHRlZEJvZHkpIHtcbiAgICAgICAgICAgIGlmIChoaWdobGlnaHRlcikge1xuICAgICAgICAgICAgICAgIC8vIFhYWDogV2Ugc2FuaXRpemUgdGhlIEhUTUwgd2hpbHN0IGFsc28gaGlnaGxpZ2h0aW5nIGl0cyB0ZXh0IG5vZGVzLCB0byBhdm9pZCBhY2NpZGVudGFsbHkgdHJ5aW5nXG4gICAgICAgICAgICAgICAgLy8gdG8gaGlnaGxpZ2h0IEhUTUwgdGFncyB0aGVtc2VsdmVzLiBIb3dldmVyLCB0aGlzIGRvZXMgbWVhbiB0aGF0IHdlIGRvbid0IGhpZ2hsaWdodCB0ZXh0bm9kZXMgd2hpY2hcbiAgICAgICAgICAgICAgICAvLyBhcmUgaW50ZXJydXB0ZWQgYnkgSFRNTCB0YWdzIChub3QgdGhhdCB3ZSBkaWQgYmVmb3JlKSAtIGUuZy4gZm9vPHNwYW4vPmJhciB3b24ndCBnZXQgaGlnaGxpZ2h0ZWRcbiAgICAgICAgICAgICAgICAvLyBieSBhbiBhdHRlbXB0IHRvIHNlYXJjaCBmb3IgJ2Zvb2JhcicuICBUaGVuIGFnYWluLCB0aGUgc2VhcmNoIHF1ZXJ5IHByb2JhYmx5IHdvdWxkbid0IHdvcmsgZWl0aGVyXG4gICAgICAgICAgICAgICAgLy8gWFhYOiBoYWNreSBib2RnZSB0byB0ZW1wb3JhcmlseSBhcHBseSBhIHRleHRGaWx0ZXIgdG8gdGhlIHNhbml0aXplUGFyYW1zIHN0cnVjdHVyZS5cbiAgICAgICAgICAgICAgICBzYW5pdGl6ZVBhcmFtcy50ZXh0RmlsdGVyID0gZnVuY3Rpb24gKHNhZmVUZXh0KSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBoaWdobGlnaHRlci5hcHBseUhpZ2hsaWdodHMoc2FmZVRleHQsIHNhZmVIaWdobGlnaHRzISkuam9pbihcIlwiKTtcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBzYWZlQm9keSA9IHNhbml0aXplSHRtbChmb3JtYXR0ZWRCb2R5ISwgc2FuaXRpemVQYXJhbXMpO1xuICAgICAgICAgICAgY29uc3QgcGh0bWwgPSBuZXcgRE9NUGFyc2VyKCkucGFyc2VGcm9tU3RyaW5nKHNhZmVCb2R5LCBcInRleHQvaHRtbFwiKTtcbiAgICAgICAgICAgIGNvbnN0IGlzUGxhaW5UZXh0ID0gcGh0bWwuYm9keS5pbm5lckhUTUwgPT09IHBodG1sLmJvZHkudGV4dENvbnRlbnQ7XG4gICAgICAgICAgICBpc0h0bWxNZXNzYWdlID0gIWlzUGxhaW5UZXh0O1xuXG4gICAgICAgICAgICBpZiAoaXNIdG1sTWVzc2FnZSAmJiBTZXR0aW5nc1N0b3JlLmdldFZhbHVlKFwiZmVhdHVyZV9sYXRleF9tYXRoc1wiKSkge1xuICAgICAgICAgICAgICAgIFsuLi5waHRtbC5xdWVyeVNlbGVjdG9yQWxsPEhUTUxFbGVtZW50PihcImRpdltkYXRhLW14LW1hdGhzXSwgc3BhbltkYXRhLW14LW1hdGhzXVwiKV0uZm9yRWFjaCgoZSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBlLm91dGVySFRNTCA9IGthdGV4LnJlbmRlclRvU3RyaW5nKGRlY29kZShlLmdldEF0dHJpYnV0ZShcImRhdGEtbXgtbWF0aHNcIikpLCB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aHJvd09uRXJyb3I6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICAgICAgZGlzcGxheU1vZGU6IGUudGFnTmFtZSA9PSBcIkRJVlwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0OiBcImh0bWxBbmRNYXRobWxcIixcbiAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgc2FmZUJvZHkgPSBwaHRtbC5ib2R5LmlubmVySFRNTDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChoaWdobGlnaHRlcikge1xuICAgICAgICAgICAgc2FmZUJvZHkgPSBoaWdobGlnaHRlci5hcHBseUhpZ2hsaWdodHMoZXNjYXBlSHRtbChwbGFpbkJvZHkpLCBzYWZlSGlnaGxpZ2h0cyEpLmpvaW4oXCJcIik7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4geyBib2R5SGFzRW1vamksIGlzSHRtbE1lc3NhZ2UsIHN0cmlwcGVkQm9keSwgc2FmZUJvZHksIGlzRm9ybWF0dGVkQm9keSB9O1xuICAgIH0gZmluYWxseSB7XG4gICAgICAgIGRlbGV0ZSBzYW5pdGl6ZVBhcmFtcy50ZXh0RmlsdGVyO1xuICAgIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGJvZHlUb0RpdihcbiAgICBjb250ZW50OiBJQ29udGVudCxcbiAgICBoaWdobGlnaHRzOiBPcHRpb25hbDxzdHJpbmdbXT4sXG4gICAgb3B0czogRXZlbnRSZW5kZXJPcHRzID0ge30sXG4gICAgcmVmPzogUmVhY3QuUmVmPEhUTUxEaXZFbGVtZW50Pixcbik6IFJlYWN0Tm9kZSB7XG4gICAgY29uc3QgeyBzdHJpcHBlZEJvZHksIGZvcm1hdHRlZEJvZHksIGVtb2ppQm9keUVsZW1lbnRzLCBjbGFzc05hbWUgfSA9IGJvZHlUb05vZGUoY29udGVudCwgaGlnaGxpZ2h0cywgb3B0cyk7XG5cbiAgICByZXR1cm4gZm9ybWF0dGVkQm9keSA/IChcbiAgICAgICAgPGRpdlxuICAgICAgICAgICAga2V5PVwiYm9keVwiXG4gICAgICAgICAgICByZWY9e3JlZn1cbiAgICAgICAgICAgIGNsYXNzTmFtZT17Y2xhc3NOYW1lfVxuICAgICAgICAgICAgZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUw9e3sgX19odG1sOiBmb3JtYXR0ZWRCb2R5IH19XG4gICAgICAgICAgICBkaXI9XCJhdXRvXCJcbiAgICAgICAgLz5cbiAgICApIDogKFxuICAgICAgICA8ZGl2IGtleT1cImJvZHlcIiByZWY9e3JlZn0gY2xhc3NOYW1lPXtjbGFzc05hbWV9IGRpcj1cImF1dG9cIj5cbiAgICAgICAgICAgIHtlbW9qaUJvZHlFbGVtZW50cyB8fCBzdHJpcHBlZEJvZHl9XG4gICAgICAgIDwvZGl2PlxuICAgICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBib2R5VG9TcGFuKFxuICAgIGNvbnRlbnQ6IElDb250ZW50LFxuICAgIGhpZ2hsaWdodHM6IE9wdGlvbmFsPHN0cmluZ1tdPixcbiAgICBvcHRzOiBFdmVudFJlbmRlck9wdHMgPSB7fSxcbiAgICByZWY/OiBSZWFjdC5SZWY8SFRNTFNwYW5FbGVtZW50PixcbiAgICBpbmNsdWRlRGlyID0gdHJ1ZSxcbik6IFJlYWN0Tm9kZSB7XG4gICAgY29uc3QgeyBzdHJpcHBlZEJvZHksIGZvcm1hdHRlZEJvZHksIGVtb2ppQm9keUVsZW1lbnRzLCBjbGFzc05hbWUgfSA9IGJvZHlUb05vZGUoY29udGVudCwgaGlnaGxpZ2h0cywgb3B0cyk7XG5cbiAgICByZXR1cm4gZm9ybWF0dGVkQm9keSA/IChcbiAgICAgICAgPHNwYW5cbiAgICAgICAgICAgIGtleT1cImJvZHlcIlxuICAgICAgICAgICAgcmVmPXtyZWZ9XG4gICAgICAgICAgICBjbGFzc05hbWU9e2NsYXNzTmFtZX1cbiAgICAgICAgICAgIGRhbmdlcm91c2x5U2V0SW5uZXJIVE1MPXt7IF9faHRtbDogZm9ybWF0dGVkQm9keSB9fVxuICAgICAgICAgICAgZGlyPXtpbmNsdWRlRGlyID8gXCJhdXRvXCIgOiB1bmRlZmluZWR9XG4gICAgICAgIC8+XG4gICAgKSA6IChcbiAgICAgICAgPHNwYW4ga2V5PVwiYm9keVwiIHJlZj17cmVmfSBjbGFzc05hbWU9e2NsYXNzTmFtZX0gZGlyPXtpbmNsdWRlRGlyID8gXCJhdXRvXCIgOiB1bmRlZmluZWR9PlxuICAgICAgICAgICAge2Vtb2ppQm9keUVsZW1lbnRzIHx8IHN0cmlwcGVkQm9keX1cbiAgICAgICAgPC9zcGFuPlxuICAgICk7XG59XG5cbmludGVyZmFjZSBCb2R5VG9Ob2RlUmV0dXJuIHtcbiAgICBzdHJpcHBlZEJvZHk6IHN0cmluZztcbiAgICBmb3JtYXR0ZWRCb2R5Pzogc3RyaW5nO1xuICAgIGVtb2ppQm9keUVsZW1lbnRzOiBKU1guRWxlbWVudFtdIHwgdW5kZWZpbmVkO1xuICAgIGNsYXNzTmFtZTogc3RyaW5nO1xufVxuXG5mdW5jdGlvbiBib2R5VG9Ob2RlKGNvbnRlbnQ6IElDb250ZW50LCBoaWdobGlnaHRzOiBPcHRpb25hbDxzdHJpbmdbXT4sIG9wdHM6IEV2ZW50UmVuZGVyT3B0cyA9IHt9KTogQm9keVRvTm9kZVJldHVybiB7XG4gICAgY29uc3QgZXZlbnRJbmZvID0gYW5hbHlzZUV2ZW50KGNvbnRlbnQsIGhpZ2hsaWdodHMsIG9wdHMpO1xuXG4gICAgbGV0IGVtb2ppQm9keSA9IGZhbHNlO1xuICAgIGlmICghb3B0cy5kaXNhYmxlQmlnRW1vamkgJiYgZXZlbnRJbmZvLmJvZHlIYXNFbW9qaSkge1xuICAgICAgICBjb25zdCBjb250ZW50Qm9keSA9IGV2ZW50SW5mby5zYWZlQm9keSA/PyBldmVudEluZm8uc3RyaXBwZWRCb2R5O1xuICAgICAgICBsZXQgY29udGVudEJvZHlUcmltbWVkID0gY29udGVudEJvZHkgIT09IHVuZGVmaW5lZCA/IGNvbnRlbnRCb2R5LnRyaW0oKSA6IFwiXCI7XG5cbiAgICAgICAgLy8gUmVtb3ZlIHplcm8gd2lkdGggam9pbmVyLCB6ZXJvIHdpZHRoIHNwYWNlcyBhbmQgb3RoZXIgc3BhY2VzIGluIGJvZHlcbiAgICAgICAgLy8gdGV4dC4gVGhpcyBlbnN1cmVzIHRoYXQgZW1vamlzIHdpdGggc3BhY2VzIGluIGJldHdlZW4gb3IgdGhhdCBhcmUgbWFkZVxuICAgICAgICAvLyB1cCBvZiBtdWx0aXBsZSB1bmljb2RlIGNoYXJhY3RlcnMgYXJlIHN0aWxsIGNvdW50ZWQgYXMgcHVyZWx5IGVtb2ppXG4gICAgICAgIC8vIG1lc3NhZ2VzLlxuICAgICAgICBjb250ZW50Qm9keVRyaW1tZWQgPSBjb250ZW50Qm9keVRyaW1tZWQucmVwbGFjZShFTU9KSV9TRVBBUkFUT1JfUkVHRVgsIFwiXCIpO1xuXG4gICAgICAgIGNvbnN0IG1hdGNoID0gQklHRU1PSklfUkVHRVguZXhlYyhjb250ZW50Qm9keVRyaW1tZWQpO1xuICAgICAgICBlbW9qaUJvZHkgPVxuICAgICAgICAgICAgbWF0Y2g/LlswXT8ubGVuZ3RoID09PSBjb250ZW50Qm9keVRyaW1tZWQubGVuZ3RoICYmXG4gICAgICAgICAgICAvLyBQcmV2ZW50IHVzZXIgcGlsbHMgZXhwYW5kaW5nIGZvciB1c2VycyB3aXRoIG9ubHkgZW1vamkgaW5cbiAgICAgICAgICAgIC8vIHRoZWlyIHVzZXJuYW1lLiBQZXJtYWxpbmtzIChsaW5rcyBpbiBwaWxscykgY2FuIGJlIGFueSBVUkxcbiAgICAgICAgICAgIC8vIG5vdywgc28gd2UganVzdCBjaGVjayBmb3IgYW4gSFRUUC1sb29raW5nIHRoaW5nLlxuICAgICAgICAgICAgKGV2ZW50SW5mby5zdHJpcHBlZEJvZHkgPT09IGV2ZW50SW5mby5zYWZlQm9keSB8fCAvLyByZXBsaWVzIGhhdmUgdGhlIGh0bWwgZmFsbGJhY2tzLCBhY2NvdW50IGZvciB0aGF0IGhlcmVcbiAgICAgICAgICAgICAgICBjb250ZW50LmZvcm1hdHRlZF9ib2R5ID09PSB1bmRlZmluZWQgfHxcbiAgICAgICAgICAgICAgICAoIWNvbnRlbnQuZm9ybWF0dGVkX2JvZHkuaW5jbHVkZXMoXCJodHRwOlwiKSAmJiAhY29udGVudC5mb3JtYXR0ZWRfYm9keS5pbmNsdWRlcyhcImh0dHBzOlwiKSkpO1xuICAgIH1cblxuICAgIGNvbnN0IGNsYXNzTmFtZSA9IGNsYXNzTmFtZXMoe1xuICAgICAgICBcIm14X0V2ZW50VGlsZV9ib2R5XCI6IHRydWUsXG4gICAgICAgIFwibXhfRXZlbnRUaWxlX2JpZ0Vtb2ppXCI6IGVtb2ppQm9keSxcbiAgICAgICAgXCJtYXJrZG93bi1ib2R5XCI6IGV2ZW50SW5mby5pc0h0bWxNZXNzYWdlICYmICFlbW9qaUJvZHksXG4gICAgICAgIC8vIE92ZXJyaWRlIHRoZSBnbG9iYWwgYG5vdHJhbnNsYXRlYCBjbGFzcyBzZXQgYnkgdGhlIHRvcC1sZXZlbCBgbWF0cml4Y2hhdGAgZGl2LlxuICAgICAgICBcInRyYW5zbGF0ZVwiOiB0cnVlLFxuICAgIH0pO1xuXG4gICAgbGV0IGZvcm1hdHRlZEJvZHkgPSBldmVudEluZm8uc2FmZUJvZHk7XG4gICAgaWYgKGV2ZW50SW5mby5pc0Zvcm1hdHRlZEJvZHkgJiYgZXZlbnRJbmZvLmJvZHlIYXNFbW9qaSAmJiBldmVudEluZm8uc2FmZUJvZHkpIHtcbiAgICAgICAgLy8gVGhpcyBoYXMgdG8gYmUgZG9uZSBhZnRlciB0aGUgZW1vamlCb2R5IGNoZWNrIGFzIHRvIG5vdCBicmVhayBiaWcgZW1vamkgb24gcmVwbGllc1xuICAgICAgICBmb3JtYXR0ZWRCb2R5ID0gZm9ybWF0RW1vamlzKGV2ZW50SW5mby5zYWZlQm9keSwgdHJ1ZSkuam9pbihcIlwiKTtcbiAgICB9XG5cbiAgICBsZXQgZW1vamlCb2R5RWxlbWVudHM6IEpTWC5FbGVtZW50W10gfCB1bmRlZmluZWQ7XG4gICAgaWYgKCFldmVudEluZm8uc2FmZUJvZHkgJiYgZXZlbnRJbmZvLmJvZHlIYXNFbW9qaSkge1xuICAgICAgICBlbW9qaUJvZHlFbGVtZW50cyA9IGZvcm1hdEVtb2ppcyhldmVudEluZm8uc3RyaXBwZWRCb2R5LCBmYWxzZSkgYXMgSlNYLkVsZW1lbnRbXTtcbiAgICB9XG5cbiAgICByZXR1cm4geyBzdHJpcHBlZEJvZHk6IGV2ZW50SW5mby5zdHJpcHBlZEJvZHksIGZvcm1hdHRlZEJvZHksIGVtb2ppQm9keUVsZW1lbnRzLCBjbGFzc05hbWUgfTtcbn1cblxuLyoqXG4gKiBUdXJuIGEgbWF0cml4IGV2ZW50IGJvZHkgaW50byBodG1sXG4gKlxuICogY29udGVudDogJ2NvbnRlbnQnIG9mIHRoZSBNYXRyaXhFdmVudFxuICpcbiAqIGhpZ2hsaWdodHM6IG9wdGlvbmFsIGxpc3Qgb2Ygd29yZHMgdG8gaGlnaGxpZ2h0LCBvcmRlcmVkIGJ5IGxvbmdlc3Qgd29yZCBmaXJzdFxuICpcbiAqIG9wdHMuaGlnaGxpZ2h0TGluazogb3B0aW9uYWwgaHJlZiB0byBhZGQgdG8gaGlnaGxpZ2h0ZWQgd29yZHNcbiAqIG9wdHMuZGlzYWJsZUJpZ0Vtb2ppOiBvcHRpb25hbCBhcmd1bWVudCB0byBkaXNhYmxlIHRoZSBiaWcgZW1vamkg