@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.
210 lines • 23.9 kB
JavaScript
/**
* TriggerValidationService - Centralized trigger validation for all element types
*
* Eliminates duplicate trigger validation code across PersonaManager, SkillManager,
* MemoryManager, TemplateManager, AgentManager, and EnsembleManager.
*
* Key Features:
* - Unified validation rules for all element types (max 20 triggers, max 50 chars)
* - Consistent validation algorithm across all element types
* - Comprehensive error reporting and logging
* - Full TypeScript type safety
*
* @example
* ```typescript
* import { triggerValidationService } from './services/TriggerValidationService';
*
* const result = triggerValidationService.validateTriggers(
* ['create', 'build', 'invalid!@#'],
* ElementType.SKILL,
* 'my-skill'
* );
*
* console.log(result.validTriggers); // ['create', 'build']
* console.log(result.rejectedTriggers); // [{ original: 'invalid!@#', reason: '...' }]
* ```
*/
import { sanitizeInput } from '../../security/InputValidator.js';
import { logger } from '../../utils/logger.js';
/**
* Maximum number of triggers allowed for all element types
*/
const DEFAULT_MAX_TRIGGERS = 20;
/**
* Maximum length of each trigger string after sanitization
*/
const DEFAULT_MAX_TRIGGER_LENGTH = 50;
/**
* Validation pattern for triggers
*
* Allowed characters: a-z, A-Z, 0-9, hyphen (-), underscore (_), at (@), period (.)
*
* Valid trigger examples:
* - "code-review" - kebab-case keywords
* - "bug_fix" - snake_case keywords
* - "@username" - social media mentions
* - "user@example.com" - email addresses
* - "api.docs" - domain-style patterns
* - "v2.0" - version numbers
*
* Invalid trigger examples (will be rejected):
* - "bad!trigger" - shell metacharacter !
* - "cmd;injection" - shell metacharacter ;
* - "$(evil)" - command substitution
* - "with spaces" - spaces not allowed
* - "<script>" - HTML characters
*
* Security note: @ and . are intentionally allowed as they are NOT shell
* metacharacters and enable common use cases (mentions, emails, domains).
*/
const DEFAULT_VALIDATION_PATTERN = /^[a-zA-Z0-9\-_@.]+$/;
/**
* Service for validating and processing trigger arrays across all element types
*
* This service provides centralized trigger validation to eliminate code duplication
* across element managers. All element types use the same validation rules:
* - Maximum 20 triggers
* - Maximum 50 characters per trigger
* - Only alphanumeric, hyphens, underscores, @, and . allowed
*/
export class TriggerValidationService {
constructor() {
// No configuration needed - all element types use same rules
}
/**
* Validate an array of triggers for a specific element type
*
* Validation process:
* 1. Limit input array to MAX_TRIGGERS (20)
* 2. For each trigger:
* a. Convert to string
* b. Sanitize (remove dangerous characters, trim, limit length to 50)
* c. Check if empty after sanitization
* d. Validate against regex pattern (/^[a-zA-Z0-9\-_@.]+$/)
* 3. Collect valid and rejected triggers
* 4. Log warnings if truncation or rejections occurred
*
* @param triggers - Raw trigger array from user input or metadata
* @param elementType - Type of element being validated (used for logging context)
* @param elementName - Name of element (used for logging context)
* @returns Validation result with valid/rejected triggers and warnings
*
* @example
* ```typescript
* const result = service.validateTriggers(
* ['create', 'build', 'test!@#', ''],
* ElementType.SKILL,
* 'code-generator'
* );
* // result.validTriggers: ['create', 'build']
* // result.rejectedTriggers: [
* // { original: 'test!@#', reason: '(invalid format ...)' },
* // { original: '', reason: '(empty)' }
* // ]
* ```
*/
validateTriggers(triggers, elementType, elementName) {
// Handle edge cases
if (!triggers || !Array.isArray(triggers)) {
return this.createEmptyResult(0);
}
if (triggers.length === 0) {
return this.createEmptyResult(0);
}
const totalInput = triggers.length;
// Initialize result containers
const validTriggers = [];
const rejectedTriggers = [];
const warnings = [];
// Limit input to MAX_TRIGGERS
const limitedTriggers = triggers.slice(0, DEFAULT_MAX_TRIGGERS);
// Warn if we had to truncate
if (totalInput > DEFAULT_MAX_TRIGGERS) {
const warning = `Trigger limit exceeded (${totalInput} > ${DEFAULT_MAX_TRIGGERS}), truncating`;
warnings.push(warning);
logger.warn(`${elementType} "${elementName}": ${warning}`, {
elementType,
elementName,
providedCount: totalInput,
limit: DEFAULT_MAX_TRIGGERS,
truncated: totalInput - DEFAULT_MAX_TRIGGERS
});
}
// Validate each trigger
for (const rawTrigger of limitedTriggers) {
// Convert to string (handle non-string inputs)
const triggerString = String(rawTrigger).trim();
// Check if empty
if (!triggerString || triggerString.length === 0) {
rejectedTriggers.push({
original: rawTrigger,
reason: '(empty)'
});
continue;
}
// SECURITY: Validate BEFORE sanitization to reject invalid characters
// This prevents 'bad!trigger' from becoming 'badtrigger' and passing
if (!DEFAULT_VALIDATION_PATTERN.test(triggerString)) {
rejectedTriggers.push({
original: rawTrigger,
reason: '(invalid format - allowed: letters, numbers, hyphens, underscores, @ and .)'
});
continue;
}
// Now sanitize the already-valid trigger (for length limits, etc.)
const sanitized = sanitizeInput(triggerString, DEFAULT_MAX_TRIGGER_LENGTH);
// Check if empty after sanitization
if (!sanitized || sanitized.length === 0) {
rejectedTriggers.push({
original: rawTrigger,
reason: '(empty after sanitization)'
});
continue;
}
// Passed all validation - add to valid list
validTriggers.push(sanitized);
}
// Log rejections if any
if (rejectedTriggers.length > 0) {
logger.warn(`${elementType} "${elementName}": Rejected ${rejectedTriggers.length} invalid trigger(s)`, {
elementType,
elementName,
rejectedTriggers: rejectedTriggers.map(r => `"${r.original}" ${r.reason}`),
acceptedCount: validTriggers.length
});
}
// Log debug info
logger.debug(`[TriggerValidationService] Validated triggers for ${elementType} "${elementName}"`, {
elementType,
elementName,
totalInput,
validCount: validTriggers.length,
rejectedCount: rejectedTriggers.length,
truncated: totalInput > DEFAULT_MAX_TRIGGERS
});
return {
validTriggers,
rejectedTriggers,
hasRejections: rejectedTriggers.length > 0,
totalInput,
warnings
};
}
/**
* Create an empty validation result
*
* @param totalInput - Total count of input triggers
* @returns Empty result with no valid or rejected triggers
* @private
*/
createEmptyResult(totalInput) {
return {
validTriggers: [],
rejectedTriggers: [],
hasRejections: false,
totalInput,
warnings: []
};
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZpY2VzL3ZhbGlkYXRpb24vVHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBeUJHO0FBR0gsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQWlDL0M7O0dBRUc7QUFDSCxNQUFNLG9CQUFvQixHQUFHLEVBQUUsQ0FBQztBQUVoQzs7R0FFRztBQUNILE1BQU0sMEJBQTBCLEdBQUcsRUFBRSxDQUFDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JHO0FBQ0gsTUFBTSwwQkFBMEIsR0FBRyxxQkFBcUIsQ0FBQztBQUV6RDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sT0FBTyx3QkFBd0I7SUFDbkM7UUFDRSw2REFBNkQ7SUFDL0QsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O09BK0JHO0lBQ0gsZ0JBQWdCLENBQ2QsUUFBa0IsRUFDbEIsV0FBd0IsRUFDeEIsV0FBbUI7UUFFbkIsb0JBQW9CO1FBQ3BCLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7WUFDMUMsT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbkMsQ0FBQztRQUVELElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUMxQixPQUFPLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNuQyxDQUFDO1FBRUQsTUFBTSxVQUFVLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQztRQUVuQywrQkFBK0I7UUFD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LE1BQU07WUFDaEMsYUFBYSxFQUFFLGdCQUFnQixDQUFDLE1BQU07WUFDdEMsU0FBUyxFQUFFLFVBQVUsR0FBRyxvQkFBb0I7U0FDN0MsQ0FDRixDQUFDO1FBRUYsT0FBTztZQUNMLGFBQWE7WUFDYixnQkFBZ0I7WUFDaEIsYUFBYSxFQUFFLGdCQUFnQixDQUFDLE1BQU0sR0FBRyxDQUFDO1lBQzFDLFVBQVU7WUFDVixRQUFRO1NBQ1QsQ0FBQztJQUNKLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSyxpQkFBaUIsQ0FBQyxVQUFrQjtRQUMxQyxPQUFPO1lBQ0wsYUFBYSxFQUFFLEVBQUU7WUFDakIsZ0JBQWdCLEVBQUUsRUFBRTtZQUNwQixhQUFhLEVBQUUsS0FBSztZQUNwQixVQUFVO1lBQ1YsUUFBUSxFQUFFLEVBQUU7U0FDYixDQUFDO0lBQ0osQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUcmlnZ2VyVmFsaWRhdGlvblNlcnZpY2UgLSBDZW50cmFsaXplZCB0cmlnZ2VyIHZhbGlkYXRpb24gZm9yIGFsbCBlbGVtZW50IHR5cGVzXG4gKlxuICogRWxpbWluYXRlcyBkdXBsaWNhdGUgdHJpZ2dlciB2YWxpZGF0aW9uIGNvZGUgYWNyb3NzIFBlcnNvbmFNYW5hZ2VyLCBTa2lsbE1hbmFnZXIsXG4gKiBNZW1vcnlNYW5hZ2VyLCBUZW1wbGF0ZU1hbmFnZXIsIEFnZW50TWFuYWdlciwgYW5kIEVuc2VtYmxlTWFuYWdlci5cbiAqXG4gKiBLZXkgRmVhdHVyZXM6XG4gKiAtIFVuaWZpZWQgdmFsaWRhdGlvbiBydWxlcyBmb3IgYWxsIGVsZW1lbnQgdHlwZXMgKG1heCAyMCB0cmlnZ2VycywgbWF4IDUwIGNoYXJzKVxuICogLSBDb25zaXN0ZW50IHZhbGlkYXRpb24gYWxnb3JpdGhtIGFjcm9zcyBhbGwgZWxlbWVudCB0eXBlc1xuICogLSBDb21wcmVoZW5zaXZlIGVycm9yIHJlcG9ydGluZyBhbmQgbG9nZ2luZ1xuICogLSBGdWxsIFR5cGVTY3JpcHQgdHlwZSBzYWZldHlcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogaW1wb3J0IHsgdHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlIH0gZnJvbSAnLi9zZXJ2aWNlcy9UcmlnZ2VyVmFsaWRhdGlvblNlcnZpY2UnO1xuICpcbiAqIGNvbnN0IHJlc3VsdCA9IHRyaWdnZXJWYWxpZGF0aW9uU2VydmljZS52YWxpZGF0ZVRyaWdnZXJzKFxuICogICBbJ2NyZWF0ZScsICdidWlsZCcsICdpbnZhbGlkIUAjJ10sXG4gKiAgIEVsZW1lbnRUeXBlLlNLSUxMLFxuICogICAnbXktc2tpbGwnXG4gKiApO1xuICpcbiAqIGNvbnNvbGUubG9nKHJlc3VsdC52YWxpZFRyaWdnZXJzKTsgLy8gWydjcmVhdGUnLCAnYnVpbGQnXVxuICogY29uc29sZS5sb2cocmVzdWx0LnJlamVjdGVkVHJpZ2dlcnMpOyAvLyBbeyBvcmlnaW5hbDogJ2ludmFsaWQhQCMnLCByZWFzb246ICcuLi4nIH1dXG4gKiBgYGBcbiAqL1xuXG5pbXBvcnQgeyBFbGVtZW50VHlwZSB9IGZyb20gJy4uLy4uL3BvcnRmb2xpby90eXBlcy5qcyc7XG5pbXBvcnQgeyBzYW5pdGl6ZUlucHV0IH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvSW5wdXRWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuLyoqXG4gKiBEZXRhaWxzIGFib3V0IGEgcmVqZWN0ZWQgdHJpZ2dlclxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlamVjdGVkVHJpZ2dlciB7XG4gIC8qKiBPcmlnaW5hbCB0cmlnZ2VyIHZhbHVlIGJlZm9yZSBzYW5pdGl6YXRpb24gKi9cbiAgb3JpZ2luYWw6IHN0cmluZztcblxuICAvKiogSHVtYW4tcmVhZGFibGUgcmVhc29uIHdoeSB0aGlzIHRyaWdnZXIgd2FzIHJlamVjdGVkICovXG4gIHJlYXNvbjogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJlc3VsdCBvZiB0cmlnZ2VyIHZhbGlkYXRpb24gb3BlcmF0aW9uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVHJpZ2dlclZhbGlkYXRpb25SZXN1bHQge1xuICAvKiogU3VjY2Vzc2Z1bGx5IHZhbGlkYXRlZCB0cmlnZ2VycyAoc2FuaXRpemVkIGFuZCB2YWxpZGF0ZWQpICovXG4gIHZhbGlkVHJpZ2dlcnM6IHN0cmluZ1tdO1xuXG4gIC8qKiBUcmlnZ2VycyB0aGF0IGZhaWxlZCB2YWxpZGF0aW9uIHdpdGggcmVhc29ucyAqL1xuICByZWplY3RlZFRyaWdnZXJzOiBSZWplY3RlZFRyaWdnZXJbXTtcblxuICAvKiogV2hldGhlciBhbnkgdHJpZ2dlcnMgd2VyZSByZWplY3RlZCBkdXJpbmcgdmFsaWRhdGlvbiAqL1xuICBoYXNSZWplY3Rpb25zOiBib29sZWFuO1xuXG4gIC8qKiBUb3RhbCBjb3VudCBvZiBpbnB1dCB0cmlnZ2VycyAoYmVmb3JlIHZhbGlkYXRpb24pICovXG4gIHRvdGFsSW5wdXQ6IG51bWJlcjtcblxuICAvKiogV2FybmluZ3MgYWJvdXQgdHJ1bmNhdGlvbiwgbGltaXQgZXhjZWVkZWQsIGV0Yy4gKi9cbiAgd2FybmluZ3M6IHN0cmluZ1tdO1xufVxuXG4vKipcbiAqIE1heGltdW0gbnVtYmVyIG9mIHRyaWdnZXJzIGFsbG93ZWQgZm9yIGFsbCBlbGVtZW50IHR5cGVzXG4gKi9cbmNvbnN0IERFRkFVTFRfTUFYX1RSSUdHRVJTID0gMjA7XG5cbi8qKlxuICogTWF4aW11bSBsZW5ndGggb2YgZWFjaCB0cmlnZ2VyIHN0cmluZyBhZnRlciBzYW5pdGl6YXRpb25cbiAqL1xuY29uc3QgREVGQVVMVF9NQVhfVFJJR0dFUl9MRU5HVEggPSA1MDtcblxuLyoqXG4gKiBWYWxpZGF0aW9uIHBhdHRlcm4gZm9yIHRyaWdnZXJzXG4gKlxuICogQWxsb3dlZCBjaGFyYWN0ZXJzOiBhLXosIEEtWiwgMC05LCBoeXBoZW4gKC0pLCB1bmRlcnNjb3JlIChfKSwgYXQgKEApLCBwZXJpb2QgKC4pXG4gKlxuICogVmFsaWQgdHJpZ2dlciBleGFtcGxlczpcbiAqIC0gXCJjb2RlLXJldmlld1wiICAgICAgLSBrZWJhYi1jYXNlIGtleXdvcmRzXG4gKiAtIFwiYnVnX2ZpeFwiICAgICAgICAgIC0gc25ha2VfY2FzZSBrZXl3b3Jkc1xuICogLSBcIkB1c2VybmFtZVwiICAgICAgICAtIHNvY2lhbCBtZWRpYSBtZW50aW9uc1xuICogLSBcInVzZXJAZXhhbXBsZS5jb21cIiAtIGVtYWlsIGFkZHJlc3Nlc1xuICogLSBcImFwaS5kb2NzXCIgICAgICAgICAtIGRvbWFpbi1zdHlsZSBwYXR0ZXJuc1xuICogLSBcInYyLjBcIiAgICAgICAgICAgICAtIHZlcnNpb24gbnVtYmVyc1xuICpcbiAqIEludmFsaWQgdHJpZ2dlciBleGFtcGxlcyAod2lsbCBiZSByZWplY3RlZCk6XG4gKiAtIFwiYmFkIXRyaWdnZXJcIiAgICAgIC0gc2hlbGwgbWV0YWNoYXJhY3RlciAhXG4gKiAtIFwiY21kO2luamVjdGlvblwiICAgIC0gc2hlbGwgbWV0YWNoYXJhY3RlciA7XG4gKiAtIFwiJChldmlsKVwiICAgICAgICAgIC0gY29tbWFuZCBzdWJzdGl0dXRpb25cbiAqIC0gXCJ3aXRoIHNwYWNlc1wiICAgICAgLSBzcGFjZXMgbm90IGFsbG93ZWRcbiAqIC0gXCI8c2NyaXB0PlwiICAgICAgICAgLSBIVE1MIGNoYXJhY3RlcnNcbiAqXG4gKiBTZWN1cml0eSBub3RlOiBAIGFuZCAuIGFyZSBpbnRlbnRpb25hbGx5IGFsbG93ZWQgYXMgdGhleSBhcmUgTk9UIHNoZWxsXG4gKiBtZXRhY2hhcmFjdGVycyBhbmQgZW5hYmxlIGNvbW1vbiB1c2UgY2FzZXMgKG1lbnRpb25zLCBlbWFpbHMsIGRvbWFpbnMpLlxuICovXG5jb25zdCBERUZBVUxUX1ZBTElEQVRJT05fUEFUVEVSTiA9IC9eW2EtekEtWjAtOVxcLV9ALl0rJC87XG5cbi8qKlxuICogU2VydmljZSBmb3IgdmFsaWRhdGluZyBhbmQgcHJvY2Vzc2luZyB0cmlnZ2VyIGFycmF5cyBhY3Jvc3MgYWxsIGVsZW1lbnQgdHlwZXNcbiAqXG4gKiBUaGlzIHNlcnZpY2UgcHJvdmlkZXMgY2VudHJhbGl6ZWQgdHJpZ2dlciB2YWxpZGF0aW9uIHRvIGVsaW1pbmF0ZSBjb2RlIGR1cGxpY2F0aW9uXG4gKiBhY3Jvc3MgZWxlbWVudCBtYW5hZ2Vycy4gQWxsIGVsZW1lbnQgdHlwZXMgdXNlIHRoZSBzYW1lIHZhbGlkYXRpb24gcnVsZXM6XG4gKiAtIE1heGltdW0gMjAgdHJpZ2dlcnNcbiAqIC0gTWF4aW11bSA1MCBjaGFyYWN0ZXJzIHBlciB0cmlnZ2VyXG4gKiAtIE9ubHkgYWxwaGFudW1lcmljLCBoeXBoZW5zLCB1bmRlcnNjb3JlcywgQCwgYW5kIC4gYWxsb3dlZFxuICovXG5leHBvcnQgY2xhc3MgVHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgLy8gTm8gY29uZmlndXJhdGlvbiBuZWVkZWQgLSBhbGwgZWxlbWVudCB0eXBlcyB1c2Ugc2FtZSBydWxlc1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGFuIGFycmF5IG9mIHRyaWdnZXJzIGZvciBhIHNwZWNpZmljIGVsZW1lbnQgdHlwZVxuICAgKlxuICAgKiBWYWxpZGF0aW9uIHByb2Nlc3M6XG4gICAqIDEuIExpbWl0IGlucHV0IGFycmF5IHRvIE1BWF9UUklHR0VSUyAoMjApXG4gICAqIDIuIEZvciBlYWNoIHRyaWdnZXI6XG4gICAqICAgIGEuIENvbnZlcnQgdG8gc3RyaW5nXG4gICAqICAgIGIuIFNhbml0aXplIChyZW1vdmUgZGFuZ2Vyb3VzIGNoYXJhY3RlcnMsIHRyaW0sIGxpbWl0IGxlbmd0aCB0byA1MClcbiAgICogICAgYy4gQ2hlY2sgaWYgZW1wdHkgYWZ0ZXIgc2FuaXRpemF0aW9uXG4gICAqICAgIGQuIFZhbGlkYXRlIGFnYWluc3QgcmVnZXggcGF0dGVybiAoL15bYS16QS1aMC05XFwtX0AuXSskLylcbiAgICogMy4gQ29sbGVjdCB2YWxpZCBhbmQgcmVqZWN0ZWQgdHJpZ2dlcnNcbiAgICogNC4gTG9nIHdhcm5pbmdzIGlmIHRydW5jYXRpb24gb3IgcmVqZWN0aW9ucyBvY2N1cnJlZFxuICAgKlxuICAgKiBAcGFyYW0gdHJpZ2dlcnMgLSBSYXcgdHJpZ2dlciBhcnJheSBmcm9tIHVzZXIgaW5wdXQgb3IgbWV0YWRhdGFcbiAgICogQHBhcmFtIGVsZW1lbnRUeXBlIC0gVHlwZSBvZiBlbGVtZW50IGJlaW5nIHZhbGlkYXRlZCAodXNlZCBmb3IgbG9nZ2luZyBjb250ZXh0KVxuICAgKiBAcGFyYW0gZWxlbWVudE5hbWUgLSBOYW1lIG9mIGVsZW1lbnQgKHVzZWQgZm9yIGxvZ2dpbmcgY29udGV4dClcbiAgICogQHJldHVybnMgVmFsaWRhdGlvbiByZXN1bHQgd2l0aCB2YWxpZC9yZWplY3RlZCB0cmlnZ2VycyBhbmQgd2FybmluZ3NcbiAgICpcbiAgICogQGV4YW1wbGVcbiAgICogYGBgdHlwZXNjcmlwdFxuICAgKiBjb25zdCByZXN1bHQgPSBzZXJ2aWNlLnZhbGlkYXRlVHJpZ2dlcnMoXG4gICAqICAgWydjcmVhdGUnLCAnYnVpbGQnLCAndGVzdCFAIycsICcnXSxcbiAgICogICBFbGVtZW50VHlwZS5TS0lMTCxcbiAgICogICAnY29kZS1nZW5lcmF0b3InXG4gICAqICk7XG4gICAqIC8vIHJlc3VsdC52YWxpZFRyaWdnZXJzOiBbJ2NyZWF0ZScsICdidWlsZCddXG4gICAqIC8vIHJlc3VsdC5yZWplY3RlZFRyaWdnZXJzOiBbXG4gICAqIC8vICAgeyBvcmlnaW5hbDogJ3Rlc3QhQCMnLCByZWFzb246ICcoaW52YWxpZCBmb3JtYXQgLi4uKScgfSxcbiAgICogLy8gICB7IG9yaWdpbmFsOiAnJywgcmVhc29uOiAnKGVtcHR5KScgfVxuICAgKiAvLyBdXG4gICAqIGBgYFxuICAgKi9cbiAgdmFsaWRhdGVUcmlnZ2VycyhcbiAgICB0cmlnZ2Vyczogc3RyaW5nW10sXG4gICAgZWxlbWVudFR5cGU6IEVsZW1lbnRUeXBlLFxuICAgIGVsZW1lbnROYW1lOiBzdHJpbmdcbiAgKTogVHJpZ2dlclZhbGlkYXRpb25SZXN1bHQge1xuICAgIC8vIEhhbmRsZSBlZGdlIGNhc2VzXG4gICAgaWYgKCF0cmlnZ2VycyB8fCAhQXJyYXkuaXNBcnJheSh0cmlnZ2VycykpIHtcbiAgICAgIHJldHVybiB0aGlzLmNyZWF0ZUVtcHR5UmVzdWx0KDApO1xuICAgIH1cblxuICAgIGlmICh0cmlnZ2Vycy5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiB0aGlzLmNyZWF0ZUVtcHR5UmVzdWx0KDApO1xuICAgIH1cblxuICAgIGNvbnN0IHRvdGFsSW5wdXQgPSB0cmlnZ2Vycy5sZW5ndGg7XG5cbiAgICAvLyBJbml0aWFsaXplIHJlc3VsdCBjb250YWluZXJzXG4gICAgY29uc3QgdmFsaWRUcmlnZ2Vyczogc3RyaW5nW10gPSBbXTtcbiAgICBjb25zdCByZWplY3RlZFRyaWdnZXJzOiBSZWplY3RlZFRyaWdnZXJbXSA9IFtdO1xuICAgIGNvbnN0IHdhcm5pbmdzOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAgLy8gTGltaXQgaW5wdXQgdG8gTUFYX1RSSUdHRVJTXG4gICAgY29uc3QgbGltaXRlZFRyaWdnZXJzID0gdHJpZ2dlcnMuc2xpY2UoMCwgREVGQVVMVF9NQVhfVFJJR0dFUlMpO1xuXG4gICAgLy8gV2FybiBpZiB3ZSBoYWQgdG8gdHJ1bmNhdGVcbiAgICBpZiAodG90YWxJbnB1dCA+IERFRkFVTFRfTUFYX1RSSUdHRVJTKSB7XG4gICAgICBjb25zdCB3YXJuaW5nID0gYFRyaWdnZXIgbGltaXQgZXhjZWVkZWQgKCR7dG90YWxJbnB1dH0gPiAke0RFRkFVTFRfTUFYX1RSSUdHRVJTfSksIHRydW5jYXRpbmdgO1xuICAgICAgd2FybmluZ3MucHVzaCh3YXJuaW5nKTtcblxuICAgICAgbG9nZ2VyLndhcm4oXG4gICAgICAgIGAke2VsZW1lbnRUeXBlfSBcIiR7ZWxlbWVudE5hbWV9XCI6ICR7d2FybmluZ31gLFxuICAgICAgICB7XG4gICAgICAgICAgZWxlbWVudFR5cGUsXG4gICAgICAgICAgZWxlbWVudE5hbWUsXG4gICAgICAgICAgcHJvdmlkZWRDb3VudDogdG90YWxJbnB1dCxcbiAgICAgICAgICBsaW1pdDogREVGQVVMVF9NQVhfVFJJR0dFUlMsXG4gICAgICAgICAgdHJ1bmNhdGVkOiB0b3RhbElucHV0IC0gREVGQVVMVF9NQVhfVFJJR0dFUlNcbiAgICAgICAgfVxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBWYWxpZGF0ZSBlYWNoIHRyaWdnZXJcbiAgICBmb3IgKGNvbnN0IHJhd1RyaWdnZXIgb2YgbGltaXRlZFRyaWdnZXJzKSB7XG4gICAgICAvLyBDb252ZXJ0IHRvIHN0cmluZyAoaGFuZGxlIG5vbi1zdHJpbmcgaW5wdXRzKVxuICAgICAgY29uc3QgdHJpZ2dlclN0cmluZyA9IFN0cmluZyhyYXdUcmlnZ2VyKS50cmltKCk7XG5cbiAgICAgIC8vIENoZWNrIGlmIGVtcHR5XG4gICAgICBpZiAoIXRyaWdnZXJTdHJpbmcgfHwgdHJpZ2dlclN0cmluZy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmVqZWN0ZWRUcmlnZ2Vycy5wdXNoKHtcbiAgICAgICAgICBvcmlnaW5hbDogcmF3VHJpZ2dlcixcbiAgICAgICAgICByZWFzb246ICcoZW1wdHkpJ1xuICAgICAgICB9KTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIC8vIFNFQ1VSSVRZOiBWYWxpZGF0ZSBCRUZPUkUgc2FuaXRpemF0aW9uIHRvIHJlamVjdCBpbnZhbGlkIGNoYXJhY3RlcnNcbiAgICAgIC8vIFRoaXMgcHJldmVudHMgJ2JhZCF0cmlnZ2VyJyBmcm9tIGJlY29taW5nICdiYWR0cmlnZ2VyJyBhbmQgcGFzc2luZ1xuICAgICAgaWYgKCFERUZBVUxUX1ZBTElEQVRJT05fUEFUVEVSTi50ZXN0KHRyaWdnZXJTdHJpbmcpKSB7XG4gICAgICAgIHJlamVjdGVkVHJpZ2dlcnMucHVzaCh7XG4gICAgICAgICAgb3JpZ2luYWw6IHJhd1RyaWdnZXIsXG4gICAgICAgICAgcmVhc29uOiAnKGludmFsaWQgZm9ybWF0IC0gYWxsb3dlZDogbGV0dGVycywgbnVtYmVycywgaHlwaGVucywgdW5kZXJzY29yZXMsIEAgYW5kIC4pJ1xuICAgICAgICB9KTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIC8vIE5vdyBzYW5pdGl6ZSB0aGUgYWxyZWFkeS12YWxpZCB0cmlnZ2VyIChmb3IgbGVuZ3RoIGxpbWl0cywgZXRjLilcbiAgICAgIGNvbnN0IHNhbml0aXplZCA9IHNhbml0aXplSW5wdXQodHJpZ2dlclN0cmluZywgREVGQVVMVF9NQVhfVFJJR0dFUl9MRU5HVEgpO1xuXG4gICAgICAvLyBDaGVjayBpZiBlbXB0eSBhZnRlciBzYW5pdGl6YXRpb25cbiAgICAgIGlmICghc2FuaXRpemVkIHx8IHNhbml0aXplZC5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmVqZWN0ZWRUcmlnZ2Vycy5wdXNoKHtcbiAgICAgICAgICBvcmlnaW5hbDogcmF3VHJpZ2dlcixcbiAgICAgICAgICByZWFzb246ICcoZW1wdHkgYWZ0ZXIgc2FuaXRpemF0aW9uKSdcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICAvLyBQYXNzZWQgYWxsIHZhbGlkYXRpb24gLSBhZGQgdG8gdmFsaWQgbGlzdFxuICAgICAgdmFsaWRUcmlnZ2Vycy5wdXNoKHNhbml0aXplZCk7XG4gICAgfVxuXG4gICAgLy8gTG9nIHJlamVjdGlvbnMgaWYgYW55XG4gICAgaWYgKHJlamVjdGVkVHJpZ2dlcnMubGVuZ3RoID4gMCkge1xuICAgICAgbG9nZ2VyLndhcm4oXG4gICAgICAgIGAke2VsZW1lbnRUeXBlfSBcIiR7ZWxlbWVudE5hbWV9XCI6IFJlamVjdGVkICR7cmVqZWN0ZWRUcmlnZ2Vycy5sZW5ndGh9IGludmFsaWQgdHJpZ2dlcihzKWAsXG4gICAgICAgIHtcbiAgICAgICAgICBlbGVtZW50VHlwZSxcbiAgICAgICAgICBlbGVtZW50TmFtZSxcbiAgICAgICAgICByZWplY3RlZFRyaWdnZXJzOiByZWplY3RlZFRyaWdnZXJzLm1hcChyID0+IGBcIiR7ci5vcmlnaW5hbH1cIiAke3IucmVhc29ufWApLFxuICAgICAgICAgIGFjY2VwdGVkQ291bnQ6IHZhbGlkVHJpZ2dlcnMubGVuZ3RoXG4gICAgICAgIH1cbiAgICAgICk7XG4gICAgfVxuXG4gICAgLy8gTG9nIGRlYnVnIGluZm9cbiAgICBsb2dnZXIuZGVidWcoXG4gICAgICBgW1RyaWdnZXJWYWxpZGF0aW9uU2VydmljZV0gVmFsaWRhdGVkIHRyaWdnZXJzIGZvciAke2VsZW1lbnRUeXBlfSBcIiR7ZWxlbWVudE5hbWV9XCJgLFxuICAgICAge1xuICAgICAgICBlbGVtZW50VHlwZSxcbiAgICAgICAgZWxlbWVudE5hbWUsXG4gICAgICAgIHRvdGFsSW5wdXQsXG4gICAgICAgIHZhbGlkQ291bnQ6IHZhbGlkVHJpZ2dlcnMubGVuZ3RoLFxuICAgICAgICByZWplY3RlZENvdW50OiByZWplY3RlZFRyaWdnZXJzLmxlbmd0aCxcbiAgICAgICAgdHJ1bmNhdGVkOiB0b3RhbElucHV0ID4gREVGQVVMVF9NQVhfVFJJR0dFUlNcbiAgICAgIH1cbiAgICApO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIHZhbGlkVHJpZ2dlcnMsXG4gICAgICByZWplY3RlZFRyaWdnZXJzLFxuICAgICAgaGFzUmVqZWN0aW9uczogcmVqZWN0ZWRUcmlnZ2Vycy5sZW5ndGggPiAwLFxuICAgICAgdG90YWxJbnB1dCxcbiAgICAgIHdhcm5pbmdzXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYW4gZW1wdHkgdmFsaWRhdGlvbiByZXN1bHRcbiAgICpcbiAgICogQHBhcmFtIHRvdGFsSW5wdXQgLSBUb3RhbCBjb3VudCBvZiBpbnB1dCB0cmlnZ2Vyc1xuICAgKiBAcmV0dXJucyBFbXB0eSByZXN1bHQgd2l0aCBubyB2YWxpZCBvciByZWplY3RlZCB0cmlnZ2Vyc1xuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgcHJpdmF0ZSBjcmVhdGVFbXB0eVJlc3VsdCh0b3RhbElucHV0OiBudW1iZXIpOiBUcmlnZ2VyVmFsaWRhdGlvblJlc3VsdCB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHZhbGlkVHJpZ2dlcnM6IFtdLFxuICAgICAgcmVqZWN0ZWRUcmlnZ2VyczogW10sXG4gICAgICBoYXNSZWplY3Rpb25zOiBmYWxzZSxcbiAgICAgIHRvdGFsSW5wdXQsXG4gICAgICB3YXJuaW5nczogW11cbiAgICB9O1xuICB9XG59XG4iXX0=