@dollhousemcp/mcp-server 1.5.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +494 -111
- package/data/agents/code-reviewer.md +8 -1
- package/data/agents/research-assistant.md +8 -1
- package/data/agents/task-manager.md +8 -1
- package/data/ensembles/business-advisor.md +8 -1
- package/data/ensembles/creative-studio.md +8 -1
- package/data/ensembles/development-team.md +8 -1
- package/data/ensembles/security-analysis-team.md +8 -1
- package/data/memories/conversation-history.md +8 -1
- package/data/memories/learning-progress.md +8 -1
- package/data/memories/project-context.md +8 -1
- package/data/personas/business-consultant.md +8 -1
- package/data/personas/creative-writer.md +8 -1
- package/data/personas/debug-detective.md +8 -1
- package/data/personas/eli5-explainer.md +8 -1
- package/data/personas/security-analyst.md +8 -1
- package/data/personas/technical-analyst.md +8 -1
- package/data/skills/code-review.md +8 -1
- package/data/skills/creative-writing.md +8 -1
- package/data/skills/data-analysis.md +8 -1
- package/data/skills/penetration-testing.md +8 -1
- package/data/skills/research.md +8 -1
- package/data/skills/threat-modeling.md +8 -1
- package/data/skills/translation.md +8 -1
- package/data/templates/code-documentation.md +8 -1
- package/data/templates/email-professional.md +8 -1
- package/data/templates/meeting-notes.md +8 -1
- package/data/templates/penetration-test-report.md +8 -1
- package/data/templates/project-brief.md +8 -1
- package/data/templates/report-executive.md +8 -1
- package/data/templates/security-vulnerability-report.md +8 -1
- package/data/templates/threat-assessment-report.md +8 -1
- package/dist/auth/GitHubAuthManager.d.ts +6 -1
- package/dist/auth/GitHubAuthManager.d.ts.map +1 -1
- package/dist/auth/GitHubAuthManager.js +45 -18
- package/dist/benchmarks/IndexPerformanceBenchmark.d.ts +98 -0
- package/dist/benchmarks/IndexPerformanceBenchmark.d.ts.map +1 -0
- package/dist/benchmarks/IndexPerformanceBenchmark.js +531 -0
- package/dist/cache/CollectionCache.d.ts.map +1 -1
- package/dist/cache/CollectionCache.js +13 -3
- package/dist/cache/CollectionIndexCache.d.ts +77 -0
- package/dist/cache/CollectionIndexCache.d.ts.map +1 -0
- package/dist/cache/CollectionIndexCache.js +349 -0
- package/dist/cache/LRUCache.d.ts +93 -0
- package/dist/cache/LRUCache.d.ts.map +1 -0
- package/dist/cache/LRUCache.js +299 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +2 -1
- package/dist/collection/CollectionBrowser.d.ts +21 -1
- package/dist/collection/CollectionBrowser.d.ts.map +1 -1
- package/dist/collection/CollectionBrowser.js +130 -10
- package/dist/collection/CollectionIndexManager.d.ts +151 -0
- package/dist/collection/CollectionIndexManager.d.ts.map +1 -0
- package/dist/collection/CollectionIndexManager.js +499 -0
- package/dist/collection/CollectionSearch.d.ts +55 -0
- package/dist/collection/CollectionSearch.d.ts.map +1 -1
- package/dist/collection/CollectionSearch.js +338 -13
- package/dist/collection/CollectionSeeder.d.ts.map +1 -1
- package/dist/collection/CollectionSeeder.js +38 -1
- package/dist/collection/ElementInstaller.d.ts +31 -0
- package/dist/collection/ElementInstaller.d.ts.map +1 -1
- package/dist/collection/ElementInstaller.js +77 -15
- package/dist/collection/PersonaSubmitter.d.ts +1 -1
- package/dist/collection/PersonaSubmitter.d.ts.map +1 -1
- package/dist/collection/PersonaSubmitter.js +2 -2
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.d.ts.map +1 -1
- package/dist/collection/index.js +2 -1
- package/dist/config/ConfigManager.d.ts +78 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +216 -0
- package/dist/config/element-types.d.ts +135 -0
- package/dist/config/element-types.d.ts.map +1 -0
- package/dist/config/element-types.js +108 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/portfolio-constants.d.ts +83 -0
- package/dist/config/portfolio-constants.d.ts.map +1 -0
- package/dist/config/portfolio-constants.js +99 -0
- package/dist/elements/BaseElement.d.ts +14 -2
- package/dist/elements/BaseElement.d.ts.map +1 -1
- package/dist/elements/BaseElement.js +88 -6
- package/dist/elements/agents/Agent.d.ts +10 -1
- package/dist/elements/agents/Agent.d.ts.map +1 -1
- package/dist/elements/agents/Agent.js +66 -19
- package/dist/elements/agents/AgentManager.d.ts +2 -0
- package/dist/elements/agents/AgentManager.d.ts.map +1 -1
- package/dist/elements/agents/AgentManager.js +12 -10
- package/dist/elements/skills/Skill.d.ts +10 -1
- package/dist/elements/skills/Skill.d.ts.map +1 -1
- package/dist/elements/skills/Skill.js +40 -3
- package/dist/elements/skills/SkillManager.d.ts +1 -0
- package/dist/elements/skills/SkillManager.d.ts.map +1 -1
- package/dist/elements/skills/SkillManager.js +10 -4
- package/dist/elements/templates/Template.d.ts +10 -1
- package/dist/elements/templates/Template.d.ts.map +1 -1
- package/dist/elements/templates/Template.js +35 -18
- package/dist/elements/templates/TemplateManager.d.ts +1 -1
- package/dist/elements/templates/TemplateManager.d.ts.map +1 -1
- package/dist/elements/templates/TemplateManager.js +6 -5
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +3 -3
- package/dist/index.barrel.d.ts +1 -2
- package/dist/index.barrel.d.ts.map +1 -1
- package/dist/index.barrel.js +2 -4
- package/dist/index.d.ts +143 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1883 -310
- package/dist/persona/PersonaElement.d.ts +10 -0
- package/dist/persona/PersonaElement.d.ts.map +1 -1
- package/dist/persona/PersonaElement.js +55 -32
- package/dist/persona/PersonaElementManager.d.ts.map +1 -1
- package/dist/persona/PersonaElementManager.js +13 -11
- package/dist/persona/PersonaLoader.d.ts.map +1 -1
- package/dist/persona/PersonaLoader.js +8 -2
- package/dist/persona/export-import/PersonaImporter.d.ts.map +1 -1
- package/dist/persona/export-import/PersonaImporter.js +24 -5
- package/dist/persona/export-import/PersonaSharer.d.ts +21 -0
- package/dist/persona/export-import/PersonaSharer.d.ts.map +1 -1
- package/dist/persona/export-import/PersonaSharer.js +198 -22
- package/dist/portfolio/DefaultElementProvider.d.ts +90 -0
- package/dist/portfolio/DefaultElementProvider.d.ts.map +1 -1
- package/dist/portfolio/DefaultElementProvider.js +499 -7
- package/dist/portfolio/GitHubPortfolioIndexer.d.ts +129 -0
- package/dist/portfolio/GitHubPortfolioIndexer.d.ts.map +1 -0
- package/dist/portfolio/GitHubPortfolioIndexer.js +475 -0
- package/dist/portfolio/MigrationManager.d.ts.map +1 -1
- package/dist/portfolio/MigrationManager.js +136 -3
- package/dist/portfolio/PortfolioIndexManager.d.ts +130 -0
- package/dist/portfolio/PortfolioIndexManager.d.ts.map +1 -0
- package/dist/portfolio/PortfolioIndexManager.js +478 -0
- package/dist/portfolio/PortfolioManager.d.ts +5 -0
- package/dist/portfolio/PortfolioManager.d.ts.map +1 -1
- package/dist/portfolio/PortfolioManager.js +61 -20
- package/dist/portfolio/PortfolioRepoManager.d.ts +75 -0
- package/dist/portfolio/PortfolioRepoManager.d.ts.map +1 -0
- package/dist/portfolio/PortfolioRepoManager.js +337 -0
- package/dist/portfolio/UnifiedIndexManager.d.ts +388 -0
- package/dist/portfolio/UnifiedIndexManager.d.ts.map +1 -0
- package/dist/portfolio/UnifiedIndexManager.js +1434 -0
- package/dist/portfolio/index.d.ts +15 -0
- package/dist/portfolio/index.d.ts.map +1 -0
- package/dist/portfolio/index.js +15 -0
- package/dist/portfolio/types.d.ts +7 -0
- package/dist/portfolio/types.d.ts.map +1 -1
- package/dist/portfolio/types.js +6 -1
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +50 -48
- package/dist/security/audit/SecurityAuditor.d.ts.map +1 -1
- package/dist/security/audit/SecurityAuditor.js +17 -9
- package/dist/security/audit/config/suppressions.d.ts.map +1 -1
- package/dist/security/audit/config/suppressions.js +19 -3
- package/dist/security/contentValidator.d.ts +2 -0
- package/dist/security/contentValidator.d.ts.map +1 -1
- package/dist/security/contentValidator.js +115 -4
- package/dist/security/secureYamlParser.d.ts +1 -0
- package/dist/security/secureYamlParser.d.ts.map +1 -1
- package/dist/security/secureYamlParser.js +29 -7
- package/dist/security/securityMonitor.d.ts +1 -1
- package/dist/security/securityMonitor.d.ts.map +1 -1
- package/dist/security/securityMonitor.js +1 -1
- package/dist/security/tokenManager.d.ts +1 -1
- package/dist/security/tokenManager.d.ts.map +1 -1
- package/dist/security/tokenManager.js +30 -10
- package/dist/server/ServerSetup.d.ts +22 -2
- package/dist/server/ServerSetup.d.ts.map +1 -1
- package/dist/server/ServerSetup.js +77 -12
- package/dist/server/tools/AuthTools.d.ts.map +1 -1
- package/dist/server/tools/AuthTools.js +33 -1
- package/dist/server/tools/BuildInfoTools.d.ts +25 -0
- package/dist/server/tools/BuildInfoTools.d.ts.map +1 -0
- package/dist/server/tools/BuildInfoTools.js +36 -0
- package/dist/server/tools/CollectionTools.d.ts.map +1 -1
- package/dist/server/tools/CollectionTools.js +55 -46
- package/dist/server/tools/ConfigTools.d.ts.map +1 -1
- package/dist/server/tools/ConfigTools.js +29 -1
- package/dist/server/tools/PersonaTools.d.ts +4 -2
- package/dist/server/tools/PersonaTools.d.ts.map +1 -1
- package/dist/server/tools/PersonaTools.js +5 -152
- package/dist/server/tools/PortfolioTools.d.ts +12 -0
- package/dist/server/tools/PortfolioTools.d.ts.map +1 -0
- package/dist/server/tools/PortfolioTools.js +221 -0
- package/dist/server/tools/index.d.ts +3 -1
- package/dist/server/tools/index.d.ts.map +1 -1
- package/dist/server/tools/index.js +4 -2
- package/dist/server/types.d.ts +40 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +1 -1
- package/dist/services/BuildInfoService.d.ts +84 -0
- package/dist/services/BuildInfoService.d.ts.map +1 -0
- package/dist/services/BuildInfoService.js +271 -0
- package/dist/tools/portfolio/PortfolioElementAdapter.d.ts +54 -0
- package/dist/tools/portfolio/PortfolioElementAdapter.d.ts.map +1 -0
- package/dist/tools/portfolio/PortfolioElementAdapter.js +229 -0
- package/dist/tools/portfolio/submitToPortfolioTool.d.ts +164 -0
- package/dist/tools/portfolio/submitToPortfolioTool.d.ts.map +1 -0
- package/dist/tools/portfolio/submitToPortfolioTool.js +1523 -0
- package/dist/tools/portfolio/types.d.ts +41 -0
- package/dist/tools/portfolio/types.d.ts.map +1 -0
- package/dist/tools/portfolio/types.js +15 -0
- package/dist/types/collection.d.ts +51 -0
- package/dist/types/collection.d.ts.map +1 -1
- package/dist/types/collection.js +1 -1
- package/dist/utils/EarlyTerminationSearch.d.ts +41 -0
- package/dist/utils/EarlyTerminationSearch.d.ts.map +1 -0
- package/dist/utils/EarlyTerminationSearch.js +164 -0
- package/dist/utils/ErrorHandler.d.ts +86 -0
- package/dist/utils/ErrorHandler.d.ts.map +1 -0
- package/dist/utils/ErrorHandler.js +201 -0
- package/dist/utils/FileDiscoveryUtil.d.ts +53 -0
- package/dist/utils/FileDiscoveryUtil.d.ts.map +1 -0
- package/dist/utils/FileDiscoveryUtil.js +169 -0
- package/dist/utils/GitHubRateLimiter.d.ts +88 -0
- package/dist/utils/GitHubRateLimiter.d.ts.map +1 -0
- package/dist/utils/GitHubRateLimiter.js +315 -0
- package/dist/utils/PerformanceMonitor.d.ts +134 -0
- package/dist/utils/PerformanceMonitor.d.ts.map +1 -0
- package/dist/utils/PerformanceMonitor.js +347 -0
- package/dist/utils/RateLimiter.d.ts.map +1 -0
- package/dist/utils/RateLimiter.js +172 -0
- package/dist/utils/SecureDownloader.d.ts +241 -0
- package/dist/utils/SecureDownloader.d.ts.map +1 -0
- package/dist/utils/SecureDownloader.js +759 -0
- package/dist/utils/ToolCache.d.ts +82 -0
- package/dist/utils/ToolCache.d.ts.map +1 -0
- package/dist/utils/ToolCache.js +196 -0
- package/dist/utils/errorCodes.d.ts +136 -0
- package/dist/utils/errorCodes.d.ts.map +1 -0
- package/dist/utils/errorCodes.js +87 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/installation.d.ts +1 -1
- package/dist/utils/installation.d.ts.map +1 -1
- package/dist/utils/installation.js +9 -8
- package/dist/utils/searchUtils.d.ts +31 -0
- package/dist/utils/searchUtils.d.ts.map +1 -1
- package/dist/utils/searchUtils.js +62 -1
- package/package.json +17 -7
- package/dist/config/updateConfig.d.ts +0 -84
- package/dist/config/updateConfig.d.ts.map +0 -1
- package/dist/config/updateConfig.js +0 -148
- package/dist/server/tools/UpdateTools.d.ts +0 -10
- package/dist/server/tools/UpdateTools.d.ts.map +0 -1
- package/dist/server/tools/UpdateTools.js +0 -85
- package/dist/update/BackupManager.d.ts +0 -63
- package/dist/update/BackupManager.d.ts.map +0 -1
- package/dist/update/BackupManager.js +0 -370
- package/dist/update/DependencyChecker.d.ts +0 -41
- package/dist/update/DependencyChecker.d.ts.map +0 -1
- package/dist/update/DependencyChecker.js +0 -132
- package/dist/update/RateLimiter.d.ts.map +0 -1
- package/dist/update/RateLimiter.js +0 -172
- package/dist/update/SignatureVerifier.d.ts +0 -71
- package/dist/update/SignatureVerifier.d.ts.map +0 -1
- package/dist/update/SignatureVerifier.js +0 -214
- package/dist/update/UpdateChecker.d.ts +0 -132
- package/dist/update/UpdateChecker.d.ts.map +0 -1
- package/dist/update/UpdateChecker.js +0 -506
- package/dist/update/UpdateManager.d.ts +0 -60
- package/dist/update/UpdateManager.d.ts.map +0 -1
- package/dist/update/UpdateManager.js +0 -730
- package/dist/update/VersionManager.d.ts +0 -31
- package/dist/update/VersionManager.d.ts.map +0 -1
- package/dist/update/VersionManager.js +0 -181
- package/dist/update/index.d.ts +0 -9
- package/dist/update/index.d.ts.map +0 -1
- package/dist/update/index.js +0 -9
- /package/dist/{update → utils}/RateLimiter.d.ts +0 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecureDownloader - Reusable utility for safe content downloads
|
|
3
|
+
*
|
|
4
|
+
* Implements the validate-before-write pattern with comprehensive security features:
|
|
5
|
+
* - Content validation hooks (customizable validators)
|
|
6
|
+
* - Atomic file operations with temp files
|
|
7
|
+
* - Guaranteed cleanup on failure
|
|
8
|
+
* - Memory-efficient streaming for large files
|
|
9
|
+
* - Size limits to prevent DoS attacks
|
|
10
|
+
* - Path validation to prevent traversal
|
|
11
|
+
* - Timeout handling for network operations
|
|
12
|
+
* - Content type validation
|
|
13
|
+
*
|
|
14
|
+
* Usage Examples:
|
|
15
|
+
*
|
|
16
|
+
* // Basic download with validation
|
|
17
|
+
* const downloader = new SecureDownloader();
|
|
18
|
+
* await downloader.downloadToFile(
|
|
19
|
+
* 'https://example.com/file.md',
|
|
20
|
+
* './downloads/file.md',
|
|
21
|
+
* {
|
|
22
|
+
* validator: async (content) => ({
|
|
23
|
+
* isValid: !content.includes('malicious'),
|
|
24
|
+
* errorMessage: content.includes('malicious') ? 'Malicious content detected' : undefined
|
|
25
|
+
* }),
|
|
26
|
+
* maxSize: 1024 * 1024, // 1MB limit
|
|
27
|
+
* timeout: 30000 // 30 second timeout
|
|
28
|
+
* }
|
|
29
|
+
* );
|
|
30
|
+
*
|
|
31
|
+
* // Download to memory with validation
|
|
32
|
+
* const content = await downloader.downloadToMemory(
|
|
33
|
+
* 'https://example.com/data.json',
|
|
34
|
+
* {
|
|
35
|
+
* validator: async (content) => {
|
|
36
|
+
* try {
|
|
37
|
+
* JSON.parse(content);
|
|
38
|
+
* return { isValid: true };
|
|
39
|
+
* } catch {
|
|
40
|
+
* return { isValid: false, errorMessage: 'Invalid JSON format' };
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* );
|
|
45
|
+
*
|
|
46
|
+
* // Streaming download for large files
|
|
47
|
+
* await downloader.downloadStream(
|
|
48
|
+
* 'https://example.com/large-file.zip',
|
|
49
|
+
* './downloads/large-file.zip',
|
|
50
|
+
* {
|
|
51
|
+
* streamValidator: (chunk) => !chunk.includes(Buffer.from('VIRUS')),
|
|
52
|
+
* maxSize: 100 * 1024 * 1024, // 100MB limit
|
|
53
|
+
* timeout: 300000 // 5 minute timeout
|
|
54
|
+
* }
|
|
55
|
+
* );
|
|
56
|
+
*/
|
|
57
|
+
import * as fs from 'fs/promises';
|
|
58
|
+
import * as path from 'path';
|
|
59
|
+
import { randomBytes, createHash } from 'crypto';
|
|
60
|
+
import { Readable } from 'stream';
|
|
61
|
+
import { pipeline } from 'stream/promises';
|
|
62
|
+
import { createWriteStream } from 'fs';
|
|
63
|
+
import { SecurityError } from '../errors/SecurityError.js';
|
|
64
|
+
import { SECURITY_LIMITS } from '../security/constants.js';
|
|
65
|
+
import { ContentValidator as SecurityContentValidator } from '../security/contentValidator.js';
|
|
66
|
+
import { PathValidator } from '../security/pathValidator.js';
|
|
67
|
+
import { FileLockManager } from '../security/fileLockManager.js';
|
|
68
|
+
import { SecurityMonitor } from '../security/securityMonitor.js';
|
|
69
|
+
import { UnicodeValidator } from '../security/validators/unicodeValidator.js';
|
|
70
|
+
import { RateLimiter } from './RateLimiter.js';
|
|
71
|
+
import { logger } from './logger.js';
|
|
72
|
+
/**
|
|
73
|
+
* Custom error types for different failure scenarios
|
|
74
|
+
*/
|
|
75
|
+
export class DownloadError extends Error {
|
|
76
|
+
code;
|
|
77
|
+
originalError;
|
|
78
|
+
constructor(message, code, originalError) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.code = code;
|
|
81
|
+
this.originalError = originalError;
|
|
82
|
+
this.name = 'DownloadError';
|
|
83
|
+
}
|
|
84
|
+
static networkError(message, originalError) {
|
|
85
|
+
return new DownloadError(message, 'NETWORK_ERROR', originalError);
|
|
86
|
+
}
|
|
87
|
+
static validationError(message) {
|
|
88
|
+
return new DownloadError(message, 'VALIDATION_ERROR');
|
|
89
|
+
}
|
|
90
|
+
static securityError(message) {
|
|
91
|
+
return new DownloadError(message, 'SECURITY_ERROR');
|
|
92
|
+
}
|
|
93
|
+
static timeoutError(message) {
|
|
94
|
+
return new DownloadError(message, 'TIMEOUT_ERROR');
|
|
95
|
+
}
|
|
96
|
+
static filesystemError(message, originalError) {
|
|
97
|
+
return new DownloadError(message, 'FILESYSTEM_ERROR', originalError);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* SecureDownloader - Implements validate-before-write pattern for safe downloads
|
|
102
|
+
*
|
|
103
|
+
* Key Security Features:
|
|
104
|
+
* 1. VALIDATE-BEFORE-WRITE: All content validation occurs before any disk operations
|
|
105
|
+
* 2. ATOMIC OPERATIONS: Uses temporary files with atomic rename to prevent corruption
|
|
106
|
+
* 3. GUARANTEED CLEANUP: Automatic cleanup of temporary files on any failure
|
|
107
|
+
* 4. SIZE LIMITS: Prevents DoS attacks through large file downloads
|
|
108
|
+
* 5. PATH VALIDATION: Prevents directory traversal attacks
|
|
109
|
+
* 6. TIMEOUT PROTECTION: Prevents hanging network operations
|
|
110
|
+
* 7. CONTENT VALIDATION: Extensible validation system for different content types
|
|
111
|
+
*/
|
|
112
|
+
export class SecureDownloader {
|
|
113
|
+
defaultTimeout;
|
|
114
|
+
defaultMaxSize;
|
|
115
|
+
tempDir;
|
|
116
|
+
globalRateLimiter;
|
|
117
|
+
urlRateLimiters;
|
|
118
|
+
constructor(options) {
|
|
119
|
+
this.defaultTimeout = options?.defaultTimeout || 30000; // 30 seconds
|
|
120
|
+
this.defaultMaxSize = options?.defaultMaxSize || SECURITY_LIMITS.MAX_FILE_SIZE;
|
|
121
|
+
this.tempDir = options?.tempDir || '.tmp';
|
|
122
|
+
// Initialize rate limiters
|
|
123
|
+
const rateLimitConfig = options?.rateLimitOptions || {};
|
|
124
|
+
this.globalRateLimiter = new RateLimiter({
|
|
125
|
+
maxRequests: rateLimitConfig.maxGlobalRequests || 100, // 100 downloads per hour globally
|
|
126
|
+
windowMs: rateLimitConfig.windowMs || 60 * 60 * 1000, // 1 hour
|
|
127
|
+
minDelayMs: 1000 // Minimum 1 second between requests
|
|
128
|
+
});
|
|
129
|
+
this.urlRateLimiters = new Map();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Download content to a file with validation
|
|
133
|
+
*
|
|
134
|
+
* SECURITY: Implements validate-before-write pattern:
|
|
135
|
+
* 1. Download content to memory
|
|
136
|
+
* 2. Validate all content
|
|
137
|
+
* 3. Only then write to disk atomically
|
|
138
|
+
*
|
|
139
|
+
* @param url - URL to download from
|
|
140
|
+
* @param destinationPath - Local file path to save to
|
|
141
|
+
* @param options - Download and validation options
|
|
142
|
+
*/
|
|
143
|
+
async downloadToFile(url, destinationPath, options = {}) {
|
|
144
|
+
const startTime = Date.now();
|
|
145
|
+
logger.debug(`Starting secure download from ${url} to ${destinationPath}`);
|
|
146
|
+
try {
|
|
147
|
+
// SECURITY: Validate URL and destination path first
|
|
148
|
+
this.validateUrl(url);
|
|
149
|
+
const validatedPath = await this.validateDestinationPath(destinationPath);
|
|
150
|
+
// SECURITY: Check if file already exists (prevent accidental overwrites)
|
|
151
|
+
try {
|
|
152
|
+
await fs.access(validatedPath);
|
|
153
|
+
throw DownloadError.filesystemError(`File already exists: ${destinationPath}`);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error.code !== 'ENOENT') {
|
|
157
|
+
throw error; // Re-throw if it's not a "file not found" error
|
|
158
|
+
}
|
|
159
|
+
// File doesn't exist, proceed with download
|
|
160
|
+
}
|
|
161
|
+
// STEP 1: Check rate limits before download
|
|
162
|
+
await this.checkRateLimit(url);
|
|
163
|
+
// STEP 2: Download content to memory (no disk operations yet)
|
|
164
|
+
const content = await this.downloadToMemory(url, options);
|
|
165
|
+
// STEP 3: Validate checksum if provided
|
|
166
|
+
if (options.expectedChecksum) {
|
|
167
|
+
await this.validateChecksum(content, options.expectedChecksum);
|
|
168
|
+
}
|
|
169
|
+
// STEP 4: All validation is complete, now write atomically
|
|
170
|
+
const useAtomic = options.atomic !== false; // Default to true
|
|
171
|
+
if (useAtomic) {
|
|
172
|
+
await this.atomicWriteFile(validatedPath, content);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
await this.directWriteFile(validatedPath, content);
|
|
176
|
+
}
|
|
177
|
+
const duration = Date.now() - startTime;
|
|
178
|
+
logger.info(`Secure download completed: ${destinationPath} (${content.length} bytes, ${duration}ms)`);
|
|
179
|
+
// Log successful download for security monitoring
|
|
180
|
+
SecurityMonitor.logSecurityEvent({
|
|
181
|
+
type: 'FILE_COPIED',
|
|
182
|
+
severity: 'LOW',
|
|
183
|
+
source: 'secure_downloader',
|
|
184
|
+
details: `Downloaded ${content.length} bytes from ${url} to ${destinationPath}`,
|
|
185
|
+
metadata: {
|
|
186
|
+
url,
|
|
187
|
+
destinationPath,
|
|
188
|
+
contentLength: content.length,
|
|
189
|
+
duration
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
const duration = Date.now() - startTime;
|
|
195
|
+
logger.error(`Secure download failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
196
|
+
// Log failed download for security monitoring
|
|
197
|
+
SecurityMonitor.logSecurityEvent({
|
|
198
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
199
|
+
severity: 'MEDIUM',
|
|
200
|
+
source: 'secure_downloader',
|
|
201
|
+
details: `Download failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
202
|
+
metadata: {
|
|
203
|
+
url,
|
|
204
|
+
destinationPath,
|
|
205
|
+
duration,
|
|
206
|
+
errorType: error instanceof DownloadError ? error.code : 'UNKNOWN'
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Download content to memory with validation
|
|
214
|
+
*
|
|
215
|
+
* @param url - URL to download from
|
|
216
|
+
* @param options - Download and validation options
|
|
217
|
+
* @returns Validated content as string
|
|
218
|
+
*/
|
|
219
|
+
async downloadToMemory(url, options = {}) {
|
|
220
|
+
const timeout = options.timeout || this.defaultTimeout;
|
|
221
|
+
const maxSize = options.maxSize || this.defaultMaxSize;
|
|
222
|
+
logger.debug(`Downloading content from ${url} (max: ${maxSize} bytes, timeout: ${timeout}ms)`);
|
|
223
|
+
try {
|
|
224
|
+
// SECURITY: Validate URL format
|
|
225
|
+
this.validateUrl(url);
|
|
226
|
+
// STEP 1: Check rate limits before download
|
|
227
|
+
await this.checkRateLimit(url);
|
|
228
|
+
// STEP 2: Fetch content with size and timeout protection
|
|
229
|
+
const content = await this.fetchWithLimits(url, maxSize, timeout, options.headers);
|
|
230
|
+
// STEP 3: Validate content type if specified
|
|
231
|
+
if (options.expectedContentType) {
|
|
232
|
+
await this.validateContentType(content, options.expectedContentType);
|
|
233
|
+
}
|
|
234
|
+
// STEP 4: Validate checksum if provided
|
|
235
|
+
if (options.expectedChecksum) {
|
|
236
|
+
await this.validateChecksum(content, options.expectedChecksum);
|
|
237
|
+
}
|
|
238
|
+
// STEP 5: Run built-in security validation
|
|
239
|
+
const securityResult = SecurityContentValidator.validateAndSanitize(content);
|
|
240
|
+
if (!securityResult.isValid && securityResult.severity === 'critical') {
|
|
241
|
+
throw DownloadError.securityError(`Critical security threat detected: ${securityResult.detectedPatterns?.join(', ')}`);
|
|
242
|
+
}
|
|
243
|
+
// STEP 6: Run custom validator if provided
|
|
244
|
+
if (options.validator) {
|
|
245
|
+
logger.debug('Running custom content validation');
|
|
246
|
+
const validationResult = await options.validator(content);
|
|
247
|
+
if (!validationResult.isValid) {
|
|
248
|
+
throw DownloadError.validationError(validationResult.errorMessage || 'Content validation failed');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
logger.debug(`Content validation passed (${content.length} bytes)`);
|
|
252
|
+
return securityResult.sanitizedContent || content;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (error instanceof DownloadError) {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
throw DownloadError.networkError(`Failed to download content from ${url}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Download large files using streaming with chunk-level validation
|
|
263
|
+
*
|
|
264
|
+
* @param url - URL to download from
|
|
265
|
+
* @param destinationPath - Local file path to save to
|
|
266
|
+
* @param options - Streaming download options
|
|
267
|
+
*/
|
|
268
|
+
async downloadStream(url, destinationPath, options = {}) {
|
|
269
|
+
const startTime = Date.now();
|
|
270
|
+
const maxSize = options.maxSize || this.defaultMaxSize;
|
|
271
|
+
const timeout = options.timeout || this.defaultTimeout;
|
|
272
|
+
logger.debug(`Starting streaming download from ${url} to ${destinationPath}`);
|
|
273
|
+
try {
|
|
274
|
+
// SECURITY: Check rate limits before download
|
|
275
|
+
await this.checkRateLimit(url);
|
|
276
|
+
// SECURITY: Validate URL and destination path
|
|
277
|
+
this.validateUrl(url);
|
|
278
|
+
const validatedPath = await this.validateDestinationPath(destinationPath);
|
|
279
|
+
// Generate temporary file path for atomic operation
|
|
280
|
+
const tempPath = await this.getTempFilePath(validatedPath);
|
|
281
|
+
let downloadedSize = 0;
|
|
282
|
+
let timeoutHandle;
|
|
283
|
+
// Create abort controller for timeout handling
|
|
284
|
+
const abortController = new AbortController();
|
|
285
|
+
timeoutHandle = setTimeout(() => {
|
|
286
|
+
abortController.abort();
|
|
287
|
+
}, timeout);
|
|
288
|
+
try {
|
|
289
|
+
// SECURITY: Fetch with abort signal for timeout
|
|
290
|
+
const response = await fetch(url, {
|
|
291
|
+
signal: abortController.signal,
|
|
292
|
+
headers: options.headers
|
|
293
|
+
});
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
296
|
+
}
|
|
297
|
+
if (!response.body) {
|
|
298
|
+
throw new Error('Response body is null');
|
|
299
|
+
}
|
|
300
|
+
// Ensure temp directory exists
|
|
301
|
+
await fs.mkdir(path.dirname(tempPath), { recursive: true });
|
|
302
|
+
// Create write stream to temporary file
|
|
303
|
+
const writeStream = createWriteStream(tempPath);
|
|
304
|
+
// Create a transform stream for validation and size checking
|
|
305
|
+
const validationStream = new Readable({
|
|
306
|
+
async read() {
|
|
307
|
+
// This stream will be fed by the pipeline
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
// Set up chunk validation and size checking
|
|
311
|
+
const reader = response.body.getReader();
|
|
312
|
+
const pump = async () => {
|
|
313
|
+
try {
|
|
314
|
+
while (true) {
|
|
315
|
+
const { done, value } = await reader.read();
|
|
316
|
+
if (done)
|
|
317
|
+
break;
|
|
318
|
+
// SECURITY: Check size limit
|
|
319
|
+
downloadedSize += value.length;
|
|
320
|
+
if (downloadedSize > maxSize) {
|
|
321
|
+
throw DownloadError.securityError(`File size exceeds limit: ${downloadedSize} > ${maxSize} bytes`);
|
|
322
|
+
}
|
|
323
|
+
// SECURITY: Run chunk validator if provided
|
|
324
|
+
if (options.streamValidator && !options.streamValidator(value)) {
|
|
325
|
+
throw DownloadError.validationError('Chunk validation failed');
|
|
326
|
+
}
|
|
327
|
+
validationStream.push(value);
|
|
328
|
+
}
|
|
329
|
+
validationStream.push(null); // End stream
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
validationStream.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
// Start the pump and pipeline concurrently
|
|
336
|
+
const [pumpResult] = await Promise.all([
|
|
337
|
+
pump(),
|
|
338
|
+
pipeline(validationStream, writeStream)
|
|
339
|
+
]);
|
|
340
|
+
// Clear timeout
|
|
341
|
+
if (timeoutHandle) {
|
|
342
|
+
clearTimeout(timeoutHandle);
|
|
343
|
+
timeoutHandle = undefined;
|
|
344
|
+
}
|
|
345
|
+
// SECURITY: Atomic rename to final destination
|
|
346
|
+
await fs.rename(tempPath, validatedPath);
|
|
347
|
+
const duration = Date.now() - startTime;
|
|
348
|
+
logger.info(`Streaming download completed: ${destinationPath} (${downloadedSize} bytes, ${duration}ms)`);
|
|
349
|
+
// Log successful streaming download
|
|
350
|
+
SecurityMonitor.logSecurityEvent({
|
|
351
|
+
type: 'FILE_COPIED',
|
|
352
|
+
severity: 'LOW',
|
|
353
|
+
source: 'secure_downloader',
|
|
354
|
+
details: `Streamed ${downloadedSize} bytes from ${url} to ${destinationPath}`,
|
|
355
|
+
metadata: {
|
|
356
|
+
url,
|
|
357
|
+
destinationPath,
|
|
358
|
+
contentLength: downloadedSize,
|
|
359
|
+
duration
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
// SECURITY: Guaranteed cleanup of temporary file
|
|
365
|
+
try {
|
|
366
|
+
await fs.unlink(tempPath);
|
|
367
|
+
logger.debug(`Cleaned up temp file: ${tempPath}`);
|
|
368
|
+
}
|
|
369
|
+
catch (cleanupError) {
|
|
370
|
+
logger.warn(`Failed to clean up temp file ${tempPath}: ${cleanupError}`);
|
|
371
|
+
}
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
if (timeoutHandle) {
|
|
376
|
+
clearTimeout(timeoutHandle);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
const duration = Date.now() - startTime;
|
|
382
|
+
logger.error(`Streaming download failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
383
|
+
// Log failed streaming download
|
|
384
|
+
SecurityMonitor.logSecurityEvent({
|
|
385
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
386
|
+
severity: 'MEDIUM',
|
|
387
|
+
source: 'secure_downloader',
|
|
388
|
+
details: `Streaming download failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
389
|
+
metadata: {
|
|
390
|
+
url,
|
|
391
|
+
destinationPath,
|
|
392
|
+
duration,
|
|
393
|
+
errorType: error instanceof DownloadError ? error.code : 'UNKNOWN'
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
397
|
+
throw DownloadError.timeoutError(`Download timed out after ${timeout}ms`);
|
|
398
|
+
}
|
|
399
|
+
if (error instanceof DownloadError) {
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
throw DownloadError.networkError(`Streaming download failed: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Validate URL format and security with Unicode normalization
|
|
407
|
+
*/
|
|
408
|
+
validateUrl(url) {
|
|
409
|
+
if (!url || typeof url !== 'string') {
|
|
410
|
+
throw DownloadError.validationError('URL must be a non-empty string');
|
|
411
|
+
}
|
|
412
|
+
// SECURITY FIX: DMCP-SEC-004 - Unicode normalization on user input
|
|
413
|
+
const unicodeValidation = UnicodeValidator.normalize(url);
|
|
414
|
+
const normalizedUrl = unicodeValidation.normalizedContent;
|
|
415
|
+
if (!unicodeValidation.isValid) {
|
|
416
|
+
SecurityMonitor.logSecurityEvent({
|
|
417
|
+
type: 'UNICODE_VALIDATION_ERROR',
|
|
418
|
+
severity: 'MEDIUM',
|
|
419
|
+
source: 'secure_downloader',
|
|
420
|
+
details: `URL contains suspicious Unicode patterns: ${unicodeValidation.detectedIssues?.join(', ')}`,
|
|
421
|
+
metadata: { originalUrl: url, normalizedUrl }
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
// Use normalized URL for further validation
|
|
425
|
+
url = normalizedUrl;
|
|
426
|
+
let parsedUrl;
|
|
427
|
+
try {
|
|
428
|
+
parsedUrl = new URL(url);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
throw DownloadError.validationError(`Invalid URL format: ${url}`);
|
|
432
|
+
}
|
|
433
|
+
// SECURITY: Only allow HTTPS and HTTP protocols
|
|
434
|
+
if (!['https:', 'http:'].includes(parsedUrl.protocol)) {
|
|
435
|
+
throw DownloadError.securityError(`Unsupported protocol: ${parsedUrl.protocol}. Only HTTP/HTTPS allowed.`);
|
|
436
|
+
}
|
|
437
|
+
// SECURITY: Prevent requests to localhost/private networks
|
|
438
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
439
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
|
|
440
|
+
throw DownloadError.securityError('Downloads from localhost are not allowed');
|
|
441
|
+
}
|
|
442
|
+
// SECURITY: Check for private IP ranges (basic protection)
|
|
443
|
+
if (hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.')) {
|
|
444
|
+
throw DownloadError.securityError('Downloads from private IP ranges are not allowed');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Validate destination path for security
|
|
449
|
+
*/
|
|
450
|
+
async validateDestinationPath(filePath) {
|
|
451
|
+
try {
|
|
452
|
+
// Use existing PathValidator for comprehensive path validation
|
|
453
|
+
return await PathValidator.validatePersonaPath(filePath);
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
throw DownloadError.securityError(`Invalid destination path: ${error instanceof Error ? error.message : String(error)}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Fetch content with size and timeout limits
|
|
461
|
+
*/
|
|
462
|
+
async fetchWithLimits(url, maxSize, timeout, headers) {
|
|
463
|
+
const abortController = new AbortController();
|
|
464
|
+
const timeoutHandle = setTimeout(() => abortController.abort(), timeout);
|
|
465
|
+
try {
|
|
466
|
+
const response = await fetch(url, {
|
|
467
|
+
signal: abortController.signal,
|
|
468
|
+
headers: {
|
|
469
|
+
'User-Agent': 'DollhouseMCP-SecureDownloader/1.0',
|
|
470
|
+
...headers
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
if (!response.ok) {
|
|
474
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
475
|
+
}
|
|
476
|
+
// SECURITY: Check Content-Length header if available
|
|
477
|
+
const contentLength = response.headers.get('content-length');
|
|
478
|
+
if (contentLength && parseInt(contentLength, 10) > maxSize) {
|
|
479
|
+
throw DownloadError.securityError(`Content size ${contentLength} exceeds limit of ${maxSize} bytes`);
|
|
480
|
+
}
|
|
481
|
+
// Read content with size checking
|
|
482
|
+
const chunks = [];
|
|
483
|
+
let totalSize = 0;
|
|
484
|
+
if (!response.body) {
|
|
485
|
+
throw new Error('Response body is null');
|
|
486
|
+
}
|
|
487
|
+
const reader = response.body.getReader();
|
|
488
|
+
try {
|
|
489
|
+
while (true) {
|
|
490
|
+
const { done, value } = await reader.read();
|
|
491
|
+
if (done)
|
|
492
|
+
break;
|
|
493
|
+
totalSize += value.length;
|
|
494
|
+
if (totalSize > maxSize) {
|
|
495
|
+
throw DownloadError.securityError(`Content size ${totalSize} exceeds limit of ${maxSize} bytes`);
|
|
496
|
+
}
|
|
497
|
+
chunks.push(value);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
finally {
|
|
501
|
+
reader.releaseLock();
|
|
502
|
+
}
|
|
503
|
+
// Combine chunks and decode
|
|
504
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
505
|
+
const combined = new Uint8Array(totalLength);
|
|
506
|
+
let offset = 0;
|
|
507
|
+
for (const chunk of chunks) {
|
|
508
|
+
combined.set(chunk, offset);
|
|
509
|
+
offset += chunk.length;
|
|
510
|
+
}
|
|
511
|
+
return new TextDecoder('utf-8').decode(combined);
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
515
|
+
throw DownloadError.timeoutError(`Request timed out after ${timeout}ms`);
|
|
516
|
+
}
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
finally {
|
|
520
|
+
clearTimeout(timeoutHandle);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Validate content type if specified
|
|
525
|
+
*/
|
|
526
|
+
async validateContentType(content, expectedType) {
|
|
527
|
+
// Basic content type validation based on content analysis
|
|
528
|
+
switch (expectedType.toLowerCase()) {
|
|
529
|
+
case 'json':
|
|
530
|
+
try {
|
|
531
|
+
JSON.parse(content);
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
throw DownloadError.validationError('Content is not valid JSON');
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
case 'yaml':
|
|
538
|
+
case 'yml':
|
|
539
|
+
// Use existing YAML validation
|
|
540
|
+
if (!SecurityContentValidator.validateYamlContent(content)) {
|
|
541
|
+
throw DownloadError.validationError('Content is not valid YAML');
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
544
|
+
case 'markdown':
|
|
545
|
+
case 'md':
|
|
546
|
+
// Basic markdown validation (check for frontmatter format)
|
|
547
|
+
if (content.startsWith('---')) {
|
|
548
|
+
const frontmatterEnd = content.indexOf('\n---\n', 3);
|
|
549
|
+
if (frontmatterEnd === -1) {
|
|
550
|
+
throw DownloadError.validationError('Invalid markdown frontmatter format');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
default:
|
|
555
|
+
logger.debug(`No specific validation for content type: ${expectedType}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Atomic file write using FileLockManager
|
|
560
|
+
*/
|
|
561
|
+
async atomicWriteFile(filePath, content) {
|
|
562
|
+
const resource = `download:${filePath}`;
|
|
563
|
+
await FileLockManager.withLock(resource, async () => {
|
|
564
|
+
// Ensure directory exists
|
|
565
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
566
|
+
// Use FileLockManager's atomic write
|
|
567
|
+
await FileLockManager.atomicWriteFile(filePath, content, { encoding: 'utf-8' });
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Direct file write (non-atomic, for when atomic is disabled)
|
|
572
|
+
*/
|
|
573
|
+
async directWriteFile(filePath, content) {
|
|
574
|
+
// Ensure directory exists
|
|
575
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
576
|
+
// Direct write
|
|
577
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Generate temporary file path for atomic operations
|
|
581
|
+
*/
|
|
582
|
+
async getTempFilePath(originalPath) {
|
|
583
|
+
const dir = path.dirname(originalPath);
|
|
584
|
+
const basename = path.basename(originalPath);
|
|
585
|
+
const random = randomBytes(8).toString('hex');
|
|
586
|
+
const tempDir = path.join(dir, this.tempDir);
|
|
587
|
+
// Ensure temp directory exists
|
|
588
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
589
|
+
return path.join(tempDir, `${basename}.${random}.tmp`);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Check rate limits for downloads
|
|
593
|
+
*/
|
|
594
|
+
async checkRateLimit(url) {
|
|
595
|
+
// Check global rate limit
|
|
596
|
+
const globalStatus = this.globalRateLimiter.checkLimit();
|
|
597
|
+
if (!globalStatus.allowed) {
|
|
598
|
+
SecurityMonitor.logSecurityEvent({
|
|
599
|
+
type: 'RATE_LIMIT_EXCEEDED',
|
|
600
|
+
severity: 'MEDIUM',
|
|
601
|
+
source: 'secure_downloader',
|
|
602
|
+
details: `Global download rate limit exceeded. Retry after ${globalStatus.retryAfterMs}ms`,
|
|
603
|
+
metadata: { url, retryAfterMs: globalStatus.retryAfterMs }
|
|
604
|
+
});
|
|
605
|
+
throw DownloadError.securityError(`Download rate limit exceeded. Please retry after ${Math.ceil(globalStatus.retryAfterMs / 1000)} seconds`);
|
|
606
|
+
}
|
|
607
|
+
// Check per-URL rate limit
|
|
608
|
+
const parsedUrl = new URL(url);
|
|
609
|
+
const urlKey = `${parsedUrl.hostname}:${parsedUrl.port || (parsedUrl.protocol === 'https:' ? '443' : '80')}`;
|
|
610
|
+
if (!this.urlRateLimiters.has(urlKey)) {
|
|
611
|
+
this.urlRateLimiters.set(urlKey, new RateLimiter({
|
|
612
|
+
maxRequests: 10, // 10 requests per hour per URL
|
|
613
|
+
windowMs: 60 * 60 * 1000,
|
|
614
|
+
minDelayMs: 5000 // 5 second minimum delay between requests to same URL
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
const urlLimiter = this.urlRateLimiters.get(urlKey);
|
|
618
|
+
const urlStatus = urlLimiter.checkLimit();
|
|
619
|
+
if (!urlStatus.allowed) {
|
|
620
|
+
SecurityMonitor.logSecurityEvent({
|
|
621
|
+
type: 'RATE_LIMIT_EXCEEDED',
|
|
622
|
+
severity: 'MEDIUM',
|
|
623
|
+
source: 'secure_downloader',
|
|
624
|
+
details: `Per-URL download rate limit exceeded for ${urlKey}. Retry after ${urlStatus.retryAfterMs}ms`,
|
|
625
|
+
metadata: { url, urlKey, retryAfterMs: urlStatus.retryAfterMs }
|
|
626
|
+
});
|
|
627
|
+
throw DownloadError.securityError(`Too many requests to ${urlKey}. Please retry after ${Math.ceil(urlStatus.retryAfterMs / 1000)} seconds`);
|
|
628
|
+
}
|
|
629
|
+
// Consume rate limit tokens
|
|
630
|
+
this.globalRateLimiter.consumeToken();
|
|
631
|
+
urlLimiter.consumeToken();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Validate content checksum for integrity verification
|
|
635
|
+
*/
|
|
636
|
+
async validateChecksum(content, expectedChecksum) {
|
|
637
|
+
const normalizedExpected = expectedChecksum.toLowerCase().trim();
|
|
638
|
+
// Validate checksum format (SHA-256 should be 64 hex characters)
|
|
639
|
+
if (!/^[a-f0-9]{64}$/.test(normalizedExpected)) {
|
|
640
|
+
throw DownloadError.validationError('Invalid checksum format. Expected SHA-256 (64 hex characters)');
|
|
641
|
+
}
|
|
642
|
+
const contentBuffer = Buffer.from(content, 'utf-8');
|
|
643
|
+
const actualChecksum = createHash('sha256').update(contentBuffer).digest('hex');
|
|
644
|
+
if (actualChecksum !== normalizedExpected) {
|
|
645
|
+
SecurityMonitor.logSecurityEvent({
|
|
646
|
+
type: 'CONTENT_INJECTION_ATTEMPT',
|
|
647
|
+
severity: 'HIGH',
|
|
648
|
+
source: 'secure_downloader',
|
|
649
|
+
details: `Checksum mismatch detected - possible content tampering`,
|
|
650
|
+
metadata: {
|
|
651
|
+
expectedChecksum: normalizedExpected,
|
|
652
|
+
actualChecksum,
|
|
653
|
+
contentLength: content.length
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
throw DownloadError.securityError(`Content checksum verification failed. Expected: ${normalizedExpected}, Got: ${actualChecksum}`);
|
|
657
|
+
}
|
|
658
|
+
logger.debug(`Checksum validation passed: ${actualChecksum}`);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Create a content validator that combines multiple validators
|
|
662
|
+
*/
|
|
663
|
+
static combineValidators(...validators) {
|
|
664
|
+
return async (content) => {
|
|
665
|
+
for (const validator of validators) {
|
|
666
|
+
const result = await validator(content);
|
|
667
|
+
if (!result.isValid) {
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return { isValid: true };
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Create a content validator for JSON content
|
|
676
|
+
*/
|
|
677
|
+
static jsonValidator() {
|
|
678
|
+
return async (content) => {
|
|
679
|
+
try {
|
|
680
|
+
JSON.parse(content);
|
|
681
|
+
return { isValid: true };
|
|
682
|
+
}
|
|
683
|
+
catch (error) {
|
|
684
|
+
return {
|
|
685
|
+
isValid: false,
|
|
686
|
+
errorMessage: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
687
|
+
severity: 'medium'
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Create a content validator for YAML content
|
|
694
|
+
*/
|
|
695
|
+
static yamlValidator() {
|
|
696
|
+
return async (content) => {
|
|
697
|
+
const isValid = SecurityContentValidator.validateYamlContent(content);
|
|
698
|
+
return {
|
|
699
|
+
isValid,
|
|
700
|
+
errorMessage: isValid ? undefined : 'Invalid or malicious YAML content',
|
|
701
|
+
severity: isValid ? 'low' : 'high'
|
|
702
|
+
};
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Create a content validator for markdown content
|
|
707
|
+
*/
|
|
708
|
+
static markdownValidator() {
|
|
709
|
+
return async (content) => {
|
|
710
|
+
try {
|
|
711
|
+
// Use existing persona content sanitization for markdown
|
|
712
|
+
SecurityContentValidator.sanitizePersonaContent(content);
|
|
713
|
+
return { isValid: true };
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
return {
|
|
717
|
+
isValid: false,
|
|
718
|
+
errorMessage: `Invalid markdown: ${error instanceof Error ? error.message : String(error)}`,
|
|
719
|
+
severity: error instanceof SecurityError ? 'critical' : 'medium'
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Create a content validator with size limits
|
|
726
|
+
*/
|
|
727
|
+
static sizeValidator(maxSize) {
|
|
728
|
+
return async (content) => {
|
|
729
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
730
|
+
if (size > maxSize) {
|
|
731
|
+
return {
|
|
732
|
+
isValid: false,
|
|
733
|
+
errorMessage: `Content size ${size} exceeds limit of ${maxSize} bytes`,
|
|
734
|
+
severity: 'high'
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
return { isValid: true };
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Create a content validator that checks for forbidden patterns
|
|
742
|
+
*/
|
|
743
|
+
static patternValidator(forbiddenPatterns, errorMessage = 'Forbidden pattern detected') {
|
|
744
|
+
return async (content) => {
|
|
745
|
+
for (const pattern of forbiddenPatterns) {
|
|
746
|
+
if (pattern.test(content)) {
|
|
747
|
+
return {
|
|
748
|
+
isValid: false,
|
|
749
|
+
errorMessage,
|
|
750
|
+
severity: 'high',
|
|
751
|
+
metadata: { pattern: pattern.source }
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return { isValid: true };
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VjdXJlRG93bmxvYWRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9TZWN1cmVEb3dubG9hZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBdURHO0FBRUgsT0FBTyxLQUFLLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDakQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUNsQyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDM0MsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sSUFBSSxDQUFDO0FBRXZDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDM0QsT0FBTyxFQUFFLGdCQUFnQixJQUFJLHdCQUF3QixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDL0YsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzdELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNqRSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDakUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sNENBQTRDLENBQUM7QUFDOUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUE0RHJDOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGFBQWMsU0FBUSxLQUFLO0lBR3BCO0lBQ0E7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SyxFQUFFLE9BQWUsRUFBNkIsRUFBRTtZQUMxRCxLQUFLLE1BQU0sT0FBTyxJQUFJLGlCQUFpQixFQUFFLENBQUM7Z0JBQ3hDLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO29CQUMxQixPQUFPO3dCQUNMLE9BQU8sRUFBRSxLQUFLO3dCQUNkLFlBQVk7d0JBQ1osUUFBUSxFQUFFLE1BQU07d0JBQ2hCLFFBQVEsRUFBRSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsTUFBTSxFQUFFO3FCQUN0QyxDQUFDO2dCQUNKLENBQUM7WUFDSCxDQUFDO1lBQ0QsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQztRQUMzQixDQUFDLENBQUM7SUFDSixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNlY3VyZURvd25sb2FkZXIgLSBSZXVzYWJsZSB1dGlsaXR5IGZvciBzYWZlIGNvbnRlbnQgZG93bmxvYWRzXG4gKiBcbiAqIEltcGxlbWVudHMgdGhlIHZhbGlkYXRlLWJlZm9yZS13cml0ZSBwYXR0ZXJuIHdpdGggY29tcHJlaGVuc2l2ZSBzZWN1cml0eSBmZWF0dXJlczpcbiAqIC0gQ29udGVudCB2YWxpZGF0aW9uIGhvb2tzIChjdXN0b21pemFibGUgdmFsaWRhdG9ycylcbiAqIC0gQXRvbWljIGZpbGUgb3BlcmF0aW9ucyB3aXRoIHRlbXAgZmlsZXNcbiAqIC0gR3VhcmFudGVlZCBjbGVhbnVwIG9uIGZhaWx1cmVcbiAqIC0gTWVtb3J5LWVmZmljaWVudCBzdHJlYW1pbmcgZm9yIGxhcmdlIGZpbGVzXG4gKiAtIFNpemUgbGltaXRzIHRvIHByZXZlbnQgRG9TIGF0dGFja3NcbiAqIC0gUGF0aCB2YWxpZGF0aW9uIHRvIHByZXZlbnQgdHJhdmVyc2FsXG4gKiAtIFRpbWVvdXQgaGFuZGxpbmcgZm9yIG5ldHdvcmsgb3BlcmF0aW9uc1xuICogLSBDb250ZW50IHR5cGUgdmFsaWRhdGlvblxuICogXG4gKiBVc2FnZSBFeGFtcGxlczpcbiAqIFxuICogLy8gQmFzaWMgZG93bmxvYWQgd2l0aCB2YWxpZGF0aW9uXG4gKiBjb25zdCBkb3dubG9hZGVyID0gbmV3IFNlY3VyZURvd25sb2FkZXIoKTtcbiAqIGF3YWl0IGRvd25sb2FkZXIuZG93bmxvYWRUb0ZpbGUoXG4gKiAgICdodHRwczovL2V4YW1wbGUuY29tL2ZpbGUubWQnLFxuICogICAnLi9kb3dubG9hZHMvZmlsZS5tZCcsXG4gKiAgIHtcbiAqICAgICB2YWxpZGF0b3I6IGFzeW5jIChjb250ZW50KSA9PiAoe1xuICogICAgICAgaXNWYWxpZDogIWNvbnRlbnQuaW5jbHVkZXMoJ21hbGljaW91cycpLFxuICogICAgICAgZXJyb3JNZXNzYWdlOiBjb250ZW50LmluY2x1ZGVzKCdtYWxpY2lvdXMnKSA/ICdNYWxpY2lvdXMgY29udGVudCBkZXRlY3RlZCcgOiB1bmRlZmluZWRcbiAqICAgICB9KSxcbiAqICAgICBtYXhTaXplOiAxMDI0ICogMTAyNCwgLy8gMU1CIGxpbWl0XG4gKiAgICAgdGltZW91dDogMzAwMDAgLy8gMzAgc2Vjb25kIHRpbWVvdXRcbiAqICAgfVxuICogKTtcbiAqIFxuICogLy8gRG93bmxvYWQgdG8gbWVtb3J5IHdpdGggdmFsaWRhdGlvblxuICogY29uc3QgY29udGVudCA9IGF3YWl0IGRvd25sb2FkZXIuZG93bmxvYWRUb01lbW9yeShcbiAqICAgJ2h0dHBzOi8vZXhhbXBsZS5jb20vZGF0YS5qc29uJyxcbiAqICAge1xuICogICAgIHZhbGlkYXRvcjogYXN5bmMgKGNvbnRlbnQpID0+IHtcbiAqICAgICAgIHRyeSB7XG4gKiAgICAgICAgIEpTT04ucGFyc2UoY29udGVudCk7XG4gKiAgICAgICAgIHJldHVybiB7IGlzVmFsaWQ6IHRydWUgfTtcbiAqICAgICAgIH0gY2F0Y2gge1xuICogICAgICAgICByZXR1cm4geyBpc1ZhbGlkOiBmYWxzZSwgZXJyb3JNZXNzYWdlOiAnSW52YWxpZCBKU09OIGZvcm1hdCcgfTtcbiAqICAgICAgIH1cbiAqICAgICB9XG4gKiAgIH1cbiAqICk7XG4gKiBcbiAqIC8vIFN0cmVhbWluZyBkb3dubG9hZCBmb3IgbGFyZ2UgZmlsZXNcbiAqIGF3YWl0IGRvd25sb2FkZXIuZG93bmxvYWRTdHJlYW0oXG4gKiAgICdodHRwczovL2V4YW1wbGUuY29tL2xhcmdlLWZpbGUuemlwJyxcbiAqICAgJy4vZG93bmxvYWRzL2xhcmdlLWZpbGUuemlwJyxcbiAqICAge1xuICogICAgIHN0cmVhbVZhbGlkYXRvcjogKGNodW5rKSA9PiAhY2h1bmsuaW5jbHVkZXMoQnVmZmVyLmZyb20oJ1ZJUlVTJykpLFxuICogICAgIG1heFNpemU6IDEwMCAqIDEwMjQgKiAxMDI0LCAvLyAxMDBNQiBsaW1pdFxuICogICAgIHRpbWVvdXQ6IDMwMDAwMCAvLyA1IG1pbnV0ZSB0aW1lb3V0XG4gKiAgIH1cbiAqICk7XG4gKi9cblxuaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMvcHJvbWlzZXMnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IHJhbmRvbUJ5dGVzLCBjcmVhdGVIYXNoIH0gZnJvbSAnY3J5cHRvJztcbmltcG9ydCB7IFJlYWRhYmxlIH0gZnJvbSAnc3RyZWFtJztcbmltcG9ydCB7IHBpcGVsaW5lIH0gZnJvbSAnc3RyZWFtL3Byb21pc2VzJztcbmltcG9ydCB7IGNyZWF0ZVdyaXRlU3RyZWFtIH0gZnJvbSAnZnMnO1xuXG5pbXBvcnQgeyBTZWN1cml0eUVycm9yIH0gZnJvbSAnLi4vZXJyb3JzL1NlY3VyaXR5RXJyb3IuanMnO1xuaW1wb3J0IHsgU0VDVVJJVFlfTElNSVRTIH0gZnJvbSAnLi4vc2VjdXJpdHkvY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IENvbnRlbnRWYWxpZGF0b3IgYXMgU2VjdXJpdHlDb250ZW50VmFsaWRhdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvY29udGVudFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBQYXRoVmFsaWRhdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvcGF0aFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBGaWxlTG9ja01hbmFnZXIgfSBmcm9tICcuLi9zZWN1cml0eS9maWxlTG9ja01hbmFnZXIuanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlNb25pdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvc2VjdXJpdHlNb25pdG9yLmpzJztcbmltcG9ydCB7IFVuaWNvZGVWYWxpZGF0b3IgfSBmcm9tICcuLi9zZWN1cml0eS92YWxpZGF0b3JzL3VuaWNvZGVWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgUmF0ZUxpbWl0ZXIgfSBmcm9tICcuL1JhdGVMaW1pdGVyLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4vbG9nZ2VyLmpzJztcblxuLyoqXG4gKiBSZXN1bHQgb2YgY29udGVudCB2YWxpZGF0aW9uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVmFsaWRhdGlvblJlc3VsdCB7XG4gIC8qKiBXaGV0aGVyIHRoZSBjb250ZW50IGlzIHZhbGlkIGFuZCBzYWZlICovXG4gIGlzVmFsaWQ6IGJvb2xlYW47XG4gIC8qKiBFcnJvciBtZXNzYWdlIGlmIHZhbGlkYXRpb24gZmFpbGVkICovXG4gIGVycm9yTWVzc2FnZT86IHN0cmluZztcbiAgLyoqIFNldmVyaXR5IG9mIGFueSBkZXRlY3RlZCBpc3N1ZXMgKi9cbiAgc2V2ZXJpdHk/OiAnbG93JyB8ICdtZWRpdW0nIHwgJ2hpZ2gnIHwgJ2NyaXRpY2FsJztcbiAgLyoqIEFkZGl0aW9uYWwgbWV0YWRhdGEgYWJvdXQgdmFsaWRhdGlvbiAqL1xuICBtZXRhZGF0YT86IFJlY29yZDxzdHJpbmcsIGFueT47XG59XG5cbi8qKlxuICogQ29udGVudCB2YWxpZGF0b3IgZnVuY3Rpb24gdHlwZVxuICovXG5leHBvcnQgdHlwZSBDb250ZW50VmFsaWRhdG9yRnVuY3Rpb24gPSAoY29udGVudDogc3RyaW5nKSA9PiBQcm9taXNlPFZhbGlkYXRpb25SZXN1bHQ+O1xuXG4vKipcbiAqIFN0cmVhbSBjaHVuayB2YWxpZGF0b3IgZnVuY3Rpb24gdHlwZVxuICovXG5leHBvcnQgdHlwZSBTdHJlYW1WYWxpZGF0b3IgPSAoY2h1bms6IFVpbnQ4QXJyYXkpID0+IGJvb2xlYW47XG5cbi8qKlxuICogT3B0aW9ucyBmb3IgZG93bmxvYWQgb3BlcmF0aW9uc1xuICovXG5leHBvcnQgaW50ZXJmYWNlIERvd25sb2FkT3B0aW9ucyB7XG4gIC8qKiBDdXN0b20gY29udGVudCB2YWxpZGF0b3IgZnVuY3Rpb24gKi9cbiAgdmFsaWRhdG9yPzogQ29udGVudFZhbGlkYXRvckZ1bmN0aW9uO1xuICAvKiogTWF4aW11bSBmaWxlIHNpemUgaW4gYnl0ZXMgKGRlZmF1bHQ6IFNFQ1VSSVRZX0xJTUlUUy5NQVhfRklMRV9TSVpFKSAqL1xuICBtYXhTaXplPzogbnVtYmVyO1xuICAvKiogTmV0d29yayB0aW1lb3V0IGluIG1pbGxpc2Vjb25kcyAoZGVmYXVsdDogMzAwMDApICovXG4gIHRpbWVvdXQ/OiBudW1iZXI7XG4gIC8qKiBXaGV0aGVyIHRvIHVzZSBhdG9taWMgZmlsZSBvcGVyYXRpb25zIChkZWZhdWx0OiB0cnVlKSAqL1xuICBhdG9taWM/OiBib29sZWFuO1xuICAvKiogRXhwZWN0ZWQgY29udGVudCB0eXBlIChmb3IgdmFsaWRhdGlvbikgKi9cbiAgZXhwZWN0ZWRDb250ZW50VHlwZT86IHN0cmluZztcbiAgLyoqIEN1c3RvbSBIVFRQIGhlYWRlcnMgKi9cbiAgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG4gIC8qKiBFeHBlY3RlZCBTSEEtMjU2IGNoZWNrc3VtIGZvciBpbnRlZ3JpdHkgdmFsaWRhdGlvbiAqL1xuICBleHBlY3RlZENoZWNrc3VtPzogc3RyaW5nO1xufVxuXG4vKipcbiAqIE9wdGlvbnMgZm9yIHN0cmVhbWluZyBkb3dubG9hZHNcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTdHJlYW1Eb3dubG9hZE9wdGlvbnMge1xuICAvKiogQ2h1bmstbGV2ZWwgdmFsaWRhdG9yIGZvciBzdHJlYW1pbmcgdmFsaWRhdGlvbiAqL1xuICBzdHJlYW1WYWxpZGF0b3I/OiBTdHJlYW1WYWxpZGF0b3I7XG4gIC8qKiBNYXhpbXVtIGZpbGUgc2l6ZSBpbiBieXRlcyAoZGVmYXVsdDogU0VDVVJJVFlfTElNSVRTLk1BWF9GSUxFX1NJWkUpICovXG4gIG1heFNpemU/OiBudW1iZXI7XG4gIC8qKiBOZXR3b3JrIHRpbWVvdXQgaW4gbWlsbGlzZWNvbmRzIChkZWZhdWx0OiAzMDAwMCkgKi9cbiAgdGltZW91dD86IG51bWJlcjtcbiAgLyoqIEN1c3RvbSBIVFRQIGhlYWRlcnMgKi9cbiAgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG59XG5cbi8qKlxuICogQ3VzdG9tIGVycm9yIHR5cGVzIGZvciBkaWZmZXJlbnQgZmFpbHVyZSBzY2VuYXJpb3NcbiAqL1xuZXhwb3J0IGNsYXNzIERvd25sb2FkRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKFxuICAgIG1lc3NhZ2U6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgY29kZTogc3RyaW5nLFxuICAgIHB1YmxpYyByZWFkb25seSBvcmlnaW5hbEVycm9yPzogRXJyb3JcbiAgKSB7XG4gICAgc3VwZXIobWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ0Rvd25sb2FkRXJyb3InO1xuICB9XG5cbiAgc3RhdGljIG5ldHdvcmtFcnJvcihtZXNzYWdlOiBzdHJpbmcsIG9yaWdpbmFsRXJyb3I/OiBFcnJvcik6IERvd25sb2FkRXJyb3Ige1xuICAgIHJldHVybiBuZXcgRG93bmxvYWRFcnJvcihtZXNzYWdlLCAnTkVUV09SS19FUlJPUicsIG9yaWdpbmFsRXJyb3IpO1xuICB9XG5cbiAgc3RhdGljIHZhbGlkYXRpb25FcnJvcihtZXNzYWdlOiBzdHJpbmcpOiBEb3dubG9hZEVycm9yIHtcbiAgICByZXR1cm4gbmV3IERvd25sb2FkRXJyb3IobWVzc2FnZSwgJ1ZBTElEQVRJT05fRVJST1InKTtcbiAgfVxuXG4gIHN0YXRpYyBzZWN1cml0eUVycm9yKG1lc3NhZ2U6IHN0cmluZyk6IERvd25sb2FkRXJyb3Ige1xuICAgIHJldHVybiBuZXcgRG93bmxvYWRFcnJvcihtZXNzYWdlLCAnU0VDVVJJVFlfRVJST1InKTtcbiAgfVxuXG4gIHN0YXRpYyB0aW1lb3V0RXJyb3IobWVzc2FnZTogc3RyaW5nKTogRG93bmxvYWRFcnJvciB7XG4gICAgcmV0dXJuIG5ldyBEb3dubG9hZEVycm9yKG1lc3NhZ2UsICdUSU1FT1VUX0VSUk9SJyk7XG4gIH1cblxuICBzdGF0aWMgZmlsZXN5c3RlbUVycm9yKG1lc3NhZ2U6IHN0cmluZywgb3JpZ2luYWxFcnJvcj86IEVycm9yKTogRG93bmxvYWRFcnJvciB7XG4gICAgcmV0dXJuIG5ldyBEb3dubG9hZEVycm9yKG1lc3NhZ2UsICdGSUxFU1lTVEVNX0VSUk9SJywgb3JpZ2luYWxFcnJvcik7XG4gIH1cbn1cblxuLyoqXG4gKiBTZWN1cmVEb3dubG9hZGVyIC0gSW1wbGVtZW50cyB2YWxpZGF0ZS1iZWZvcmUtd3JpdGUgcGF0dGVybiBmb3Igc2FmZSBkb3dubG9hZHNcbiAqIFxuICogS2V5IFNlY3VyaXR5IEZlYXR1cmVzOlxuICogMS4gVkFMSURBVEUtQkVGT1JFLVdSSVRFOiBBbGwgY29udGVudCB2YWxpZGF0aW9uIG9jY3VycyBiZWZvcmUgYW55IGRpc2sgb3BlcmF0aW9uc1xuICogMi4gQVRPTUlDIE9QRVJBVElPTlM6IFVzZXMgdGVtcG9yYXJ5IGZpbGVzIHdpdGggYXRvbWljIHJlbmFtZSB0byBwcmV2ZW50IGNvcnJ1cHRpb25cbiAqIDMuIEdVQVJBTlRFRUQgQ0xFQU5VUDogQXV0b21hdGljIGNsZWFudXAgb2YgdGVtcG9yYXJ5IGZpbGVzIG9uIGFueSBmYWlsdXJlXG4gKiA0LiBTSVpFIExJTUlUUzogUHJldmVudHMgRG9TIGF0dGFja3MgdGhyb3VnaCBsYXJnZSBmaWxlIGRvd25sb2Fkc1xuICogNS4gUEFUSCBWQUxJREFUSU9OOiBQcmV2ZW50cyBkaXJlY3RvcnkgdHJhdmVyc2FsIGF0dGFja3NcbiAqIDYuIFRJTUVPVVQgUFJPVEVDVElPTjogUHJldmVudHMgaGFuZ2luZyBuZXR3b3JrIG9wZXJhdGlvbnNcbiAqIDcuIENPTlRFTlQgVkFMSURBVElPTjogRXh0ZW5zaWJsZSB2YWxpZGF0aW9uIHN5c3RlbSBmb3IgZGlmZmVyZW50IGNvbnRlbnQgdHlwZXNcbiAqL1xuZXhwb3J0IGNsYXNzIFNlY3VyZURvd25sb2FkZXIge1xuICBwcml2YXRlIHJlYWRvbmx5IGRlZmF1bHRUaW1lb3V0OiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgZGVmYXVsdE1heFNpemU6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSB0ZW1wRGlyOiBzdHJpbmc7XG4gIHByaXZhdGUgcmVhZG9ubHkgZ2xvYmFsUmF0ZUxpbWl0ZXI6IFJhdGVMaW1pdGVyO1xuICBwcml2YXRlIHJlYWRvbmx5IHVybFJhdGVMaW1pdGVyczogTWFwPHN0cmluZywgUmF0ZUxpbWl0ZXI+O1xuXG4gIGNvbnN0cnVjdG9yKG9wdGlvbnM/OiB7XG4gICAgZGVmYXVsdFRpbWVvdXQ/OiBudW1iZXI7XG4gICAgZGVmYXVsdE1heFNpemU/OiBudW1iZXI7XG4gICAgdGVtcERpcj86IHN0cmluZztcbiAgICByYXRlTGltaXRPcHRpb25zPzoge1xuICAgICAgbWF4UmVxdWVzdHNQZXJVcmw/OiBudW1iZXI7XG4gICAgICBtYXhHbG9iYWxSZXF1ZXN0cz86IG51bWJlcjtcbiAgICAgIHdpbmRvd01zPzogbnVtYmVyO1xuICAgIH07XG4gIH0pIHtcbiAgICB0aGlzLmRlZmF1bHRUaW1lb3V0ID0gb3B0aW9ucz8uZGVmYXVsdFRpbWVvdXQgfHwgMzAwMDA7IC8vIDMwIHNlY29uZHNcbiAgICB0aGlzLmRlZmF1bHRNYXhTaXplID0gb3B0aW9ucz8uZGVmYXVsdE1heFNpemUgfHwgU0VDVVJJVFlfTElNSVRTLk1BWF9GSUxFX1NJWkU7XG4gICAgdGhpcy50ZW1wRGlyID0gb3B0aW9ucz8udGVtcERpciB8fCAnLnRtcCc7XG4gICAgXG4gICAgLy8gSW5pdGlhbGl6ZSByYXRlIGxpbWl0ZXJzXG4gICAgY29uc3QgcmF0ZUxpbWl0Q29uZmlnID0gb3B0aW9ucz8ucmF0ZUxpbWl0T3B0aW9ucyB8fCB7fTtcbiAgICB0aGlzLmdsb2JhbFJhdGVMaW1pdGVyID0gbmV3IFJhdGVMaW1pdGVyKHtcbiAgICAgIG1heFJlcXVlc3RzOiByYXRlTGltaXRDb25maWcubWF4R2xvYmFsUmVxdWVzdHMgfHwgMTAwLCAvLyAxMDAgZG93bmxvYWRzIHBlciBob3VyIGdsb2JhbGx5XG4gICAgICB3aW5kb3dNczogcmF0ZUxpbWl0Q29uZmlnLndpbmRvd01zIHx8IDYwICogNjAgKiAxMDAwLCAvLyAxIGhvdXJcbiAgICAgIG1pbkRlbGF5TXM6IDEwMDAgLy8gTWluaW11bSAxIHNlY29uZCBiZXR3ZWVuIHJlcXVlc3RzXG4gICAgfSk7XG4gICAgdGhpcy51cmxSYXRlTGltaXRlcnMgPSBuZXcgTWFwKCk7XG4gIH1cblxuICAvKipcbiAgICogRG93bmxvYWQgY29udGVudCB0byBhIGZpbGUgd2l0aCB2YWxpZGF0aW9uXG4gICAqIFxuICAgKiBTRUNVUklUWTogSW1wbGVtZW50cyB2YWxpZGF0ZS1iZWZvcmUtd3JpdGUgcGF0dGVybjpcbiAgICogMS4gRG93bmxvYWQgY29udGVudCB0byBtZW1vcnlcbiAgICogMi4gVmFsaWRhdGUgYWxsIGNvbnRlbnRcbiAgICogMy4gT25seSB0aGVuIHdyaXRlIHRvIGRpc2sgYXRvbWljYWxseVxuICAgKiBcbiAgICogQHBhcmFtIHVybCAtIFVSTCB0byBkb3dubG9hZCBmcm9tXG4gICAqIEBwYXJhbSBkZXN0aW5hdGlvblBhdGggLSBMb2NhbCBmaWxlIHBhdGggdG8gc2F2ZSB0b1xuICAgKiBAcGFyYW0gb3B0aW9ucyAtIERvd25sb2FkIGFuZCB2YWxpZGF0aW9uIG9wdGlvbnNcbiAgICovXG4gIGFzeW5jIGRvd25sb2FkVG9GaWxlKFxuICAgIHVybDogc3RyaW5nLFxuICAgIGRlc3RpbmF0aW9uUGF0aDogc3RyaW5nLFxuICAgIG9wdGlvbnM6IERvd25sb2FkT3B0aW9ucyA9IHt9XG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG4gICAgbG9nZ2VyLmRlYnVnKGBTdGFydGluZyBzZWN1cmUgZG93bmxvYWQgZnJvbSAke3VybH0gdG8gJHtkZXN0aW5hdGlvblBhdGh9YCk7XG5cbiAgICB0cnkge1xuICAgICAgLy8gU0VDVVJJVFk6IFZhbGlkYXRlIFVSTCBhbmQgZGVzdGluYXRpb24gcGF0aCBmaXJzdFxuICAgICAgdGhpcy52YWxpZGF0ZVVybCh1cmwpO1xuICAgICAgY29uc3QgdmFsaWRhdGVkUGF0aCA9IGF3YWl0IHRoaXMudmFsaWRhdGVEZXN0aW5hdGlvblBhdGgoZGVzdGluYXRpb25QYXRoKTtcblxuICAgICAgLy8gU0VDVVJJVFk6IENoZWNrIGlmIGZpbGUgYWxyZWFkeSBleGlzdHMgKHByZXZlbnQgYWNjaWRlbnRhbCBvdmVyd3JpdGVzKVxuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgZnMuYWNjZXNzKHZhbGlkYXRlZFBhdGgpO1xuICAgICAgICB0aHJvdyBEb3dubG9hZEVycm9yLmZpbGVzeXN0ZW1FcnJvcihgRmlsZSBhbHJlYWR5IGV4aXN0czogJHtkZXN0aW5hdGlvblBhdGh9YCk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBpZiAoKGVycm9yIGFzIE5vZGVKUy5FcnJub0V4Y2VwdGlvbikuY29kZSAhPT0gJ0VOT0VOVCcpIHtcbiAgICAgICAgICB0aHJvdyBlcnJvcjsgLy8gUmUtdGhyb3cgaWYgaXQncyBub3QgYSBcImZpbGUgbm90IGZvdW5kXCIgZXJyb3JcbiAgICAgICAgfVxuICAgICAgICAvLyBGaWxlIGRvZXNuJ3QgZXhpc3QsIHByb2NlZWQgd2l0aCBkb3dubG9hZFxuICAgICAgfVxuXG4gICAgICAvLyBTVEVQIDE6IENoZWNrIHJhdGUgbGltaXRzIGJlZm9yZSBkb3dubG9hZFxuICAgICAgYXdhaXQgdGhpcy5jaGVja1JhdGVMaW1pdCh1cmwpO1xuXG4gICAgICAvLyBTVEVQIDI6IERvd25sb2FkIGNvbnRlbnQgdG8gbWVtb3J5IChubyBkaXNrIG9wZXJhdGlvbnMgeWV0KVxuICAgICAgY29uc3QgY29udGVudCA9IGF3YWl0IHRoaXMuZG93bmxvYWRUb01lbW9yeSh1cmwsIG9wdGlvbnMpO1xuXG4gICAgICAvLyBTVEVQIDM6IFZhbGlkYXRlIGNoZWNrc3VtIGlmIHByb3ZpZGVkXG4gICAgICBpZiAob3B0aW9ucy5leHBlY3RlZENoZWNrc3VtKSB7XG4gICAgICAgIGF3YWl0IHRoaXMudmFsaWRhdGVDaGVja3N1bShjb250ZW50LCBvcHRpb25zLmV4cGVjdGVkQ2hlY2tzdW0pO1xuICAgICAgfVxuXG4gICAgICAvLyBTVEVQIDQ6IEFsbCB2YWxpZGF0aW9uIGlzIGNvbXBsZXRlLCBub3cgd3JpdGUgYXRvbWljYWxseVxuICAgICAgY29uc3QgdXNlQXRvbWljID0gb3B0aW9ucy5hdG9taWMgIT09IGZhbHNlOyAvLyBEZWZhdWx0IHRvIHRydWVcbiAgICAgIGlmICh1c2VBdG9taWMpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5hdG9taWNXcml0ZUZpbGUodmFsaWRhdGVkUGF0aCwgY29udGVudCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhd2FpdCB0aGlzLmRpcmVjdFdyaXRlRmlsZSh2YWxpZGF0ZWRQYXRoLCBjb250ZW50KTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgZHVyYXRpb24gPSBEYXRlLm5vdygpIC0gc3RhcnRUaW1lO1xuICAgICAgbG9nZ2VyLmluZm8oYFNlY3VyZSBkb3dubG9hZCBjb21wbGV0ZWQ6ICR7ZGVzdGluYXRpb25QYXRofSAoJHtjb250ZW50Lmxlbmd0aH0gYnl0ZXMsICR7ZHVyYXRpb259bXMpYCk7XG5cbiAgICAgIC8vIExvZyBzdWNjZXNzZnVsIGRvd25sb2FkIGZvciBzZWN1cml0eSBtb25pdG9yaW5nXG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdGSUxFX0NPUElFRCcsXG4gICAgICAgIHNldmVyaXR5OiAnTE9XJyxcbiAgICAgICAgc291cmNlOiAnc2VjdXJlX2Rvd25sb2FkZXInLFxuICAgICAgICBkZXRhaWxzOiBgRG93bmxvYWRlZCAke2NvbnRlbnQubGVuZ3RofSBieXRlcyBmcm9tICR7dXJsfSB0byAke2Rlc3RpbmF0aW9uUGF0aH1gLFxuICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgIHVybCxcbiAgICAgICAgICBkZXN0aW5hdGlvblBhdGgsXG4gICAgICAgICAgY29udGVudExlbmd0aDogY29udGVudC5sZW5ndGgsXG4gICAgICAgICAgZHVyYXRpb25cbiAgICAgICAgfVxuICAgICAgfSk7XG5cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc3QgZHVyYXRpb24gPSBEYXRlLm5vdygpIC0gc3RhcnRUaW1lO1xuICAgICAgbG9nZ2VyLmVycm9yKGBTZWN1cmUgZG93bmxvYWQgZmFpbGVkOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKX1gKTtcblxuICAgICAgLy8gTG9nIGZhaWxlZCBkb3dubG9hZCBmb3Igc2VjdXJpdHkgbW9uaXRvcmluZ1xuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnUEFUSF9UUkFWRVJTQUxfQVRURU1QVCcsXG4gICAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgICAgc291cmNlOiAnc2VjdXJlX2Rvd25sb2FkZXInLFxuICAgICAgICBkZXRhaWxzOiBgRG93bmxvYWQgZmFpbGVkOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKX1gLFxuICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgIHVybCxcbiAgICAgICAgICBkZXN0aW5hdGlvblBhdGgsXG4gICAgICAgICAgZHVyYXRpb24sXG4gICAgICAgICAgZXJyb3JUeXBlOiBlcnJvciBpbnN0YW5jZW9mIERvd25sb2FkRXJyb3IgPyBlcnJvci5jb2RlIDogJ1VOS05PV04nXG4gICAgICAgIH1cbiAgICAgIH0pO1xuXG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRG93bmxvYWQgY29udGVudCB0byBtZW1vcnkgd2l0aCB2YWxpZGF0aW9uXG4gICAqIFxuICAgKiBAcGFyYW0gdXJsIC0gVVJMIHRvIGRvd25sb2FkIGZyb21cbiAgICogQHBhcmFtIG9wdGlvbnMgLSBEb3dubG9hZCBhbmQgdmFsaWRhdGlvbiBvcHRpb25zXG4gICAqIEByZXR1cm5zIFZhbGlkYXRlZCBjb250ZW50IGFzIHN0cmluZ1xuICAgKi9cbiAgYXN5bmMgZG93bmxvYWRUb01lbW9yeShcbiAgICB1cmw6IHN0cmluZyxcbiAgICBvcHRpb25zOiBEb3dubG9hZE9wdGlvbnMgPSB7fVxuICApOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHRpbWVvdXQgPSBvcHRpb25zLnRpbWVvdXQgfHwgdGhpcy5kZWZhdWx0VGltZW91dDtcbiAgICBjb25zdCBtYXhTaXplID0gb3B0aW9ucy5tYXhTaXplIHx8IHRoaXMuZGVmYXVsdE1heFNpemU7XG5cbiAgICBsb2dnZXIuZGVidWcoYERvd25sb2FkaW5nIGNvbnRlbnQgZnJvbSAke3VybH0gKG1heDogJHttYXhTaXplfSBieXRlcywgdGltZW91dDogJHt0aW1lb3V0fW1zKWApO1xuXG4gICAgdHJ5IHtcbiAgICAgIC8vIFNFQ1VSSVRZOiBWYWxpZGF0ZSBVUkwgZm9ybWF0XG4gICAgICB0aGlzLnZhbGlkYXRlVXJsKHVybCk7XG5cbiAgICAgIC8vIFNURVAgMTogQ2hlY2sgcmF0ZSBsaW1pdHMgYmVmb3JlIGRvd25sb2FkXG4gICAgICBhd2FpdCB0aGlzLmNoZWNrUmF0ZUxpbWl0KHVybCk7XG5cbiAgICAgIC8vIFNURVAgMjogRmV0Y2ggY29udGVudCB3aXRoIHNpemUgYW5kIHRpbWVvdXQgcHJvdGVjdGlvblxuICAgICAgY29uc3QgY29udGVudCA9IGF3YWl0IHRoaXMuZmV0Y2hXaXRoTGltaXRzKHVybCwgbWF4U2l6ZSwgdGltZW91dCwgb3B0aW9ucy5oZWFkZXJzKTtcblxuICAgICAgLy8gU1RFUCAzOiBWYWxpZGF0ZSBjb250ZW50IHR5cGUgaWYgc3BlY2lmaWVkXG4gICAgICBpZiAob3B0aW9ucy5leHBlY3RlZENvbnRlbnRUeXBlKSB7XG4gICAgICAgIGF3YWl0IHRoaXMudmFsaWRhdGVDb250ZW50VHlwZShjb250ZW50LCBvcHRpb25zLmV4cGVjdGVkQ29udGVudFR5cGUpO1xuICAgICAgfVxuXG4gICAgICAvLyBTVEVQIDQ6IFZhbGlkYXRlIGNoZWNrc3VtIGlmIHByb3ZpZGVkXG4gICAgICBpZiAob3B0aW9ucy5leHBlY3RlZENoZWNrc3VtKSB7XG4gICAgICAgIGF3YWl0IHRoaXMudmFsaWRhdGVDaGVja3N1bShjb250ZW50LCBvcHRpb25zLmV4cGVjdGVkQ2hlY2tzdW0pO1xuICAgICAgfVxuXG4gICAgICAvLyBTVEVQIDU6IFJ1biBidWlsdC1pbiBzZWN1cml0eSB2YWxpZGF0aW9uXG4gICAgICBjb25zdCBzZWN1cml0eVJlc3VsdCA9IFNlY3VyaXR5Q29udGVudFZhbGlkYXRvci52YWxpZGF0ZUFuZFNhbml0aXplKGNvbnRlbnQpO1xuICAgICAgaWYgKCFzZWN1cml0eVJlc3VsdC5pc1ZhbGlkICYmIHNlY3VyaXR5UmVzdWx0LnNldmVyaXR5ID09PSAnY3JpdGljYWwnKSB7XG4gICAgICAgIHRocm93IERvd25sb2FkRXJyb3Iuc2VjdXJpdHlFcnJvcihcbiAgICAgICAgICBgQ3JpdGljYWwgc2VjdXJpdHkgdGhyZWF0IGRldGVjdGVkOiAke3NlY3VyaXR5UmVzdWx0LmRldGVjdGVkUGF0dGVybnM/LmpvaW4oJywgJyl9YFxuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICAvLyBTVEVQIDY6IFJ1biBjdXN0b20gdmFsaWRhdG9yIGlmIHByb3ZpZGVkXG4gICAgICBpZiAob3B0aW9ucy52YWxpZGF0b3IpIHtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdSdW5uaW5nIGN1c3RvbSBjb250ZW50IHZhbGlkYXRpb24nKTtcbiAgICAgICAgY29uc3QgdmFsaWRhdGlvblJlc3VsdCA9IGF3YWl0IG9wdGlvbnMudmFsaWRhdG9yKGNvbnRlbnQpO1xuICAgICAgICBpZiAoIXZhbGlkYXRpb25SZXN1bHQuaXNWYWxpZCkge1xuICAgICAgICAgIHRocm93IERvd25sb2FkRXJyb3IudmFsaWRhdGlvbkVycm9yKFxuICAgICAgICAgICAgdmFsaWRhdGlvblJlc3VsdC5lcnJvck1lc3NhZ2UgfHwgJ0NvbnRlbnQgdmFsaWRhdGlvbiBmYWlsZWQnXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBsb2dnZXIuZGVidWcoYENvbnRlbnQgdmFsaWRhdGlvbiBwYXNzZWQgKCR7Y29udGVudC5sZW5ndGh9IGJ5dGVzKWApO1xuICAgICAgcmV0dXJuIHNlY3VyaXR5UmVzdWx0LnNhbml0aXplZENvbnRlbnQgfHwgY29udGVudDtcblxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBEb3dubG9hZEVycm9yKSB7XG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfVxuICAgICAgdGhyb3cgRG93bmxvYWRFcnJvci5uZXR3b3JrRXJyb3IoXG4gICAgICAgIGBGYWlsZWQgdG8gZG93bmxvYWQgY29udGVudCBmcm9tICR7dXJsfTogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9YCxcbiAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogdW5kZWZpbmVkXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEb3dubG9hZCBsYXJnZSBmaWxlcyB1c2luZyBzdHJlYW1pbmcgd2l0aCBjaHVuay1sZXZlbCB2YWxpZGF0aW9uXG4gICAqIFxuICAgKiBAcGFyYW0gdXJsIC0gVVJMIHRvIGRvd25sb2FkIGZyb21cbiAgICogQHBhcmFtIGRlc3RpbmF0aW9uUGF0aCAtIExvY2FsIGZpbGUgcGF0aCB0byBzYXZlIHRvXG4gICAqIEBwYXJhbSBvcHRpb25zIC0gU3RyZWFtaW5nIGRvd25sb2FkIG9wdGlvbnNcbiAgICovXG4gIGFzeW5jIGRvd25sb2FkU3RyZWFtKFxuICAgIHVybDogc3RyaW5nLFxuICAgIGRlc3RpbmF0aW9uUGF0aDogc3RyaW5nLFxuICAgIG9wdGlvbnM6IFN0cmVhbURvd25sb2FkT3B0aW9ucyA9IHt9XG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG4gICAgY29uc3QgbWF4U2l6ZSA9IG9wdGlvbnMubWF4U2l6ZSB8fCB0aGlzLmRlZmF1bHRNYXhTaXplO1xuICAgIGNvbnN0IHRpbWVvdXQgPSBvcHRpb25zLnRpbWVvdXQgfHwgdGhpcy5kZWZhdWx0VGltZW91dDtcblxuICAgIGxvZ2dlci5kZWJ1ZyhgU3RhcnRpbmcgc3RyZWFtaW5nIGRvd25sb2FkIGZyb20gJHt1cmx9IHRvICR7ZGVzdGluYXRpb25QYXRofWApO1xuXG4gICAgdHJ5IHtcbiAgICAgIC8vIFNFQ1VSSVRZOiBDaGVjayByYXRlIGxpbWl0cyBiZWZvcmUgZG93bmxvYWRcbiAgICAgIGF3YWl0IHRoaXMuY2hlY2tSYXRlTGltaXQodXJsKTtcblxuICAgICAgLy8gU0VDVVJJVFk6IFZhbGlkYXRlIFVSTCBhbmQgZGVzdGluYXRpb24gcGF0aFxuICAgICAgdGhpcy52YWxpZGF0ZVVybCh1cmwpO1xuICAgICAgY29uc3QgdmFsaWRhdGVkUGF0aCA9IGF3YWl0IHRoaXMudmFsaWRhdGVEZXN0aW5hdGlvblBhdGgoZGVzdGluYXRpb25QYXRoKTtcblxuICAgICAgLy8gR2VuZXJhdGUgdGVtcG9yYXJ5IGZpbGUgcGF0aCBmb3IgYXRvbWljIG9wZXJhdGlvblxuICAgICAgY29uc3QgdGVtcFBhdGggPSBhd2FpdCB0aGlzLmdldFRlbXBGaWxlUGF0aCh2YWxpZGF0ZWRQYXRoKTtcblxuICAgICAgbGV0IGRvd25sb2FkZWRTaXplID0gMDtcbiAgICAgIGxldCB0aW1lb3V0SGFuZGxlOiBOb2RlSlMuVGltZW91dCB8IHVuZGVmaW5lZDtcblxuICAgICAgLy8gQ3JlYXRlIGFib3J0IGNvbnRyb2xsZXIgZm9yIHRpbWVvdXQgaGFuZGxpbmdcbiAgICAgIGNvbnN0IGFib3J0Q29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICAgIHRpbWVvdXRIYW5kbGUgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgYWJvcnRDb250cm9sbGVyLmFib3J0KCk7XG4gICAgICB9LCB0aW1lb3V0KTtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gU0VDVVJJVFk6IEZldGNoIHdpdGggYWJvcnQgc2lnbmFsIGZvciB0aW1lb3V0XG4gICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCB7XG4gICAgICAgICAgc2lnbmFsOiBhYm9ydENvbnRyb2xsZXIuc2lnbmFsLFxuICAgICAgICAgIGhlYWRlcnM6IG9wdGlvbnMuaGVhZGVyc1xuICAgICAgICB9KTtcblxuICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBIVFRQICR7cmVzcG9uc2Uuc3RhdHVzfTogJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFyZXNwb25zZS5ib2R5KSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdSZXNwb25zZSBib2R5IGlzIG51bGwnKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEVuc3VyZSB0ZW1wIGRpcmVjdG9yeSBleGlzdHNcbiAgICAgICAgYXdhaXQgZnMubWtkaXIocGF0aC5kaXJuYW1lKHRlbXBQYXRoKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG5cbiAgICAgICAgLy8gQ3JlYXRlIHdyaXRlIHN0cmVhbSB0byB0ZW1wb3JhcnkgZmlsZVxuICAgICAgICBjb25zdCB3cml0ZVN0cmVhbSA9IGNyZWF0ZVdyaXRlU3RyZWFtKHRlbXBQYXRoKTtcblxuICAgICAgICAvLyBDcmVhdGUgYSB0cmFuc2Zvcm0gc3RyZWFtIGZvciB2YWxpZGF0aW9uIGFuZCBzaXplIGNoZWNraW5nXG4gICAgICAgIGNvbnN0IHZhbGlkYXRpb25TdHJlYW0gPSBuZXcgUmVhZGFibGUoe1xuICAgICAgICAgIGFzeW5jIHJlYWQoKSB7XG4gICAgICAgICAgICAvLyBUaGlzIHN0cmVhbSB3aWxsIGJlIGZlZCBieSB0aGUgcGlwZWxpbmVcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIFNldCB1cCBjaHVuayB2YWxpZGF0aW9uIGFuZCBzaXplIGNoZWNraW5nXG4gICAgICAgIGNvbnN0IHJlYWRlciA9IHJlc3BvbnNlLmJvZHkuZ2V0UmVhZGVyKCk7XG4gICAgICAgIGNvbnN0IHB1bXAgPSBhc3luYyAoKSA9PiB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICAgICAgICAgIGNvbnN0IHsgZG9uZSwgdmFsdWUgfSA9IGF3YWl0IHJlYWRlci5yZWFkKCk7XG4gICAgICAgICAgICAgIGlmIChkb25lKSBicmVhaztcblxuICAgICAgICAgICAgICAvLyBTRUNVUklUWTogQ2hlY2sgc2l6ZSBsaW1pdFxuICAgICAgICAgICAgICBkb3dubG9hZGVkU2l6ZSArPSB2YWx1ZS5sZW5ndGg7XG4gICAgICAgICAgICAgIGlmIChkb3dubG9hZGVkU2l6ZSA+IG1heFNpemUpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnNlY3VyaXR5RXJyb3IoXG4gICAgICAgICAgICAgICAgICBgRmlsZSBzaXplIGV4Y2VlZHMgbGltaXQ6ICR7ZG93bmxvYWRlZFNpemV9ID4gJHttYXhTaXplfSBieXRlc2BcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgLy8gU0VDVVJJVFk6IFJ1biBjaHVuayB2YWxpZGF0b3IgaWYgcHJvdmlkZWRcbiAgICAgICAgICAgICAgaWYgKG9wdGlvbnMuc3RyZWFtVmFsaWRhdG9yICYmICFvcHRpb25zLnN0cmVhbVZhbGlkYXRvcih2YWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnZhbGlkYXRpb25FcnJvcignQ2h1bmsgdmFsaWRhdGlvbiBmYWlsZWQnKTtcbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIHZhbGlkYXRpb25TdHJlYW0ucHVzaCh2YWx1ZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2YWxpZGF0aW9uU3RyZWFtLnB1c2gobnVsbCk7IC8vIEVuZCBzdHJlYW1cbiAgICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgdmFsaWRhdGlvblN0cmVhbS5kZXN0cm95KGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvciA6IG5ldyBFcnJvcihTdHJpbmcoZXJyb3IpKSk7XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIC8vIFN0YXJ0IHRoZSBwdW1wIGFuZCBwaXBlbGluZSBjb25jdXJyZW50bHlcbiAgICAgICAgY29uc3QgW3B1bXBSZXN1bHRdID0gYXdhaXQgUHJvbWlzZS5hbGwoW1xuICAgICAgICAgIHB1bXAoKSxcbiAgICAgICAgICBwaXBlbGluZSh2YWxpZGF0aW9uU3RyZWFtLCB3cml0ZVN0cmVhbSlcbiAgICAgICAgXSk7XG5cbiAgICAgICAgLy8gQ2xlYXIgdGltZW91dFxuICAgICAgICBpZiAodGltZW91dEhhbmRsZSkge1xuICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SGFuZGxlKTtcbiAgICAgICAgICB0aW1lb3V0SGFuZGxlID0gdW5kZWZpbmVkO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gU0VDVVJJVFk6IEF0b21pYyByZW5hbWUgdG8gZmluYWwgZGVzdGluYXRpb25cbiAgICAgICAgYXdhaXQgZnMucmVuYW1lKHRlbXBQYXRoLCB2YWxpZGF0ZWRQYXRoKTtcblxuICAgICAgICBjb25zdCBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBzdGFydFRpbWU7XG4gICAgICAgIGxvZ2dlci5pbmZvKGBTdHJlYW1pbmcgZG93bmxvYWQgY29tcGxldGVkOiAke2Rlc3RpbmF0aW9uUGF0aH0gKCR7ZG93bmxvYWRlZFNpemV9IGJ5dGVzLCAke2R1cmF0aW9ufW1zKWApO1xuXG4gICAgICAgIC8vIExvZyBzdWNjZXNzZnVsIHN0cmVhbWluZyBkb3dubG9hZFxuICAgICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgICAgdHlwZTogJ0ZJTEVfQ09QSUVEJyxcbiAgICAgICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICAgICAgc291cmNlOiAnc2VjdXJlX2Rvd25sb2FkZXInLFxuICAgICAgICAgIGRldGFpbHM6IGBTdHJlYW1lZCAke2Rvd25sb2FkZWRTaXplfSBieXRlcyBmcm9tICR7dXJsfSB0byAke2Rlc3RpbmF0aW9uUGF0aH1gLFxuICAgICAgICAgIG1ldGFkYXRhOiB7XG4gICAgICAgICAgICB1cmwsXG4gICAgICAgICAgICBkZXN0aW5hdGlvblBhdGgsXG4gICAgICAgICAgICBjb250ZW50TGVuZ3RoOiBkb3dubG9hZGVkU2l6ZSxcbiAgICAgICAgICAgIGR1cmF0aW9uXG4gICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgLy8gU0VDVVJJVFk6IEd1YXJhbnRlZWQgY2xlYW51cCBvZiB0ZW1wb3JhcnkgZmlsZVxuICAgICAgICB0cnkge1xuICAgICAgICAgIGF3YWl0IGZzLnVubGluayh0ZW1wUGF0aCk7XG4gICAgICAgICAgbG9nZ2VyLmRlYnVnKGBDbGVhbmVkIHVwIHRlbXAgZmlsZTogJHt0ZW1wUGF0aH1gKTtcbiAgICAgICAgfSBjYXRjaCAoY2xlYW51cEVycm9yKSB7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oYEZhaWxlZCB0byBjbGVhbiB1cCB0ZW1wIGZpbGUgJHt0ZW1wUGF0aH06ICR7Y2xlYW51cEVycm9yfWApO1xuICAgICAgICB9XG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgaWYgKHRpbWVvdXRIYW5kbGUpIHtcbiAgICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dEhhbmRsZSk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBjb25zdCBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBzdGFydFRpbWU7XG4gICAgICBsb2dnZXIuZXJyb3IoYFN0cmVhbWluZyBkb3dubG9hZCBmYWlsZWQ6ICR7ZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpfWApO1xuXG4gICAgICAvLyBMb2cgZmFpbGVkIHN0cmVhbWluZyBkb3dubG9hZFxuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnUEFUSF9UUkFWRVJTQUxfQVRURU1QVCcsXG4gICAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgICAgc291cmNlOiAnc2VjdXJlX2Rvd25sb2FkZXInLFxuICAgICAgICBkZXRhaWxzOiBgU3RyZWFtaW5nIGRvd25sb2FkIGZhaWxlZDogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9YCxcbiAgICAgICAgbWV0YWRhdGE6IHtcbiAgICAgICAgICB1cmwsXG4gICAgICAgICAgZGVzdGluYXRpb25QYXRoLFxuICAgICAgICAgIGR1cmF0aW9uLFxuICAgICAgICAgIGVycm9yVHlwZTogZXJyb3IgaW5zdGFuY2VvZiBEb3dubG9hZEVycm9yID8gZXJyb3IuY29kZSA6ICdVTktOT1dOJ1xuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IgJiYgZXJyb3IubmFtZSA9PT0gJ0Fib3J0RXJyb3InKSB7XG4gICAgICAgIHRocm93IERvd25sb2FkRXJyb3IudGltZW91dEVycm9yKGBEb3dubG9hZCB0aW1lZCBvdXQgYWZ0ZXIgJHt0aW1lb3V0fW1zYCk7XG4gICAgICB9XG5cbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIERvd25sb2FkRXJyb3IpIHtcbiAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICB9XG5cbiAgICAgIHRocm93IERvd25sb2FkRXJyb3IubmV0d29ya0Vycm9yKFxuICAgICAgICBgU3RyZWFtaW5nIGRvd25sb2FkIGZhaWxlZDogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9YCxcbiAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogdW5kZWZpbmVkXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZSBVUkwgZm9ybWF0IGFuZCBzZWN1cml0eSB3aXRoIFVuaWNvZGUgbm9ybWFsaXphdGlvblxuICAgKi9cbiAgcHJpdmF0ZSB2YWxpZGF0ZVVybCh1cmw6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghdXJsIHx8IHR5cGVvZiB1cmwgIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnZhbGlkYXRpb25FcnJvcignVVJMIG11c3QgYmUgYSBub24tZW1wdHkgc3RyaW5nJyk7XG4gICAgfVxuXG4gICAgLy8gU0VDVVJJVFkgRklYOiBETUNQLVNFQy0wMDQgLSBVbmljb2RlIG5vcm1hbGl6YXRpb24gb24gdXNlciBpbnB1dFxuICAgIGNvbnN0IHVuaWNvZGVWYWxpZGF0aW9uID0gVW5pY29kZVZhbGlkYXRvci5ub3JtYWxpemUodXJsKTtcbiAgICBjb25zdCBub3JtYWxpemVkVXJsID0gdW5pY29kZVZhbGlkYXRpb24ubm9ybWFsaXplZENvbnRlbnQ7XG4gICAgXG4gICAgaWYgKCF1bmljb2RlVmFsaWRhdGlvbi5pc1ZhbGlkKSB7XG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdVTklDT0RFX1ZBTElEQVRJT05fRVJST1InLFxuICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgIHNvdXJjZTogJ3NlY3VyZV9kb3dubG9hZGVyJyxcbiAgICAgICAgZGV0YWlsczogYFVSTCBjb250YWlucyBzdXNwaWNpb3VzIFVuaWNvZGUgcGF0dGVybnM6ICR7dW5pY29kZVZhbGlkYXRpb24uZGV0ZWN0ZWRJc3N1ZXM/LmpvaW4oJywgJyl9YCxcbiAgICAgICAgbWV0YWRhdGE6IHsgb3JpZ2luYWxVcmw6IHVybCwgbm9ybWFsaXplZFVybCB9XG4gICAgICB9KTtcbiAgICB9XG4gICAgXG4gICAgLy8gVXNlIG5vcm1hbGl6ZWQgVVJMIGZvciBmdXJ0aGVyIHZhbGlkYXRpb25cbiAgICB1cmwgPSBub3JtYWxpemVkVXJsO1xuXG4gICAgbGV0IHBhcnNlZFVybDogVVJMO1xuICAgIHRyeSB7XG4gICAgICBwYXJzZWRVcmwgPSBuZXcgVVJMKHVybCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRocm93IERvd25sb2FkRXJyb3IudmFsaWRhdGlvbkVycm9yKGBJbnZhbGlkIFVSTCBmb3JtYXQ6ICR7dXJsfWApO1xuICAgIH1cblxuICAgIC8vIFNFQ1VSSVRZOiBPbmx5IGFsbG93IEhUVFBTIGFuZCBIVFRQIHByb3RvY29sc1xuICAgIGlmICghWydodHRwczonLCAnaHR0cDonXS5pbmNsdWRlcyhwYXJzZWRVcmwucHJvdG9jb2wpKSB7XG4gICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnNlY3VyaXR5RXJyb3IoYFVuc3VwcG9ydGVkIHByb3RvY29sOiAke3BhcnNlZFVybC5wcm90b2NvbH0uIE9ubHkgSFRUUC9IVFRQUyBhbGxvd2VkLmApO1xuICAgIH1cblxuICAgIC8vIFNFQ1VSSVRZOiBQcmV2ZW50IHJlcXVlc3RzIHRvIGxvY2FsaG9zdC9wcml2YXRlIG5ldHdvcmtzXG4gICAgY29uc3QgaG9zdG5hbWUgPSBwYXJzZWRVcmwuaG9zdG5hbWUudG9Mb3dlckNhc2UoKTtcbiAgICBpZiAoaG9zdG5hbWUgPT09ICdsb2NhbGhvc3QnIHx8IGhvc3RuYW1lID09PSAnMTI3LjAuMC4xJyB8fCBob3N0bmFtZSA9PT0gJzo6MScpIHtcbiAgICAgIHRocm93IERvd25sb2FkRXJyb3Iuc2VjdXJpdHlFcnJvcignRG93bmxvYWRzIGZyb20gbG9jYWxob3N0IGFyZSBub3QgYWxsb3dlZCcpO1xuICAgIH1cblxuICAgIC8vIFNFQ1VSSVRZOiBDaGVjayBmb3IgcHJpdmF0ZSBJUCByYW5nZXMgKGJhc2ljIHByb3RlY3Rpb24pXG4gICAgaWYgKGhvc3RuYW1lLnN0YXJ0c1dpdGgoJzE5Mi4xNjguJykgfHwgaG9zdG5hbWUuc3RhcnRzV2l0aCgnMTAuJykgfHwgaG9zdG5hbWUuc3RhcnRzV2l0aCgnMTcyLicpKSB7XG4gICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnNlY3VyaXR5RXJyb3IoJ0Rvd25sb2FkcyBmcm9tIHByaXZhdGUgSVAgcmFuZ2VzIGFyZSBub3QgYWxsb3dlZCcpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZSBkZXN0aW5hdGlvbiBwYXRoIGZvciBzZWN1cml0eVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyB2YWxpZGF0ZURlc3RpbmF0aW9uUGF0aChmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICB0cnkge1xuICAgICAgLy8gVXNlIGV4aXN0aW5nIFBhdGhWYWxpZGF0b3IgZm9yIGNvbXByZWhlbnNpdmUgcGF0aCB2YWxpZGF0aW9uXG4gICAgICByZXR1cm4gYXdhaXQgUGF0aFZhbGlkYXRvci52YWxpZGF0ZVBlcnNvbmFQYXRoKGZpbGVQYXRoKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhyb3cgRG93bmxvYWRFcnJvci5zZWN1cml0eUVycm9yKFxuICAgICAgICBgSW52YWxpZCBkZXN0aW5hdGlvbiBwYXRoOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKX1gXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBGZXRjaCBjb250ZW50IHdpdGggc2l6ZSBhbmQgdGltZW91dCBsaW1pdHNcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZmV0Y2hXaXRoTGltaXRzKFxuICAgIHVybDogc3RyaW5nLFxuICAgIG1heFNpemU6IG51bWJlcixcbiAgICB0aW1lb3V0OiBudW1iZXIsXG4gICAgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz5cbiAgKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBhYm9ydENvbnRyb2xsZXIgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgY29uc3QgdGltZW91dEhhbmRsZSA9IHNldFRpbWVvdXQoKCkgPT4gYWJvcnRDb250cm9sbGVyLmFib3J0KCksIHRpbWVvdXQpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCB7XG4gICAgICAgIHNpZ25hbDogYWJvcnRDb250cm9sbGVyLnNpZ25hbCxcbiAgICAgICAgaGVhZGVyczoge1xuICAgICAgICAgICdVc2VyLUFnZW50JzogJ0RvbGxob3VzZU1DUC1TZWN1cmVEb3dubG9hZGVyLzEuMCcsXG4gICAgICAgICAgLi4uaGVhZGVyc1xuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEhUVFAgJHtyZXNwb25zZS5zdGF0dXN9OiAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICB9XG5cbiAgICAgIC8vIFNFQ1VSSVRZOiBDaGVjayBDb250ZW50LUxlbmd0aCBoZWFkZXIgaWYgYXZhaWxhYmxlXG4gICAgICBjb25zdCBjb250ZW50TGVuZ3RoID0gcmVzcG9uc2UuaGVhZGVycy5nZXQoJ2NvbnRlbnQtbGVuZ3RoJyk7XG4gICAgICBpZiAoY29udGVudExlbmd0aCAmJiBwYXJzZUludChjb250ZW50TGVuZ3RoLCAxMCkgPiBtYXhTaXplKSB7XG4gICAgICAgIHRocm93IERvd25sb2FkRXJyb3Iuc2VjdXJpdHlFcnJvcihcbiAgICAgICAgICBgQ29udGVudCBzaXplICR7Y29udGVudExlbmd0aH0gZXhjZWVkcyBsaW1pdCBvZiAke21heFNpemV9IGJ5dGVzYFxuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICAvLyBSZWFkIGNvbnRlbnQgd2l0aCBzaXplIGNoZWNraW5nXG4gICAgICBjb25zdCBjaHVua3M6IFVpbnQ4QXJyYXlbXSA9IFtdO1xuICAgICAgbGV0IHRvdGFsU2l6ZSA9IDA7XG5cbiAgICAgIGlmICghcmVzcG9uc2UuYm9keSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1Jlc3BvbnNlIGJvZHkgaXMgbnVsbCcpO1xuICAgICAgfVxuXG4gICAgICBjb25zdCByZWFkZXIgPSByZXNwb25zZS5ib2R5LmdldFJlYWRlcigpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgd2hpbGUgKHRydWUpIHtcbiAgICAgICAgICBjb25zdCB7IGRvbmUsIHZhbHVlIH0gPSBhd2FpdCByZWFkZXIucmVhZCgpO1xuICAgICAgICAgIGlmIChkb25lKSBicmVhaztcblxuICAgICAgICAgIHRvdGFsU2l6ZSArPSB2YWx1ZS5sZW5ndGg7XG4gICAgICAgICAgaWYgKHRvdGFsU2l6ZSA+IG1heFNpemUpIHtcbiAgICAgICAgICAgIHRocm93IERvd25sb2FkRXJyb3Iuc2VjdXJpdHlFcnJvcihcbiAgICAgICAgICAgICAgYENvbnRlbnQgc2l6ZSAke3RvdGFsU2l6ZX0gZXhjZWVkcyBsaW1pdCBvZiAke21heFNpemV9IGJ5dGVzYFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBjaHVua3MucHVzaCh2YWx1ZSk7XG4gICAgICAgIH1cbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHJlYWRlci5yZWxlYXNlTG9jaygpO1xuICAgICAgfVxuXG4gICAgICAvLyBDb21iaW5lIGNodW5rcyBhbmQgZGVjb2RlXG4gICAgICBjb25zdCB0b3RhbExlbmd0aCA9IGNodW5rcy5yZWR1Y2UoKHN1bSwgY2h1bmspID0+IHN1bSArIGNodW5rLmxlbmd0aCwgMCk7XG4gICAgICBjb25zdCBjb21iaW5lZCA9IG5ldyBVaW50OEFycmF5KHRvdGFsTGVuZ3RoKTtcbiAgICAgIGxldCBvZmZzZXQgPSAwO1xuICAgICAgZm9yIChjb25zdCBjaHVuayBvZiBjaHVua3MpIHtcbiAgICAgICAgY29tYmluZWQuc2V0KGNodW5rLCBvZmZzZXQpO1xuICAgICAgICBvZmZzZXQgKz0gY2h1bmsubGVuZ3RoO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gbmV3IFRleHREZWNvZGVyKCd1dGYtOCcpLmRlY29kZShjb21iaW5lZCk7XG5cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IgJiYgZXJyb3IubmFtZSA9PT0gJ0Fib3J0RXJyb3InKSB7XG4gICAgICAgIHRocm93IERvd25sb2FkRXJyb3IudGltZW91dEVycm9yKGBSZXF1ZXN0IHRpbWVkIG91dCBhZnRlciAke3RpbWVvdXR9bXNgKTtcbiAgICAgIH1cbiAgICAgIHRocm93IGVycm9yO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dEhhbmRsZSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGNvbnRlbnQgdHlwZSBpZiBzcGVjaWZpZWRcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgdmFsaWRhdGVDb250ZW50VHlwZShjb250ZW50OiBzdHJpbmcsIGV4cGVjdGVkVHlwZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gQmFzaWMgY29udGVudCB0eXBlIHZhbGlkYXRpb24gYmFzZWQgb24gY29udGVudCBhbmFseXNpc1xuICAgIHN3aXRjaCAoZXhwZWN0ZWRUeXBlLnRvTG93ZXJDYXNlKCkpIHtcbiAgICAgIGNhc2UgJ2pzb24nOlxuICAgICAgICB0cnkge1xuICAgICAgICAgIEpTT04ucGFyc2UoY29udGVudCk7XG4gICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgIHRocm93IERvd25sb2FkRXJyb3IudmFsaWRhdGlvbkVycm9yKCdDb250ZW50IGlzIG5vdCB2YWxpZCBKU09OJyk7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICd5YW1sJzpcbiAgICAgIGNhc2UgJ3ltbCc6XG4gICAgICAgIC8vIFVzZSBleGlzdGluZyBZQU1MIHZhbGlkYXRpb25cbiAgICAgICAgaWYgKCFTZWN1cml0eUNvbnRlbnRWYWxpZGF0b3IudmFsaWRhdGVZYW1sQ29udGVudChjb250ZW50KSkge1xuICAgICAgICAgIHRocm93IERvd25sb2FkRXJyb3IudmFsaWRhdGlvbkVycm9yKCdDb250ZW50IGlzIG5vdCB2YWxpZCBZQU1MJyk7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICdtYXJrZG93bic6XG4gICAgICBjYXNlICdtZCc6XG4gICAgICAgIC8vIEJhc2ljIG1hcmtkb3duIHZhbGlkYXRpb24gKGNoZWNrIGZvciBmcm9udG1hdHRlciBmb3JtYXQpXG4gICAgICAgIGlmIChjb250ZW50LnN0YXJ0c1dpdGgoJy0tLScpKSB7XG4gICAgICAgICAgY29uc3QgZnJvbnRtYXR0ZXJFbmQgPSBjb250ZW50LmluZGV4T2YoJ1xcbi0tLVxcbicsIDMpO1xuICAgICAgICAgIGlmIChmcm9udG1hdHRlckVuZCA9PT0gLTEpIHtcbiAgICAgICAgICAgIHRocm93IERvd25sb2FkRXJyb3IudmFsaWRhdGlvbkVycm9yKCdJbnZhbGlkIG1hcmtkb3duIGZyb250bWF0dGVyIGZvcm1hdCcpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgTm8gc3BlY2lmaWMgdmFsaWRhdGlvbiBmb3IgY29udGVudCB0eXBlOiAke2V4cGVjdGVkVHlwZX1gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQXRvbWljIGZpbGUgd3JpdGUgdXNpbmcgRmlsZUxvY2tNYW5hZ2VyXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGF0b21pY1dyaXRlRmlsZShmaWxlUGF0aDogc3RyaW5nLCBjb250ZW50OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCByZXNvdXJjZSA9IGBkb3dubG9hZDoke2ZpbGVQYXRofWA7XG4gICAgXG4gICAgYXdhaXQgRmlsZUxvY2tNYW5hZ2VyLndpdGhMb2NrKHJlc291cmNlLCBhc3luYyAoKSA9PiB7XG4gICAgICAvLyBFbnN1cmUgZGlyZWN0b3J5IGV4aXN0c1xuICAgICAgYXdhaXQgZnMubWtkaXIocGF0aC5kaXJuYW1lKGZpbGVQYXRoKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICBcbiAgICAgIC8vIFVzZSBGaWxlTG9ja01hbmFnZXIncyBhdG9taWMgd3JpdGVcbiAgICAgIGF3YWl0IEZpbGVMb2NrTWFuYWdlci5hdG9taWNXcml0ZUZpbGUoZmlsZVBhdGgsIGNvbnRlbnQsIHsgZW5jb2Rpbmc6ICd1dGYtOCcgfSk7XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogRGlyZWN0IGZpbGUgd3JpdGUgKG5vbi1hdG9taWMsIGZvciB3aGVuIGF0b21pYyBpcyBkaXNhYmxlZClcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZGlyZWN0V3JpdGVGaWxlKGZpbGVQYXRoOiBzdHJpbmcsIGNvbnRlbnQ6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzXG4gICAgYXdhaXQgZnMubWtkaXIocGF0aC5kaXJuYW1lKGZpbGVQYXRoKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgXG4gICAgLy8gRGlyZWN0IHdyaXRlXG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKGZpbGVQYXRoLCBjb250ZW50LCAndXRmLTgnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZW5lcmF0ZSB0ZW1wb3JhcnkgZmlsZSBwYXRoIGZvciBhdG9taWMgb3BlcmF0aW9uc1xuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBnZXRUZW1wRmlsZVBhdGgob3JpZ2luYWxQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGRpciA9IHBhdGguZGlybmFtZShvcmlnaW5hbFBhdGgpO1xuICAgIGNvbnN0IGJhc2VuYW1lID0gcGF0aC5iYXNlbmFtZShvcmlnaW5hbFBhdGgpO1xuICAgIGNvbnN0IHJhbmRvbSA9IHJhbmRvbUJ5dGVzKDgpLnRvU3RyaW5nKCdoZXgnKTtcbiAgICBjb25zdCB0ZW1wRGlyID0gcGF0aC5qb2luKGRpciwgdGhpcy50ZW1wRGlyKTtcbiAgICBcbiAgICAvLyBFbnN1cmUgdGVtcCBkaXJlY3RvcnkgZXhpc3RzXG4gICAgYXdhaXQgZnMubWtkaXIodGVtcERpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgXG4gICAgcmV0dXJuIHBhdGguam9pbih0ZW1wRGlyLCBgJHtiYXNlbmFtZX0uJHtyYW5kb219LnRtcGApO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIHJhdGUgbGltaXRzIGZvciBkb3dubG9hZHNcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgY2hlY2tSYXRlTGltaXQodXJsOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAvLyBDaGVjayBnbG9iYWwgcmF0ZSBsaW1pdFxuICAgIGNvbnN0IGdsb2JhbFN0YXR1cyA9IHRoaXMuZ2xvYmFsUmF0ZUxpbWl0ZXIuY2hlY2tMaW1pdCgpO1xuICAgIGlmICghZ2xvYmFsU3RhdHVzLmFsbG93ZWQpIHtcbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ1JBVEVfTElNSVRfRVhDRUVERUQnLFxuICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgIHNvdXJjZTogJ3NlY3VyZV9kb3dubG9hZGVyJyxcbiAgICAgICAgZGV0YWlsczogYEdsb2JhbCBkb3dubG9hZCByYXRlIGxpbWl0IGV4Y2VlZGVkLiBSZXRyeSBhZnRlciAke2dsb2JhbFN0YXR1cy5yZXRyeUFmdGVyTXN9bXNgLFxuICAgICAgICBtZXRhZGF0YTogeyB1cmwsIHJldHJ5QWZ0ZXJNczogZ2xvYmFsU3RhdHVzLnJldHJ5QWZ0ZXJNcyB9XG4gICAgICB9KTtcbiAgICAgIHRocm93IERvd25sb2FkRXJyb3Iuc2VjdXJpdHlFcnJvcihcbiAgICAgICAgYERvd25sb2FkIHJhdGUgbGltaXQgZXhjZWVkZWQuIFBsZWFzZSByZXRyeSBhZnRlciAke01hdGguY2VpbChnbG9iYWxTdGF0dXMucmV0cnlBZnRlck1zISAvIDEwMDApfSBzZWNvbmRzYFxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBwZXItVVJMIHJhdGUgbGltaXRcbiAgICBjb25zdCBwYXJzZWRVcmwgPSBuZXcgVVJMKHVybCk7XG4gICAgY29uc3QgdXJsS2V5ID0gYCR7cGFyc2VkVXJsLmhvc3RuYW1lfToke3BhcnNlZFVybC5wb3J0IHx8IChwYXJzZWRVcmwucHJvdG9jb2wgPT09ICdodHRwczonID8gJzQ0MycgOiAnODAnKX1gO1xuICAgIFxuICAgIGlmICghdGhpcy51cmxSYXRlTGltaXRlcnMuaGFzKHVybEtleSkpIHtcbiAgICAgIHRoaXMudXJsUmF0ZUxpbWl0ZXJzLnNldCh1cmxLZXksIG5ldyBSYXRlTGltaXRlcih7XG4gICAgICAgIG1heFJlcXVlc3RzOiAxMCwgLy8gMTAgcmVxdWVzdHMgcGVyIGhvdXIgcGVyIFVSTFxuICAgICAgICB3aW5kb3dNczogNjAgKiA2MCAqIDEwMDAsXG4gICAgICAgIG1pbkRlbGF5TXM6IDUwMDAgLy8gNSBzZWNvbmQgbWluaW11bSBkZWxheSBiZXR3ZWVuIHJlcXVlc3RzIHRvIHNhbWUgVVJMXG4gICAgICB9KSk7XG4gICAgfVxuICAgIFxuICAgIGNvbnN0IHVybExpbWl0ZXIgPSB0aGlzLnVybFJhdGVMaW1pdGVycy5nZXQodXJsS2V5KSE7XG4gICAgY29uc3QgdXJsU3RhdHVzID0gdXJsTGltaXRlci5jaGVja0xpbWl0KCk7XG4gICAgaWYgKCF1cmxTdGF0dXMuYWxsb3dlZCkge1xuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnUkFURV9MSU1JVF9FWENFRURFRCcsXG4gICAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgICAgc291cmNlOiAnc2VjdXJlX2Rvd25sb2FkZXInLFxuICAgICAgICBkZXRhaWxzOiBgUGVyLVVSTCBkb3dubG9hZCByYXRlIGxpbWl0IGV4Y2VlZGVkIGZvciAke3VybEtleX0uIFJldHJ5IGFmdGVyICR7dXJsU3RhdHVzLnJldHJ5QWZ0ZXJNc31tc2AsXG4gICAgICAgIG1ldGFkYXRhOiB7IHVybCwgdXJsS2V5LCByZXRyeUFmdGVyTXM6IHVybFN0YXR1cy5yZXRyeUFmdGVyTXMgfVxuICAgICAgfSk7XG4gICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnNlY3VyaXR5RXJyb3IoXG4gICAgICAgIGBUb28gbWFueSByZXF1ZXN0cyB0byAke3VybEtleX0uIFBsZWFzZSByZXRyeSBhZnRlciAke01hdGguY2VpbCh1cmxTdGF0dXMucmV0cnlBZnRlck1zISAvIDEwMDApfSBzZWNvbmRzYFxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBDb25zdW1lIHJhdGUgbGltaXQgdG9rZW5zXG4gICAgdGhpcy5nbG9iYWxSYXRlTGltaXRlci5jb25zdW1lVG9rZW4oKTtcbiAgICB1cmxMaW1pdGVyLmNvbnN1bWVUb2tlbigpO1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGNvbnRlbnQgY2hlY2tzdW0gZm9yIGludGVncml0eSB2ZXJpZmljYXRpb25cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgdmFsaWRhdGVDaGVja3N1bShjb250ZW50OiBzdHJpbmcsIGV4cGVjdGVkQ2hlY2tzdW06IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IG5vcm1hbGl6ZWRFeHBlY3RlZCA9IGV4cGVjdGVkQ2hlY2tzdW0udG9Mb3dlckNhc2UoKS50cmltKCk7XG4gICAgXG4gICAgLy8gVmFsaWRhdGUgY2hlY2tzdW0gZm9ybWF0IChTSEEtMjU2IHNob3VsZCBiZSA2NCBoZXggY2hhcmFjdGVycylcbiAgICBpZiAoIS9eW2EtZjAtOV17NjR9JC8udGVzdChub3JtYWxpemVkRXhwZWN0ZWQpKSB7XG4gICAgICB0aHJvdyBEb3dubG9hZEVycm9yLnZhbGlkYXRpb25FcnJvcignSW52YWxpZCBjaGVja3N1bSBmb3JtYXQuIEV4cGVjdGVkIFNIQS0yNTYgKDY0IGhleCBjaGFyYWN0ZXJzKScpO1xuICAgIH1cblxuICAgIGNvbnN0IGNvbnRlbnRCdWZmZXIgPSBCdWZmZXIuZnJvbShjb250ZW50LCAndXRmLTgnKTtcbiAgICBjb25zdCBhY3R1YWxDaGVja3N1bSA9IGNyZWF0ZUhhc2goJ3NoYTI1NicpLnVwZGF0ZShjb250ZW50QnVmZmVyKS5kaWdlc3QoJ2hleCcpO1xuICAgIFxuICAgIGlmIChhY3R1YWxDaGVja3N1bSAhPT0gbm9ybWFsaXplZEV4cGVjdGVkKSB7XG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdDT05URU5UX0lOSkVDVElPTl9BVFRFTVBUJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdISUdIJyxcbiAgICAgICAgc291cmNlOiAnc2VjdXJlX2Rvd25sb2FkZXInLFxuICAgICAgICBkZXRhaWxzOiBgQ2hlY2tzdW0gbWlzbWF0Y2ggZGV0ZWN0ZWQgLSBwb3NzaWJsZSBjb250ZW50IHRhbXBlcmluZ2AsXG4gICAgICAgIG1ldGFkYXRhOiB7IFxuICAgICAgICAgIGV4cGVjdGVkQ2hlY2tzdW06IG5vcm1hbGl6ZWRFeHBlY3RlZCxcbiAgICAgICAgICBhY3R1YWxDaGVja3N1bSxcbiAgICAgICAgICBjb250ZW50TGVuZ3RoOiBjb250ZW50Lmxlbmd0aFxuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHRocm93IERvd25sb2FkRXJyb3Iuc2VjdXJpdHlFcnJvcihcbiAgICAgICAgYENvbnRlbnQgY2hlY2tzdW0gdmVyaWZpY2F0aW9uIGZhaWxlZC4gRXhwZWN0ZWQ6ICR7bm9ybWFsaXplZEV4cGVjdGVkfSwgR290OiAke2FjdHVhbENoZWNrc3VtfWBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgbG9nZ2VyLmRlYnVnKGBDaGVja3N1bSB2YWxpZGF0aW9uIHBhc3NlZDogJHthY3R1YWxDaGVja3N1bX1gKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBjb250ZW50IHZhbGlkYXRvciB0aGF0IGNvbWJpbmVzIG11bHRpcGxlIHZhbGlkYXRvcnNcbiAgICovXG4gIHN0YXRpYyBjb21iaW5lVmFsaWRhdG9ycyguLi52YWxpZGF0b3JzOiBDb250ZW50VmFsaWRhdG9yRnVuY3Rpb25bXSk6IENvbnRlbnRWYWxpZGF0b3JGdW5jdGlvbiB7XG4gICAgcmV0dXJuIGFzeW5jIChjb250ZW50OiBzdHJpbmcpOiBQcm9taXNlPFZhbGlkYXRpb25SZXN1bHQ+ID0+IHtcbiAgICAgIGZvciAoY29uc3QgdmFsaWRhdG9yIG9mIHZhbGlkYXRvcnMpIHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdmFsaWRhdG9yKGNvbnRlbnQpO1xuICAgICAgICBpZiAoIXJlc3VsdC5pc1ZhbGlkKSB7XG4gICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHsgaXNWYWxpZDogdHJ1ZSB9O1xuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgY29udGVudCB2YWxpZGF0b3IgZm9yIEpTT04gY29udGVudFxuICAgKi9cbiAgc3RhdGljIGpzb25WYWxpZGF0b3IoKTogQ29udGVudFZhbGlkYXRvckZ1bmN0aW9uIHtcbiAgICByZXR1cm4gYXN5bmMgKGNvbnRlbnQ6IHN0cmluZyk6IFByb21pc2U8VmFsaWRhdGlvblJlc3VsdD4gPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgSlNPTi5wYXJzZShjb250ZW50KTtcbiAgICAgICAgcmV0dXJuIHsgaXNWYWxpZDogdHJ1ZSB9O1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBpc1ZhbGlkOiBmYWxzZSxcbiAgICAgICAgICBlcnJvck1lc3NhZ2U6IGBJbnZhbGlkIEpTT046ICR7ZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpfWAsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdtZWRpdW0nXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBjb250ZW50IHZhbGlkYXRvciBmb3IgWUFNTCBjb250ZW50XG4gICAqL1xuICBzdGF0aWMgeWFtbFZhbGlkYXRvcigpOiBDb250ZW50VmFsaWRhdG9yRnVuY3Rpb24ge1xuICAgIHJldHVybiBhc3luYyAoY29udGVudDogc3RyaW5nKTogUHJvbWlzZTxWYWxpZGF0aW9uUmVzdWx0PiA9PiB7XG4gICAgICBjb25zdCBpc1ZhbGlkID0gU2VjdXJpdHlDb250ZW50VmFsaWRhdG9yLnZhbGlkYXRlWWFtbENvbnRlbnQoY29udGVudCk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBpc1ZhbGlkLFxuICAgICAgICBlcnJvck1lc3NhZ2U6IGlzVmFsaWQgPyB1bmRlZmluZWQgOiAnSW52YWxpZCBvciBtYWxpY2lvdXMgWUFNTCBjb250ZW50JyxcbiAgICAgICAgc2V2ZXJpdHk6IGlzVmFsaWQgPyAnbG93JyA6ICdoaWdoJ1xuICAgICAgfTtcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIGNvbnRlbnQgdmFsaWRhdG9yIGZvciBtYXJrZG93biBjb250ZW50XG4gICAqL1xuICBzdGF0aWMgbWFya2Rvd25WYWxpZGF0b3IoKTogQ29udGVudFZhbGlkYXRvckZ1bmN0aW9uIHtcbiAgICByZXR1cm4gYXN5bmMgKGNvbnRlbnQ6IHN0cmluZyk6IFByb21pc2U8VmFsaWRhdGlvblJlc3VsdD4gPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gVXNlIGV4aXN0aW5nIHBlcnNvbmEgY29udGVudCBzYW5pdGl6YXRpb24gZm9yIG1hcmtkb3duXG4gICAgICAgIFNlY3VyaXR5Q29udGVudFZhbGlkYXRvci5zYW5pdGl6ZVBlcnNvbmFDb250ZW50KGNvbnRlbnQpO1xuICAgICAgICByZXR1cm4geyBpc1ZhbGlkOiB0cnVlIH07XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgIGVycm9yTWVzc2FnZTogYEludmFsaWQgbWFya2Rvd246ICR7ZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpfWAsXG4gICAgICAgICAgc2V2ZXJpdHk6IGVycm9yIGluc3RhbmNlb2YgU2VjdXJpdHlFcnJvciA/ICdjcml0aWNhbCcgOiAnbWVkaXVtJ1xuICAgICAgICB9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgY29udGVudCB2YWxpZGF0b3Igd2l0aCBzaXplIGxpbWl0c1xuICAgKi9cbiAgc3RhdGljIHNpemVWYWxpZGF0b3IobWF4U2l6ZTogbnVtYmVyKTogQ29udGVudFZhbGlkYXRvckZ1bmN0aW9uIHtcbiAgICByZXR1cm4gYXN5bmMgKGNvbnRlbnQ6IHN0cmluZyk6IFByb21pc2U8VmFsaWRhdGlvblJlc3VsdD4gPT4ge1xuICAgICAgY29uc3Qgc2l6ZSA9IEJ1ZmZlci5ieXRlTGVuZ3RoKGNvbnRlbnQsICd1dGYtOCcpO1xuICAgICAgaWYgKHNpemUgPiBtYXhTaXplKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgaXNWYWxpZDogZmFsc2UsXG4gICAgICAgICAgZXJyb3JNZXNzYWdlOiBgQ29udGVudCBzaXplICR7c2l6ZX0gZXhjZWVkcyBsaW1pdCBvZiAke21heFNpemV9IGJ5dGVzYCxcbiAgICAgICAgICBzZXZlcml0eTogJ2hpZ2gnXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgICByZXR1cm4geyBpc1ZhbGlkOiB0cnVlIH07XG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBjb250ZW50IHZhbGlkYXRvciB0aGF0IGNoZWNrcyBmb3IgZm9yYmlkZGVuIHBhdHRlcm5zXG4gICAqL1xuICBzdGF0aWMgcGF0dGVyblZhbGlkYXRvcihcbiAgICBmb3JiaWRkZW5QYXR0ZXJuczogUmVnRXhwW10sXG4gICAgZXJyb3JNZXNzYWdlOiBzdHJpbmcgPSAnRm9yYmlkZGVuIHBhdHRlcm4gZGV0ZWN0ZWQnXG4gICk6IENvbnRlbnRWYWxpZGF0b3JGdW5jdGlvbiB7XG4gICAgcmV0dXJuIGFzeW5jIChjb250ZW50OiBzdHJpbmcpOiBQcm9taXNlPFZhbGlkYXRpb25SZXN1bHQ+ID0+IHtcbiAgICAgIGZvciAoY29uc3QgcGF0dGVybiBvZiBmb3JiaWRkZW5QYXR0ZXJucykge1xuICAgICAgICBpZiAocGF0dGVybi50ZXN0KGNvbnRlbnQpKSB7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgICAgZXJyb3JNZXNzYWdlLFxuICAgICAgICAgICAgc2V2ZXJpdHk6ICdoaWdoJyxcbiAgICAgICAgICAgIG1ldGFkYXRhOiB7IHBhdHRlcm46IHBhdHRlcm4uc291cmNlIH1cbiAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4geyBpc1ZhbGlkOiB0cnVlIH07XG4gICAgfTtcbiAgfVxufSJdfQ==
|