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.

701 lines 108 kB
/** * Web UI API Routes * * REST endpoints for reading portfolio elements. * Read-only — no mutations in V1. * * All element content is sanitized before serving to prevent XSS. * * Security note: This web server binds to 127.0.0.1 only (see server.ts). * Rate limiting on read-only GET endpoints is not required for localhost-only * management interfaces. The POST /api/install endpoint has explicit rate limiting * via SlidingWindowRateLimiter (max 10 per minute). * codeql[js/missing-rate-limiting] — Acknowledged; localhost-only binding mitigates DoS risk. */ import express, { Router } from 'express'; import { readdir, readFile, stat } from 'node:fs/promises'; import { join, extname, resolve } from 'node:path'; import { SecureYamlParser } from '../security/secureYamlParser.js'; import { logger } from '../utils/logger.js'; import { validateElementContent } from './contentPipeline.js'; /** Normalize user input to NFC form to prevent Unicode homograph attacks */ function normalizeInput(input) { return input.normalize('NFC'); } import { ContentValidator } from '../security/contentValidator.js'; import { SlidingWindowRateLimiter } from '../utils/SlidingWindowRateLimiter.js'; const ELEMENT_TYPES = ['personas', 'skills', 'templates', 'agents', 'memories', 'ensembles']; /** Max file size for element reads (1 MB) */ const MAX_FILE_SIZE_BYTES = 1_048_576; /** Valid element file extensions */ const VALID_EXTENSIONS = new Set(['.md', '.yaml', '.yml']); /** Known-safe filename pattern: starts alphanumeric, body allows hyphens/underscores, valid extension */ const SAFE_FILENAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*\.(yaml|yml|md)$/; /** Check if a filename is a backup or cruft file */ function isBackupOrCruft(filename) { // Guaranteed safe filenames skip blacklist checks entirely if (SAFE_FILENAME_RE.test(filename)) return false; if (filename.startsWith('.')) return true; if (filename === '_index.json') return true; if (filename.includes('.backup') || filename.includes('.state')) return true; if (filename.endsWith('.bak') || filename.endsWith('~')) return true; if (filename.includes(' copy')) return true; return false; } /** * Scan a directory for valid elements, running each through the security * validation pipeline. Returns metadata objects ready for API responses. */ async function scanElementDirectory(typeDir, type, logPrefix) { const files = await readdir(typeDir); const elements = []; for (const file of files) { if (isBackupOrCruft(file)) continue; const ext = extname(file); if (!VALID_EXTENSIONS.has(ext)) continue; try { const filePath = join(typeDir, file); const fileStat = await stat(filePath); if (!fileStat.isFile() || fileStat.size > MAX_FILE_SIZE_BYTES) continue; const content = await readFile(filePath, 'utf-8'); const validationResult = validateElementContent(file, content, type); if (!validationResult.valid) { logger.debug(`${logPrefix} Skipping rejected file ${file}: ${validationResult.rejection?.reason}`); continue; } const { metadata } = validationResult; elements.push({ name: metadata.name || file.replace(ext, ''), description: metadata.description || '', type: type.slice(0, -1), version: metadata.version || '1.0.0', author: metadata.author || '', category: metadata.category || '', tags: metadata.tags || '', created: metadata.created || '', filename: file, }); } catch (err) { logger.debug(`${logPrefix} Failed to parse ${file}:`, err); } } return elements; } /** Normalize plural element type to singular form */ const PLURAL_TO_SINGULAR = { personas: 'persona', skills: 'skill', templates: 'template', agents: 'agent', memories: 'memory', ensembles: 'ensemble', }; function toSingularType(type) { return PLURAL_TO_SINGULAR[type] || (type.endsWith('s') ? type.slice(0, -1) : type); } /** Build a structured validation response for element detail routes */ function buildValidationResponse(validation, content, type) { return { metadata: validation.metadata, body: validation.body, raw: content, type: toSingularType(type), validation: { status: validation.valid ? 'pass' : 'warn', ...(validation.rejection && { reason: validation.rejection.reason, severity: validation.rejection.severity, patterns: validation.rejection.patterns, }), }, }; } /** * Resolve a file path for an element, handling memory date-paths * and name-with-or-without-extension matching. * Returns the resolved path or null with an error to send. */ async function resolveElementFilePath(portfolioDir, type, name) { if (type === 'memories' && name.includes('/')) { const parts = name.split('/'); if (parts.length !== 2 || !/^\d{4}-\d{2}-\d{2}$/.test(parts[0]) || isBackupOrCruft(parts[1])) { return { error: 'Invalid memory path', status: 400 }; } const filePath = join(portfolioDir, type, name); const resolvedPath = resolve(filePath); if (!resolvedPath.startsWith(resolve(portfolioDir))) { return { error: 'Path traversal detected', status: 400 }; } return { filePath }; } const typeDir = join(portfolioDir, type); const files = await readdir(typeDir); const match = files.find(f => { const base = f.replace(extname(f), ''); return base === name || f === name; }); if (!match) { return { error: `Element not found: ${type}/${name}`, status: 404 }; } const filePath = join(portfolioDir, type, match); const resolvedPath = resolve(filePath); if (!resolvedPath.startsWith(resolve(portfolioDir))) { return { error: 'Path traversal detected', status: 400 }; } return { filePath }; } /** * Load memories from the _index.json file. * Memories use date-partitioned storage with an index, unlike other * element types which are flat files in a directory. */ async function loadMemoriesFromIndex(portfolioDir) { const indexPath = join(portfolioDir, 'memories', '_index.json'); try { const raw = await readFile(indexPath, 'utf-8'); const index = JSON.parse(raw); const entries = index.entries || {}; const elements = []; for (const [path, entry] of Object.entries(entries)) { if (!entry || typeof entry !== 'object') continue; const pathParts = path.split('/'); const filename = pathParts.pop() || path; // Extract date from directory path (e.g., "2025-09-19/code-patterns.yaml" -> "2025-09-19") const dateFromPath = pathParts.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(pathParts[0]) ? pathParts[0] : ''; // Fall back to filename if index has no name or stored "unnamed" (upstream indexer bug) const indexName = entry.name && entry.name !== 'unnamed' ? entry.name : null; const name = indexName || filename.replace(/\.(yaml|yml|md)$/, ''); elements.push({ name, description: entry.description || '', type: 'memory', version: entry.version || '1.0.0', author: entry.author || '', category: entry.category || entry.memoryType || '', tags: entry.tags || [], created: entry.created || dateFromPath, filename: path, // date/filename path for content loading }); } return elements; } catch { // Fall back to empty if no index return []; } } /** Rate limiter for /api/install: max 10 installs per 60 seconds */ const installRateLimiter = new SlidingWindowRateLimiter(10, 60_000); /** Sanitize text content to prevent XSS in rendered HTML */ function sanitizeForHtml(text) { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } /** Parse YAML front matter from a markdown file */ function parseFrontMatter(content) { const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); if (!match) { return { metadata: {}, body: content }; } try { const parsed = SecureYamlParser.parseRawYaml(match[1]); const metadata = (typeof parsed === 'object' && parsed !== null) ? parsed : {}; return { metadata, body: match[2] }; } catch { return { metadata: {}, body: match[2] || content }; } } /** Parse a YAML-only file (memories) */ function parseYamlFile(content) { try { const parsed = SecureYamlParser.parseRawYaml(content); const metadata = (typeof parsed === 'object' && parsed !== null) ? parsed : {}; return { metadata, body: '' }; } catch { return { metadata: {}, body: content }; } } /** * Register portfolio routes shared between simple and gateway modes. * The structuredDetail option controls whether detail routes return * structured JSON (gateway) or plain text (simple/legacy). */ function registerPortfolioRoutes(router, portfolioDir, options) { const { structuredDetail, logPrefix } = options; router.get('/elements', async (req, res) => { try { const pageParam = req.query.page; const pageSizeParam = req.query.pageSize; const wantPagination = pageParam !== undefined && pageSizeParam !== undefined; const page = Math.max(1, Number.parseInt(pageParam || '1', 10) || 1); const pageSize = Math.max(1, Math.min(200, Number.parseInt(pageSizeParam || '50', 10) || 50)); const result = {}; let totalCount = 0; for (const type of ELEMENT_TYPES) { try { if (type === 'memories') { const memElements = await loadMemoriesFromIndex(portfolioDir); result[type] = memElements; totalCount += memElements.length; continue; } const typeDir = join(portfolioDir, type); const dirStat = await stat(typeDir); if (!dirStat.isDirectory()) continue; const elements = await scanElementDirectory(typeDir, type, logPrefix); result[type] = elements; totalCount += elements.length; } catch { result[type] = []; } } if (wantPagination) { const allElements = []; for (const type of ELEMENT_TYPES) { allElements.push(...(result[type] || [])); } const start = (page - 1) * pageSize; const paged = allElements.slice(start, start + pageSize); const totalPages = Math.ceil(allElements.length / pageSize); res.json({ elements: paged, totalCount: allElements.length, page, pageSize, totalPages }); } else { res.json({ elements: result, totalCount }); } } catch (err) { logger.error(`${logPrefix} Failed to list elements:`, err); res.status(500).json({ error: 'Failed to list elements' }); } }); router.get('/elements/:type', async (req, res) => { const type = normalizeInput(req.params.type); if (!ELEMENT_TYPES.includes(type)) { res.status(400).json({ error: `Invalid element type: ${type}` }); return; } try { if (type === 'memories') { const memElements = await loadMemoriesFromIndex(portfolioDir); res.json({ type, elements: memElements, count: memElements.length }); return; } const elements = await scanElementDirectory(join(portfolioDir, type), type, logPrefix); res.json({ type, elements, count: elements.length }); } catch { res.status(500).json({ error: `Failed to list ${type}` }); } }); router.get('/elements/memories/:date/:file', async (req, res) => { const { date } = req.params; // NFC-normalize file before safety checks to prevent Unicode homograph bypasses (#1736) const file = normalizeInput(req.params.file); if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) { res.status(400).json({ error: 'Invalid date format' }); return; } if (file.includes('..') || file.includes('/') || file.includes('\\') || isBackupOrCruft(file)) { res.status(400).json({ error: 'Invalid filename' }); return; } try { const filePath = join(portfolioDir, 'memories', date, file); const resolvedPath = resolve(filePath); if (!resolvedPath.startsWith(resolve(portfolioDir))) { res.status(400).json({ error: 'Path traversal detected' }); return; } const fileStat = await stat(filePath); if (!fileStat.isFile() || fileStat.size > MAX_FILE_SIZE_BYTES) { res.status(fileStat.isFile() ? 413 : 404).json({ error: fileStat.isFile() ? 'File too large' : 'Not found' }); return; } const content = await readFile(filePath, 'utf-8'); if (structuredDetail) { const validation = validateElementContent(file, content, 'memories'); res.json(buildValidationResponse(validation, content, 'memories')); } else { res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.send(content); } } catch { res.status(404).json({ error: `Memory not found: ${date}/${file}` }); } }); router.get('/elements/:type/:name', async (req, res) => { const type = req.params.type; const name = normalizeInput(req.params.name); if (!ELEMENT_TYPES.includes(type)) { res.status(400).json({ error: `Invalid element type: ${type}` }); return; } if (name.includes('..') || name.includes('\\')) { res.status(400).json({ error: 'Invalid element name' }); return; } if (type !== 'memories' && name.includes('/')) { res.status(400).json({ error: 'Invalid element name' }); return; } try { const resolved = await resolveElementFilePath(portfolioDir, type, name); if ('error' in resolved) { res.status(resolved.status).json({ error: resolved.error }); return; } const fileStat = await stat(resolved.filePath); if (fileStat.size > MAX_FILE_SIZE_BYTES) { res.status(413).json({ error: `File too large (${fileStat.size} bytes). Max 1 MB.` }); return; } const content = await readFile(resolved.filePath, 'utf-8'); if (structuredDetail) { const filename = resolved.filePath.split('/').pop() || name; const validation = validateElementContent(filename, content, type); res.json(buildValidationResponse(validation, content, type)); } else { res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.send(content); } } catch { res.status(500).json({ error: `Failed to get element: ${type}/${name}` }); } }); } /** Register filesystem-based stats route (shared between simple and gateway) */ function registerStatsRoute(router, portfolioDir) { router.get('/stats', async (_req, res) => { try { const stats = {}; let total = 0; for (const type of ELEMENT_TYPES) { try { if (type === 'memories') { const memElements = await loadMemoriesFromIndex(portfolioDir); stats[type] = memElements.length; total += memElements.length; continue; } const typeDir = join(portfolioDir, type); const files = await readdir(typeDir); const count = files.filter(f => !isBackupOrCruft(f) && VALID_EXTENSIONS.has(extname(f))).length; stats[type] = count; total += count; } catch { stats[type] = 0; } } res.json({ stats, total }); } catch { res.status(500).json({ error: 'Failed to get stats' }); } }); } /** Register collection index proxy route (shared between simple and gateway) */ function registerCollectionRoute(router, portfolioDir) { router.get('/collection', async (_req, res) => { res.setHeader('Cache-Control', 'no-cache'); try { const response = await fetch('https://raw.githubusercontent.com/DollhouseMCP/collection/main/public/collection-index.json'); if (response.ok) { const data = await response.text(); res.setHeader('Content-Type', 'application/json'); res.send(data); return; } } catch { /* GitHub unreachable — fall back to local */ } const localPaths = [ join(portfolioDir, '..', '..', '..', 'collection', 'public', 'collection-index.json'), join(portfolioDir, '..', 'collection', 'public', 'collection-index.json'), ]; for (const localPath of localPaths) { try { const content = await readFile(localPath, 'utf-8'); res.setHeader('Content-Type', 'application/json'); res.send(content); return; } catch { /* try next */ } } res.status(404).json({ error: 'Collection index not available' }); }); } export function createApiRoutes(portfolioDir) { const router = Router(); registerPortfolioRoutes(router, portfolioDir, { structuredDetail: false, logPrefix: '[WebUI]' }); registerStatsRoute(router, portfolioDir); registerCollectionRoute(router, portfolioDir); /** * POST /api/install * Install a collection element into the local portfolio. * Routes through DollhouseMCP's install_collection_content pipeline * for proper validation, gatekeeper checks, and element management. * * Requires the MCP server's CollectionHandler to be available. * Falls back to direct file write if not (standalone web mode). */ router.post('/install', express.json(), async (req, res) => { if (!installRateLimiter.tryAcquire()) { res.status(429).json({ error: 'Too many install requests. Max 10 per minute.' }); return; } const { path: elementPath, name: rawName, type } = req.body; if (!elementPath || !type || !rawName) { res.status(400).json({ error: 'Missing required fields: path, name, type' }); return; } // NFC-normalize name before safety checks to prevent Unicode homograph bypasses (#1736) const name = normalizeInput(rawName); // Validate type const pluralType = type.endsWith('s') ? type : `${type}s`; if (!ELEMENT_TYPES.includes(pluralType)) { res.status(400).json({ error: `Invalid element type: ${type}` }); return; } // Prevent path traversal if (elementPath.includes('..') || name.includes('..')) { res.status(400).json({ error: 'Invalid path or name' }); return; } // Validate elementPath contains only safe characters (alphanumeric, hyphens, underscores, dots, slashes) if (!/^[a-zA-Z0-9/_.-]+$/.test(elementPath)) { res.status(400).json({ error: 'Invalid element path characters' }); return; } try { // Fetch content from GitHub const ghUrl = `https://raw.githubusercontent.com/DollhouseMCP/collection/main/${elementPath}`; // Validate the constructed URL stays within expected domain and path const parsedUrl = new URL(ghUrl); if (parsedUrl.hostname !== 'raw.githubusercontent.com' || !parsedUrl.pathname.startsWith('/DollhouseMCP/collection/')) { res.status(400).json({ error: 'Invalid collection path' }); return; } const response = await fetch(ghUrl); if (!response.ok) { res.status(502).json({ error: `Failed to fetch from collection: HTTP ${response.status}` }); return; } const content = await response.text(); // Validate content through the security pipeline before writing const ext = extname(elementPath); try { if (ext === '.yaml' || ext === '.yml') { // Validate YAML content for bombs, circular references, malicious patterns if (!ContentValidator.validateYamlContent(content)) { res.status(422).json({ error: 'Content failed YAML validation — potentially malicious patterns detected' }); return; } SecureYamlParser.parseRawYaml(content); } else { // Validate markdown+frontmatter content SecureYamlParser.parse(content); } } catch (parseErr) { res.status(422).json({ error: `Content failed parse validation: ${parseErr.message}` }); return; } // Validate content body for injection patterns const singularType = pluralType.slice(0, -1); const contextTypes = ['persona', 'skill', 'template', 'agent', 'memory']; const contentContext = contextTypes.includes(singularType) ? singularType : undefined; const validationResult = ContentValidator.validateAndSanitize(content, { contentContext, }); if (!validationResult.isValid) { logger.warn('[WebUI] Install blocked — content validation failed', { element: `${pluralType}/${name}`, patterns: validationResult.detectedPatterns, severity: validationResult.severity, }); res.status(422).json({ error: 'Content failed security validation', patterns: validationResult.detectedPatterns, severity: validationResult.severity, }); return; } // Determine filename const filename = elementPath.split('/').pop() || `${name}.md`; // Ensure type directory exists const typeDir = join(portfolioDir, pluralType); const { mkdir } = await import('node:fs/promises'); await mkdir(typeDir, { recursive: true }); // Write to portfolio const destPath = join(typeDir, filename); // Verify resolved destination path stays within portfolio directory (defense in depth) const resolvedDest = resolve(destPath); if (!resolvedDest.startsWith(resolve(portfolioDir))) { res.status(400).json({ error: 'Path traversal detected' }); return; } // Check if file already exists try { await stat(destPath); res.status(409).json({ error: `Element already exists: ${pluralType}/${filename}. Delete it first or rename.` }); return; } catch { /* doesn't exist — good */ } const { writeFile: writeFileFs } = await import('node:fs/promises'); await writeFileFs(destPath, content, 'utf-8'); logger.info(`[WebUI] Installed collection element: ${pluralType}/${filename}`); res.json({ success: true, message: `Installed ${name} to portfolio`, path: `${pluralType}/${filename}` }); } catch (err) { logger.error('[WebUI] Install failed:', err); res.status(500).json({ error: `Install failed: ${err.message}` }); } }); return router; } function asSingleResult(r) { if (typeof r === 'object' && r !== null && 'success' in r) { return r; } return { success: false, error: 'Invalid operation result format' }; } /** * Create API routes that route through MCPAQLHandler (gateway mode). * All operations go through the MCP-AQL pipeline: validation, cache, gatekeeper. * * Falls back to direct filesystem for /api/collection (external fetch, no MCP-AQL equivalent) * and /api/stats (lightweight aggregate, no matching operation). */ export function createGatewayApiRoutes(handler, portfolioDir) { const router = Router(); // Shared portfolio routes — structured JSON for detail views // codeql[js/missing-rate-limiting] — Rate-limited by router.use() middleware in server.ts registerPortfolioRoutes(router, portfolioDir, { structuredDetail: true, logPrefix: '[WebUI/Gateway]' }); registerStatsRoute(router, portfolioDir); registerCollectionRoute(router, portfolioDir); /** * GET /api/collection/content/* * Proxies collection element content from GitHub, validates through the * security pipeline, and returns structured JSON (same format as portfolio detail). * codeql[js/missing-rate-limiting] — Rate-limited by router.use() middleware above. */ router.get('/collection/content/:prefix/:type/:name', async (req, res) => { const prefix = normalizeInput(req.params.prefix); const elementType = normalizeInput(req.params.type); const filename = normalizeInput(req.params.name); const elementPath = `${prefix}/${elementType}/${filename}`; if (!elementPath || elementPath.includes('..') || elementPath.includes('\\')) { res.status(400).json({ error: 'Invalid element path' }); return; } // Validate element type against known types to prevent arbitrary path construction if (!ELEMENT_TYPES.includes(elementType)) { res.status(400).json({ error: `Invalid element type: ${elementType}` }); return; } try { // codeql[js/request-forgery] — mitigated: domain and repo are hardcoded constants, // elementType is validated against ELEMENT_TYPES whitelist above, path traversal // is checked, and the only reachable target is a specific public GitHub repository. const githubUrl = `https://raw.githubusercontent.com/DollhouseMCP/collection/main/${elementPath}`; const response = await fetch(githubUrl); if (!response.ok) { res.status(response.status === 404 ? 404 : 502).json({ error: response.status === 404 ? `Collection element not found: ${elementPath}` : `Failed to fetch from GitHub (HTTP ${response.status})`, }); return; } const content = await response.text(); const validation = validateElementContent(filename, content, elementType); const singularType = PLURAL_TO_SINGULAR[elementType] || (elementType.endsWith('s') ? elementType.slice(0, -1) : elementType); res.json({ metadata: validation.metadata, body: validation.body, raw: content, type: singularType, validation: { status: validation.valid ? 'pass' : 'warn', ...(validation.rejection && { reason: validation.rejection.reason, severity: validation.rejection.severity, patterns: validation.rejection.patterns, }), }, }); } catch (err) { logger.error('[WebUI/Gateway] Failed to fetch collection content:', err); res.status(502).json({ error: 'Failed to fetch collection element' }); } }); /** * POST /api/install * Routes through install_collection_content MCP-AQL operation. * All validation, gatekeeper checks handled by the pipeline. */ const installLimiter = new SlidingWindowRateLimiter(10, 60_000); router.post('/install', express.json(), async (req, res) => { if (!installLimiter.tryAcquire()) { res.status(429).json({ error: 'Too many install requests. Max 10 per minute.' }); return; } const { path: elementPath, name: rawName, type } = req.body; if (!elementPath || !type || !rawName) { res.status(400).json({ error: 'Missing required fields: path, name, type' }); return; } // NFC-normalize name before safety checks to prevent Unicode homograph bypasses (#1736) const name = normalizeInput(rawName); if (elementPath.includes('..') || name.includes('..')) { res.status(400).json({ error: 'Invalid path or name' }); return; } // Validate elementPath contains only safe characters (alphanumeric, hyphens, underscores, dots, slashes) if (!/^[a-zA-Z0-9/_.-]+$/.test(elementPath)) { res.status(400).json({ error: 'Invalid element path characters' }); return; } try { // Route through MCP-AQL install_collection_content operation const installPath = elementPath.replace(/^library\//, ''); const opResult = asSingleResult(await handler.handleCreate({ operation: 'install_collection_content', params: { path: installPath }, })); if (!opResult.success) { res.status(422).json({ error: opResult.error || 'Install failed' }); return; } logger.info(`[WebUI/Gateway] Installed collection element: ${installPath}`); res.json({ success: true, message: `Installed ${name} to portfolio`, data: opResult.data }); } catch (err) { logger.error('[WebUI/Gateway] Install failed:', err); res.status(500).json({ error: `Install failed: ${err.message}` }); } }); return router; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dlYi9yb3V0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUVILE9BQU8sT0FBTyxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQzFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzNELE9BQU8sRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNuRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUMsT0FBTyxFQUFFLHNCQUFzQixFQUFvRCxNQUFNLHNCQUFzQixDQUFDO0FBR2hILDRFQUE0RTtBQUM1RSxTQUFTLGNBQWMsQ0FBQyxLQUFhO0lBQ25DLE9BQU8sS0FBSyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNoQyxDQUFDO0FBQ0QsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbkUsT0FBTyxFQUFFLHdCQUF3QixFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFFaEYsTUFBTSxhQUFhLEdBQUcsQ0FBQyxVQUFVLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLFdBQVcsQ0FBVSxDQUFDO0FBRXRHLDZDQUE2QztBQUM3QyxNQUFNLG1CQUFtQixHQUFHLFNBQVMsQ0FBQztBQUV0QyxvQ0FBb0M7QUFDcEMsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUUzRCx5R0FBeUc7QUFDekcsTUFBTSxnQkFBZ0IsR0FBRyw0Q0FBNEMs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