@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
292 lines • 43.4 kB
JavaScript
import path from 'node:path';
import fs from 'node:fs/promises';
import { logger } from '../utils/logger.js';
import { RegexValidator } from './regexValidator.js';
import { SECURITY_LIMITS } from './constants.js';
export class PathValidator {
static ALLOWED_DIRECTORIES = [];
static ALLOWED_EXTENSIONS = ['.md', '.markdown', '.txt', '.yml', '.yaml'];
static resolvedAllowedDirs = null;
static initialize(personasDir, allowedExtensions) {
this.ALLOWED_DIRECTORIES = [
path.resolve(personasDir),
path.resolve('./personas'),
path.resolve('./custom-personas'),
path.resolve('./backups'),
path.resolve(process.env.PERSONAS_DIR || './personas')
];
if (allowedExtensions) {
this.ALLOWED_EXTENSIONS = allowedExtensions;
}
// Clear cached resolved directories when reinitializing
this.resolvedAllowedDirs = null;
}
/**
* Get allowed directories with symlinks resolved
* Caches the result to avoid repeated filesystem calls
*/
static async getResolvedAllowedDirectories() {
if (this.resolvedAllowedDirs) {
return this.resolvedAllowedDirs;
}
this.resolvedAllowedDirs = (async () => {
const allowedDirs = this.ALLOWED_DIRECTORIES.length === 0
? [path.resolve('./personas'), path.resolve(process.env.PERSONAS_DIR || './personas')]
: this.ALLOWED_DIRECTORIES;
// Resolve symlinks for each allowed directory
const resolved = await Promise.all(allowedDirs.map(async (dir) => {
try {
return await fs.realpath(dir);
}
catch {
// If directory doesn't exist yet, use the original path
return dir;
}
}));
return resolved;
})();
return this.resolvedAllowedDirs;
}
/**
* SECURITY FIX #1290: Resolve symlinks to prevent path traversal
* Helper function to resolve symlinks in paths
*/
static async resolveSymlinks(resolvedPath, userPath) {
try {
// Try to resolve symlinks in the full path
const realPath = await fs.realpath(resolvedPath);
// Log symlink resolution for security auditing
if (realPath !== resolvedPath) {
logger.warn('Symlink detected and resolved', {
requestedPath: userPath,
resolvedPath,
realPath
});
}
return realPath;
}
catch (err) {
// If path doesn't exist (e.g., creating new file), resolve parent directory
if (err.code === 'ENOENT') {
return this.resolveParentSymlink(resolvedPath, userPath);
}
throw err;
}
}
/**
* Helper to resolve symlinks in parent directory when target doesn't exist
*/
static async resolveParentSymlink(resolvedPath, userPath) {
const parentDir = path.dirname(resolvedPath);
try {
const realParent = await fs.realpath(parentDir);
// Log parent symlink resolution for security auditing
if (realParent !== parentDir) {
logger.warn('Parent directory symlink detected and resolved', {
requestedPath: userPath,
parentDir,
realParent
});
}
// Reconstruct path with resolved parent and original filename
return path.join(realParent, path.basename(resolvedPath));
}
catch {
// Parent directory doesn't exist - use resolved path
// (will fail later in file operations, but not a security issue)
return resolvedPath;
}
}
/**
* Validate that the real path is within allowed directories
* FIX: Now resolves symlinks in allowed directories to handle macOS /tmp -> /private/tmp
*/
static async validatePathIsAllowed(realPath, userPath) {
const allowedDirs = await this.getResolvedAllowedDirectories();
const isAllowed = allowedDirs.some(allowedDir => realPath.startsWith(allowedDir + path.sep) || realPath === allowedDir);
if (!isAllowed) {
// SECURITY FIX #206: Don't expose user paths in error messages
logger.error('Path access denied', { path: userPath, realPath, allowedDirs });
throw new Error('Path access denied');
}
}
/**
* Validate filename extension and format
*/
static validateFilename(realPath) {
if (!path.extname(realPath)) {
return; // Not a file, skip validation
}
const filename = path.basename(realPath);
const ext = path.extname(filename).toLowerCase();
// Check if extension is allowed
if (!this.ALLOWED_EXTENSIONS.includes(ext)) {
throw new Error(`File extension not allowed: ${ext}. Allowed: ${this.ALLOWED_EXTENSIONS.join(', ')}`);
}
// Validate filename format (alphanumeric, dash, underscore, dot)
if (!RegexValidator.validate(filename, /^[a-zA-Z0-9_.-]+$/, { maxLength: SECURITY_LIMITS.MAX_FILENAME_LENGTH })) {
throw new Error(`Invalid filename format: ${filename}`);
}
}
/**
* Validate element path WITHOUT resolving symlinks
* Used by BaseElementManager to validate paths while preserving the original path representation
*
* SECURITY: Rejects symlinks that point outside the allowed directory
*
* @param absolutePath - Absolute path to validate
* @param allowedDir - Base directory that the path must be within
* @throws Error if path is invalid, is a symlink pointing outside allowed directory, or outside allowed directory
*/
static async validateElementPathOnly(absolutePath, allowedDir) {
if (!absolutePath || typeof absolutePath !== 'string') {
throw new Error('Path must be a non-empty string');
}
if (!allowedDir || typeof allowedDir !== 'string') {
throw new Error('allowedDir must be a non-empty string');
}
// Remove any null bytes
// eslint-disable-next-line no-control-regex -- Intentionally removing null bytes for security
const cleanPath = absolutePath.replaceAll(/\u0000/g, ''); // NOSONAR - Removing null bytes for security
// Check for path traversal attempts in the normalized path
const normalizedPath = path.normalize(cleanPath);
if (normalizedPath.includes('..') || cleanPath.includes('..')) {
logger.warn('Path traversal attempt detected', { userPath: absolutePath });
throw new Error('Path traversal detected');
}
// Validate path is within allowed directory (without resolving symlinks)
const normalizedAllowedDir = path.normalize(allowedDir);
const pathWithSep = normalizedPath + path.sep;
const allowedDirWithSep = normalizedAllowedDir + path.sep;
if (!pathWithSep.startsWith(allowedDirWithSep) && normalizedPath !== normalizedAllowedDir) {
logger.error('Path access denied', { path: absolutePath, allowedDir: normalizedAllowedDir });
throw new Error('Path access denied');
}
// SECURITY FIX: Check if path is a symlink and if so, ensure it points within allowed directory
try {
const stats = await fs.lstat(normalizedPath); // lstat doesn't follow symlinks
if (stats.isSymbolicLink()) {
// Resolve the symlink target to check if it's within allowed directory
const realPath = await fs.realpath(normalizedPath);
const realPathWithSep = realPath + path.sep;
if (!realPathWithSep.startsWith(allowedDirWithSep) && realPath !== normalizedAllowedDir) {
logger.error('Symlink target outside allowed directory', {
path: absolutePath,
realPath,
allowedDir: normalizedAllowedDir
});
throw new Error('Path access denied');
}
}
}
catch (err) {
// If file doesn't exist, that's okay (might be creating a new file)
// Re-throw if it's NOT an ENOENT error
if (err.code !== 'ENOENT') {
throw err;
}
}
// Validate filename extension and format
this.validateFilename(normalizedPath);
}
/**
* Stateless element path validation (generic for all element types)
* Used by BaseElementManager for validating paths across all element types.
*
* @param userPath - User-provided path to validate
* @param allowedDir - Base directory that the path must be within
* @returns Validated absolute path with symlinks resolved
* @deprecated Use validateElementPathOnly() for path validation without resolution
*/
static async validateElementPath(userPath, allowedDir) {
if (!userPath || typeof userPath !== 'string') {
throw new Error('Path must be a non-empty string');
}
if (!allowedDir || typeof allowedDir !== 'string') {
throw new Error('allowedDir must be a non-empty string');
}
// Remove any null bytes
// eslint-disable-next-line no-control-regex -- Intentionally removing null bytes for security
const cleanPath = userPath.replaceAll(/\u0000/g, ''); // NOSONAR - Removing null bytes for security
// Normalize and resolve path
const normalizedPath = path.normalize(cleanPath);
const resolvedPath = path.resolve(normalizedPath);
// Check for path traversal attempts
if (normalizedPath.includes('..') || cleanPath.includes('..')) {
logger.warn('Path traversal attempt detected', { userPath });
throw new Error('Path traversal detected');
}
// Resolve symlinks to get real path
const realPath = await this.resolveSymlinks(resolvedPath, userPath);
// Validate path is within allowed directory
const resolvedAllowedDir = await fs.realpath(allowedDir).catch(() => allowedDir);
if (!realPath.startsWith(resolvedAllowedDir + path.sep) && realPath !== resolvedAllowedDir) {
logger.error('Path access denied', { path: userPath, realPath, allowedDir: resolvedAllowedDir });
throw new Error('Path access denied');
}
// Validate filename extension and format
this.validateFilename(realPath);
// Return the real path (with symlinks resolved) for safe file operations
return realPath;
}
/**
* @deprecated Use validateElementPath() instead. This method uses class-level state.
* Validate a persona path against pre-initialized allowed directories.
*
* @param userPath - User-provided path to validate
* @returns Validated absolute path with symlinks resolved
*/
static async validatePersonaPath(userPath) {
if (!userPath || typeof userPath !== 'string') {
throw new Error('Path must be a non-empty string');
}
// Remove any null bytes
// eslint-disable-next-line no-control-regex -- Intentionally removing null bytes for security
const cleanPath = userPath.replaceAll(/\u0000/g, ''); // NOSONAR - Removing null bytes for security
// Normalize and resolve path
const normalizedPath = path.normalize(cleanPath);
const resolvedPath = path.resolve(normalizedPath);
// Check for path traversal attempts
if (normalizedPath.includes('..') || cleanPath.includes('..')) {
logger.warn('Path traversal attempt detected', { userPath });
throw new Error('Path traversal detected');
}
// Resolve symlinks to get real path
const realPath = await this.resolveSymlinks(resolvedPath, userPath);
// Validate path is within allowed directories (now async to resolve symlinks in allowed dirs)
await this.validatePathIsAllowed(realPath, userPath);
// Validate filename extension and format
this.validateFilename(realPath);
// Return the real path (with symlinks resolved) for safe file operations
return realPath;
}
static async safeReadFile(filePath) {
const validatedPath = await this.validatePersonaPath(filePath);
// Check file exists and is not a directory
const stats = await fs.stat(validatedPath);
if (stats.isDirectory()) {
throw new Error('Path is a directory, not a file');
}
// Size check
if (stats.size > 500000) { // 500KB
throw new Error('File too large');
}
return fs.readFile(validatedPath, 'utf-8');
}
static async safeWriteFile(filePath, content) {
const validatedPath = await this.validatePersonaPath(filePath);
// Content validation
if (content.length > 500000) {
throw new Error('Content too large');
}
// Ensure directory exists before atomic write
const dirPath = path.dirname(validatedPath);
await fs.mkdir(dirPath, { recursive: true });
// Write to temp file first (atomic write)
const tempPath = `${validatedPath}.tmp`;
await fs.writeFile(tempPath, content, 'utf-8');
// Rename to final path (atomic on most filesystems)
await fs.rename(tempPath, validatedPath);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aFZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZWN1cml0eS9wYXRoVmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUM3QixPQUFPLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNsQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVqRCxNQUFNLE9BQU8sYUFBYTtJQUNoQixNQUFNLENBQUMsbUJBQW1CLEdBQWEsRUFBRSxDQUFDO0lBQzFDLE1BQU0sQ0FBQyxrQkFBa0IsR0FBYSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNwRixNQUFNLENBQUMsbUJBQW1CLEdBQTZCLElBQUksQ0FBQztJQUVwRSxNQUFNLENBQUMsVUFBVSxDQUFDLFdBQW1CLEVBQUUsaUJBQTRCO1FBQ2pFLElBQUksQ0FBQyxtQkFBbUIsR0FBRztZQUN6QixJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztZQUN6QixJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQztZQUMxQixJQUFJLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDO1lBQ2pDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDO1lBQ3pCLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLElBQUksWUFBWSxDQUFDO1NBQ3ZELENBQUM7UUFFRixJQUFJLGlCQUFpQixFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGlCQUFpQixDQUFDO1FBQzlDLENBQUM7UUFFRCx3REFBd0Q7UUFDeEQsSUFB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QUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBRUQsYUFBYTtRQUNiLElBQUksS0FBSyxDQUFDLElBQUksR0FBRyxNQUFNLEVBQUUsQ0FBQyxDQUFDLFFBQVE7WUFDakMsTUFBTSxJQUFJLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQ3BDLENBQUM7UUFFRCxPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFFRCxNQUFNLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxRQUFnQixFQUFFLE9BQWU7UUFDMUQsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFL0QscUJBQXFCO1FBQ3JCLElBQUksT0FBTyxDQUFDLE1BQU0sR0FBRyxNQUFNLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDdkMsQ0FBQztRQUVELDhDQUE4QztRQUM5QyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQzVDLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUU3QywwQ0FBMEM7UUFDMUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxhQUFhLE1BQU0sQ0FBQztRQUN4QyxNQUFNLEVBQUUsQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztRQUUvQyxvREFBb0Q7UUFDcEQsTUFBTSxFQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUMzQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCBmcyBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBSZWdleFZhbGlkYXRvciB9IGZyb20gJy4vcmVnZXhWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgU0VDVVJJVFlfTElNSVRTIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuXG5leHBvcnQgY2xhc3MgUGF0aFZhbGlkYXRvciB7XG4gIHByaXZhdGUgc3RhdGljIEFMTE9XRURfRElSRUNUT1JJRVM6IHN0cmluZ1tdID0gW107XG4gIHByaXZhdGUgc3RhdGljIEFMTE9XRURfRVhURU5TSU9OUzogc3RyaW5nW10gPSBbJy5tZCcsICcubWFya2Rvd24nLCAnLnR4dCcsICcueW1sJywgJy55YW1sJ107XG4gIHByaXZhdGUgc3RhdGljIHJlc29sdmVkQWxsb3dlZERpcnM6IFByb21pc2U8c3RyaW5nW10+IHwgbnVsbCA9IG51bGw7XG5cbiAgc3RhdGljIGluaXRpYWxpemUocGVyc29uYXNEaXI6IHN0cmluZywgYWxsb3dlZEV4dGVuc2lvbnM/OiBzdHJpbmdbXSk6IHZvaWQge1xuICAgIHRoaXMuQUxMT1dFRF9ESVJFQ1RPUklFUyA9IFtcbiAgICAgIHBhdGgucmVzb2x2ZShwZXJzb25hc0RpciksXG4gICAgICBwYXRoLnJlc29sdmUoJy4vcGVyc29uYXMnKSxcbiAgICAgIHBhdGgucmVzb2x2ZSgnLi9jdXN0b20tcGVyc29uYXMnKSxcbiAgICAgIHBhdGgucmVzb2x2ZSgnLi9iYWNrdXBzJyksXG4gICAgICBwYXRoLnJlc29sdmUocHJvY2Vzcy5lbnYuUEVSU09OQVNfRElSIHx8ICcuL3BlcnNvbmFzJylcbiAgICBdO1xuXG4gICAgaWYgKGFsbG93ZWRFeHRlbnNpb25zKSB7XG4gICAgICB0aGlzLkFMTE9XRURfRVhURU5TSU9OUyA9IGFsbG93ZWRFeHRlbnNpb25zO1xuICAgIH1cblxuICAgIC8vIENsZWFyIGNhY2hlZCByZXNvbHZlZCBkaXJlY3RvcmllcyB3aGVuIHJlaW5pdGlhbGl6aW5nXG4gICAgdGhpcy5yZXNvbHZlZEFsbG93ZWREaXJzID0gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgYWxsb3dlZCBkaXJlY3RvcmllcyB3aXRoIHN5bWxpbmtzIHJlc29sdmVkXG4gICAqIENhY2hlcyB0aGUgcmVzdWx0IHRvIGF2b2lkIHJlcGVhdGVkIGZpbGVzeXN0ZW0gY2FsbHNcbiAgICovXG4gIHByaXZhdGUgc3RhdGljIGFzeW5jIGdldFJlc29sdmVkQWxsb3dlZERpcmVjdG9yaWVzKCk6IFByb21pc2U8c3RyaW5nW10+IHtcbiAgICBpZiAodGhpcy5yZXNvbHZlZEFsbG93ZWREaXJzKSB7XG4gICAgICByZXR1cm4gdGhpcy5yZXNvbHZlZEFsbG93ZWREaXJzO1xuICAgIH1cblxuICAgIHRoaXMucmVzb2x2ZWRBbGxvd2VkRGlycyA9IChhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBhbGxvd2VkRGlycyA9IHRoaXMuQUxMT1dFRF9ESVJFQ1RPUklFUy5sZW5ndGggPT09IDBcbiAgICAgICAgPyBbcGF0aC5yZXNvbHZlKCcuL3BlcnNvbmFzJyksIHBhdGgucmVzb2x2ZShwcm9jZXNzLmVudi5QRVJTT05BU19ESVIgfHwgJy4vcGVyc29uYXMnKV1cbiAgICAgICAgOiB0aGlzLkFMTE9XRURfRElSRUNUT1JJRVM7XG5cbiAgICAgIC8vIFJlc29sdmUgc3ltbGlua3MgZm9yIGVhY2ggYWxsb3dlZCBkaXJlY3RvcnlcbiAgICAgIGNvbnN0IHJlc29sdmVkID0gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgICAgIGFsbG93ZWREaXJzLm1hcChhc3luYyAoZGlyKSA9PiB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJldHVybiBhd2FpdCBmcy5yZWFscGF0aChkaXIpO1xuICAgICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgICAgLy8gSWYgZGlyZWN0b3J5IGRvZXNuJ3QgZXhpc3QgeWV0LCB1c2UgdGhlIG9yaWdpbmFsIHBhdGhcbiAgICAgICAgICAgIHJldHVybiBkaXI7XG4gICAgICAgICAgfVxuICAgICAgICB9KVxuICAgICAgKTtcblxuICAgICAgcmV0dXJuIHJlc29sdmVkO1xuICAgIH0pKCk7XG5cbiAgICByZXR1cm4gdGhpcy5yZXNvbHZlZEFsbG93ZWREaXJzO1xuICB9XG5cbiAgLyoqXG4gICAqIFNFQ1VSSVRZIEZJWCAjMTI5MDogUmVzb2x2ZSBzeW1saW5rcyB0byBwcmV2ZW50IHBhdGggdHJhdmVyc2FsXG4gICAqIEhlbHBlciBmdW5jdGlvbiB0byByZXNvbHZlIHN5bWxpbmtzIGluIHBhdGhzXG4gICAqL1xuICBwcml2YXRlIHN0YXRpYyBhc3luYyByZXNvbHZlU3ltbGlua3MocmVzb2x2ZWRQYXRoOiBzdHJpbmcsIHVzZXJQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBUcnkgdG8gcmVzb2x2ZSBzeW1saW5rcyBpbiB0aGUgZnVsbCBwYXRoXG4gICAgICBjb25zdCByZWFsUGF0aCA9IGF3YWl0IGZzLnJlYWxwYXRoKHJlc29sdmVkUGF0aCk7XG5cbiAgICAgIC8vIExvZyBzeW1saW5rIHJlc29sdXRpb24gZm9yIHNlY3VyaXR5IGF1ZGl0aW5nXG4gICAgICBpZiAocmVhbFBhdGggIT09IHJlc29sdmVkUGF0aCkge1xuICAgICAgICBsb2dnZXIud2FybignU3ltbGluayBkZXRlY3RlZCBhbmQgcmVzb2x2ZWQnLCB7XG4gICAgICAgICAgcmVxdWVzdGVkUGF0aDogdXNlclBhdGgsXG4gICAgICAgICAgcmVzb2x2ZWRQYXRoLFxuICAgICAgICAgIHJlYWxQYXRoXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHJlYWxQYXRoO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gSWYgcGF0aCBkb2Vzbid0IGV4aXN0IChlLmcuLCBjcmVhdGluZyBuZXcgZmlsZSksIHJlc29sdmUgcGFyZW50IGRpcmVjdG9yeVxuICAgICAgaWYgKChlcnIgYXMgTm9kZUpTLkVycm5vRXhjZXB0aW9uKS5jb2RlID09PSAnRU5PRU5UJykge1xuICAgICAgICByZXR1cm4gdGhpcy5yZXNvbHZlUGFyZW50U3ltbGluayhyZXNvbHZlZFBhdGgsIHVzZXJQYXRoKTtcbiAgICAgIH1cbiAgICAgIHRocm93IGVycjtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSGVscGVyIHRvIHJlc29sdmUgc3ltbGlua3MgaW4gcGFyZW50IGRpcmVjdG9yeSB3aGVuIHRhcmdldCBkb2Vzbid0IGV4aXN0XG4gICAqL1xuICBwcml2YXRlIHN0YXRpYyBhc3luYyByZXNvbHZlUGFyZW50U3ltbGluayhyZXNvbHZlZFBhdGg6IHN0cmluZywgdXNlclBhdGg6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgcGFyZW50RGlyID0gcGF0aC5kaXJuYW1lKHJlc29sdmVkUGF0aCk7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlYWxQYXJlbnQgPSBhd2FpdCBmcy5yZWFscGF0aChwYXJlbnREaXIpO1xuXG4gICAgICAvLyBMb2cgcGFyZW50IHN5bWxpbmsgcmVzb2x1dGlvbiBmb3Igc2VjdXJpdHkgYXVkaXRpbmdcbiAgICAgIGlmIChyZWFsUGFyZW50ICE9PSBwYXJlbnREaXIpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1BhcmVudCBkaXJlY3Rvcnkgc3ltbGluayBkZXRlY3RlZCBhbmQgcmVzb2x2ZWQnLCB7XG4gICAgICAgICAgcmVxdWVzdGVkUGF0aDogdXNlclBhdGgsXG4gICAgICAgICAgcGFyZW50RGlyLFxuICAgICAgICAgIHJlYWxQYXJlbnRcbiAgICAgICAgfSk7XG4gICAgICB9XG5cbiAgICAgIC8vIFJlY29uc3RydWN0IHBhdGggd2l0aCByZXNvbHZlZCBwYXJlbnQgYW5kIG9yaWdpbmFsIGZpbGVuYW1lXG4gICAgICByZXR1cm4gcGF0aC5qb2luKHJlYWxQYXJlbnQsIHBhdGguYmFzZW5hbWUocmVzb2x2ZWRQYXRoKSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBQYXJlbnQgZGlyZWN0b3J5IGRvZXNuJ3QgZXhpc3QgLSB1c2UgcmVzb2x2ZWQgcGF0aFxuICAgICAgLy8gKHdpbGwgZmFpbCBsYXRlciBpbiBmaWxlIG9wZXJhdGlvbnMsIGJ1dCBub3QgYSBzZWN1cml0eSBpc3N1ZSlcbiAgICAgIHJldHVybiByZXNvbHZlZFBhdGg7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIHRoYXQgdGhlIHJlYWwgcGF0aCBpcyB3aXRoaW4gYWxsb3dlZCBkaXJlY3Rvcmllc1xuICAgKiBGSVg6IE5vdyByZXNvbHZlcyBzeW1saW5rcyBpbiBhbGxvd2VkIGRpcmVjdG9yaWVzIHRvIGhhbmRsZSBtYWNPUyAvdG1wIC0+IC9wcml2YXRlL3RtcFxuICAgKi9cbiAgcHJpdmF0ZSBzdGF0aWMgYXN5bmMgdmFsaWRhdGVQYXRoSXNBbGxvd2VkKHJlYWxQYXRoOiBzdHJpbmcsIHVzZXJQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBhbGxvd2VkRGlycyA9IGF3YWl0IHRoaXMuZ2V0UmVzb2x2ZWRBbGxvd2VkRGlyZWN0b3JpZXMoKTtcblxuICAgIGNvbnN0IGlzQWxsb3dlZCA9IGFsbG93ZWREaXJzLnNvbWUoYWxsb3dlZERpciA9PlxuICAgICAgcmVhbFBhdGguc3RhcnRzV2l0aChhbGxvd2VkRGlyICsgcGF0aC5zZXApIHx8IHJlYWxQYXRoID09PSBhbGxvd2VkRGlyXG4gICAgKTtcblxuICAgIGlmICghaXNBbGxvd2VkKSB7XG4gICAgICAvLyBTRUNVUklUWSBGSVggIzIwNjogRG9uJ3QgZXhwb3NlIHVzZXIgcGF0aHMgaW4gZXJyb3IgbWVzc2FnZXNcbiAgICAgIGxvZ2dlci5lcnJvcignUGF0aCBhY2Nlc3MgZGVuaWVkJywgeyBwYXRoOiB1c2VyUGF0aCwgcmVhbFBhdGgsIGFsbG93ZWREaXJzIH0pO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdQYXRoIGFjY2VzcyBkZW5pZWQnKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGUgZmlsZW5hbWUgZXh0ZW5zaW9uIGFuZCBmb3JtYXRcbiAgICovXG4gIHByaXZhdGUgc3RhdGljIHZhbGlkYXRlRmlsZW5hbWUocmVhbFBhdGg6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghcGF0aC5leHRuYW1lKHJlYWxQYXRoKSkge1xuICAgICAgcmV0dXJuOyAvLyBOb3QgYSBmaWxlLCBza2lwIHZhbGlkYXRpb25cbiAgICB9XG5cbiAgICBjb25zdCBmaWxlbmFtZSA9IHBhdGguYmFzZW5hbWUocmVhbFBhdGgpO1xuICAgIGNvbnN0IGV4dCA9IHBhdGguZXh0bmFtZShmaWxlbmFtZSkudG9Mb3dlckNhc2UoKTtcblxuICAgIC8vIENoZWNrIGlmIGV4dGVuc2lvbiBpcyBhbGxvd2VkXG4gICAgaWYgKCF0aGlzLkFMTE9XRURfRVhURU5TSU9OUy5pbmNsdWRlcyhleHQpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEZpbGUgZXh0ZW5zaW9uIG5vdCBhbGxvd2VkOiAke2V4dH0uIEFsbG93ZWQ6ICR7dGhpcy5BTExPV0VEX0VYVEVOU0lPTlMuam9pbignLCAnKX1gKTtcbiAgICB9XG5cbiAgICAvLyBWYWxpZGF0ZSBmaWxlbmFtZSBmb3JtYXQgKGFscGhhbnVtZXJpYywgZGFzaCwgdW5kZXJzY29yZSwgZG90KVxuICAgIGlmICghUmVnZXhWYWxpZGF0b3IudmFsaWRhdGUoZmlsZW5hbWUsIC9eW2EtekEtWjAtOV8uLV0rJC8sIHsgbWF4TGVuZ3RoOiBTRUNVUklUWV9MSU1JVFMuTUFYX0ZJTEVOQU1FX0xFTkdUSCB9KSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIGZpbGVuYW1lIGZvcm1hdDogJHtmaWxlbmFtZX1gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGUgZWxlbWVudCBwYXRoIFdJVEhPVVQgcmVzb2x2aW5nIHN5bWxpbmtzXG4gICAqIFVzZWQgYnkgQmFzZUVsZW1lbnRNYW5hZ2VyIHRvIHZhbGlkYXRlIHBhdGhzIHdoaWxlIHByZXNlcnZpbmcgdGhlIG9yaWdpbmFsIHBhdGggcmVwcmVzZW50YXRpb25cbiAgICpcbiAgICogU0VDVVJJVFk6IFJlamVjdHMgc3ltbGlua3MgdGhhdCBwb2ludCBvdXRzaWRlIHRoZSBhbGxvd2VkIGRpcmVjdG9yeVxuICAgKlxuICAgKiBAcGFyYW0gYWJzb2x1dGVQYXRoIC0gQWJzb2x1dGUgcGF0aCB0byB2YWxpZGF0ZVxuICAgKiBAcGFyYW0gYWxsb3dlZERpciAtIEJhc2UgZGlyZWN0b3J5IHRoYXQgdGhlIHBhdGggbXVzdCBiZSB3aXRoaW5cbiAgICogQHRocm93cyBFcnJvciBpZiBwYXRoIGlzIGludmFsaWQsIGlzIGEgc3ltbGluayBwb2ludGluZyBvdXRzaWRlIGFsbG93ZWQgZGlyZWN0b3J5LCBvciBvdXRzaWRlIGFsbG93ZWQgZGlyZWN0b3J5XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgdmFsaWRhdGVFbGVtZW50UGF0aE9ubHkoYWJzb2x1dGVQYXRoOiBzdHJpbmcsIGFsbG93ZWREaXI6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICghYWJzb2x1dGVQYXRoIHx8IHR5cGVvZiBhYnNvbHV0ZVBhdGggIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICB9XG5cbiAgICBpZiAoIWFsbG93ZWREaXIgfHwgdHlwZW9mIGFsbG93ZWREaXIgIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2FsbG93ZWREaXIgbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgYW55IG51bGwgYnl0ZXNcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29udHJvbC1yZWdleCAtLSBJbnRlbnRpb25hbGx5IHJlbW92aW5nIG51bGwgYnl0ZXMgZm9yIHNlY3VyaXR5XG4gICAgY29uc3QgY2xlYW5QYXRoID0gYWJzb2x1dGVQYXRoLnJlcGxhY2VBbGwoL1xcdTAwMDAvZywgJycpOyAvLyBOT1NPTkFSIC0gUmVtb3ZpbmcgbnVsbCBieXRlcyBmb3Igc2VjdXJpdHlcblxuICAgIC8vIENoZWNrIGZvciBwYXRoIHRyYXZlcnNhbCBhdHRlbXB0cyBpbiB0aGUgbm9ybWFsaXplZCBwYXRoXG4gICAgY29uc3Qgbm9ybWFsaXplZFBhdGggPSBwYXRoLm5vcm1hbGl6ZShjbGVhblBhdGgpO1xuICAgIGlmIChub3JtYWxpemVkUGF0aC5pbmNsdWRlcygnLi4nKSB8fCBjbGVhblBhdGguaW5jbHVkZXMoJy4uJykpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdQYXRoIHRyYXZlcnNhbCBhdHRlbXB0IGRldGVjdGVkJywgeyB1c2VyUGF0aDogYWJzb2x1dGVQYXRoIH0pO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdQYXRoIHRyYXZlcnNhbCBkZXRlY3RlZCcpO1xuICAgIH1cblxuICAgIC8vIFZhbGlkYXRlIHBhdGggaXMgd2l0aGluIGFsbG93ZWQgZGlyZWN0b3J5ICh3aXRob3V0IHJlc29sdmluZyBzeW1saW5rcylcbiAgICBjb25zdCBub3JtYWxpemVkQWxsb3dlZERpciA9IHBhdGgubm9ybWFsaXplKGFsbG93ZWREaXIpO1xuICAgIGNvbnN0IHBhdGhXaXRoU2VwID0gbm9ybWFsaXplZFBhdGggKyBwYXRoLnNlcDtcbiAgICBjb25zdCBhbGxvd2VkRGlyV2l0aFNlcCA9IG5vcm1hbGl6ZWRBbGxvd2VkRGlyICsgcGF0aC5zZXA7XG5cbiAgICBpZiAoIXBhdGhXaXRoU2VwLnN0YXJ0c1dpdGgoYWxsb3dlZERpcldpdGhTZXApICYmIG5vcm1hbGl6ZWRQYXRoICE9PSBub3JtYWxpemVkQWxsb3dlZERpcikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdQYXRoIGFjY2VzcyBkZW5pZWQnLCB7IHBhdGg6IGFic29sdXRlUGF0aCwgYWxsb3dlZERpcjogbm9ybWFsaXplZEFsbG93ZWREaXIgfSk7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggYWNjZXNzIGRlbmllZCcpO1xuICAgIH1cblxuICAgIC8vIFNFQ1VSSVRZIEZJWDogQ2hlY2sgaWYgcGF0aCBpcyBhIHN5bWxpbmsgYW5kIGlmIHNvLCBlbnN1cmUgaXQgcG9pbnRzIHdpdGhpbiBhbGxvd2VkIGRpcmVjdG9yeVxuICAgIHRyeSB7XG4gICAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGZzLmxzdGF0KG5vcm1hbGl6ZWRQYXRoKTsgLy8gbHN0YXQgZG9lc24ndCBmb2xsb3cgc3ltbGlua3NcbiAgICAgIGlmIChzdGF0cy5pc1N5bWJvbGljTGluaygpKSB7XG4gICAgICAgIC8vIFJlc29sdmUgdGhlIHN5bWxpbmsgdGFyZ2V0IHRvIGNoZWNrIGlmIGl0J3Mgd2l0aGluIGFsbG93ZWQgZGlyZWN0b3J5XG4gICAgICAgIGNvbnN0IHJlYWxQYXRoID0gYXdhaXQgZnMucmVhbHBhdGgobm9ybWFsaXplZFBhdGgpO1xuICAgICAgICBjb25zdCByZWFsUGF0aFdpdGhTZXAgPSByZWFsUGF0aCArIHBhdGguc2VwO1xuXG4gICAgICAgIGlmICghcmVhbFBhdGhXaXRoU2VwLnN0YXJ0c1dpdGgoYWxsb3dlZERpcldpdGhTZXApICYmIHJlYWxQYXRoICE9PSBub3JtYWxpemVkQWxsb3dlZERpcikge1xuICAgICAgICAgIGxvZ2dlci5lcnJvcignU3ltbGluayB0YXJnZXQgb3V0c2lkZSBhbGxvd2VkIGRpcmVjdG9yeScsIHtcbiAgICAgICAgICAgIHBhdGg6IGFic29sdXRlUGF0aCxcbiAgICAgICAgICAgIHJlYWxQYXRoLFxuICAgICAgICAgICAgYWxsb3dlZERpcjogbm9ybWFsaXplZEFsbG93ZWREaXJcbiAgICAgICAgICB9KTtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggYWNjZXNzIGRlbmllZCcpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAvLyBJZiBmaWxlIGRvZXNuJ3QgZXhpc3QsIHRoYXQncyBva2F5IChtaWdodCBiZSBjcmVhdGluZyBhIG5ldyBmaWxlKVxuICAgICAgLy8gUmUtdGhyb3cgaWYgaXQncyBOT1QgYW4gRU5PRU5UIGVycm9yXG4gICAgICBpZiAoKGVyciBhcyBOb2RlSlMuRXJybm9FeGNlcHRpb24pLmNvZGUgIT09ICdFTk9FTlQnKSB7XG4gICAgICAgIHRocm93IGVycjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBWYWxpZGF0ZSBmaWxlbmFtZSBleHRlbnNpb24gYW5kIGZvcm1hdFxuICAgIHRoaXMudmFsaWRhdGVGaWxlbmFtZShub3JtYWxpemVkUGF0aCk7XG4gIH1cblxuICAvKipcbiAgICogU3RhdGVsZXNzIGVsZW1lbnQgcGF0aCB2YWxpZGF0aW9uIChnZW5lcmljIGZvciBhbGwgZWxlbWVudCB0eXBlcylcbiAgICogVXNlZCBieSBCYXNlRWxlbWVudE1hbmFnZXIgZm9yIHZhbGlkYXRpbmcgcGF0aHMgYWNyb3NzIGFsbCBlbGVtZW50IHR5cGVzLlxuICAgKlxuICAgKiBAcGFyYW0gdXNlclBhdGggLSBVc2VyLXByb3ZpZGVkIHBhdGggdG8gdmFsaWRhdGVcbiAgICogQHBhcmFtIGFsbG93ZWREaXIgLSBCYXNlIGRpcmVjdG9yeSB0aGF0IHRoZSBwYXRoIG11c3QgYmUgd2l0aGluXG4gICAqIEByZXR1cm5zIFZhbGlkYXRlZCBhYnNvbHV0ZSBwYXRoIHdpdGggc3ltbGlua3MgcmVzb2x2ZWRcbiAgICogQGRlcHJlY2F0ZWQgVXNlIHZhbGlkYXRlRWxlbWVudFBhdGhPbmx5KCkgZm9yIHBhdGggdmFsaWRhdGlvbiB3aXRob3V0IHJlc29sdXRpb25cbiAgICovXG4gIHN0YXRpYyBhc3luYyB2YWxpZGF0ZUVsZW1lbnRQYXRoKHVzZXJQYXRoOiBzdHJpbmcsIGFsbG93ZWREaXI6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgaWYgKCF1c2VyUGF0aCB8fCB0eXBlb2YgdXNlclBhdGggIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICB9XG5cbiAgICBpZiAoIWFsbG93ZWREaXIgfHwgdHlwZW9mIGFsbG93ZWREaXIgIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2FsbG93ZWREaXIgbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgYW55IG51bGwgYnl0ZXNcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29udHJvbC1yZWdleCAtLSBJbnRlbnRpb25hbGx5IHJlbW92aW5nIG51bGwgYnl0ZXMgZm9yIHNlY3VyaXR5XG4gICAgY29uc3QgY2xlYW5QYXRoID0gdXNlclBhdGgucmVwbGFjZUFsbCgvXFx1MDAwMC9nLCAnJyk7IC8vIE5PU09OQVIgLSBSZW1vdmluZyBudWxsIGJ5dGVzIGZvciBzZWN1cml0eVxuXG4gICAgLy8gTm9ybWFsaXplIGFuZCByZXNvbHZlIHBhdGhcbiAgICBjb25zdCBub3JtYWxpemVkUGF0aCA9IHBhdGgubm9ybWFsaXplKGNsZWFuUGF0aCk7XG4gICAgY29uc3QgcmVzb2x2ZWRQYXRoID0gcGF0aC5yZXNvbHZlKG5vcm1hbGl6ZWRQYXRoKTtcblxuICAgIC8vIENoZWNrIGZvciBwYXRoIHRyYXZlcnNhbCBhdHRlbXB0c1xuICAgIGlmIChub3JtYWxpemVkUGF0aC5pbmNsdWRlcygnLi4nKSB8fCBjbGVhblBhdGguaW5jbHVkZXMoJy4uJykpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdQYXRoIHRyYXZlcnNhbCBhdHRlbXB0IGRldGVjdGVkJywgeyB1c2VyUGF0aCB9KTtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCB0cmF2ZXJzYWwgZGV0ZWN0ZWQnKTtcbiAgICB9XG5cbiAgICAvLyBSZXNvbHZlIHN5bWxpbmtzIHRvIGdldCByZWFsIHBhdGhcbiAgICBjb25zdCByZWFsUGF0aCA9IGF3YWl0IHRoaXMucmVzb2x2ZVN5bWxpbmtzKHJlc29sdmVkUGF0aCwgdXNlclBhdGgpO1xuXG4gICAgLy8gVmFsaWRhdGUgcGF0aCBpcyB3aXRoaW4gYWxsb3dlZCBkaXJlY3RvcnlcbiAgICBjb25zdCByZXNvbHZlZEFsbG93ZWREaXIgPSBhd2FpdCBmcy5yZWFscGF0aChhbGxvd2VkRGlyKS5jYXRjaCgoKSA9PiBhbGxvd2VkRGlyKTtcbiAgICBpZiAoIXJlYWxQYXRoLnN0YXJ0c1dpdGgocmVzb2x2ZWRBbGxvd2VkRGlyICsgcGF0aC5zZXApICYmIHJlYWxQYXRoICE9PSByZXNvbHZlZEFsbG93ZWREaXIpIHtcbiAgICAgIGxvZ2dlci5lcnJvcignUGF0aCBhY2Nlc3MgZGVuaWVkJywgeyBwYXRoOiB1c2VyUGF0aCwgcmVhbFBhdGgsIGFsbG93ZWREaXI6IHJlc29sdmVkQWxsb3dlZERpciB9KTtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBhY2Nlc3MgZGVuaWVkJyk7XG4gICAgfVxuXG4gICAgLy8gVmFsaWRhdGUgZmlsZW5hbWUgZXh0ZW5zaW9uIGFuZCBmb3JtYXRcbiAgICB0aGlzLnZhbGlkYXRlRmlsZW5hbWUocmVhbFBhdGgpO1xuXG4gICAgLy8gUmV0dXJuIHRoZSByZWFsIHBhdGggKHdpdGggc3ltbGlua3MgcmVzb2x2ZWQpIGZvciBzYWZlIGZpbGUgb3BlcmF0aW9uc1xuICAgIHJldHVybiByZWFsUGF0aDtcbiAgfVxuXG4gIC8qKlxuICAgKiBAZGVwcmVjYXRlZCBVc2UgdmFsaWRhdGVFbGVtZW50UGF0aCgpIGluc3RlYWQuIFRoaXMgbWV0aG9kIHVzZXMgY2xhc3MtbGV2ZWwgc3RhdGUuXG4gICAqIFZhbGlkYXRlIGEgcGVyc29uYSBwYXRoIGFnYWluc3QgcHJlLWluaXRpYWxpemVkIGFsbG93ZWQgZGlyZWN0b3JpZXMuXG4gICAqXG4gICAqIEBwYXJhbSB1c2VyUGF0aCAtIFVzZXItcHJvdmlkZWQgcGF0aCB0byB2YWxpZGF0ZVxuICAgKiBAcmV0dXJucyBWYWxpZGF0ZWQgYWJzb2x1dGUgcGF0aCB3aXRoIHN5bWxpbmtzIHJlc29sdmVkXG4gICAqL1xuICBzdGF0aWMgYXN5bmMgdmFsaWRhdGVQZXJzb25hUGF0aCh1c2VyUGF0aDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBpZiAoIXVzZXJQYXRoIHx8IHR5cGVvZiB1c2VyUGF0aCAhPT0gJ3N0cmluZycpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBtdXN0IGJlIGEgbm9uLWVtcHR5IHN0cmluZycpO1xuICAgIH1cblxuICAgIC8vIFJlbW92ZSBhbnkgbnVsbCBieXRlc1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby1jb250cm9sLXJlZ2V4IC0tIEludGVudGlvbmFsbHkgcmVtb3ZpbmcgbnVsbCBieXRlcyBmb3Igc2VjdXJpdHlcbiAgICBjb25zdCBjbGVhblBhdGggPSB1c2VyUGF0aC5yZXBsYWNlQWxsKC9cXHUwMDAwL2csICcnKTsgLy8gTk9TT05BUiAtIFJlbW92aW5nIG51bGwgYnl0ZXMgZm9yIHNlY3VyaXR5XG5cbiAgICAvLyBOb3JtYWxpemUgYW5kIHJlc29sdmUgcGF0aFxuICAgIGNvbnN0IG5vcm1hbGl6ZWRQYXRoID0gcGF0aC5ub3JtYWxpemUoY2xlYW5QYXRoKTtcbiAgICBjb25zdCByZXNvbHZlZFBhdGggPSBwYXRoLnJlc29sdmUobm9ybWFsaXplZFBhdGgpO1xuXG4gICAgLy8gQ2hlY2sgZm9yIHBhdGggdHJhdmVyc2FsIGF0dGVtcHRzXG4gICAgaWYgKG5vcm1hbGl6ZWRQYXRoLmluY2x1ZGVzKCcuLicpIHx8IGNsZWFuUGF0aC5pbmNsdWRlcygnLi4nKSkge1xuICAgICAgbG9nZ2VyLndhcm4oJ1BhdGggdHJhdmVyc2FsIGF0dGVtcHQgZGV0ZWN0ZWQnLCB7IHVzZXJQYXRoIH0pO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdQYXRoIHRyYXZlcnNhbCBkZXRlY3RlZCcpO1xuICAgIH1cblxuICAgIC8vIFJlc29sdmUgc3ltbGlua3MgdG8gZ2V0IHJlYWwgcGF0aFxuICAgIGNvbnN0IHJlYWxQYXRoID0gYXdhaXQgdGhpcy5yZXNvbHZlU3ltbGlua3MocmVzb2x2ZWRQYXRoLCB1c2VyUGF0aCk7XG5cbiAgICAvLyBWYWxpZGF0ZSBwYXRoIGlzIHdpdGhpbiBhbGxvd2VkIGRpcmVjdG9yaWVzIChub3cgYXN5bmMgdG8gcmVzb2x2ZSBzeW1saW5rcyBpbiBhbGxvd2VkIGRpcnMpXG4gICAgYXdhaXQgdGhpcy52YWxpZGF0ZVBhdGhJc0FsbG93ZWQocmVhbFBhdGgsIHVzZXJQYXRoKTtcblxuICAgIC8vIFZhbGlkYXRlIGZpbGVuYW1lIGV4dGVuc2lvbiBhbmQgZm9ybWF0XG4gICAgdGhpcy52YWxpZGF0ZUZpbGVuYW1lKHJlYWxQYXRoKTtcblxuICAgIC8vIFJldHVybiB0aGUgcmVhbCBwYXRoICh3aXRoIHN5bWxpbmtzIHJlc29sdmVkKSBmb3Igc2FmZSBmaWxlIG9wZXJhdGlvbnNcbiAgICByZXR1cm4gcmVhbFBhdGg7XG4gIH1cblxuICBzdGF0aWMgYXN5bmMgc2FmZVJlYWRGaWxlKGZpbGVQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHZhbGlkYXRlZFBhdGggPSBhd2FpdCB0aGlzLnZhbGlkYXRlUGVyc29uYVBhdGgoZmlsZVBhdGgpO1xuICAgIFxuICAgIC8vIENoZWNrIGZpbGUgZXhpc3RzIGFuZCBpcyBub3QgYSBkaXJlY3RvcnlcbiAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGZzLnN0YXQodmFsaWRhdGVkUGF0aCk7XG4gICAgaWYgKHN0YXRzLmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBpcyBhIGRpcmVjdG9yeSwgbm90IGEgZmlsZScpO1xuICAgIH1cbiAgICBcbiAgICAvLyBTaXplIGNoZWNrXG4gICAgaWYgKHN0YXRzLnNpemUgPiA1MDAwMDApIHsgLy8gNTAwS0JcbiAgICAgIHRocm93IG5ldyBFcnJvcignRmlsZSB0b28gbGFyZ2UnKTtcbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGZzLnJlYWRGaWxlKHZhbGlkYXRlZFBhdGgsICd1dGYtOCcpO1xuICB9XG5cbiAgc3RhdGljIGFzeW5jIHNhZmVXcml0ZUZpbGUoZmlsZVBhdGg6IHN0cmluZywgY29udGVudDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgdmFsaWRhdGVkUGF0aCA9IGF3YWl0IHRoaXMudmFsaWRhdGVQZXJzb25hUGF0aChmaWxlUGF0aCk7XG4gICAgXG4gICAgLy8gQ29udGVudCB2YWxpZGF0aW9uXG4gICAgaWYgKGNvbnRlbnQubGVuZ3RoID4gNTAwMDAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvbnRlbnQgdG9vIGxhcmdlJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzIGJlZm9yZSBhdG9taWMgd3JpdGVcbiAgICBjb25zdCBkaXJQYXRoID0gcGF0aC5kaXJuYW1lKHZhbGlkYXRlZFBhdGgpO1xuICAgIGF3YWl0IGZzLm1rZGlyKGRpclBhdGgsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgIFxuICAgIC8vIFdyaXRlIHRvIHRlbXAgZmlsZSBmaXJzdCAoYXRvbWljIHdyaXRlKVxuICAgIGNvbnN0IHRlbXBQYXRoID0gYCR7dmFsaWRhdGVkUGF0aH0udG1wYDtcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUodGVtcFBhdGgsIGNvbnRlbnQsICd1dGYtOCcpO1xuICAgIFxuICAgIC8vIFJlbmFtZSB0byBmaW5hbCBwYXRoIChhdG9taWMgb24gbW9zdCBmaWxlc3lzdGVtcylcbiAgICBhd2FpdCBmcy5yZW5hbWUodGVtcFBhdGgsIHZhbGlkYXRlZFBhdGgpO1xuICB9XG59XG4iXX0=