@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.
102 lines • 16 kB
JavaScript
import path from 'path';
import fs from 'fs/promises';
import { logger } from '../utils/logger.js';
import { RegexValidator } from './regexValidator.js';
export class PathValidator {
static ALLOWED_DIRECTORIES = [];
static ALLOWED_EXTENSIONS = ['.md', '.markdown', '.txt', '.yml', '.yaml'];
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;
}
}
static async validatePersonaPath(userPath) {
if (!userPath || typeof userPath !== 'string') {
throw new Error('Path must be a non-empty string');
}
// Remove any null bytes
const cleanPath = userPath.replace(/\x00/g, '');
// 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');
}
// Check if path is within allowed directories
if (this.ALLOWED_DIRECTORIES.length === 0) {
// If not initialized, allow paths under current working directory's personas folder
const defaultAllowed = [
path.resolve('./personas'),
path.resolve(process.env.PERSONAS_DIR || './personas')
];
const isAllowed = defaultAllowed.some(allowedDir => resolvedPath.startsWith(allowedDir + path.sep) ||
resolvedPath === allowedDir);
if (!isAllowed) {
// SECURITY FIX #206: Don't expose user paths in error messages
logger.error('Path access denied', { path: userPath });
throw new Error('Path access denied');
}
}
else {
const isAllowed = this.ALLOWED_DIRECTORIES.some(allowedDir => resolvedPath.startsWith(allowedDir + path.sep) ||
resolvedPath === allowedDir);
if (!isAllowed) {
// SECURITY FIX #206: Don't expose user paths in error messages
logger.error('Path access denied', { path: userPath });
throw new Error('Path access denied');
}
}
// Validate filename if it's a file
if (path.extname(resolvedPath)) {
const filename = path.basename(resolvedPath);
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\-_.]+$/i, { maxLength: 255 })) {
throw new Error(`Invalid filename format: ${filename}`);
}
}
return resolvedPath;
}
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aFZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZWN1cml0eS9wYXRoVmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUN4QixPQUFPLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDN0IsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUdyRCxNQUFNLE9BQU8sYUFBYTtJQUNoQixNQUFNLENBQUMsbUJBQW1CLEdBQWEsRUFBRSxDQUFDO0lBQzFDLE1BQU0sQ0FBQyxrQkFBa0IsR0FBYSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztJQUU1RixNQUFNLENBQUMsVUFBVSxDQUFDLFdBQW1CLEVBQUUsaUJBQTRCO1FBQ2pFLElBQUksQ0FBQyxtQkFBbUIsR0FBRztZQUN6QixJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztZQUN6QixJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQztZQUMxQixJQUFJLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDO1lBQ2pDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDO1lBQ3pCLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLElBQUksWUFBWSxDQUFDO1NBQ3ZELENBQUM7UUFFRixJQUFJLGlCQUFpQixFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGlCQUFpQixDQUFDO1FBQzlDLENBQUM7SUFDSCxDQUFDO0lBRUQsTUFBTSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxRQUFnQjtRQUMvQyxJQUFJLENBQUMsUUFBUSxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQzlDLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBRUQsd0JBQXdCO1FBQ3hCLE1BQU0sU0FBUyxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRWhELDZCQUE2QjtRQUM3QixNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ2pELE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7UUFFbEQsb0NBQW9DO1FBQ3BDLElBQUksY0FBYyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxTQUFTLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDOUQsTUFBTSxDQUFDLElBQUksQ0FBQyxpQ0FBaUMsRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDN0QsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO1FBQzdDLENBQUM7UUFFRCw4Q0FBOEM7UUFDOUMsSUFBSSxJQUFJLENBQUMsbUJBQW1CLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQzFDLG9GQUFvRjtZQUNwRixNQUFNLGNBQWMsR0FBRztnQkFDckIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUM7Z0JBQzFCLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLElBQUksWUFBWSxDQUFDO2FBQ3ZELENBQUM7WUFDRixNQUFNLFNBQVMsR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQ2pELFlBQVksQ0FBQyxVQUFVLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUM7Z0JBQzlDLFlBQVksS0FBSyxVQUFVLENBQzVCLENBQUM7WUFDRixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ2YsK0RBQStEO2dCQUMvRCxNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7Z0JBQ3ZELE1BQU0sSUFBSSxLQUFLLENBQUMsb0JBQW9CLENBQUMsQ0FBQztZQUN4QyxDQUFDO1FBQ0gsQ0FBQzthQUFNLENBQUM7WUFDTixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsbUJBQW1CLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQzNELFlBQVksQ0FBQyxVQUFVLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUM7Z0JBQzlDLFlBQVksS0FBSyxVQUFVLENBQzVCLENBQUM7WUFFRixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ2YsK0RBQStEO2dCQUMvRCxNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7Z0JBQ3ZELE1BQU0sSUFBSSxLQUFLLENBQUMsb0JBQW9CLENBQUMsQ0FBQztZQUN4QyxDQUFDO1FBQ0gsQ0FBQztRQUVELG1DQUFtQztRQUNuQyxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUMvQixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzdDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7WUFFakQsZ0NBQWdDO1lBQ2hDLElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQzNDLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQStCLEdBQUcsY0FBYyxJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUN4RyxDQUFDO1lBRUQsaUVBQWlFO1lBQ2pFLElBQUksQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxxQkFBcUIsRUFBRSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxFQUFFLENBQUM7Z0JBQ2xGLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDMUQsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLFlBQVksQ0FBQztJQUN0QixDQUFDO0lBRUQsTUFBTSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsUUFBZ0I7UUFDeEMsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFL0QsMkNBQTJDO1FBQzNDLE1BQU0sS0FBSyxHQUFHLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUMzQyxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO1lBQ3hCLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBRUQsYUFBYTtRQUNiLElBQUksS0FBSyxDQUFDLElBQUksR0FBRyxNQUFNLEVBQUUsQ0FBQyxDQUFDLFFBQVE7WUFDakMsTUFBTSxJQUFJLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQ3BDLENBQUM7UUFFRCxPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFFRCxNQUFNLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxRQUFnQixFQUFFLE9BQWU7UUFDMUQsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFL0QscUJBQXFCO1FBQ3JCLElBQUksT0FBTyxDQUFDLE1BQU0sR0FBRyxNQUFNLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDdkMsQ0FBQztRQUVELDhDQUE4QztRQUM5QyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQzVDLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUU3QywwQ0FBMEM7UUFDMUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxhQUFhLE1BQU0sQ0FBQztRQUN4QyxNQUFNLEVBQUUsQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztRQUUvQyxvREFBb0Q7UUFDcEQsTUFBTSxFQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUMzQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgZnMgZnJvbSAnZnMvcHJvbWlzZXMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFJlZ2V4VmFsaWRhdG9yIH0gZnJvbSAnLi9yZWdleFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTZWN1cmVFcnJvckhhbmRsZXIgfSBmcm9tICcuL2Vycm9ySGFuZGxlci5qcyc7XG5cbmV4cG9ydCBjbGFzcyBQYXRoVmFsaWRhdG9yIHtcbiAgcHJpdmF0ZSBzdGF0aWMgQUxMT1dFRF9ESVJFQ1RPUklFUzogc3RyaW5nW10gPSBbXTtcbiAgcHJpdmF0ZSBzdGF0aWMgQUxMT1dFRF9FWFRFTlNJT05TOiBzdHJpbmdbXSA9IFsnLm1kJywgJy5tYXJrZG93bicsICcudHh0JywgJy55bWwnLCAnLnlhbWwnXTtcbiAgXG4gIHN0YXRpYyBpbml0aWFsaXplKHBlcnNvbmFzRGlyOiBzdHJpbmcsIGFsbG93ZWRFeHRlbnNpb25zPzogc3RyaW5nW10pOiB2b2lkIHtcbiAgICB0aGlzLkFMTE9XRURfRElSRUNUT1JJRVMgPSBbXG4gICAgICBwYXRoLnJlc29sdmUocGVyc29uYXNEaXIpLFxuICAgICAgcGF0aC5yZXNvbHZlKCcuL3BlcnNvbmFzJyksXG4gICAgICBwYXRoLnJlc29sdmUoJy4vY3VzdG9tLXBlcnNvbmFzJyksXG4gICAgICBwYXRoLnJlc29sdmUoJy4vYmFja3VwcycpLFxuICAgICAgcGF0aC5yZXNvbHZlKHByb2Nlc3MuZW52LlBFUlNPTkFTX0RJUiB8fCAnLi9wZXJzb25hcycpXG4gICAgXTtcbiAgICBcbiAgICBpZiAoYWxsb3dlZEV4dGVuc2lvbnMpIHtcbiAgICAgIHRoaXMuQUxMT1dFRF9FWFRFTlNJT05TID0gYWxsb3dlZEV4dGVuc2lvbnM7XG4gICAgfVxuICB9XG5cbiAgc3RhdGljIGFzeW5jIHZhbGlkYXRlUGVyc29uYVBhdGgodXNlclBhdGg6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgaWYgKCF1c2VyUGF0aCB8fCB0eXBlb2YgdXNlclBhdGggIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgYW55IG51bGwgYnl0ZXNcbiAgICBjb25zdCBjbGVhblBhdGggPSB1c2VyUGF0aC5yZXBsYWNlKC9cXHgwMC9nLCAnJyk7XG4gICAgXG4gICAgLy8gTm9ybWFsaXplIGFuZCByZXNvbHZlIHBhdGhcbiAgICBjb25zdCBub3JtYWxpemVkUGF0aCA9IHBhdGgubm9ybWFsaXplKGNsZWFuUGF0aCk7XG4gICAgY29uc3QgcmVzb2x2ZWRQYXRoID0gcGF0aC5yZXNvbHZlKG5vcm1hbGl6ZWRQYXRoKTtcbiAgICBcbiAgICAvLyBDaGVjayBmb3IgcGF0aCB0cmF2ZXJzYWwgYXR0ZW1wdHNcbiAgICBpZiAobm9ybWFsaXplZFBhdGguaW5jbHVkZXMoJy4uJykgfHwgY2xlYW5QYXRoLmluY2x1ZGVzKCcuLicpKSB7XG4gICAgICBsb2dnZXIud2FybignUGF0aCB0cmF2ZXJzYWwgYXR0ZW1wdCBkZXRlY3RlZCcsIHsgdXNlclBhdGggfSk7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggdHJhdmVyc2FsIGRldGVjdGVkJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIENoZWNrIGlmIHBhdGggaXMgd2l0aGluIGFsbG93ZWQgZGlyZWN0b3JpZXNcbiAgICBpZiAodGhpcy5BTExPV0VEX0RJUkVDVE9SSUVTLmxlbmd0aCA9PT0gMCkge1xuICAgICAgLy8gSWYgbm90IGluaXRpYWxpemVkLCBhbGxvdyBwYXRocyB1bmRlciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5J3MgcGVyc29uYXMgZm9sZGVyXG4gICAgICBjb25zdCBkZWZhdWx0QWxsb3dlZCA9IFtcbiAgICAgICAgcGF0aC5yZXNvbHZlKCcuL3BlcnNvbmFzJyksXG4gICAgICAgIHBhdGgucmVzb2x2ZShwcm9jZXNzLmVudi5QRVJTT05BU19ESVIgfHwgJy4vcGVyc29uYXMnKVxuICAgICAgXTtcbiAgICAgIGNvbnN0IGlzQWxsb3dlZCA9IGRlZmF1bHRBbGxvd2VkLnNvbWUoYWxsb3dlZERpciA9PiBcbiAgICAgICAgcmVzb2x2ZWRQYXRoLnN0YXJ0c1dpdGgoYWxsb3dlZERpciArIHBhdGguc2VwKSB8fCBcbiAgICAgICAgcmVzb2x2ZWRQYXRoID09PSBhbGxvd2VkRGlyXG4gICAgICApO1xuICAgICAgaWYgKCFpc0FsbG93ZWQpIHtcbiAgICAgICAgLy8gU0VDVVJJVFkgRklYICMyMDY6IERvbid0IGV4cG9zZSB1c2VyIHBhdGhzIGluIGVycm9yIG1lc3NhZ2VzXG4gICAgICAgIGxvZ2dlci5lcnJvcignUGF0aCBhY2Nlc3MgZGVuaWVkJywgeyBwYXRoOiB1c2VyUGF0aCB9KTtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdQYXRoIGFjY2VzcyBkZW5pZWQnKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgaXNBbGxvd2VkID0gdGhpcy5BTExPV0VEX0RJUkVDVE9SSUVTLnNvbWUoYWxsb3dlZERpciA9PiBcbiAgICAgICAgcmVzb2x2ZWRQYXRoLnN0YXJ0c1dpdGgoYWxsb3dlZERpciArIHBhdGguc2VwKSB8fCBcbiAgICAgICAgcmVzb2x2ZWRQYXRoID09PSBhbGxvd2VkRGlyXG4gICAgICApO1xuICAgICAgXG4gICAgICBpZiAoIWlzQWxsb3dlZCkge1xuICAgICAgICAvLyBTRUNVUklUWSBGSVggIzIwNjogRG9uJ3QgZXhwb3NlIHVzZXIgcGF0aHMgaW4gZXJyb3IgbWVzc2FnZXNcbiAgICAgICAgbG9nZ2VyLmVycm9yKCdQYXRoIGFjY2VzcyBkZW5pZWQnLCB7IHBhdGg6IHVzZXJQYXRoIH0pO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggYWNjZXNzIGRlbmllZCcpO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBWYWxpZGF0ZSBmaWxlbmFtZSBpZiBpdCdzIGEgZmlsZVxuICAgIGlmIChwYXRoLmV4dG5hbWUocmVzb2x2ZWRQYXRoKSkge1xuICAgICAgY29uc3QgZmlsZW5hbWUgPSBwYXRoLmJhc2VuYW1lKHJlc29sdmVkUGF0aCk7XG4gICAgICBjb25zdCBleHQgPSBwYXRoLmV4dG5hbWUoZmlsZW5hbWUpLnRvTG93ZXJDYXNlKCk7XG4gICAgICBcbiAgICAgIC8vIENoZWNrIGlmIGV4dGVuc2lvbiBpcyBhbGxvd2VkXG4gICAgICBpZiAoIXRoaXMuQUxMT1dFRF9FWFRFTlNJT05TLmluY2x1ZGVzKGV4dCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGaWxlIGV4dGVuc2lvbiBub3QgYWxsb3dlZDogJHtleHR9LiBBbGxvd2VkOiAke3RoaXMuQUxMT1dFRF9FWFRFTlNJT05TLmpvaW4oJywgJyl9YCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIFZhbGlkYXRlIGZpbGVuYW1lIGZvcm1hdCAoYWxwaGFudW1lcmljLCBkYXNoLCB1bmRlcnNjb3JlLCBkb3QpXG4gICAgICBpZiAoIVJlZ2V4VmFsaWRhdG9yLnZhbGlkYXRlKGZpbGVuYW1lLCAvXlthLXpBLVowLTlcXC1fLl0rJC9pLCB7IG1heExlbmd0aDogMjU1IH0pKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBmaWxlbmFtZSBmb3JtYXQ6ICR7ZmlsZW5hbWV9YCk7XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIHJldHVybiByZXNvbHZlZFBhdGg7XG4gIH1cblxuICBzdGF0aWMgYXN5bmMgc2FmZVJlYWRGaWxlKGZpbGVQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHZhbGlkYXRlZFBhdGggPSBhd2FpdCB0aGlzLnZhbGlkYXRlUGVyc29uYVBhdGgoZmlsZVBhdGgpO1xuICAgIFxuICAgIC8vIENoZWNrIGZpbGUgZXhpc3RzIGFuZCBpcyBub3QgYSBkaXJlY3RvcnlcbiAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGZzLnN0YXQodmFsaWRhdGVkUGF0aCk7XG4gICAgaWYgKHN0YXRzLmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBpcyBhIGRpcmVjdG9yeSwgbm90IGEgZmlsZScpO1xuICAgIH1cbiAgICBcbiAgICAvLyBTaXplIGNoZWNrXG4gICAgaWYgKHN0YXRzLnNpemUgPiA1MDAwMDApIHsgLy8gNTAwS0JcbiAgICAgIHRocm93IG5ldyBFcnJvcignRmlsZSB0b28gbGFyZ2UnKTtcbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGZzLnJlYWRGaWxlKHZhbGlkYXRlZFBhdGgsICd1dGYtOCcpO1xuICB9XG5cbiAgc3RhdGljIGFzeW5jIHNhZmVXcml0ZUZpbGUoZmlsZVBhdGg6IHN0cmluZywgY29udGVudDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgdmFsaWRhdGVkUGF0aCA9IGF3YWl0IHRoaXMudmFsaWRhdGVQZXJzb25hUGF0aChmaWxlUGF0aCk7XG4gICAgXG4gICAgLy8gQ29udGVudCB2YWxpZGF0aW9uXG4gICAgaWYgKGNvbnRlbnQubGVuZ3RoID4gNTAwMDAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvbnRlbnQgdG9vIGxhcmdlJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzIGJlZm9yZSBhdG9taWMgd3JpdGVcbiAgICBjb25zdCBkaXJQYXRoID0gcGF0aC5kaXJuYW1lKHZhbGlkYXRlZFBhdGgpO1xuICAgIGF3YWl0IGZzLm1rZGlyKGRpclBhdGgsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgIFxuICAgIC8vIFdyaXRlIHRvIHRlbXAgZmlsZSBmaXJzdCAoYXRvbWljIHdyaXRlKVxuICAgIGNvbnN0IHRlbXBQYXRoID0gYCR7dmFsaWRhdGVkUGF0aH0udG1wYDtcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUodGVtcFBhdGgsIGNvbnRlbnQsICd1dGYtOCcpO1xuICAgIFxuICAgIC8vIFJlbmFtZSB0byBmaW5hbCBwYXRoIChhdG9taWMgb24gbW9zdCBmaWxlc3lzdGVtcylcbiAgICBhd2FpdCBmcy5yZW5hbWUodGVtcFBhdGgsIHZhbGlkYXRlZFBhdGgpO1xuICB9XG59Il19