UNPKG

@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.

422 lines 58.3 kB
/** * Portfolio Manager - Manages the portfolio directory structure for all element types */ import * as path from 'path'; import { homedir } from 'os'; import { logger } from '../utils/logger.js'; import { ElementType } from './types.js'; import { SecurityMonitor } from '../security/securityMonitor.js'; import { UnicodeValidator } from '../security/validators/unicodeValidator.js'; import { DefaultElementProvider } from './DefaultElementProvider.js'; import { ErrorHandler, ErrorCategory } from '../utils/ErrorHandler.js'; // Constants const ELEMENT_FILE_EXTENSIONS = { [ElementType.PERSONA]: '.md', [ElementType.SKILL]: '.md', [ElementType.TEMPLATE]: '.md', [ElementType.AGENT]: '.md', [ElementType.MEMORY]: '.yaml', [ElementType.ENSEMBLE]: '.md' }; // Default extension for backward compatibility const DEFAULT_ELEMENT_FILE_EXTENSION = '.md'; /** * Get the file extension for an element type without requiring a PortfolioManager instance. * Issue #815: Shared by PortfolioRepoManager and submitToPortfolioTool to avoid * hardcoded '.md' assumptions. */ export function getElementFileExtension(type) { return ELEMENT_FILE_EXTENSIONS[type] || DEFAULT_ELEMENT_FILE_EXTENSION; } export { ElementType }; export class PortfolioManager { initializationPromise = null; baseDir; fileOperations; /** * Create a new PortfolioManager instance * * @param config - Optional portfolio configuration * @param fileOperations - Optional FileOperationsService for dependency injection. * BREAKING CHANGE (v1.5.0): Added as second parameter for DI. * Direct instantiation without DI container should pass undefined * or provide a FileOperationsService instance. */ constructor(fileOperations, config) { this.fileOperations = fileOperations; // Get potential directory from environment or config const envDir = process.env.DOLLHOUSE_PORTFOLIO_DIR; const configDir = config?.baseDir; const defaultDir = path.join(homedir(), '.dollhouse', 'portfolio'); // Validate environment variable if provided if (envDir) { if (!path.isAbsolute(envDir)) { throw new Error('DOLLHOUSE_PORTFOLIO_DIR must be an absolute path'); } // Additional validation for suspicious paths if (envDir.includes('..') || envDir.startsWith('/etc') || envDir.startsWith('/sys')) { throw new Error('DOLLHOUSE_PORTFOLIO_DIR contains suspicious path segments'); } } // Validate config directory if provided if (configDir && !path.isAbsolute(configDir)) { throw new Error('Portfolio config baseDir must be an absolute path'); } // Use environment variable if set, otherwise config, otherwise default this.baseDir = envDir || configDir || defaultDir; logger.info(`[PortfolioManager] Portfolio base directory: ${this.baseDir}`); } /** * Get the base portfolio directory */ getBaseDir() { return this.baseDir; } /** * Get the directory for a specific element type */ getElementDir(type) { return path.join(this.baseDir, type); } /** * Get the file extension for a specific element type * FIX (#1213): Expose ELEMENT_FILE_EXTENSIONS mapping for correct extension display */ getFileExtension(type) { return ELEMENT_FILE_EXTENSIONS[type] || DEFAULT_ELEMENT_FILE_EXTENSION; } /** * Initialize the portfolio directory structure * Uses locking to prevent race conditions during concurrent initialization */ async initialize() { // If already initializing, wait for the existing initialization if (this.initializationPromise) { return this.initializationPromise; } // Check if portfolio is fully initialized (base dir + all subdirectories exist) if (await this.isFullyInitialized()) { logger.debug('[PortfolioManager] Portfolio already fully initialized'); return; } // Create initialization promise to prevent concurrent initialization this.initializationPromise = this.performInitialization(); try { await this.initializationPromise; } finally { // Clear the promise after completion this.initializationPromise = null; } } /** * Perform the actual initialization - should only be called once */ async performInitialization() { logger.info('[PortfolioManager] Initializing portfolio directory structure'); // Create base directory try { await this.fileOperations.createDirectory(this.baseDir); } catch (error) { const err = error; // In read-only environments (like Docker), we can't create directories // Log but continue - the portfolio will be empty but functional if (err.code === 'EACCES' || err.code === 'EROFS' || err.code === 'ENOENT') { logger.warn(`[PortfolioManager] Cannot create portfolio directory (read-only environment?): ${err.message}`); logger.info(`[DollhouseMCP] Running in read-only mode - portfolio features disabled`); return; } throw error; } // Create subdirectories for each element type for (const elementType of Object.values(ElementType)) { const elementDir = path.join(this.baseDir, elementType); await this.fileOperations.createDirectory(elementDir); logger.debug(`[PortfolioManager] Created directory: ${elementDir}`); } // Create special directories for stateful elements const agentStateDir = path.join(this.baseDir, ElementType.AGENT, '.state'); await this.fileOperations.createDirectory(agentStateDir); logger.info('[PortfolioManager] Portfolio directory structure initialized'); // Migration for v1.4.2 users: rename singular directories to plural await this.migrateFromSingularDirectories(); // Populate with default elements if this is a new installation // Skip during tests to avoid interference if (process.env.NODE_ENV !== 'test') { try { const defaultProvider = new DefaultElementProvider(); await defaultProvider.populateDefaults(this.baseDir); } catch (error) { logger.error('[PortfolioManager] Error populating default elements:', error); // Log to stderr for MCP client visibility logger.error(`[PortfolioManager] CRITICAL: Failed to populate default elements: ${error instanceof Error ? error.message : String(error)}`); // Continue anyway - empty portfolio is valid } } } /** * Check if portfolio directory exists */ async exists() { try { return await this.fileOperations.exists(this.baseDir); } catch { return false; } } /** * Check if portfolio is fully initialized (base dir + all subdirectories exist) * This prevents the bug where base dir exists but subdirectories were deleted */ async isFullyInitialized() { // First check if base directory exists if (!await this.exists()) { return false; } // Check if all required subdirectories exist try { for (const elementType of Object.values(ElementType)) { const elementDir = path.join(this.baseDir, elementType); if (!await this.fileOperations.exists(elementDir)) return false; } // Check special directories const agentStateDir = path.join(this.baseDir, ElementType.AGENT, '.state'); if (!await this.fileOperations.exists(agentStateDir)) return false; return true; } catch { // If any subdirectory is missing, portfolio is not fully initialized return false; } } /** * Check if a filename appears to be a test element * SAFETY: Pattern-based filtering only, no content parsing * * This method IDENTIFIES test patterns (always returns true for test files). * The actual FILTERING decision (whether to exclude them) is made in listElements(). */ isTestElement(filename) { // Dangerous test patterns that should never appear in production const dangerousPatterns = [ /^bin-sh/i, /^rm-rf/i, /^nc-e-bin/i, /^python-c-import/i, /^curl.*evil/i, /^wget.*malicious/i, /^eval-/i, /^exec-/i, /^bash-c-/i, /^sh-c-/i, /^powershell-/i, /^cmd-c-/i, /shell-injection/i ]; // NOTE: Test-pattern filtering removed entirely (Issue #287) // Users can legitimately create elements with "test" in the name. // Only dangerous patterns (security concerns) are filtered. // Check dangerous patterns only if (dangerousPatterns.some(pattern => pattern.test(filename))) { logger.warn(`[PortfolioManager] Filtered dangerous element: ${filename}`); return true; } return false; } /** * List all elements of a specific type */ async listElements(type) { const elementDir = this.getElementDir(type); const fileExtension = ELEMENT_FILE_EXTENSIONS[type] || DEFAULT_ELEMENT_FILE_EXTENSION; try { const files = await this.fileOperations.listDirectory(elementDir); // Filter for correct file extension based on element type let filteredFiles = files.filter(file => file.endsWith(fileExtension)); // Issue #654: Exclude backup files from all element types. // Safety net — MemoryStorageLayer also filters, but any code path that // calls listElements() directly should never return backup artifacts. filteredFiles = filteredFiles.filter(file => !file.includes('.backup-') && !file.includes('.backup.')); // Filter out test/dangerous elements unless explicitly disabled // DISABLE_ELEMENT_FILTERING can be set in E2E tests that need test elements // Integration tests and production both use filtering by default const shouldFilter = process.env.DISABLE_ELEMENT_FILTERING !== 'true'; if (shouldFilter) { filteredFiles = filteredFiles.filter(file => !this.isTestElement(file)); } return filteredFiles; } catch (error) { const err = error; if (err.code === 'ENOENT') { // Directory doesn't exist yet - this is expected for new installations logger.debug(`[PortfolioManager] Element directory doesn't exist yet: ${elementDir}`); return []; } if (err.code === 'EACCES' || err.code === 'EPERM') { // Permission denied - log but return empty array ErrorHandler.logError('PortfolioManager.listElements', error, { elementDir }); return []; } if (err.code === 'ENOTDIR') { // Path exists but is not a directory ErrorHandler.logError('PortfolioManager.listElements', error, { elementDir }); throw ErrorHandler.createError(`Path is not a directory: ${elementDir}`, ErrorCategory.SYSTEM_ERROR); } // For any other errors, throw with context ErrorHandler.logError('PortfolioManager.listElements', error, { elementDir }); throw ErrorHandler.wrapError(error, 'Failed to list elements', ErrorCategory.SYSTEM_ERROR); } } /** * Get full path to an element file */ getElementPath(type, filename) { // Validate filename to prevent path traversal if (!filename || typeof filename !== 'string') { SecurityMonitor.logSecurityEvent({ type: 'PATH_TRAVERSAL_ATTEMPT', severity: 'MEDIUM', source: 'PortfolioManager.getElementPath', details: `Invalid filename provided: ${typeof filename}`, additionalData: { elementType: type, filename: String(filename) } }); throw new Error('Invalid filename: must be a non-empty string'); } // Check for path traversal attempts if (filename.includes('..') || filename.includes('/') || filename.includes('\\') || path.isAbsolute(filename)) { SecurityMonitor.logSecurityEvent({ type: 'PATH_TRAVERSAL_ATTEMPT', severity: 'HIGH', source: 'PortfolioManager.getElementPath', details: `Path traversal attempt detected in filename: ${filename}`, additionalData: { elementType: type, filename } }); throw new Error(`Invalid filename: contains path traversal characters: ${filename}`); } // Additional validation for hidden files and special characters if (filename.startsWith('.') || filename.includes('\0')) { SecurityMonitor.logSecurityEvent({ type: 'PATH_TRAVERSAL_ATTEMPT', severity: 'MEDIUM', source: 'PortfolioManager.getElementPath', details: `Invalid filename characters detected: ${filename}`, additionalData: { elementType: type, filename, hasHiddenFile: filename.startsWith('.'), hasNullByte: filename.includes('\0') } }); throw new Error(`Invalid filename: contains invalid characters: ${filename}`); } // Ensure filename ends with .md const safeFilename = filename.endsWith('.md') ? filename : `${filename}.md`; return path.join(this.getElementDir(type), safeFilename); } /** * Check if an element exists */ async elementExists(type, filename) { try { return await this.fileOperations.exists(this.getElementPath(type, filename)); } catch { return false; } } /** * Get legacy personas directory path (for migration) */ getLegacyPersonasDir() { return path.join(homedir(), '.dollhouse', 'personas'); } /** * Check if legacy personas directory exists */ async hasLegacyPersonas() { try { const legacyDir = this.getLegacyPersonasDir(); if (!await this.fileOperations.exists(legacyDir)) return false; const files = await this.fileOperations.listDirectory(legacyDir); return files.some(file => file.endsWith('.md')); } catch { return false; } } /** * Get portfolio statistics */ async getStatistics() { const stats = {}; for (const elementType of Object.values(ElementType)) { const elements = await this.listElements(elementType); stats[elementType] = elements.length; } return stats; } /** * Migrate from v1.4.2 singular directory names to v1.4.3 plural names * This handles the upgrade path for existing users */ async migrateFromSingularDirectories() { const oldToNew = { 'persona': 'personas', 'skill': 'skills', 'template': 'templates', 'agent': 'agents', 'memory': 'memories', 'ensemble': 'ensembles' }; for (const [oldName, newName] of Object.entries(oldToNew)) { // Unicode normalize the directory names (even though they're hardcoded, for security audit) const normalizedOld = UnicodeValidator.normalize(oldName); const normalizedNew = UnicodeValidator.normalize(newName); if (!normalizedOld.isValid || !normalizedNew.isValid) { // This should never happen with our hardcoded values, but for completeness logger.error(`[PortfolioManager] Invalid Unicode in directory names during migration`); continue; } const oldDir = path.join(this.baseDir, normalizedOld.normalizedContent); const newDir = path.join(this.baseDir, normalizedNew.normalizedContent); try { // Check if old directory exists if (!await this.fileOperations.exists(oldDir)) continue; // Check if new directory already has content try { if (await this.fileOperations.exists(newDir)) { const newDirFiles = await this.fileOperations.listDirectory(newDir); if (newDirFiles.length > 0) { logger.warn(`[PortfolioManager] Both ${oldName} and ${newName} directories exist. Keeping ${newName}, skipping migration.`, { oldDir, newDir, fileCount: newDirFiles.length }); continue; } } } catch { // New directory doesn't exist or is empty, proceed with migration } // Perform the migration logger.info(`[PortfolioManager] Migrating ${oldName} → ${newName}`); await this.fileOperations.renameFile(oldDir, newDir); // Log security event for audit trail SecurityMonitor.logSecurityEvent({ type: 'DIRECTORY_MIGRATION', severity: 'LOW', source: 'PortfolioManager.migrateFromSingularDirectories', details: `Migrated directory from ${oldName} to ${newName} for v1.4.3 compatibility`, metadata: { oldDir, newDir } }); } catch (error) { // Old directory doesn't exist, which is fine if (error.code !== 'ENOENT') { logger.error(`[PortfolioManager] Error during migration of ${oldName}:`, error); } } } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUG9ydGZvbGlvTWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wb3J0Zm9saW8vUG9ydGZvbGlvTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUdILE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDN0IsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxXQUFXLEVBQW1CLE1BQU0sWUFBWSxDQUFDO0FBQzFELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNqRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQztBQUM5RSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNyRSxPQUFPLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBR3ZFLFlBQVk7QUFDWixNQUFNLHVCQUF1QixHQUFnQztJQUMzRCxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsRUFBRSxLQUFLO0lBQzVCLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxFQUFFLEtBQUs7SUFDMUIsQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDLEVBQUUsS0FBSztJQUM3QixDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsRUFBRSxLQUFLO0lBQzFCLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxFQUFFLE9BQU87SUFDN0IsQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDLEVBQUUsS0FBSztDQUM5QixDQUFDO0FBRUYsK0NBQStDO0FBQy9DLE1BQU0sOEJBQThCLEdBQUcsS0FBSyxDQUFDO0FBRTdDOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsdUJBQXVCLENBQUMsSUFBWTtJQUNsRCxPQUFPLHVCQUF1QixDQUFDLElBQW1CLENBQUMsSUFBSSw4QkFBOEIsQ0FBQztBQUN4RixDQUFDO0FBRUQsT0FBTyxFQUFFLFdBQVcsRUFBRSxDQUFDO0FBR3ZCLE1BQU0sT0FBTyxnQkFBZ0I7SUFDbkIscUJBQXFCLEdBQXlCLElBQUksQ0FBQztJQUNuRCxPQUFPLENBQVM7SUFDaEIsY0FBYyxDQUF3QjtJQUU5Qzs7Ozs7Ozs7T0FRRztJQUNILFlBQVksY0FBcUMsRUFBRSxNQUF3QjtRQUN6RSxJQUFJLENBQUMsY0FBYyxHQUFHLGNBQWMsQ0FBQztRQUNyQyxxREFBcUQ7UUFDckQsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsQ0FBQztRQUNuRCxNQUFNLFNBQVMsR0FBRyxNQUFNLEVBQUUsT0FBTyxDQUFDO1FBQ2xDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsWUFBWSxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBRW5FLDRDQUE0QztRQUM1QyxJQUFJLE1BQU0sRUFBRSxDQUFDO1lBQ1gsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztnQkFDN0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxrREFBa0QsQ0FBQyxDQUFDO1lBQ3RFLENBQUM7WUFDRCw2Q0FBNkM7WUFDN0MsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLElBQUksTUFBTSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO2dCQUNwRixNQUFNLElBQUksS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7WUFDL0UsQ0FBQztRQUNILENBQUM7UUFFRCx3Q0FBd0M7UUFDeEMsSUFBSSxTQUFTLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDN0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxtREFBbUQsQ0FBQyxDQUFDO1FBQ3ZFLENBQUM7UUFFRCx1RUFBdUU7UUFDdkUsSUFBSSxDQUFDLE9BQU8sR0FBRyxNQUFNLElBQUksU0FBUyxJQUFJLFVBQVUsQ0FBQztRQUVqRCxNQUFNLENBQUMsSUFBSSxDQUFDLGdEQUFnRCxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUM5RSxDQUFDO0lBRUQ7O09BRUc7SUFDSSxVQUFVO1FBQ2YsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDO0lBQ3RCLENBQUM7SUFFRDs7T0FFRztJQUNJLGFBQWEsQ0FBQyxJQUFpQjtRQUNwQyxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUN2QyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksZ0JBQWdCLENBQUMsSUFBaUI7UUFDdkMsT0FBTyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsSUFBSSw4QkFBOEIsQ0FBQztJQUN6RSxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLFVBQVU7UUFDckIsZ0VBQWdFO1FBQ2hFLElBQUksSUFBSSxDQUFDLHFCQUFxQixFQUFFLENBQUM7WUFDL0IsT0FBTyxJQUFJLENBQUMscUJBQXFCLENBQUM7UUFDcEMsQ0FBQztRQUVELGdGQUFnRjtRQUNoRixJQUFJLE1BQU0sSUFBSSxDQUFDLGtCQUFrQixFQUFFLEVBQUUsQ0FBQztZQUNwQyxNQUFNLENBQUMsS0FBSyxDQUFDLHdEQUF3RCxDQUFDLENBQUM7WUFDdkUsT0FBTztRQUNULENBQUM7UUFFRCxxRUFBcUU7UUFDckUsSUFBSSxDQUFDLHFCQUFxQixHQUFHLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBRTFELElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLHFCQUFxQixDQUFDO1FBQ25DLENBQUM7Z0JBQVMsQ0FBQztZQUNULHFDQUFxQztZQUNyQyxJQUFJLENBQUMscUJBQXFCLEdBQUcsSUFBSSxDQUFDO1FBQ3BDLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMscUJBQXFCO1FBQ2pDLE1BQU0sQ0FBQyxJQUFJLENBQUMsK0RBQStELENBQUMsQ0FBQztRQUU3RSx3QkFBd0I7UUFDeEIsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDMUQsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLEdBQUcsR0FBRyxLQUE4QixDQUFDO1lBQzNDLHVFQUF1RTtZQUN2RSxnRUFBZ0U7WUFDaEUsSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLFFBQVEsSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLE9BQU8sSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO2dCQUMzRSxNQUFNLENBQUMsSUFBSSxDQUFDLGtGQUFrRixHQUFHLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztnQkFDN0csTUFBTSxDQUFDLElBQUksQ0FBQyx3RUFBd0UsQ0FBQyxDQUFDO2dCQUN0RixPQUFPO1lBQ1QsQ0FBQztZQUNELE1BQU0sS0FBSyxDQUFDO1FBQ2QsQ0FBQztRQUVELDhDQUE4QztRQUM5QyxLQUFLLE1BQU0sV0FBVyxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUNyRCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsV0FBVyxDQUFDLENBQUM7WUFDeEQsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGVBQWUsQ0FBQyxVQUFVLENBQUMsQ0FBQztZQUN0RCxNQUFNLENBQUMsS0FBSyxDQUFDLHlDQUF5QyxVQUFVLEVBQUUsQ0FBQyxDQUFDO1FBQ3RFLENBQUM7UUFFRCxtREFBbUQ7UUFDbkQsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFdBQVcsQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDM0UsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUV6RCxNQUFNLENBQUMsSUFBSSxDQUFDLDhEQUE4RCxDQUFDLENBQUM7UUFFNUUsb0VBQW9FO1FBQ3BFLE1BQU0sSUFBSSxDQUFDLDhCQUE4QixFQUFFLENBQUM7UUFFNUMsK0RBQStEO1FBQy9ELDBDQUEwQztRQUMxQyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQ3BDLElBQUksQ0FBQztnQkFDSCxNQUFNLGVBQWUsR0FBRyxJQUFJLHNCQUFzQixFQUFFLENBQUM7Z0JBQ3JELE1BQU0sZUFBZSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUN2RCxDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixNQUFNLENBQUMsS0FBSyxDQUFDLHVEQUF1RCxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUM3RSwwQ0FBMEM7Z0JBQzFDLE1BQU0sQ0FBQyxLQUFLLENBQUMscUVBQXFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQzVJLDZDQUE2QztZQUMvQyxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxNQUFNO1FBQ2pCLElBQUksQ0FBQztZQUNILE9BQU8sTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDeEQsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztJQUNILENBQUM7SUFFRDs7O09BR0c7SUFDSyxLQUFLLENBQUMsa0JBQWtCO1FBQzlCLHVDQUF1QztRQUN2QyxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQztZQUN6QixPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCw2Q0FBNkM7UUFDN0MsSUFBSSxDQUFDO1lBQ0gsS0FBSyxNQUFNLFdBQVcsSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3JELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxXQUFXLENBQUMsQ0FBQztnQkFDeEQsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDO29CQUFFLE9BQU8sS0FBSyxDQUFDO1lBQ2xFLENBQUM7WUFFRCw0QkFBNEI7WUFDNUIsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFdBQVcsQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDM0UsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDO2dCQUFFLE9BQU8sS0FBSyxDQUFDO1lBRW5FLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLHFFQUFxRTtZQUNyRSxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0ksYUFBYSxDQUFDLFFBQWdCO1FBQ25DLGlFQUFpRTtRQUNqRSxNQUFNLGlCQUFpQixHQUFHO1lBQ3hCLFVBQVU7WUFDVixTQUFTO1lBQ1QsWUFBWTtZQUNaLG1CQUFtQjtZQUNuQixjQUFjO1lBQ2QsbUJBQW1CO1lBQ25CLFNBQVM7WUFDVCxTQUFTO1lBQ1QsV0FBVztZQUNYLFNBQVM7WUFDVCxlQUFlO1lBQ2YsVUFBVTtZQUNWLGtCQUFrQjtTQUNuQixDQUFDO1FBRUYsNkRBQTZEO1FBQzdELGtFQUFrRTtRQUNsRSw0REFBNEQ7UUFFNUQsZ0NBQWdDO1FBQ2hDLElBQUksaUJBQWlCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDOUQsTUFBTSxDQUFDLElBQUksQ0FBQyxrREFBa0QsUUFBUSxFQUFFLENBQUMsQ0FBQztZQUMxRSxPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxZQUFZLENBQUMsSUFBaUI7UUFDekMsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QyxNQUFNLGFBQWEsR0FBRyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsSUFBSSw4QkFBOEIsQ0FBQztRQUV0RixJQUFJLENBQUM7WUFDSCxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQ2xFLDBEQUEwRDtZQUMxRCxJQUFJLGFBQWEsR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDO1lBRXZFLDJEQUEyRDtZQUMzRCx1RUFBdUU7WUFDdkUsc0VBQXNFO1lBQ3RFLGFBQWEsR0FBRyxhQUFhLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQzFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQ3pELENBQUM7WUFFRixnRUFBZ0U7WUFDaEUsNEVBQTRFO1lBQzVFLGlFQUFpRTtZQUNqRSxNQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLHlCQUF5QixLQUFLLE1BQU0sQ0FBQztZQUN0RSxJQUFJLFlBQVksRUFBRSxDQUFDO2dCQUNqQixhQUFhLEdBQUcsYUFBYSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQzFFLENBQUM7WUFFRCxPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sR0FBRyxHQUFHLEtBQThCLENBQUM7WUFFM0MsSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO2dCQUMxQix1RUFBdUU7Z0JBQ3ZFLE1BQU0sQ0FBQyxLQUFLLENBQUMsMkRBQTJELFVBQVUsRUFBRSxDQUFDLENBQUM7Z0JBQ3RGLE9BQU8sRUFBRSxDQUFDO1lBQ1osQ0FBQztZQUVELElBQUksR0FBRyxDQUFDLElBQUksS0FBSyxRQUFRLElBQUksR0FBRyxDQUFDLElBQUksS0FBSyxPQUFPLEVBQUUsQ0FBQztnQkFDbEQsaURBQWlEO2dCQUNqRCxZQUFZLENBQUMsUUFBUSxDQUFDLCtCQUErQixFQUFFLEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7Z0JBQzlFLE9BQU8sRUFBRSxDQUFDO1lBQ1osQ0FBQztZQUVELElBQUksR0FBRyxDQUFDLElBQUksS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDM0IscUNBQXFDO2dCQUNyQyxZQUFZLENBQUMsUUFBUSxDQUFDLCtCQUErQixFQUFFLEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7Z0JBQzlFLE1BQU0sWUFBWSxDQUFDLFdBQVcsQ0FBQyw0QkFBNEIsVUFBVSxFQUFFLEVBQUUsYUFBYSxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQ3ZHLENBQUM7WUFFRCwyQ0FBMkM7WUFDM0MsWUFBWSxDQUFDLFFBQVEsQ0FBQywrQkFBK0IsRUFBRSxLQUFLLEVBQUUsRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDO1lBQzlFLE1BQU0sWUFBWSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUseUJBQXlCLEVBQUUsYUFBYSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQzdGLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxjQUFjLENBQUMsSUFBaUIsRUFBRSxRQUFnQjtRQUN2RCw4Q0FBOEM7UUFDOUMsSUFBSSxDQUFDLFFBQVEsSUFBSSxPQUFPLFFBQVEsS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUM5QyxlQUFlLENBQUMsZ0JBQWdCLENBQUM7Z0JBQy9CLElBQUksRUFBRSx3QkFBd0I7Z0JBQzlCLFFBQVEsRUFBRSxRQUFRO2dCQUNsQixNQUFNLEVBQUUsaUNBQWlDO2dCQUN6QyxPQUFPLEVBQUUsOEJBQThCLE9BQU8sUUFBUSxFQUFFO2dCQUN4RCxjQUFjLEVBQUUsRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUU7YUFDbEUsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxJQUFJLEtBQUssQ0FBQyw4Q0FBOEMsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7UUFFRCxvQ0FBb0M7UUFDcEMsSUFBSSxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLFFBQVEsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7WUFDOUcsZUFBZSxDQUFDLGdCQUFnQixDQUFDO2dCQUMvQixJQUFJLEVBQUUsd0JBQXdCO2dCQUM5QixRQUFRLEVBQUUsTUFBTTtnQkFDaEIsTUFBTSxFQUFFLGlDQUFpQztnQkFDekMsT0FBTyxFQUFFLGdEQUFnRCxRQUFRLEVBQUU7Z0JBQ25FLGNBQWMsRUFBRSxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO2FBQ2hELENBQUMsQ0FBQztZQUNILE1BQU0sSUFBSSxLQUFLLENBQUMseURBQXlELFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDdkYsQ0FBQztRQUVELGdFQUFnRTtRQUNoRSxJQUFJLFFBQVEsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ3hELGVBQWUsQ0FBQyxnQkFBZ0IsQ0FBQztnQkFDL0IsSUFBSSxFQUFFLHdCQUF3QjtnQkFDOUIsUUFBUSxFQUFFLFFBQVE7Z0JBQ2xCLE1BQU0sRUFBRSxpQ0FBaUM7Z0JBQ3pDLE9BQU8sRUFBRSx5Q0FBeUMsUUFBUSxFQUFFO2dCQUM1RCxjQUFjLEVBQUUsRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsUUFBUSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRSxXQUFXLEVBQUUsUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRTthQUMvSCxDQUFDLENBQUM7WUFDSCxNQUFNLElBQUksS0FBSyxDQUFDLGtEQUFrRCxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ2hGLENBQUM7UUFFRCxnQ0FBZ0M7UUFDaEMsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLFFBQVEsS0FBSyxDQUFDO1FBQzVFLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhLENBQUMsSUFBaUIsRUFBRSxRQUFnQjtRQUM1RCxJQUFJLENBQUM7WUFDSCxPQUFPLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQztRQUMvRSxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksb0JBQW9CO1FBQ3pCLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsRUFBRSxZQUFZLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGlCQUFpQjtRQUM1QixJQUFJLENBQUM7WUFDSCxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztZQUM5QyxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUM7Z0JBQUUsT0FBTyxLQUFLLENBQUM7WUFDL0QsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUNqRSxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDbEQsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhO1FBQ3hCLE1BQU0sS0FBSyxHQUEyQixFQUFFLENBQUM7UUFFekMsS0FBSyxNQUFNLFdBQVcsSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7WUFDckQsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1lBQ3RELEtBQUssQ0FBQyxXQUFXLENBQUMsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDO1FBQ3ZDLENBQUM7UUFFRCxPQUFPLEtBQW9DLENBQUM7SUFDOUMsQ0FBQztJQUVEOzs7T0FHRztJQUNLLEtBQUssQ0FBQyw4QkFBOEI7UUFDMUMsTUFBTSxRQUFRLEdBQTJCO1lBQ3ZDLFNBQVMsRUFBRSxVQUFVO1lBQ3JCLE9BQU8sRUFBRSxRQUFRO1lBQ2pCLFVBQVUsRUFBRSxXQUFXO1lBQ3ZCLE9BQU8sRUFBRSxRQUFRO1lBQ2pCLFFBQVEsRUFBRSxVQUFVO1lBQ3BCLFVBQVUsRUFBRSxXQUFXO1NBQ3hCLENBQUM7UUFFRixLQUFLLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQzFELDRGQUE0RjtZQUM1RixNQUFNLGFBQWEsR0FBRyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDMUQsTUFBTSxhQUFhLEdBQUcsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBRTFELElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNyRCwyRUFBMkU7Z0JBQzNFLE1BQU0sQ0FBQyxLQUFLLENBQUMsd0VBQXdFLENBQUMsQ0FBQztnQkFDdkYsU0FBUztZQUNYLENBQUM7WUFFRCxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsYUFBYSxDQUFDLGlCQUFpQixDQUFDLENBQUM7WUFDeEUsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1lBRXhFLElBQUksQ0FBQztnQkFDSCxnQ0FBZ0M7Z0JBQ2hDLElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQztvQkFBRSxTQUFTO2dCQUV4RCw2Q0FBNkM7Z0JBQzdDLElBQUksQ0FBQztvQkFDSCxJQUFJLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQzt3QkFDN0MsTUFBTSxXQUFXLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQzt3QkFDcEUsSUFBSSxXQUFXLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDOzRCQUMzQixNQUFNLENBQUMsSUFBSSxDQUNULDJCQUEyQixPQUFPLFFBQVEsT0FBTywrQkFBK0IsT0FBTyx1QkFBdUIsRUFDOUcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxXQUFXLENBQUMsTUFBTSxFQUFFLENBQ2xELENBQUM7NEJBQ0YsU0FBUzt3QkFDWCxDQUFDO29CQUNILENBQUM7Z0JBQ0gsQ0FBQztnQkFBQyxNQUFNLENBQUM7b0JBQ1Asa0VBQWtFO2dCQUNwRSxDQUFDO2dCQUVELHdCQUF3QjtnQkFDeEIsTUFBTSxDQUFDLElBQUksQ0FBQyxnQ0FBZ0MsT0FBTyxNQUFNLE9BQU8sRUFBRSxDQUFDLENBQUM7Z0JBQ3BFLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO2dCQUVyRCxxQ0FBcUM7Z0JBQ3JDLGVBQWUsQ0FBQyxnQkFBZ0IsQ0FBQztvQkFDL0IsSUFBSSxFQUFFLHFCQUFxQjtvQkFDM0IsUUFBUSxFQUFFLEtBQUs7b0JBQ2YsTUFBTSxFQUFFLGlEQUFpRDtvQkFDekQsT0FBTyxFQUFFLDJCQUEyQixPQUFPLE9BQU8sT0FBTywyQkFBMkI7b0JBQ3BGLFFBQVEsRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUU7aUJBQzdCLENBQUMsQ0FBQztZQUVMLENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNmLDZDQUE2QztnQkFDN0MsSUFBSyxLQUFhLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO29CQUNyQyxNQUFNLENBQUMsS0FBSyxDQUFDLGdEQUFnRCxPQUFPLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztnQkFDbEYsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBQb3J0Zm9saW8gTWFuYWdlciAtIE1hbmFnZXMgdGhlIHBvcnRmb2xpbyBkaXJlY3Rvcnkgc3RydWN0dXJlIGZvciBhbGwgZWxlbWVudCB0eXBlc1xuICovXG5cblxuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IGhvbWVkaXIgfSBmcm9tICdvcyc7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgRWxlbWVudFR5cGUsIFBvcnRmb2xpb0NvbmZpZyB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlNb25pdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvc2VjdXJpdHlNb25pdG9yLmpzJztcbmltcG9ydCB7IFVuaWNvZGVWYWxpZGF0b3IgfSBmcm9tICcuLi9zZWN1cml0eS92YWxpZGF0b3JzL3VuaWNvZGVWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgRGVmYXVsdEVsZW1lbnRQcm92aWRlciB9IGZyb20gJy4vRGVmYXVsdEVsZW1lbnRQcm92aWRlci5qcyc7XG5pbXBvcnQgeyBFcnJvckhhbmRsZXIsIEVycm9yQ2F0ZWdvcnkgfSBmcm9tICcuLi91dGlscy9FcnJvckhhbmRsZXIuanMnO1xuaW1wb3J0IHsgRmlsZU9wZXJhdGlvbnNTZXJ2aWNlIH0gZnJvbSAnLi4vc2VydmljZXMvRmlsZU9wZXJhdGlvbnNTZXJ2aWNlLmpzJztcblxuLy8gQ29uc3RhbnRzXG5jb25zdCBFTEVNRU5UX0ZJTEVfRVhURU5TSU9OUzogUmVjb3JkPEVsZW1lbnRUeXBlLCBzdHJpbmc+ID0ge1xuICBbRWxlbWVudFR5cGUuUEVSU09OQV06ICcubWQnLFxuICBbRWxlbWVudFR5cGUuU0tJTExdOiAnLm1kJyxcbiAgW0VsZW1lbnRUeXBlLlRFTVBMQVRFXTogJy5tZCcsXG4gIFtFbGVtZW50VHlwZS5BR0VOVF06ICcubWQnLFxuICBbRWxlbWVudFR5cGUuTUVNT1JZXTogJy55YW1sJyxcbiAgW0VsZW1lbnRUeXBlLkVOU0VNQkxFXTogJy5tZCdcbn07XG5cbi8vIERlZmF1bHQgZXh0ZW5zaW9uIGZvciBiYWNrd2FyZCBjb21wYXRpYmlsaXR5XG5jb25zdCBERUZBVUxUX0VMRU1FTlRfRklMRV9FWFRFTlNJT04gPSAnLm1kJztcblxuLyoqXG4gKiBHZXQgdGhlIGZpbGUgZXh0ZW5zaW9uIGZvciBhbiBlbGVtZW50IHR5cGUgd2l0aG91dCByZXF1aXJpbmcgYSBQb3J0Zm9saW9NYW5hZ2VyIGluc3RhbmNlLlxuICogSXNzdWUgIzgxNTogU2hhcmVkIGJ5IFBvcnRmb2xpb1JlcG9NYW5hZ2VyIGFuZCBzdWJtaXRUb1BvcnRmb2xpb1Rvb2wgdG8gYXZvaWRcbiAqIGhhcmRjb2RlZCAnLm1kJyBhc3N1bXB0aW9ucy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEVsZW1lbnRGaWxlRXh0ZW5zaW9uKHR5cGU6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBFTEVNRU5UX0ZJTEVfRVhURU5TSU9OU1t0eXBlIGFzIEVsZW1lbnRUeXBlXSB8fCBERUZBVUxUX0VMRU1FTlRfRklMRV9FWFRFTlNJT047XG59XG5cbmV4cG9ydCB7IEVsZW1lbnRUeXBlIH07XG5leHBvcnQgdHlwZSB7IFBvcnRmb2xpb0NvbmZpZyB9O1xuXG5leHBvcnQgY2xhc3MgUG9ydGZvbGlvTWFuYWdlciB7XG4gIHByaXZhdGUgaW5pdGlhbGl6YXRpb25Qcm9taXNlOiBQcm9taXNlPHZvaWQ+IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgYmFzZURpcjogc3RyaW5nO1xuICBwcml2YXRlIGZpbGVPcGVyYXRpb25zOiBGaWxlT3BlcmF0aW9uc1NlcnZpY2U7XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIG5ldyBQb3J0Zm9saW9NYW5hZ2VyIGluc3RhbmNlXG4gICAqXG4gICAqIEBwYXJhbSBjb25maWcgLSBPcHRpb25hbCBwb3J0Zm9saW8gY29uZmlndXJhdGlvblxuICAgKiBAcGFyYW0gZmlsZU9wZXJhdGlvbnMgLSBPcHRpb25hbCBGaWxlT3BlcmF0aW9uc1NlcnZpY2UgZm9yIGRlcGVuZGVuY3kgaW5qZWN0aW9uLlxuICAgKiAgICAgICAgICAgICAgICAgICAgICAgICBCUkVBS0lORyBDSEFOR0UgKHYxLjUuMCk6IEFkZGVkIGFzIHNlY29uZCBwYXJhbWV0ZXIgZm9yIERJLlxuICAgKiAgICAgICAgICAgICAgICAgICAgICAgICBEaXJlY3QgaW5zdGFudGlhdGlvbiB3aXRob3V0IERJIGNvbnRhaW5lciBzaG91bGQgcGFzcyB1bmRlZmluZWRcbiAgICogICAgICAgICAgICAgICAgICAgICAgICAgb3IgcHJvdmlkZSBhIEZpbGVPcGVyYXRpb25zU2VydmljZSBpbnN0YW5jZS5cbiAgICovXG4gIGNvbnN0cnVjdG9yKGZpbGVPcGVyYXRpb25zOiBGaWxlT3BlcmF0aW9uc1NlcnZpY2UsIGNvbmZpZz86IFBvcnRmb2xpb0NvbmZpZykge1xuICAgIHRoaXMuZmlsZU9wZXJhdGlvbnMgPSBmaWxlT3BlcmF0aW9ucztcbiAgICAvLyBHZXQgcG90ZW50aWFsIGRpcmVjdG9yeSBmcm9tIGVudmlyb25tZW50IG9yIGNvbmZpZ1xuICAgIGNvbnN0IGVudkRpciA9IHByb2Nlc3MuZW52LkRPTExIT1VTRV9QT1JURk9MSU9fRElSO1xuICAgIGNvbnN0IGNvbmZpZ0RpciA9IGNvbmZpZz8uYmFzZURpcjtcbiAgICBjb25zdCBkZWZhdWx0RGlyID0gcGF0aC5qb2luKGhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAncG9ydGZvbGlvJyk7XG4gICAgXG4gICAgLy8gVmFsaWRhdGUgZW52aXJvbm1lbnQgdmFyaWFibGUgaWYgcHJvdmlkZWRcbiAgICBpZiAoZW52RGlyKSB7XG4gICAgICBpZiAoIXBhdGguaXNBYnNvbHV0ZShlbnZEaXIpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignRE9MTEhPVVNFX1BPUlRGT0xJT19ESVIgbXVzdCBiZSBhbiBhYnNvbHV0ZSBwYXRoJyk7XG4gICAgICB9XG4gICAgICAvLyBBZGRpdGlvbmFsIHZhbGlkYXRpb24gZm9yIHN1c3BpY2lvdXMgcGF0aHNcbiAgICAgIGlmIChlbnZEaXIuaW5jbHVkZXMoJy4uJykgfHwgZW52RGlyLnN0YXJ0c1dpdGgoJy9ldGMnKSB8fCBlbnZEaXIuc3RhcnRzV2l0aCgnL3N5cycpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignRE9MTEhPVVNFX1BPUlRGT0xJT19ESVIgY29udGFpbnMgc3VzcGljaW91cyBwYXRoIHNlZ21lbnRzJyk7XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIC8vIFZhbGlkYXRlIGNvbmZpZyBkaXJlY3RvcnkgaWYgcHJvdmlkZWRcbiAgICBpZiAoY29uZmlnRGlyICYmICFwYXRoLmlzQWJzb2x1dGUoY29uZmlnRGlyKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdQb3J0Zm9saW8gY29uZmlnIGJhc2VEaXIgbXVzdCBiZSBhbiBhYnNvbHV0ZSBwYXRoJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIFVzZSBlbnZpcm9ubWVudCB2YXJpYWJsZSBpZiBzZXQsIG90aGVyd2lzZSBjb25maWcsIG90aGVyd2lzZSBkZWZhdWx0XG4gICAgdGhpcy5iYXNlRGlyID0gZW52RGlyIHx8IGNvbmZpZ0RpciB8fCBkZWZhdWx0RGlyO1xuICAgIFxuICAgIGxvZ2dlci5pbmZvKGBbUG9ydGZvbGlvTWFuYWdlcl0gUG9ydGZvbGlvIGJhc2UgZGlyZWN0b3J5OiAke3RoaXMuYmFzZURpcn1gKTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIEdldCB0aGUgYmFzZSBwb3J0Zm9saW8gZGlyZWN0b3J5XG4gICAqL1xuICBwdWJsaWMgZ2V0QmFzZURpcigpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLmJhc2VEaXI7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBHZXQgdGhlIGRpcmVjdG9yeSBmb3IgYSBzcGVjaWZpYyBlbGVtZW50IHR5cGVcbiAgICovXG4gIHB1YmxpYyBnZXRFbGVtZW50RGlyKHR5cGU6IEVsZW1lbnRUeXBlKTogc3RyaW5nIHtcbiAgICByZXR1cm4gcGF0aC5qb2luKHRoaXMuYmFzZURpciwgdHlwZSk7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBmaWxlIGV4dGVuc2lvbiBmb3IgYSBzcGVjaWZpYyBlbGVtZW50IHR5cGVcbiAgICogRklYICgjMTIxMyk6IEV4cG9zZSBFTEVNRU5UX0ZJTEVfRVhURU5TSU9OUyBtYXBwaW5nIGZvciBjb3JyZWN0IGV4dGVuc2lvbiBkaXNwbGF5XG4gICAqL1xuICBwdWJsaWMgZ2V0RmlsZUV4dGVuc2lvbih0eXBlOiBFbGVtZW50VHlwZSk6IHN0cmluZyB7XG4gICAgcmV0dXJuIEVMRU1FTlRfRklMRV9FWFRFTlNJT05TW3R5cGVdIHx8IERFRkFVTFRfRUxFTUVOVF9GSUxFX0VYVEVOU0lPTjtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplIHRoZSBwb3J0Zm9saW8gZGlyZWN0b3J5IHN0cnVjdHVyZVxuICAgKiBVc2VzIGxvY2tpbmcgdG8gcHJldmVudCByYWNlIGNvbmRpdGlvbnMgZHVyaW5nIGNvbmN1cnJlbnQgaW5pdGlhbGl6YXRpb25cbiAgICovXG4gIHB1YmxpYyBhc3luYyBpbml0aWFsaXplKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIC8vIElmIGFscmVhZHkgaW5pdGlhbGl6aW5nLCB3YWl0IGZvciB0aGUgZXhpc3RpbmcgaW5pdGlhbGl6YXRpb25cbiAgICBpZiAodGhpcy5pbml0aWFsaXphdGlvblByb21pc2UpIHtcbiAgICAgIHJldHVybiB0aGlzLmluaXRpYWxpemF0aW9uUHJvbWlzZTtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiBwb3J0Zm9saW8gaXMgZnVsbHkgaW5pdGlhbGl6ZWQgKGJhc2UgZGlyICsgYWxsIHN1YmRpcmVjdG9yaWVzIGV4aXN0KVxuICAgIGlmIChhd2FpdCB0aGlzLmlzRnVsbHlJbml0aWFsaXplZCgpKSB7XG4gICAgICBsb2dnZXIuZGVidWcoJ1tQb3J0Zm9saW9NYW5hZ2VyXSBQb3J0Zm9saW8gYWxyZWFkeSBmdWxseSBpbml0aWFsaXplZCcpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZSBpbml0aWFsaXphdGlvbiBwcm9taXNlIHRvIHByZXZlbnQgY29uY3VycmVudCBpbml0aWFsaXphdGlvblxuICAgIHRoaXMuaW5pdGlhbGl6YXRpb25Qcm9taXNlID0gdGhpcy5wZXJmb3JtSW5pdGlhbGl6YXRpb24oKTtcblxuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLmluaXRpYWxpemF0aW9uUHJvbWlzZTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgLy8gQ2xlYXIgdGhlIHByb21pc2UgYWZ0ZXIgY29tcGxldGlvblxuICAgICAgdGhpcy5pbml0aWFsaXphdGlvblByb21pc2UgPSBudWxsO1xuICAgIH1cbiAgfVxuICBcbiAgLyoqXG4gICAqIFBlcmZvcm0gdGhlIGFjdHVhbCBpbml0aWFsaXphdGlvbiAtIHNob3VsZCBvbmx5IGJlIGNhbGxlZCBvbmNlXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHBlcmZvcm1Jbml0aWFsaXphdGlvbigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsb2dnZXIuaW5mbygnW1BvcnRmb2xpb01hbmFnZXJdIEluaXRpYWxpemluZyBwb3J0Zm9saW8gZGlyZWN0b3J5IHN0cnVjdHVyZScpO1xuICAgIFxuICAgIC8vIENyZWF0ZSBiYXNlIGRpcmVjdG9yeVxuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLmZpbGVPcGVyYXRpb25zLmNyZWF0ZURpcmVjdG9yeSh0aGlzLmJhc2VEaXIpO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBjb25zdCBlcnIgPSBlcnJvciBhcyBOb2RlSlMuRXJybm9FeGNlcHRpb247XG4gICAgICAvLyBJbiByZWFkLW9ubHkgZW52aXJvbm1lbnRzIChsaWtlIERvY2tlciksIHdlIGNhbid0IGNyZWF0ZSBkaXJlY3Rvcmllc1xuICAgICAgLy8gTG9nIGJ1dCBjb250aW51ZSAtIHRoZSBwb3J0Zm9saW8gd2lsbCBiZSBlbXB0eSBidXQgZnVuY3Rpb25hbFxuICAgICAgaWYgKGVyci5jb2RlID09PSAnRUFDQ0VTJyB8fCBlcnIuY29kZSA9PT0gJ0VST0ZTJyB8fCBlcnIuY29kZSA9PT0gJ0VOT0VOVCcpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oYFtQb3J0Zm9saW9NYW5hZ2VyXSBDYW5ub3QgY3JlYXRlIHBvcnRmb2xpbyBkaXJlY3RvcnkgKHJlYWQtb25seSBlbnZpcm9ubWVudD8pOiAke2Vyci5tZXNzYWdlfWApO1xuICAgICAgICBsb2dnZXIuaW5mbyhgW0RvbGxob3VzZU1DUF0gUnVubmluZyBpbiByZWFkLW9ubHkgbW9kZSAtIHBvcnRmb2xpbyBmZWF0dXJlcyBkaXNhYmxlZGApO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gICAgXG4gICAgLy8gQ3JlYXRlIHN1YmRpcmVjdG9yaWVzIGZvciBlYWNoIGVsZW1lbnQgdHlwZVxuICAgIGZvciAoY29uc3QgZWxlbWVudFR5cGUgb2YgT2JqZWN0LnZhbHVlcyhFbGVtZW50VHlwZSkpIHtcbiAgICAgIGNvbnN0IGVsZW1lbnREaXIgPSBwYXRoLmpvaW4odGhpcy5iYXNlRGlyLCBlbGVtZW50VHlwZSk7XG4gICAgICBhd2FpdCB0aGlzLmZpbGVPcGVyYXRpb25zLmNyZWF0ZURpcmVjdG9yeShlbGVtZW50RGlyKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgW1BvcnRmb2xpb01hbmFnZXJdIENyZWF0ZWQgZGlyZWN0b3J5OiAke2VsZW1lbnREaXJ9YCk7XG4gICAgfVxuICAgIFxuICAgIC8vIENyZWF0ZSBzcGVjaWFsIGRpcmVjdG9yaWVzIGZvciBzdGF0ZWZ1bCBlbGVtZW50c1xuICAgIGNvbnN0IGFnZW50U3RhdGVEaXIgPSBwYXRoLmpvaW4odGhpcy5iYXNlRGlyLCBFbGVtZW50VHlwZS5BR0VOVCwgJy5zdGF0ZScpO1xuICAgIGF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMuY3JlYXRlRGlyZWN0b3J5KGFnZW50U3RhdGVEaXIpO1xuICAgIFxuICAgIGxvZ2dlci5pbmZvKCdbUG9ydGZvbGlvTWFuYWdlcl0gUG9ydGZvbGlvIGRpcmVjdG9yeSBzdHJ1Y3R1cmUgaW5pdGlhbGl6ZWQnKTtcbiAgICBcbiAgICAvLyBNaWdyYXRpb24gZm9yIHYxLjQuMiB1c2VyczogcmVuYW1lIHNpbmd1bGFyIGRpcmVjdG9yaWVzIHRvIHBsdXJhbFxuICAgIGF3YWl0IHRoaXMubWlncmF0ZUZyb21TaW5ndWxhckRpcmVjdG9yaWVzKCk7XG4gICAgXG4gICAgLy8gUG9wdWxhdGUgd2l0aCBkZWZhdWx0IGVsZW1lbnRzIGlmIHRoaXMgaXMgYSBuZXcgaW5zdGFsbGF0aW9uXG4gICAgLy8gU2tpcCBkdXJpbmcgdGVzdHMgdG8gYXZvaWQgaW50ZXJmZXJlbmNlXG4gICAgaWYgKHByb2Nlc3MuZW52Lk5PREVfRU5WICE9PSAndGVzdCcpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGRlZmF1bHRQcm92aWRlciA9IG5ldyBEZWZhdWx0RWxlbWVudFByb3ZpZGVyKCk7XG4gICAgICAgIGF3YWl0IGRlZmF1bHRQcm92aWRlci5wb3B1bGF0ZURlZmF1bHRzKHRoaXMuYmFzZURpcik7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBsb2dnZXIuZXJyb3IoJ1tQb3J0Zm9saW9NYW5hZ2VyXSBFcnJvciBwb3B1bGF0aW5nIGRlZmF1bHQgZWxlbWVudHM6JywgZXJyb3IpO1xuICAgICAgICAvLyBMb2cgdG8gc3RkZXJyIGZvciBNQ1AgY2xpZW50IHZpc2liaWxpdHlcbiAgICAgICAgbG9nZ2VyLmVycm9yKGBbUG9ydGZvbGlvTWFuYWdlcl0gQ1JJVElDQUw6IEZhaWxlZCB0byBwb3B1bGF0ZSBkZWZhdWx0IGVsZW1lbnRzOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKX1gKTtcbiAgICAgICAgLy8gQ29udGludWUgYW55d2F5IC0gZW1wdHkgcG9ydGZvbGlvIGlzIHZhbGlkXG4gICAgICB9XG4gICAgfVxuICB9XG4gIFxuICAvKipcbiAgICogQ2hlY2sgaWYgcG9ydGZvbGlvIGRpcmVjdG9yeSBleGlzdHNcbiAgICovXG4gIHB1YmxpYyBhc3luYyBleGlzdHMoKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLmZpbGVPcGVyYXRpb25zLmV4aXN0cyh0aGlzLmJhc2VEaXIpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBwb3J0Zm9saW8gaXMgZnVsbHkgaW5pdGlhbGl6ZWQgKGJhc2UgZGlyICsgYWxsIHN1YmRpcmVjdG9yaWVzIGV4aXN0KVxuICAgKiBUaGlzIHByZXZlbnRzIHRoZSBidWcgd2hlcmUgYmFzZSBkaXIgZXhpc3RzIGJ1dCBzdWJkaXJlY3RvcmllcyB3ZXJlIGRlbGV0ZWRcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgaXNGdWxseUluaXRpYWxpemVkKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgIC8vIEZpcnN0IGNoZWNrIGlmIGJhc2UgZGlyZWN0b3J5IGV4aXN0c1xuICAgIGlmICghYXdhaXQgdGhpcy5leGlzdHMoKSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGlmIGFsbCByZXF1aXJlZCBzdWJkaXJlY3RvcmllcyBleGlzdFxuICAgIHRyeSB7XG4gICAgICBmb3IgKGNvbnN0IGVsZW1lbnRUeXBlIG9mIE9iamVjdC52YWx1ZXMoRWxlbWVudFR5cGUpKSB7XG4gICAgICAgIGNvbnN0IGVsZW1lbnREaXIgPSBwYXRoLmpvaW4odGhpcy5iYXNlRGlyLCBlbGVtZW50VHlwZSk7XG4gICAgICAgIGlmICghYXdhaXQgdGhpcy5maWxlT3BlcmF0aW9ucy5leGlzdHMoZWxlbWVudERpcikpIHJldHVybiBmYWxzZTtcbiAgICAgIH1cblxuICAgICAgLy8gQ2hlY2sgc3BlY2lhbCBkaXJlY3Rvcmllc1xuICAgICAgY29uc3QgYWdlbnRTdGF0ZURpciA9IHBhdGguam9pbih0aGlzLmJhc2VEaXIsIEVsZW1lbnRUeXBlLkFHRU5ULCAnLnN0YXRlJyk7XG4gICAgICBpZiAoIWF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMuZXhpc3RzKGFnZW50U3RhdGVEaXIpKSByZXR1cm4gZmFsc2U7XG5cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gSWYgYW55IHN1YmRpcmVjdG9yeSBpcyBtaXNzaW5nLCBwb3J0Zm9saW8gaXMgbm90IGZ1bGx5IGluaXRpYWxpemVkXG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9XG4gIFxuICAvKipcbiAgICogQ2hlY2sgaWYgYSBmaWxlbmFtZSBhcHBlYXJzIHRvIGJlIGEgdGVzdCBlbGVtZW50XG4gICAqIFNBRkVUWTogUGF0dGVybi1iYXNlZCBmaWx0ZXJpbmcgb25seSwgbm8gY29udGVudCBwYXJzaW5nXG4gICAqXG4gICAqIFRoaXMgbWV0aG9kIElERU5USUZJRVMgdGVzdCBwYXR0ZXJucyAoYWx3YXlzIHJldHVybnMgdHJ1ZSBmb3IgdGVzdCBmaWxlcykuXG4gICAqIFRoZSBhY3R1YWwgRklMVEVSSU5HIGRlY2lzaW9uICh3aGV0aGVyIHRvIGV4Y2x1ZGUgdGhlbSkgaXMgbWFkZSBpbiBsaXN0RWxlbWVudHMoKS5cbiAgICovXG4gIHB1YmxpYyBpc1Rlc3RFbGVtZW50KGZpbGVuYW1lOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICAvLyBEYW5nZXJvdXMgdGVzdCBwYXR0ZXJucyB0aGF0IHNob3VsZCBuZXZlciBhcHBlYXIgaW4gcHJvZHVjdGlvblxuICAgIGNvbnN0IGRhbmdlcm91c1BhdHRlcm5zID0gW1xuICAgICAgL15iaW4tc2gvaSxcbiAgICAgIC9ecm0tcmYvaSxcbiAgICAgIC9ebmMtZS1iaW4vaSxcbiAgICAgIC9ecHl0aG9uLWMtaW1wb3J0L2ksXG4gICAgICAvXmN1cmwuKmV2aWwvaSxcbiAgICAgIC9ed2dldC4qbWFsaWNpb3VzL2ksXG4gICAgICAvXmV2YWwtL2ksXG4gICAgICAvXmV4ZWMtL2ksXG4gICAgICAvXmJhc2gtYy0vaSxcbiAgICAgIC9ec2gtYy0vaSxcbiAgICAgIC9ecG93ZXJzaGVsbC0vaSxcbiAgICAgIC9eY21kLWMtL2ksXG4gICAgICAvc2hlbGwtaW5qZWN0aW9uL2lcbiAgICBdO1xuXG4gICAgLy8gTk9URTogVGVzdC1wYXR0ZXJuIGZpbHRlcmluZyByZW1vdmVkIGVudGlyZWx5IChJc3N1ZSAjMjg3KVxuICAgIC8vIFVzZXJzIGNhbiBsZWdpdGltYXRlbHkgY3JlYXRlIGVsZW1lbnRzIHdpdGggXCJ0ZXN0XCIgaW4gdGhlIG5hbWUuXG4gICAgLy8gT25seSBkYW5nZXJvdXMgcGF0dGVybnMgKHNlY3VyaXR5IGNvbmNlcm5zKSBhcmUgZmlsdGVyZWQuXG5cbiAgICAvLyBDaGVjayBkYW5nZXJvdXMgcGF0dGVybnMgb25seVxuICAgIGlmIChkYW5nZXJvdXNQYXR0ZXJucy5zb21lKHBhdHRlcm4gPT4gcGF0dGVybi50ZXN0KGZpbGVuYW1lKSkpIHtcbiAgICAgIGxvZ2dlci53YXJuKGBbUG9ydGZvbGlvTWFuYWdlcl0gRmlsdGVyZWQgZGFuZ2Vyb3VzIGVsZW1lbnQ6ICR7ZmlsZW5hbWV9YCk7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogTGlzdCBhbGwgZWxlbWVudHMgb2YgYSBzcGVjaWZpYyB0eXBlXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgbGlzdEVsZW1lbnRzKHR5cGU6IEVsZW1lbnRUeXBlKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICAgIGNvbnN0IGVsZW1lbnREaXIgPSB0aGlzLmdldEVsZW1lbnREaXIodHlwZSk7XG4gICAgY29uc3QgZmlsZUV4dGVuc2lvbiA9IEVMRU1FTlRfRklMRV9FWFRFTlNJT05TW3R5cGVdIHx8IERFRkFVTFRfRUxFTUVOVF9GSUxFX0VYVEVOU0lPTjtcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCBmaWxlcyA9IGF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMubGlzdERpcmVjdG9yeShlbGVtZW50RGlyKTtcbiAgICAgIC8vIEZpbHRlciBmb3IgY29ycmVjdCBmaWxlIGV4dGVuc2lvbiBiYXNlZCBvbiBlbGVtZW50IHR5cGVcbiAgICAgIGxldCBmaWx0ZXJlZEZpbGVzID0gZmlsZXMuZmlsdGVyKGZpbGUgPT4gZmlsZS5lbmRzV2l0aChmaWxlRXh0ZW5zaW9uKSk7XG5cbiAgICAgIC8vIElzc3VlICM2NTQ6IEV4Y2x1ZGUgYmFja3VwIGZpbGVzIGZyb20gYWxsIGVsZW1lbnQgdHlwZXMuXG4gICAgICAvLyBTYWZldHkgbmV0IOKAlCBNZW1vcnlTdG9yYWdlTGF5ZXIgYWxzbyBmaWx0ZXJzLCBidXQgYW55IGNvZGUgcGF0aCB0aGF0XG4gICAgICAvLyBjYWxscyBsaXN0RWxlbWVudHMoKSBkaXJlY3RseSBzaG91bGQgbmV2ZXIgcmV0dXJuIGJhY2t1cCBhcnRpZmFjdHMuXG4gICAgICBmaWx0ZXJlZEZpbGVzID0gZmlsdGVyZWRGaWxlcy5maWx0ZXIoZmlsZSA9PlxuICAgICAgICAhZmlsZS5pbmNsdWRlcygnLmJhY2t1cC0nKSAmJiAhZmlsZS5pbmNsdWRlcygnLmJhY2t1cC4nKVxuICAgICAgKTtcblxuICAgICAgLy8gRmlsdGVyIG91dCB0ZXN0L2Rhbmdlcm91cyBlbGVtZW50cyB1bmxlc3MgZXhwbGljaXRseSBkaXNhYmxlZFxuICAgICAgLy8gRElTQUJMRV9FTEVNRU5UX0ZJTFRFUklORyBjYW4gYmUgc2V0IGluIEUyRSB0ZXN0cyB0aGF0IG5lZWQgdGVzdCBlbGVtZW50c1xuICAgICAgLy8gSW50ZWdyYXRpb24gdGVzdHMgYW5kIHByb2R1Y3Rpb24gYm90aCB1c2UgZmlsdGVyaW5nIGJ5IGRlZmF1bHRcbiAgICAgIGNvbnN0IHNob3VsZEZpbHRlciA9IHByb2Nlc3MuZW52LkRJU0FCTEVfRUxFTUVOVF9GSUxURVJJTkcgIT09ICd0cnVlJztcbiAgICAgIGlmIChzaG91bGRGaWx0ZXIpIHtcbiAgICAgICAgZmlsdGVyZWRGaWxlcyA9IGZpbHRlcmVkRmlsZXMuZmlsdGVyKGZpbGUgPT4gIXRoaXMuaXNUZXN0RWxlbWVudChmaWxlKSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBmaWx0ZXJlZEZpbGVzO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBjb25zdCBlcnIgPSBlcnJvciBhcyBOb2RlSlMuRXJybm9FeGNlcHRpb247XG4gICAgICBcbiAgICAgIGlmIChlcnIuY29kZSA9PT0gJ0VOT0VOVCcpIHtcbiAgICAgICAgLy8gRGlyZWN0b3J5IGRvZXNuJ3QgZXhpc3QgeWV0IC0gdGhpcyBpcyBleHBlY3RlZCBmb3IgbmV3IGluc3RhbGxhdGlvbnNcbiAgICAgICAgbG9nZ2VyLmRlYnVnKGBbUG9ydGZvbGlvTWFuYWdlcl0gRWxlbWVudCBkaXJlY3RvcnkgZG9lc24ndCBleGlzdCB5ZXQ6ICR7ZWxlbWVudERpcn1gKTtcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgfVxuICAgICAgXG4gICAgICBpZiAoZXJyLmNvZGUgPT09ICdFQUNDRVMnIHx8IGVyci5jb2RlID09PSAnRVBFUk0nKSB7XG4gICAgICAgIC8vIFBlcm1pc3Npb24gZGVuaWVkIC0gbG9nIGJ1dCByZXR1cm4gZW1wdHkgYXJyYXlcbiAgICAgICAgRXJyb3JIYW5kbGVyLmxvZ0Vycm9yKCdQb3J0Zm9saW9NYW5hZ2VyLmxpc3RFbGVtZW50cycsIGVycm9yLCB7IGVsZW1lbnREaXIgfSk7XG4gICAgICAgIHJldHVybiBbXTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgaWYgKGVyci5jb2RlID09PSAnRU5PVERJUicpIHtcbiAgICAgICAgLy8gUGF0aCBleGlzdHMgYnV0IGlzIG5vdCBhIGRpcmVjdG9yeVxuICAgICAgICBFcnJvckhhbmRsZXIubG9nRXJyb3IoJ1BvcnRmb2xpb01hbmFnZXIubGlzdEVsZW1lbnRzJywgZXJyb3IsIHsgZWxlbWVudERpciB9KTtcbiAgICAgICAgdGhyb3cgRXJyb3JIYW5kbGVyLmNyZWF0ZUVycm9yKGBQYXRoIGlzIG5vdCBhIGRpcmVjdG9yeTogJHtlbGVtZW50RGlyfWAsIEVycm9yQ2F0ZWdvcnkuU1lTVEVNX0VSUk9SKTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgLy8gRm9yIGFueSBvdGhlciBlcnJvcnMsIHRocm93IHdpdGggY29udGV4dFxuICAgICAgRXJyb3JIYW5kbGVyLmxvZ0Vycm9yKCdQb3J0Zm9saW9NYW5hZ2VyLmxpc3RFbGVtZW50cycsIGVycm9yLCB7IGVsZW1lbnREaXIgfSk7XG4gICAgICB0aHJvdyBFcnJvckhhbmRsZXIud3JhcEVycm9yKGVycm9yLCAnRmFpbGVkIHRvIGxpc3QgZWxlbWVudHMnLCBFcnJvckNhdGVnb3J5LlNZ