UNPKG

matrix-react-sdk

Version:
287 lines (281 loc) 39.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.editBodyDiffToHtml = editBodyDiffToHtml; var _react = _interopRequireDefault(require("react")); var _classnames = _interopRequireDefault(require("classnames")); var _diffMatchPatch = _interopRequireDefault(require("diff-match-patch")); var _diffDom = require("diff-dom"); var _logger = require("matrix-js-sdk/src/logger"); var _lodash = require("lodash"); var _HtmlUtils = require("../HtmlUtils"); /* Copyright 2024 New Vector Ltd. Copyright 2019-2021 , 2023 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ function textToHtml(text) { const container = document.createElement("div"); container.textContent = text; return container.innerHTML; } function getSanitizedHtmlBody(content) { const opts = { stripReplyFallback: true }; if (content.format === "org.matrix.custom.html") { return (0, _HtmlUtils.bodyToHtml)(content, null, opts); } else { // convert the string to something that can be safely // embedded in an html document, e.g. use html entities where needed // This is also needed so that DiffDOM wouldn't interpret something // as a tag when somebody types e.g. "</sarcasm>" // as opposed to bodyToHtml, here we also render // text messages with dangerouslySetInnerHTML, to unify // the code paths and because we need html to show differences return textToHtml((0, _HtmlUtils.bodyToHtml)(content, null, opts)); } } function wrapInsertion(child) { const wrapper = document.createElement((0, _HtmlUtils.checkBlockNode)(child) ? "div" : "span"); wrapper.className = "mx_EditHistoryMessage_insertion"; wrapper.appendChild(child); return wrapper; } function wrapDeletion(child) { const wrapper = document.createElement((0, _HtmlUtils.checkBlockNode)(child) ? "div" : "span"); wrapper.className = "mx_EditHistoryMessage_deletion"; wrapper.appendChild(child); return wrapper; } function findRefNodes(root, route, isAddition = false) { let refNode = root; let refParentNode; const end = isAddition ? route.length - 1 : route.length; for (let i = 0; i < end; ++i) { refParentNode = refNode; refNode = refNode?.childNodes[route[i]]; } return { refNode, refParentNode }; } function isTextNode(node) { return node.nodeName === "#text"; } function diffTreeToDOM(desc) { if (isTextNode(desc)) { return stringAsTextNode(desc.data); } else { const node = document.createElement(desc.nodeName); for (const [key, value] of Object.entries(desc.attributes)) { node.setAttribute(key, value.value); } if (desc.childNodes) { for (const childDesc of desc.childNodes) { node.appendChild(diffTreeToDOM(childDesc)); } } return node; } } function insertBefore(parent, nextSibling, child) { if (nextSibling) { parent.insertBefore(child, nextSibling); } else { parent.appendChild(child); } } function isRouteOfNextSibling(route1, route2) { // routes are arrays with indices, // to be interpreted as a path in the dom tree // ensure same parent for (let i = 0; i < route1.length - 1; ++i) { if (route1[i] !== route2[i]) { return false; } } // the route2 is only affected by the diff of route1 // inserting an element if the index at the level of the // last element of route1 being larger // (e.g. coming behind route1 at that level) const lastD1Idx = route1.length - 1; return route2[lastD1Idx] >= route1[lastD1Idx]; } function adjustRoutes(diff, remainingDiffs) { if (diff.action === "removeTextElement" || diff.action === "removeElement") { // as removed text is not removed from the html, but marked as deleted, // we need to readjust indices that assume the current node has been removed. const advance = 1; for (const rd of remainingDiffs) { if (isRouteOfNextSibling(diff.route, rd.route)) { rd.route[diff.route.length - 1] += advance; } } } } function stringAsTextNode(string) { return document.createTextNode((0, _lodash.unescape)(string)); } function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) { const { refNode, refParentNode } = findRefNodes(originalRootNode, diff.route); switch (diff.action) { case "replaceElement": { if (!refNode) { console.warn("Unable to apply replaceElement operation due to missing node"); return; } const container = document.createElement("span"); const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue)); const insNode = wrapInsertion(diffTreeToDOM(diff.newValue)); container.appendChild(delNode); container.appendChild(insNode); refNode.parentNode.replaceChild(container, refNode); break; } case "removeTextElement": { if (!refNode) { console.warn("Unable to apply removeTextElement operation due to missing node"); return; } const delNode = wrapDeletion(stringAsTextNode(diff.value)); refNode.parentNode.replaceChild(delNode, refNode); break; } case "removeElement": { if (!refNode) { console.warn("Unable to apply removeElement operation due to missing node"); return; } const delNode = wrapDeletion(diffTreeToDOM(diff.element)); refNode.parentNode.replaceChild(delNode, refNode); break; } case "modifyTextElement": { if (!refNode) { console.warn("Unable to apply modifyTextElement operation due to missing node"); return; } const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue); diffMathPatch.diff_cleanupSemantic(textDiffs); const container = document.createElement("span"); for (const [modifier, text] of textDiffs) { let textDiffNode = stringAsTextNode(text); if (modifier < 0) { textDiffNode = wrapDeletion(textDiffNode); } else if (modifier > 0) { textDiffNode = wrapInsertion(textDiffNode); } container.appendChild(textDiffNode); } refNode.parentNode.replaceChild(container, refNode); break; } case "addElement": { if (!refParentNode) { console.warn("Unable to apply addElement operation due to missing node"); return; } const insNode = wrapInsertion(diffTreeToDOM(diff.element)); insertBefore(refParentNode, refNode, insNode); break; } case "addTextElement": { if (!refParentNode) { console.warn("Unable to apply addTextElement operation due to missing node"); return; } // XXX: sometimes diffDOM says insert a newline when there shouldn't be one // but we must insert the node anyway so that we don't break the route child IDs. // See https://github.com/fiduswriter/diffDOM/issues/100 const insNode = wrapInsertion(stringAsTextNode(diff.value !== "\n" ? diff.value : "")); insertBefore(refParentNode, refNode, insNode); break; } // e.g. when changing a the href of a link, // show the link with old href as removed and with the new href as added case "removeAttribute": case "addAttribute": case "modifyAttribute": { if (!refNode) { console.warn(`Unable to apply ${diff.action} operation due to missing node`); return; } const delNode = wrapDeletion(refNode.cloneNode(true)); const updatedNode = refNode.cloneNode(true); if (diff.action === "addAttribute" || diff.action === "modifyAttribute") { updatedNode.setAttribute(diff.name, diff.newValue); } else { updatedNode.removeAttribute(diff.name); } const insNode = wrapInsertion(updatedNode); const container = document.createElement((0, _HtmlUtils.checkBlockNode)(refNode) ? "div" : "span"); container.appendChild(delNode); container.appendChild(insNode); refNode.parentNode.replaceChild(container, refNode); break; } default: // Should not happen (modifyComment, ???) _logger.logger.warn("MessageDiffUtils::editBodyDiffToHtml: diff action not supported atm", diff); } } /** * Renders a message with the changes made in an edit shown visually. * @param {IContent} originalContent the content for the base message * @param {IContent} editContent the content for the edit message * @return {JSX.Element} a react element similar to what `bodyToHtml` returns */ function editBodyDiffToHtml(originalContent, editContent) { // wrap the body in a div, DiffDOM needs a root element const originalBody = `<div>${getSanitizedHtmlBody(originalContent)}</div>`; const editBody = `<div>${getSanitizedHtmlBody(editContent)}</div>`; const dd = new _diffDom.DiffDOM(); // diffActions is an array of objects with at least a `action` and `route` // property. `action` tells us what the diff object changes, and `route` where. // `route` is a path on the DOM tree expressed as an array of indices. const diffActions = dd.diff(originalBody, editBody); // for diffing text fragments const diffMathPatch = new _diffMatchPatch.default(); // parse the base html message as a DOM tree, to which we'll apply the differences found. // fish out the div in which we wrapped the messages above with children[0]. const originalRootNode = new DOMParser().parseFromString(originalBody, "text/html").body.children[0]; for (let i = 0; i < diffActions.length; ++i) { const diff = diffActions[i]; renderDifferenceInDOM(originalRootNode, diff, diffMathPatch); // DiffDOM assumes in subsequent diffs route path that // the action was applied (e.g. that a removeElement action removed the element). // This is not the case for us. We render differences in the DOM tree, and don't apply them. // So we need to adjust the routes of the remaining diffs to account for this. adjustRoutes(diff, diffActions.slice(i + 1)); } // take the html out of the modified DOM tree again const safeBody = originalRootNode.innerHTML; const className = (0, _classnames.default)({ "mx_EventTile_body": true, "markdown-body": true }); return /*#__PURE__*/_react.default.createElement("span", { key: "body", className: className, dangerouslySetInnerHTML: { __html: safeBody }, dir: "auto" }); } //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwicmVxdWlyZSIsIl9jbGFzc25hbWVzIiwiX2RpZmZNYXRjaFBhdGNoIiwiX2RpZmZEb20iLCJfbG9nZ2VyIiwiX2xvZGFzaCIsIl9IdG1sVXRpbHMiLCJ0ZXh0VG9IdG1sIiwidGV4dCIsImNvbnRhaW5lciIsImRvY3VtZW50IiwiY3JlYXRlRWxlbWVudCIsInRleHRDb250ZW50IiwiaW5uZXJIVE1MIiwiZ2V0U2FuaXRpemVkSHRtbEJvZHkiLCJjb250ZW50Iiwib3B0cyIsInN0cmlwUmVwbHlGYWxsYmFjayIsImZvcm1hdCIsImJvZHlUb0h0bWwiLCJ3cmFwSW5zZXJ0aW9uIiwiY2hpbGQiLCJ3cmFwcGVyIiwiY2hlY2tCbG9ja05vZGUiLCJjbGFzc05hbWUiLCJhcHBlbmRDaGlsZCIsIndyYXBEZWxldGlvbiIsImZpbmRSZWZOb2RlcyIsInJvb3QiLCJyb3V0ZSIsImlzQWRkaXRpb24iLCJyZWZOb2RlIiwicmVmUGFyZW50Tm9kZSIsImVuZCIsImxlbmd0aCIsImkiLCJjaGlsZE5vZGVzIiwiaXNUZXh0Tm9kZSIsIm5vZGUiLCJub2RlTmFtZSIsImRpZmZUcmVlVG9ET00iLCJkZXNjIiwic3RyaW5nQXNUZXh0Tm9kZSIsImRhdGEiLCJrZXkiLCJ2YWx1ZSIsIk9iamVjdCIsImVudHJpZXMiLCJhdHRyaWJ1dGVzIiwic2V0QXR0cmlidXRlIiwiY2hpbGREZXNjIiwiaW5zZXJ0QmVmb3JlIiwicGFyZW50IiwibmV4dFNpYmxpbmciLCJpc1JvdXRlT2ZOZXh0U2libGluZyIsInJvdXRlMSIsInJvdXRlMiIsImxhc3REMUlkeCIsImFkanVzdFJvdXRlcyIsImRpZmYiLCJyZW1haW5pbmdEaWZmcyIsImFjdGlvbiIsImFkdmFuY2UiLCJyZCIsInN0cmluZyIsImNyZWF0ZVRleHROb2RlIiwidW5lc2NhcGUiLCJyZW5kZXJEaWZmZXJlbmNlSW5ET00iLCJvcmlnaW5hbFJvb3ROb2RlIiwiZGlmZk1hdGhQYXRjaCIsImNvbnNvbGUiLCJ3YXJuIiwiZGVsTm9kZSIsIm9sZFZhbHVlIiwiaW5zTm9kZSIsIm5ld1ZhbHVlIiwicGFyZW50Tm9kZSIsInJlcGxhY2VDaGlsZCIsImVsZW1lbnQiLCJ0ZXh0RGlmZnMiLCJkaWZmX21haW4iLCJkaWZmX2NsZWFudXBTZW1hbnRpYyIsIm1vZGlmaWVyIiwidGV4dERpZmZOb2RlIiwiY2xvbmVOb2RlIiwidXBkYXRlZE5vZGUiLCJuYW1lIiwicmVtb3ZlQXR0cmlidXRlIiwibG9nZ2VyIiwiZWRpdEJvZHlEaWZmVG9IdG1sIiwib3JpZ2luYWxDb250ZW50IiwiZWRpdENvbnRlbnQiLCJvcmlnaW5hbEJvZHkiLCJlZGl0Qm9keSIsImRkIiwiRGlmZkRPTSIsImRpZmZBY3Rpb25zIiwiRGlmZk1hdGNoUGF0Y2giLCJET01QYXJzZXIiLCJwYXJzZUZyb21TdHJpbmciLCJib2R5IiwiY2hpbGRyZW4iLCJzbGljZSIsInNhZmVCb2R5IiwiY2xhc3NOYW1lcyIsImRlZmF1bHQiLCJkYW5nZXJvdXNseVNldElubmVySFRNTCIsIl9faHRtbCIsImRpciJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9NZXNzYWdlRGlmZlV0aWxzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAxOS0yMDIxICwgMjAyMyBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgUmVhY3QgZnJvbSBcInJlYWN0XCI7XG5pbXBvcnQgY2xhc3NOYW1lcyBmcm9tIFwiY2xhc3NuYW1lc1wiO1xuaW1wb3J0IERpZmZNYXRjaFBhdGNoIGZyb20gXCJkaWZmLW1hdGNoLXBhdGNoXCI7XG5pbXBvcnQgeyBEaWZmRE9NLCBJRGlmZiB9IGZyb20gXCJkaWZmLWRvbVwiO1xuaW1wb3J0IHsgSUNvbnRlbnQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbWF0cml4XCI7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5pbXBvcnQgeyB1bmVzY2FwZSB9IGZyb20gXCJsb2Rhc2hcIjtcblxuaW1wb3J0IHsgYm9keVRvSHRtbCwgY2hlY2tCbG9ja05vZGUsIEV2ZW50UmVuZGVyT3B0cyB9IGZyb20gXCIuLi9IdG1sVXRpbHNcIjtcblxuZnVuY3Rpb24gdGV4dFRvSHRtbCh0ZXh0OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGNvbnN0IGNvbnRhaW5lciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XG4gICAgY29udGFpbmVyLnRleHRDb250ZW50ID0gdGV4dDtcbiAgICByZXR1cm4gY29udGFpbmVyLmlubmVySFRNTDtcbn1cblxuZnVuY3Rpb24gZ2V0U2FuaXRpemVkSHRtbEJvZHkoY29udGVudDogSUNvbnRlbnQpOiBzdHJpbmcge1xuICAgIGNvbnN0IG9wdHM6IEV2ZW50UmVuZGVyT3B0cyA9IHtcbiAgICAgICAgc3RyaXBSZXBseUZhbGxiYWNrOiB0cnVlLFxuICAgIH07XG4gICAgaWYgKGNvbnRlbnQuZm9ybWF0ID09PSBcIm9yZy5tYXRyaXguY3VzdG9tLmh0bWxcIikge1xuICAgICAgICByZXR1cm4gYm9keVRvSHRtbChjb250ZW50LCBudWxsLCBvcHRzKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICAvLyBjb252ZXJ0IHRoZSBzdHJpbmcgdG8gc29tZXRoaW5nIHRoYXQgY2FuIGJlIHNhZmVseVxuICAgICAgICAvLyBlbWJlZGRlZCBpbiBhbiBodG1sIGRvY3VtZW50LCBlLmcuIHVzZSBodG1sIGVudGl0aWVzIHdoZXJlIG5lZWRlZFxuICAgICAgICAvLyBUaGlzIGlzIGFsc28gbmVlZGVkIHNvIHRoYXQgRGlmZkRPTSB3b3VsZG4ndCBpbnRlcnByZXQgc29tZXRoaW5nXG4gICAgICAgIC8vIGFzIGEgdGFnIHdoZW4gc29tZWJvZHkgdHlwZXMgZS5nLiBcIjwvc2FyY2FzbT5cIlxuXG4gICAgICAgIC8vIGFzIG9wcG9zZWQgdG8gYm9keVRvSHRtbCwgaGVyZSB3ZSBhbHNvIHJlbmRlclxuICAgICAgICAvLyB0ZXh0IG1lc3NhZ2VzIHdpdGggZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUwsIHRvIHVuaWZ5XG4gICAgICAgIC8vIHRoZSBjb2RlIHBhdGhzIGFuZCBiZWNhdXNlIHdlIG5lZWQgaHRtbCB0byBzaG93IGRpZmZlcmVuY2VzXG4gICAgICAgIHJldHVybiB0ZXh0VG9IdG1sKGJvZHlUb0h0bWwoY29udGVudCwgbnVsbCwgb3B0cykpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gd3JhcEluc2VydGlvbihjaGlsZDogTm9kZSk6IEhUTUxFbGVtZW50IHtcbiAgICBjb25zdCB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChjaGVja0Jsb2NrTm9kZShjaGlsZCkgPyBcImRpdlwiIDogXCJzcGFuXCIpO1xuICAgIHdyYXBwZXIuY2xhc3NOYW1lID0gXCJteF9FZGl0SGlzdG9yeU1lc3NhZ2VfaW5zZXJ0aW9uXCI7XG4gICAgd3JhcHBlci5hcHBlbmRDaGlsZChjaGlsZCk7XG4gICAgcmV0dXJuIHdyYXBwZXI7XG59XG5cbmZ1bmN0aW9uIHdyYXBEZWxldGlvbihjaGlsZDogTm9kZSk6IEhUTUxFbGVtZW50IHtcbiAgICBjb25zdCB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChjaGVja0Jsb2NrTm9kZShjaGlsZCkgPyBcImRpdlwiIDogXCJzcGFuXCIpO1xuICAgIHdyYXBwZXIuY2xhc3NOYW1lID0gXCJteF9FZGl0SGlzdG9yeU1lc3NhZ2VfZGVsZXRpb25cIjtcbiAgICB3cmFwcGVyLmFwcGVuZENoaWxkKGNoaWxkKTtcbiAgICByZXR1cm4gd3JhcHBlcjtcbn1cblxuZnVuY3Rpb24gZmluZFJlZk5vZGVzKFxuICAgIHJvb3Q6IE5vZGUsXG4gICAgcm91dGU6IG51bWJlcltdLFxuICAgIGlzQWRkaXRpb24gPSBmYWxzZSxcbik6IHtcbiAgICByZWZOb2RlOiBOb2RlIHwgdW5kZWZpbmVkO1xuICAgIHJlZlBhcmVudE5vZGU6IE5vZGUgfCB1bmRlZmluZWQ7XG59IHtcbiAgICBsZXQgcmVmTm9kZTogTm9kZSB8IHVuZGVmaW5lZCA9IHJvb3Q7XG4gICAgbGV0IHJlZlBhcmVudE5vZGU6IE5vZGUgfCB1bmRlZmluZWQ7XG4gICAgY29uc3QgZW5kID0gaXNBZGRpdGlvbiA/IHJvdXRlLmxlbmd0aCAtIDEgOiByb3V0ZS5sZW5ndGg7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBlbmQ7ICsraSkge1xuICAgICAgICByZWZQYXJlbnROb2RlID0gcmVmTm9kZTtcbiAgICAgICAgcmVmTm9kZSA9IHJlZk5vZGU/LmNoaWxkTm9kZXNbcm91dGVbaV0hXTtcbiAgICB9XG4gICAgcmV0dXJuIHsgcmVmTm9kZSwgcmVmUGFyZW50Tm9kZSB9O1xufVxuXG5mdW5jdGlvbiBpc1RleHROb2RlKG5vZGU6IFRleHQgfCBIVE1MRWxlbWVudCk6IG5vZGUgaXMgVGV4dCB7XG4gICAgcmV0dXJuIG5vZGUubm9kZU5hbWUgPT09IFwiI3RleHRcIjtcbn1cblxuZnVuY3Rpb24gZGlmZlRyZWVUb0RPTShkZXNjOiBUZXh0IHwgSFRNTEVsZW1lbnQpOiBOb2RlIHtcbiAgICBpZiAoaXNUZXh0Tm9kZShkZXNjKSkge1xuICAgICAgICByZXR1cm4gc3RyaW5nQXNUZXh0Tm9kZShkZXNjLmRhdGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IG5vZGUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KGRlc2Mubm9kZU5hbWUpO1xuICAgICAgICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBPYmplY3QuZW50cmllcyhkZXNjLmF0dHJpYnV0ZXMpKSB7XG4gICAgICAgICAgICBub2RlLnNldEF0dHJpYnV0ZShrZXksIHZhbHVlLnZhbHVlKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoZGVzYy5jaGlsZE5vZGVzKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGNoaWxkRGVzYyBvZiBkZXNjLmNoaWxkTm9kZXMpIHtcbiAgICAgICAgICAgICAgICBub2RlLmFwcGVuZENoaWxkKGRpZmZUcmVlVG9ET00oY2hpbGREZXNjIGFzIFRleHQgfCBIVE1MRWxlbWVudCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBub2RlO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gaW5zZXJ0QmVmb3JlKHBhcmVudDogTm9kZSwgbmV4dFNpYmxpbmc6IE5vZGUgfCB1bmRlZmluZWQsIGNoaWxkOiBOb2RlKTogdm9pZCB7XG4gICAgaWYgKG5leHRTaWJsaW5nKSB7XG4gICAgICAgIHBhcmVudC5pbnNlcnRCZWZvcmUoY2hpbGQsIG5leHRTaWJsaW5nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBwYXJlbnQuYXBwZW5kQ2hpbGQoY2hpbGQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gaXNSb3V0ZU9mTmV4dFNpYmxpbmcocm91dGUxOiBudW1iZXJbXSwgcm91dGUyOiBudW1iZXJbXSk6IGJvb2xlYW4ge1xuICAgIC8vIHJvdXRlcyBhcmUgYXJyYXlzIHdpdGggaW5kaWNlcyxcbiAgICAvLyB0byBiZSBpbnRlcnByZXRlZCBhcyBhIHBhdGggaW4gdGhlIGRvbSB0cmVlXG5cbiAgICAvLyBlbnN1cmUgc2FtZSBwYXJlbnRcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHJvdXRlMS5sZW5ndGggLSAxOyArK2kpIHtcbiAgICAgICAgaWYgKHJvdXRlMVtpXSAhPT0gcm91dGUyW2ldKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLy8gdGhlIHJvdXRlMiBpcyBvbmx5IGFmZmVjdGVkIGJ5IHRoZSBkaWZmIG9mIHJvdXRlMVxuICAgIC8vIGluc2VydGluZyBhbiBlbGVtZW50IGlmIHRoZSBpbmRleCBhdCB0aGUgbGV2ZWwgb2YgdGhlXG4gICAgLy8gbGFzdCBlbGVtZW50IG9mIHJvdXRlMSBiZWluZyBsYXJnZXJcbiAgICAvLyAoZS5nLiBjb21pbmcgYmVoaW5kIHJvdXRlMSBhdCB0aGF0IGxldmVsKVxuICAgIGNvbnN0IGxhc3REMUlkeCA9IHJvdXRlMS5sZW5ndGggLSAxO1xuICAgIHJldHVybiByb3V0ZTJbbGFzdEQxSWR4XSEgPj0gcm91dGUxW2xhc3REMUlkeF0hO1xufVxuXG5mdW5jdGlvbiBhZGp1c3RSb3V0ZXMoZGlmZjogSURpZmYsIHJlbWFpbmluZ0RpZmZzOiBJRGlmZltdKTogdm9pZCB7XG4gICAgaWYgKGRpZmYuYWN0aW9uID09PSBcInJlbW92ZVRleHRFbGVtZW50XCIgfHwgZGlmZi5hY3Rpb24gPT09IFwicmVtb3ZlRWxlbWVudFwiKSB7XG4gICAgICAgIC8vIGFzIHJlbW92ZWQgdGV4dCBpcyBub3QgcmVtb3ZlZCBmcm9tIHRoZSBodG1sLCBidXQgbWFya2VkIGFzIGRlbGV0ZWQsXG4gICAgICAgIC8vIHdlIG5lZWQgdG8gcmVhZGp1c3QgaW5kaWNlcyB0aGF0IGFzc3VtZSB0aGUgY3VycmVudCBub2RlIGhhcyBiZWVuIHJlbW92ZWQuXG4gICAgICAgIGNvbnN0IGFkdmFuY2UgPSAxO1xuICAgICAgICBmb3IgKGNvbnN0IHJkIG9mIHJlbWFpbmluZ0RpZmZzKSB7XG4gICAgICAgICAgICBpZiAoaXNSb3V0ZU9mTmV4dFNpYmxpbmcoZGlmZi5yb3V0ZSwgcmQucm91dGUpKSB7XG4gICAgICAgICAgICAgICAgcmQucm91dGVbZGlmZi5yb3V0ZS5sZW5ndGggLSAxXSArPSBhZHZhbmNlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuXG5mdW5jdGlvbiBzdHJpbmdBc1RleHROb2RlKHN0cmluZzogc3RyaW5nKTogVGV4dCB7XG4gICAgcmV0dXJuIGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKHVuZXNjYXBlKHN0cmluZykpO1xufVxuXG5mdW5jdGlvbiByZW5kZXJEaWZmZXJlbmNlSW5ET00ob3JpZ2luYWxSb290Tm9kZTogTm9kZSwgZGlmZjogSURpZmYsIGRpZmZNYXRoUGF0Y2g6IERpZmZNYXRjaFBhdGNoKTogdm9pZCB7XG4gICAgY29uc3QgeyByZWZOb2RlLCByZWZQYXJlbnROb2RlIH0gPSBmaW5kUmVmTm9kZXMob3JpZ2luYWxSb290Tm9kZSwgZGlmZi5yb3V0ZSk7XG5cbiAgICBzd2l0Y2ggKGRpZmYuYWN0aW9uKSB7XG4gICAgICAgIGNhc2UgXCJyZXBsYWNlRWxlbWVudFwiOiB7XG4gICAgICAgICAgICBpZiAoIXJlZk5vZGUpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oXCJVbmFibGUgdG8gYXBwbHkgcmVwbGFjZUVsZW1lbnQgb3BlcmF0aW9uIGR1ZSB0byBtaXNzaW5nIG5vZGVcIik7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgY29udGFpbmVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInNwYW5cIik7XG4gICAgICAgICAgICBjb25zdCBkZWxOb2RlID0gd3JhcERlbGV0aW9uKGRpZmZUcmVlVG9ET00oZGlmZi5vbGRWYWx1ZSBhcyBIVE1MRWxlbWVudCkpO1xuICAgICAgICAgICAgY29uc3QgaW5zTm9kZSA9IHdyYXBJbnNlcnRpb24oZGlmZlRyZWVUb0RPTShkaWZmLm5ld1ZhbHVlIGFzIEhUTUxFbGVtZW50KSk7XG4gICAgICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQoZGVsTm9kZSk7XG4gICAgICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQoaW5zTm9kZSk7XG4gICAgICAgICAgICByZWZOb2RlLnBhcmVudE5vZGUhLnJlcGxhY2VDaGlsZChjb250YWluZXIsIHJlZk5vZGUpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgY2FzZSBcInJlbW92ZVRleHRFbGVtZW50XCI6IHtcbiAgICAgICAgICAgIGlmICghcmVmTm9kZSkge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybihcIlVuYWJsZSB0byBhcHBseSByZW1vdmVUZXh0RWxlbWVudCBvcGVyYXRpb24gZHVlIHRvIG1pc3Npbmcgbm9kZVwiKTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBkZWxOb2RlID0gd3JhcERlbGV0aW9uKHN0cmluZ0FzVGV4dE5vZGUoZGlmZi52YWx1ZSBhcyBzdHJpbmcpKTtcbiAgICAgICAgICAgIHJlZk5vZGUucGFyZW50Tm9kZSEucmVwbGFjZUNoaWxkKGRlbE5vZGUsIHJlZk5vZGUpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgY2FzZSBcInJlbW92ZUVsZW1lbnRcIjoge1xuICAgICAgICAgICAgaWYgKCFyZWZOb2RlKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiVW5hYmxlIHRvIGFwcGx5IHJlbW92ZUVsZW1lbnQgb3BlcmF0aW9uIGR1ZSB0byBtaXNzaW5nIG5vZGVcIik7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgZGVsTm9kZSA9IHdyYXBEZWxldGlvbihkaWZmVHJlZVRvRE9NKGRpZmYuZWxlbWVudCBhcyBIVE1MRWxlbWVudCkpO1xuICAgICAgICAgICAgcmVmTm9kZS5wYXJlbnROb2RlIS5yZXBsYWNlQ2hpbGQoZGVsTm9kZSwgcmVmTm9kZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgICBjYXNlIFwibW9kaWZ5VGV4dEVsZW1lbnRcIjoge1xuICAgICAgICAgICAgaWYgKCFyZWZOb2RlKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiVW5hYmxlIHRvIGFwcGx5IG1vZGlmeVRleHRFbGVtZW50IG9wZXJhdGlvbiBkdWUgdG8gbWlzc2luZyBub2RlXCIpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHRleHREaWZmcyA9IGRpZmZNYXRoUGF0Y2guZGlmZl9tYWluKGRpZmYub2xkVmFsdWUgYXMgc3RyaW5nLCBkaWZmLm5ld1ZhbHVlIGFzIHN0cmluZyk7XG4gICAgICAgICAgICBkaWZmTWF0aFBhdGNoLmRpZmZfY2xlYW51cFNlbWFudGljKHRleHREaWZmcyk7XG4gICAgICAgICAgICBjb25zdCBjb250YWluZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwic3BhblwiKTtcbiAgICAgICAgICAgIGZvciAoY29uc3QgW21vZGlmaWVyLCB0ZXh0XSBvZiB0ZXh0RGlmZnMpIHtcbiAgICAgICAgICAgICAgICBsZXQgdGV4dERpZmZOb2RlOiBOb2RlID0gc3RyaW5nQXNUZXh0Tm9kZSh0ZXh0KTtcbiAgICAgICAgICAgICAgICBpZiAobW9kaWZpZXIgPCAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHRleHREaWZmTm9kZSA9IHdyYXBEZWxldGlvbih0ZXh0RGlmZk5vZGUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAobW9kaWZpZXIgPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHRleHREaWZmTm9kZSA9IHdyYXBJbnNlcnRpb24odGV4dERpZmZOb2RlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKHRleHREaWZmTm9kZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZWZOb2RlLnBhcmVudE5vZGUhLnJlcGxhY2VDaGlsZChjb250YWluZXIsIHJlZk5vZGUpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgY2FzZSBcImFkZEVsZW1lbnRcIjoge1xuICAgICAgICAgICAgaWYgKCFyZWZQYXJlbnROb2RlKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiVW5hYmxlIHRvIGFwcGx5IGFkZEVsZW1lbnQgb3BlcmF0aW9uIGR1ZSB0byBtaXNzaW5nIG5vZGVcIik7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgaW5zTm9kZSA9IHdyYXBJbnNlcnRpb24oZGlmZlRyZWVUb0RPTShkaWZmLmVsZW1lbnQgYXMgSFRNTEVsZW1lbnQpKTtcbiAgICAgICAgICAgIGluc2VydEJlZm9yZShyZWZQYXJlbnROb2RlLCByZWZOb2RlLCBpbnNOb2RlKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAgIGNhc2UgXCJhZGRUZXh0RWxlbWVudFwiOiB7XG4gICAgICAgICAgICBpZiAoIXJlZlBhcmVudE5vZGUpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oXCJVbmFibGUgdG8gYXBwbHkgYWRkVGV4dEVsZW1lbnQgb3BlcmF0aW9uIGR1ZSB0byBtaXNzaW5nIG5vZGVcIik7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gWFhYOiBzb21ldGltZXMgZGlmZkRPTSBzYXlzIGluc2VydCBhIG5ld2xpbmUgd2hlbiB0aGVyZSBzaG91bGRuJ3QgYmUgb25lXG4gICAgICAgICAgICAvLyBidXQgd2UgbXVzdCBpbnNlcnQgdGhlIG5vZGUgYW55d2F5IHNvIHRoYXQgd2UgZG9uJ3QgYnJlYWsgdGhlIHJvdXRlIGNoaWxkIElEcy5cbiAgICAgICAgICAgIC8vIFNlZSBodHRwczovL2dpdGh1Yi5jb20vZmlkdXN3cml0ZXIvZGlmZkRPTS9pc3N1ZXMvMTAwXG4gICAgICAgICAgICBjb25zdCBpbnNOb2RlID0gd3JhcEluc2VydGlvbihzdHJpbmdBc1RleHROb2RlKGRpZmYudmFsdWUgIT09IFwiXFxuXCIgPyAoZGlmZi52YWx1ZSBhcyBzdHJpbmcpIDogXCJcIikpO1xuICAgICAgICAgICAgaW5zZXJ0QmVmb3JlKHJlZlBhcmVudE5vZGUsIHJlZk5vZGUsIGluc05vZGUpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgLy8gZS5nLiB3aGVuIGNoYW5naW5nIGEgdGhlIGhyZWYgb2YgYSBsaW5rLFxuICAgICAgICAvLyBzaG93IHRoZSBsaW5rIHdpdGggb2xkIGhyZWYgYXMgcmVtb3ZlZCBhbmQgd2l0aCB0aGUgbmV3IGhyZWYgYXMgYWRkZWRcbiAgICAgICAgY2FzZSBcInJlbW92ZUF0dHJpYnV0ZVwiOlxuICAgICAgICBjYXNlIFwiYWRkQXR0cmlidXRlXCI6XG4gICAgICAgIGNhc2UgXCJtb2RpZnlBdHRyaWJ1dGVcIjoge1xuICAgICAgICAgICAgaWYgKCFyZWZOb2RlKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKGBVbmFibGUgdG8gYXBwbHkgJHtkaWZmLmFjdGlvbn0gb3BlcmF0aW9uIGR1ZSB0byBtaXNzaW5nIG5vZGVgKTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBkZWxOb2RlID0gd3JhcERlbGV0aW9uKHJlZk5vZGUuY2xvbmVOb2RlKHRydWUpKTtcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZWROb2RlID0gcmVmTm9kZS5jbG9uZU5vZGUodHJ1ZSkgYXMgSFRNTEVsZW1lbnQ7XG4gICAgICAgICAgICBpZiAoZGlmZi5hY3Rpb24gPT09IFwiYWRkQXR0cmlidXRlXCIgfHwgZGlmZi5hY3Rpb24gPT09IFwibW9kaWZ5QXR0cmlidXRlXCIpIHtcbiAgICAgICAgICAgICAgICB1cGRhdGVkTm9kZS5zZXRBdHRyaWJ1dGUoZGlmZi5uYW1lLCBkaWZmLm5ld1ZhbHVlIGFzIHN0cmluZyk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHVwZGF0ZWROb2RlLnJlbW92ZUF0dHJpYnV0ZShkaWZmLm5hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgaW5zTm9kZSA9IHdyYXBJbnNlcnRpb24odXBkYXRlZE5vZGUpO1xuICAgICAgICAgICAgY29uc3QgY29udGFpbmVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChjaGVja0Jsb2NrTm9kZShyZWZOb2RlKSA/IFwiZGl2XCIgOiBcInNwYW5cIik7XG4gICAgICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQoZGVsTm9kZSk7XG4gICAgICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQoaW5zTm9kZSk7XG4gICAgICAgICAgICByZWZOb2RlLnBhcmVudE5vZGUhLnJlcGxhY2VDaGlsZChjb250YWluZXIsIHJlZk5vZGUpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIC8vIFNob3VsZCBub3QgaGFwcGVuIChtb2RpZnlDb21tZW50LCA/Pz8pXG4gICAgICAgICAgICBsb2dnZXIud2FybihcIk1lc3NhZ2VEaWZmVXRpbHM6OmVkaXRCb2R5RGlmZlRvSHRtbDogZGlmZiBhY3Rpb24gbm90IHN1cHBvcnRlZCBhdG1cIiwgZGlmZik7XG4gICAgfVxufVxuXG4vKipcbiAqIFJlbmRlcnMgYSBtZXNzYWdlIHdpdGggdGhlIGNoYW5nZXMgbWFkZSBpbiBhbiBlZGl0IHNob3duIHZpc3VhbGx5LlxuICogQHBhcmFtIHtJQ29udGVudH0gb3JpZ2luYWxDb250ZW50IHRoZSBjb250ZW50IGZvciB0aGUgYmFzZSBtZXNzYWdlXG4gKiBAcGFyYW0ge0lDb250ZW50fSBlZGl0Q29udGVudCB0aGUgY29udGVudCBmb3IgdGhlIGVkaXQgbWVzc2FnZVxuICogQHJldHVybiB7SlNYLkVsZW1lbnR9IGEgcmVhY3QgZWxlbWVudCBzaW1pbGFyIHRvIHdoYXQgYGJvZHlUb0h0bWxgIHJldHVybnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGVkaXRCb2R5RGlmZlRvSHRtbChvcmlnaW5hbENvbnRlbnQ6IElDb250ZW50LCBlZGl0Q29udGVudDogSUNvbnRlbnQpOiBKU1guRWxlbWVudCB7XG4gICAgLy8gd3JhcCB0aGUgYm9keSBpbiBhIGRpdiwgRGlmZkRPTSBuZWVkcyBhIHJvb3QgZWxlbWVudFxuICAgIGNvbnN0IG9yaWdpbmFsQm9keSA9IGA8ZGl2PiR7Z2V0U2FuaXRpemVkSHRtbEJvZHkob3JpZ2luYWxDb250ZW50KX08L2Rpdj5gO1xuICAgIGNvbnN0IGVkaXRCb2R5ID0gYDxkaXY+JHtnZXRTYW5pdGl6ZWRIdG1sQm9keShlZGl0Q29udGVudCl9PC9kaXY+YDtcbiAgICBjb25zdCBkZCA9IG5ldyBEaWZmRE9NKCk7XG4gICAgLy8gZGlmZkFjdGlvbnMgaXMgYW4gYXJyYXkgb2Ygb2JqZWN0cyB3aXRoIGF0IGxlYXN0IGEgYGFjdGlvbmAgYW5kIGByb3V0ZWBcbiAgICAvLyBwcm9wZXJ0eS4gYGFjdGlvbmAgdGVsbHMgdXMgd2hhdCB0aGUgZGlmZiBvYmplY3QgY2hhbmdlcywgYW5kIGByb3V0ZWAgd2hlcmUuXG4gICAgLy8gYHJvdXRlYCBpcyBhIHBhdGggb24gdGhlIERPTSB0cmVlIGV4cHJlc3NlZCBhcyBhbiBhcnJheSBvZiBpbmRpY2VzLlxuICAgIGNvbnN0IGRpZmZBY3Rpb25zID0gZGQuZGlmZihvcmlnaW5hbEJvZHksIGVkaXRCb2R5KTtcbiAgICAvLyBmb3IgZGlmZmluZyB0ZXh0IGZyYWdtZW50c1xuICAgIGNvbnN0IGRpZmZNYXRoUGF0Y2ggPSBuZXcgRGlmZk1hdGNoUGF0Y2goKTtcbiAgICAvLyBwYXJzZSB0aGUgYmFzZSBodG1sIG1lc3NhZ2UgYXMgYSBET00gdHJlZSwgdG8gd2hpY2ggd2UnbGwgYXBwbHkgdGhlIGRpZmZlcmVuY2VzIGZvdW5kLlxuICAgIC8vIGZpc2ggb3V0IHRoZSBkaXYgaW4gd2hpY2ggd2Ugd3JhcHBlZCB0aGUgbWVzc2FnZXMgYWJvdmUgd2l0aCBjaGlsZHJlblswXS5cbiAgICBjb25zdCBvcmlnaW5hbFJvb3ROb2RlID0gbmV3IERPTVBhcnNlcigpLnBhcnNlRnJvbVN0cmluZyhvcmlnaW5hbEJvZHksIFwidGV4dC9odG1sXCIpLmJvZHkuY2hpbGRyZW5bMF0hO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZGlmZkFjdGlvbnMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgY29uc3QgZGlmZiA9IGRpZmZBY3Rpb25zW2ldITtcbiAgICAgICAgcmVuZGVyRGlmZmVyZW5jZUluRE9NKG9yaWdpbmFsUm9vdE5vZGUsIGRpZmYsIGRpZmZNYXRoUGF0Y2gpO1xuICAgICAgICAvLyBEaWZmRE9NIGFzc3VtZXMgaW4gc3Vic2VxdWVudCBkaWZmcyByb3V0ZSBwYXRoIHRoYXRcbiAgICAgICAgLy8gdGhlIGFjdGlvbiB3YXMgYXBwbGllZCAoZS5nLiB0aGF0IGEgcmVtb3ZlRWxlbWVudCBhY3Rpb24gcmVtb3ZlZCB0aGUgZWxlbWVudCkuXG4gICAgICAgIC8vIFRoaXMgaXMgbm90IHRoZSBjYXNlIGZvciB1cy4gV2UgcmVuZGVyIGRpZmZlcmVuY2VzIGluIHRoZSBET00gdHJlZSwgYW5kIGRvbid0IGFwcGx5IHRoZW0uXG4gICAgICAgIC8vIFNvIHdlIG5lZWQgdG8gYWRqdXN0IHRoZSByb3V0ZXMgb2YgdGhlIHJlbWFpbmluZyBkaWZmcyB0byBhY2NvdW50IGZvciB0aGlzLlxuICAgICAgICBhZGp1c3RSb3V0ZXMoZGlmZiwgZGlmZkFjdGlvbnMuc2xpY2UoaSArIDEpKTtcbiAgICB9XG4gICAgLy8gdGFrZSB0aGUgaHRtbCBvdXQgb2YgdGhlIG1vZGlmaWVkIERPTSB0cmVlIGFnYWluXG4gICAgY29uc3Qgc2FmZUJvZHkgPSBvcmlnaW5hbFJvb3ROb2RlLmlubmVySFRNTDtcbiAgICBjb25zdCBjbGFzc05hbWUgPSBjbGFzc05hbWVzKHtcbiAgICAgICAgXCJteF9FdmVudFRpbGVfYm9keVwiOiB0cnVlLFxuICAgICAgICBcIm1hcmtkb3duLWJvZHlcIjogdHJ1ZSxcbiAgICB9KTtcbiAgICByZXR1cm4gPHNwYW4ga2V5PVwiYm9keVwiIGNsYXNzTmFtZT17Y2xhc3NOYW1lfSBkYW5nZXJvdXNseVNldElubmVySFRNTD17eyBfX2h0bWw6IHNhZmVCb2R5IH19IGRpcj1cImF1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