You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

529 lines
88 KiB

3 years ago
'use strict';
var obsidian = require('obsidian');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
class DecryptModal extends obsidian.Modal {
constructor(app, title, text = '') {
super(app);
this.decryptInPlace = false;
this.text = text;
this.titleEl.innerText = title;
}
onOpen() {
let { contentEl } = this;
const textEl = contentEl.createDiv().createEl('textarea', { text: this.text });
textEl.style.width = '100%';
textEl.style.height = '100%';
textEl.rows = 10;
textEl.readOnly = true;
//textEl.focus(); // Doesn't seem to work here...
setTimeout(() => { textEl.focus(); }, 100); //... but this does
const btnContainerEl = contentEl.createDiv('');
const decryptInPlaceBtnEl = btnContainerEl.createEl('button', { text: 'Decrypt in-place' });
decryptInPlaceBtnEl.addEventListener('click', () => {
this.decryptInPlace = true;
this.close();
});
const cancelBtnEl = btnContainerEl.createEl('button', { text: 'Close' });
cancelBtnEl.addEventListener('click', () => {
this.close();
});
}
}
class PasswordModal extends obsidian.Modal {
constructor(app, confirmPassword, defaultPassword = null) {
super(app);
this.password = null;
this.defaultPassword = null;
this.defaultPassword = defaultPassword;
this.confirmPassword = confirmPassword;
}
onOpen() {
var _a, _b;
let { contentEl } = this;
contentEl.empty();
const inputPwContainerEl = contentEl.createDiv();
inputPwContainerEl.createSpan({ text: '🔑 ' });
const pwInputEl = inputPwContainerEl.createEl('input', { type: 'password', value: (_a = this.defaultPassword) !== null && _a !== void 0 ? _a : '' });
pwInputEl.placeholder = 'Enter your password';
pwInputEl.style.width = '70%';
pwInputEl.focus();
const inputInputNextBtnEl = inputPwContainerEl.createEl('button', { text: '→' });
inputInputNextBtnEl.style.display = 'inline';
inputInputNextBtnEl.style.marginLeft = "1em";
inputInputNextBtnEl.style.width = "4em";
inputInputNextBtnEl.addEventListener('click', (ev) => {
inputPasswordHandler();
});
const confirmPwContainerEl = contentEl.createDiv();
confirmPwContainerEl.style.marginTop = '1em';
confirmPwContainerEl.createSpan({ text: '🔑 ' });
const pwConfirmInputEl = confirmPwContainerEl.createEl('input', { type: 'password', value: (_b = this.defaultPassword) !== null && _b !== void 0 ? _b : '' });
pwConfirmInputEl.placeholder = 'Confirm your password';
pwConfirmInputEl.style.width = '70%';
const confirmInputNextBtnEl = confirmPwContainerEl.createEl('button', { text: '→' });
confirmInputNextBtnEl.style.display = 'inline';
confirmInputNextBtnEl.style.marginLeft = "1em";
confirmInputNextBtnEl.style.width = "4em";
confirmInputNextBtnEl.addEventListener('click', (ev) => {
confirmPasswordHandler();
});
const inputPasswordHandler = () => {
if (this.confirmPassword) {
// confim password
pwConfirmInputEl.focus();
}
else {
this.password = pwInputEl.value;
this.close();
}
};
const confirmPasswordHandler = () => {
if (pwInputEl.value == pwConfirmInputEl.value) {
this.password = pwConfirmInputEl.value;
this.close();
}
else {
// passwords don't match
messageEl.setText('Passwords don\'t match');
messageEl.show();
}
};
pwConfirmInputEl.addEventListener('keypress', (ev) => {
if ((ev.code === 'Enter' || ev.code === 'NumpadEnter')
&& pwConfirmInputEl.value.length > 0) {
ev.preventDefault();
confirmPasswordHandler();
}
});
if (!this.confirmPassword) {
confirmPwContainerEl.hide();
}
const messageEl = contentEl.createDiv();
messageEl.style.marginTop = '1em';
messageEl.hide();
pwInputEl.addEventListener('keypress', (ev) => {
if ((ev.code === 'Enter' || ev.code === 'NumpadEnter')
&& pwInputEl.value.length > 0) {
ev.preventDefault();
inputPasswordHandler();
}
});
// const btnContainerEl = contentEl.createDiv('');
// btnContainerEl.style.marginTop = '1em';
// const okBtnEl = btnContainerEl.createEl('button', { text: 'OK' });
// okBtnEl.addEventListener('click', () => {
// this.password = pwInputEl.value;
// this.close();
// });
// const cancelBtnEl = btnContainerEl.createEl('button', { text: 'Cancel' });
// cancelBtnEl.addEventListener('click', () => {
// this.close();
// });
}
}
const vectorSize = 16;
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();
const iterations = 1000;
const salt = utf8Encoder.encode('XHWnDAT6ehMVY2zD');
class CryptoHelperV2 {
deriveKey(password) {
return __awaiter(this, void 0, void 0, function* () {
const buffer = utf8Encoder.encode(password);
const key = yield crypto.subtle.importKey('raw', buffer, { name: 'PBKDF2' }, false, ['deriveKey']);
const privateKey = crypto.subtle.deriveKey({
name: 'PBKDF2',
hash: { name: 'SHA-256' },
iterations,
salt
}, key, {
name: 'AES-GCM',
length: 256
}, false, ['encrypt', 'decrypt']);
return privateKey;
});
}
encryptToBase64(text, password) {
return __awaiter(this, void 0, void 0, function* () {
const key = yield this.deriveKey(password);
const textBytesToEncrypt = utf8Encoder.encode(text);
const vector = crypto.getRandomValues(new Uint8Array(vectorSize));
// encrypt into bytes
const encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt({ name: 'AES-GCM', iv: vector }, key, textBytesToEncrypt));
const finalBytes = new Uint8Array(vector.byteLength + encryptedBytes.byteLength);
finalBytes.set(vector, 0);
finalBytes.set(encryptedBytes, vector.byteLength);
//convert array to base64
const base64Text = btoa(String.fromCharCode(...finalBytes));
return base64Text;
});
}
stringToArray(str) {
var result = [];
for (var i = 0; i < str.length; i++) {
result.push(str.charCodeAt(i));
}
return new Uint8Array(result);
}
decryptFromBase64(base64Encoded, password) {
return __awaiter(this, void 0, void 0, function* () {
try {
let bytesToDecode = this.stringToArray(atob(base64Encoded));
// extract iv
const vector = bytesToDecode.slice(0, vectorSize);
// extract encrypted text
const encryptedTextBytes = bytesToDecode.slice(vectorSize);
const key = yield this.deriveKey(password);
// decrypt into bytes
let decryptedBytes = yield crypto.subtle.decrypt({ name: 'AES-GCM', iv: vector }, key, encryptedTextBytes);
// convert bytes to text
let decryptedText = utf8Decoder.decode(decryptedBytes);
return decryptedText;
}
catch (e) {
//console.error(e);
return null;
}
});
}
}
const algorithmObsolete = {
name: 'AES-GCM',
iv: new Uint8Array([196, 190, 240, 190, 188, 78, 41, 132, 15, 220, 84, 211]),
tagLength: 128
};
class CryptoHelperObsolete {
buildKey(password) {
return __awaiter(this, void 0, void 0, function* () {
let utf8Encode = new TextEncoder();
let passwordBytes = utf8Encode.encode(password);
let passwordDigest = yield crypto.subtle.digest({ name: 'SHA-256' }, passwordBytes);
let key = yield crypto.subtle.importKey('raw', passwordDigest, algorithmObsolete, false, ['encrypt', 'decrypt']);
return key;
});
}
encryptToBase64(text, password) {
return __awaiter(this, void 0, void 0, function* () {
let key = yield this.buildKey(password);
let utf8Encode = new TextEncoder();
let bytesToEncrypt = utf8Encode.encode(text);
// encrypt into bytes
let encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt(algorithmObsolete, key, bytesToEncrypt));
//convert array to base64
let base64Text = btoa(String.fromCharCode(...encryptedBytes));
return base64Text;
});
}
stringToArray(str) {
var result = [];
for (var i = 0; i < str.length; i++) {
result.push(str.charCodeAt(i));
}
return new Uint8Array(result);
}
decryptFromBase64(base64Encoded, password) {
return __awaiter(this, void 0, void 0, function* () {
try {
// convert base 64 to array
let bytesToDecrypt = this.stringToArray(atob(base64Encoded));
let key = yield this.buildKey(password);
// decrypt into bytes
let decryptedBytes = yield crypto.subtle.decrypt(algorithmObsolete, key, bytesToDecrypt);
// convert bytes to text
let utf8Decode = new TextDecoder();
let decryptedText = utf8Decode.decode(decryptedBytes);
return decryptedText;
}
catch (e) {
return null;
}
});
}
}
class MeldEncryptSettingsTab extends obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
let { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Settings for Meld Encrypt' });
new obsidian.Setting(containerEl)
.setName('Confirm password?')
.setDesc('Confirm password when encrypting.')
.addToggle(toggle => {
toggle
.setValue(this.plugin.settings.confirmPassword)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.plugin.settings.confirmPassword = value;
yield this.plugin.saveSettings();
this.updateSettingsUi();
}));
});
new obsidian.Setting(containerEl)
.setName('Remember password?')
.setDesc('Remember the last used password for this session.')
.addToggle(toggle => {
toggle
.setValue(this.plugin.settings.rememberPassword)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.plugin.settings.rememberPassword = value;
yield this.plugin.saveSettings();
this.updateSettingsUi();
}));
});
this.pwTimeoutSetting = new obsidian.Setting(containerEl)
.setName(this.buildPasswordTimeoutSettingName())
.setDesc('The number of minutes to remember the last used password.')
.addSlider(slider => {
slider
.setLimits(0, 120, 5)
.setValue(this.plugin.settings.rememberPasswordTimeout)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.plugin.settings.rememberPasswordTimeout = value;
yield this.plugin.saveSettings();
this.updateSettingsUi();
}));
});
this.updateSettingsUi();
}
updateSettingsUi() {
this.pwTimeoutSetting.setName(this.buildPasswordTimeoutSettingName());
if (this.plugin.settings.rememberPassword) {
this.pwTimeoutSetting.settingEl.show();
}
else {
this.pwTimeoutSetting.settingEl.hide();
}
}
buildPasswordTimeoutSettingName() {
const value = this.plugin.settings.rememberPasswordTimeout;
let timeoutString = `${value} minutes`;
if (value == 0) {
timeoutString = 'Never forget';
}
return `Remember Password Timeout (${timeoutString})`;
}
}
const _PREFIX_OBSOLETE = '%%🔐 ';
const _PREFIX_A = '%%🔐α ';
const _SUFFIX = ' 🔐%%';
const DEFAULT_SETTINGS = {
confirmPassword: true,
rememberPassword: true,
rememberPasswordTimeout: 30
};
class MeldEncrypt extends obsidian.Plugin {
onload() {
return __awaiter(this, void 0, void 0, function* () {
yield this.loadSettings();
this.addSettingTab(new MeldEncryptSettingsTab(this.app, this));
this.addCommand({
id: 'encrypt-decrypt',
name: 'Encrypt/Decrypt',
checkCallback: (checking) => this.processEncryptDecryptCommand(checking, false)
});
this.addCommand({
id: 'encrypt-decrypt-in-place',
name: 'Encrypt/Decrypt In-place',
checkCallback: (checking) => this.processEncryptDecryptCommand(checking, true)
});
});
}
loadSettings() {
return __awaiter(this, void 0, void 0, function* () {
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
});
}
saveSettings() {
return __awaiter(this, void 0, void 0, function* () {
yield this.saveData(this.settings);
});
}
processEncryptDecryptCommand(checking, decryptInPlace) {
const mdview = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
if (!mdview) {
return false;
}
const editor = mdview.editor;
if (!editor) {
return false;
}
const startLine = editor.getCursor('from').line;
const startPos = { line: startLine, ch: 0 }; // want the start of the first line
const endLine = editor.getCursor('to').line;
const endLineText = editor.getLine(endLine);
const endPos = { line: endLine, ch: endLineText.length }; // want the end of last line
const selectionText = editor.getRange(startPos, endPos);
if (selectionText.length == 0) {
return false;
}
const decrypt_obs = selectionText.startsWith(_PREFIX_OBSOLETE) && selectionText.endsWith(_SUFFIX);
const decrypt_a = selectionText.startsWith(_PREFIX_A) && selectionText.endsWith(_SUFFIX);
const decrypt = decrypt_obs || decrypt_a;
const encrypt = !selectionText.contains(_PREFIX_OBSOLETE) && !selectionText.contains(_SUFFIX);
if (!decrypt && !encrypt) {
return false;
}
if (checking) {
return true;
}
// Fetch password from user
// determine default password
const isRememberPasswordExpired = !this.settings.rememberPassword
|| (this.passwordLastUsedExpiry != null
&& Date.now() > this.passwordLastUsedExpiry);
const confirmPassword = encrypt && this.settings.confirmPassword;
if (isRememberPasswordExpired || confirmPassword) {
// forget password
this.passwordLastUsed = '';
}
const pwModal = new PasswordModal(this.app, confirmPassword, this.passwordLastUsed);
pwModal.onClose = () => {
var _a;
const pw = (_a = pwModal.password) !== null && _a !== void 0 ? _a : '';
if (pw.length == 0) {
return;
}
// remember password?
if (this.settings.rememberPassword) {
this.passwordLastUsed = pw;
this.passwordLastUsedExpiry =
this.settings.rememberPasswordTimeout == 0
? null
: Date.now() + this.settings.rememberPasswordTimeout * 1000 * 60 // new expiry
;
}
if (encrypt) {
this.encryptSelection(editor, selectionText, pw, startPos, endPos);
}
else {
if (decrypt_a) {
this.decryptSelection_a(editor, selectionText, pw, startPos, endPos, decryptInPlace);
}
else {
this.decryptSelectionObsolete(editor, selectionText, pw, startPos, endPos, decryptInPlace);
}
}
};
pwModal.open();
return true;
}
encryptSelection(editor, selectionText, password, finalSelectionStart, finalSelectionEnd) {
return __awaiter(this, void 0, void 0, function* () {
//encrypt
const crypto = new CryptoHelperV2();
const base64EncryptedText = this.addMarkers(yield crypto.encryptToBase64(selectionText, password));
editor.setSelection(finalSelectionStart, finalSelectionEnd);
editor.replaceSelection(base64EncryptedText);
});
}
decryptSelection_a(editor, selectionText, password, selectionStart, selectionEnd, decryptInPlace) {
return __awaiter(this, void 0, void 0, function* () {
//console.log('decryptSelection_a');
// decrypt
const base64CipherText = this.removeMarkers(selectionText);
const crypto = new CryptoHelperV2();
const decryptedText = yield crypto.decryptFromBase64(base64CipherText, password);
if (decryptedText === null) {
new obsidian.Notice('❌ Decryption failed!');
}
else {
if (decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
else {
const decryptModal = new DecryptModal(this.app, '🔓', decryptedText);
decryptModal.onClose = () => {
editor.focus();
if (decryptModal.decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
};
decryptModal.open();
}
}
});
}
decryptSelectionObsolete(editor, selectionText, password, selectionStart, selectionEnd, decryptInPlace) {
return __awaiter(this, void 0, void 0, function* () {
//console.log('decryptSelectionObsolete');
// decrypt
const base64CipherText = this.removeMarkers(selectionText);
const crypto = new CryptoHelperObsolete();
const decryptedText = yield crypto.decryptFromBase64(base64CipherText, password);
if (decryptedText === null) {
new obsidian.Notice('❌ Decryption failed!');
}
else {
if (decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
else {
const decryptModal = new DecryptModal(this.app, '🔓', decryptedText);
decryptModal.onClose = () => {
editor.focus();
if (decryptModal.decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
};
decryptModal.open();
}
}
});
}
removeMarkers(text) {
if (text.startsWith(_PREFIX_A) && text.endsWith(_SUFFIX)) {
return text.replace(_PREFIX_A, '').replace(_SUFFIX, '');
}
if (text.startsWith(_PREFIX_OBSOLETE) && text.endsWith(_SUFFIX)) {
return text.replace(_PREFIX_OBSOLETE, '').replace(_SUFFIX, '');
}
return text;
}
addMarkers(text) {
if (!text.contains(_PREFIX_OBSOLETE) && !text.contains(_PREFIX_A) && !text.contains(_SUFFIX)) {
return _PREFIX_A.concat(text, _SUFFIX);
}
return text;
}
}
module.exports = MeldEncrypt;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsiLi4vbm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsIi4uL3NyYy9EZWNyeXB0TW9kYWwudHMiLCIuLi9zcmMvUGFzc3dvcmRNb2RhbC50cyIsIi4uL3NyYy9DcnlwdG9IZWxwZXIudHMiLCIuLi9zcmMvTWVsZEVuY3J5cHRTZXR0aW5nc1RhYi50cyIsIi4uL3NyYy9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qISAqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBpZiAodHlwZW9mIGIgIT09IFwiZnVuY3Rpb25cIiAmJiBiICE9PSBudWxsKVxyXG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDbGFzcyBleHRlbmRzIHZhbHVlIFwiICsgU3RyaW5nKGIpICsgXCIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbFwiKTtcclxuICAgIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbiAgICBmdW5jdGlvbiBfXygpIHsgdGhpcy5jb25zdHJ1Y3RvciA9IGQ7IH1cclxuICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTtcclxufVxyXG5cclxuZXhwb3J0IHZhciBfX2Fzc2lnbiA9IGZ1bmN0aW9uKCkge1xyXG4gICAgX19hc3NpZ24gPSBPYmplY3QuYXNzaWduIHx8IGZ1bmN0aW9uIF9fYXNzaWduKHQpIHtcclxuICAgICAgICBmb3IgKHZhciBzLCBpID0gMSwgbiA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBuOyBpKyspIHtcclxuICAgICAgICAgICAgcyA9IGFyZ3VtZW50c1tpXTtcclxuICAgICAgICAgICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApKSB0W3BdID0gc1twXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIHQ7XHJcbiAgICB9XHJcbiAgICByZXR1cm4gX19hc3NpZ24uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpIHtcclxuICAgIHZhciBjID0gYXJndW1lbnRzLmxlbmd0aCwgciA9IGMgPCAzID8gdGFyZ2V0IDogZGVzYyA9PT0gbnVsbCA/IGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHRhcmdldCwga2V5KSA6IGRlc2MsIGQ7XHJcbiAgICBpZiAodHlwZW9mIFJlZmxlY3QgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIFJlZmxlY3QuZGVjb3JhdGUgPT09IFwiZnVuY3Rpb25cIikgciA9IFJlZmxlY3QuZGVjb3JhdGUoZGVjb3JhdG9ycyw