UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.

792 lines 69.9 kB
import { logger } from '../../../core/utils/logger.js'; /** * Validates route configurations for correctness and safety */ export class RouteValidator { static VALID_TLS_MODES = ['terminate', 'passthrough', 'terminate-and-reencrypt']; static VALID_ACTION_TYPES = ['forward', 'socket-handler']; static VALID_PROTOCOLS = ['tcp', 'http', 'https', 'ws', 'wss', 'udp', 'quic', 'http3']; static MAX_PORTS = 100; static MAX_DOMAINS = 1000; static MAX_HEADER_SIZE = 8192; /** * Validate a single route configuration */ static validateRoute(route) { const errors = []; // Validate route has a name if (!route.name || typeof route.name !== 'string') { errors.push('Route must have a valid name'); } // Validate match criteria if (!route.match) { errors.push('Route must have match criteria'); } else { // Validate ports if (route.match.ports) { const ports = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports]; if (ports.length > this.MAX_PORTS) { errors.push(`Too many ports specified (max ${this.MAX_PORTS})`); } for (const port of ports) { if (typeof port === 'number') { if (!this.isValidPort(port)) { errors.push(`Invalid port: ${port}. Must be between 1 and 65535`); } } else if (typeof port === 'object' && 'from' in port && 'to' in port) { if (!this.isValidPort(port.from)) { errors.push(`Invalid port range start: ${port.from}. Must be between 1 and 65535`); } if (!this.isValidPort(port.to)) { errors.push(`Invalid port range end: ${port.to}. Must be between 1 and 65535`); } if (port.from > port.to) { errors.push(`Invalid port range: ${port.from}-${port.to} (start > end)`); } } else { errors.push(`Invalid port configuration: ${JSON.stringify(port)}`); } } } // Validate domains if (route.match.domains) { const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains]; if (domains.length > this.MAX_DOMAINS) { errors.push(`Too many domains specified (max ${this.MAX_DOMAINS})`); } for (const domain of domains) { if (!this.isValidDomain(domain)) { errors.push(`Invalid domain pattern: ${domain}`); } } } // Validate paths if (route.match.path) { const paths = Array.isArray(route.match.path) ? route.match.path : [route.match.path]; for (const path of paths) { if (!this.isValidPath(path)) { errors.push(`Invalid path pattern: ${path}`); } } } // Validate client IPs if (route.match.clientIp) { const ips = Array.isArray(route.match.clientIp) ? route.match.clientIp : [route.match.clientIp]; for (const ip of ips) { if (!this.isValidIPPattern(ip)) { errors.push(`Invalid IP pattern: ${ip}`); } } } // Validate inbound PROXY protocol policy if (route.match.inboundProxyProtocol) { const policy = route.match.inboundProxyProtocol; if (!['reject', 'optional', 'required'].includes(policy.mode)) { errors.push(`Invalid inbound PROXY protocol mode: ${policy.mode}`); } if (policy.trustedProxyIPs !== undefined) { if (!Array.isArray(policy.trustedProxyIPs) || policy.trustedProxyIPs.length === 0) { errors.push('inboundProxyProtocol.trustedProxyIPs must be a non-empty array when specified'); } else { for (const ip of policy.trustedProxyIPs) { if (!this.isValidIPPattern(ip)) { errors.push(`Invalid trusted proxy IP pattern: ${ip}`); } } } } } // Validate headers if (route.match.headers) { for (const [key, value] of Object.entries(route.match.headers)) { if (key.length > 256) { errors.push(`Header name too long: ${key}`); } const headerValue = String(value); if (headerValue.length > this.MAX_HEADER_SIZE) { errors.push(`Header value too long for ${key} (max ${this.MAX_HEADER_SIZE} bytes)`); } if (!/^[\x20-\x7E]+$/.test(key)) { errors.push(`Invalid header name: ${key} (must be printable ASCII)`); } } } // Protocol validation removed - not part of IRouteMatch interface } // Validate action if (!route.action) { errors.push('Route must have an action'); } else { // Validate action type if (!route.action.type || !this.VALID_ACTION_TYPES.includes(route.action.type)) { errors.push(`Invalid action type: ${route.action.type}. Must be one of: ${this.VALID_ACTION_TYPES.join(', ')}`); } // Validate socket-handler (TCP socketHandler or UDP datagramHandler) if (route.action.type === 'socket-handler') { if (typeof route.action.socketHandler !== 'function' && typeof route.action.datagramHandler !== 'function') { errors.push('socket-handler action requires a socketHandler or datagramHandler function'); } } // Validate forward target if (route.action.type === 'forward') { if (!route.action.targets || route.action.targets.length === 0) { errors.push('Forward action must have at least one target'); } else { for (const target of route.action.targets) { if (!target.host) { errors.push('Target must have a host'); } else if (typeof target.host !== 'string' && !Array.isArray(target.host) && typeof target.host !== 'function') { errors.push('Target host must be a string, array of strings, or function'); } if (target.port) { if (typeof target.port === 'number' && !this.isValidPort(target.port)) { errors.push(`Invalid target port: ${target.port}`); } else if (target.port !== 'preserve' && typeof target.port !== 'function' && typeof target.port !== 'number') { errors.push(`Invalid target port configuration: ${target.port}`); } } } } } // Validate TLS settings if (route.action.tls) { if (route.action.tls.mode && !this.VALID_TLS_MODES.includes(route.action.tls.mode)) { errors.push(`Invalid TLS mode: ${route.action.tls.mode}. Must be one of: ${this.VALID_TLS_MODES.join(', ')}`); } if (route.action.tls.certificate) { if (route.action.tls.certificate !== 'auto' && typeof route.action.tls.certificate !== 'object') { errors.push('TLS certificate must be "auto" or a certificate configuration object'); } } if (route.action.tls.versions) { for (const version of route.action.tls.versions) { if (!['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'].includes(version)) { errors.push(`Invalid TLS version: ${version}`); } } } } // QUIC routes require TLS with termination (QUIC mandates TLS 1.3) if (route.action.udp?.quic && route.action.type === 'forward') { if (!route.action.tls) { errors.push('QUIC routes require TLS configuration (action.tls) — QUIC mandates TLS 1.3'); } else if (route.action.tls.mode === 'passthrough') { errors.push('QUIC routes cannot use TLS mode "passthrough" — use "terminate" or "terminate-and-reencrypt"'); } } // Protocol quic/http3 requires transport udp or all if (route.match?.protocol && ['quic', 'http3'].includes(route.match.protocol)) { if (route.match.transport && route.match.transport !== 'udp' && route.match.transport !== 'all') { errors.push(`Protocol "${route.match.protocol}" requires transport "udp" or "all"`); } } } // Validate security settings if (route.security) { // Validate IP allow/block lists if (route.security.ipAllowList) { const allowList = Array.isArray(route.security.ipAllowList) ? route.security.ipAllowList : [route.security.ipAllowList]; for (const entry of allowList) { if (typeof entry === 'string') { if (!this.isValidIPPattern(entry)) { errors.push(`Invalid IP pattern in allow list: ${entry}`); } } else if (entry && typeof entry === 'object') { if (!this.isValidIPPattern(entry.ip)) { errors.push(`Invalid IP pattern in domain-scoped allow entry: ${entry.ip}`); } if (!Array.isArray(entry.domains) || entry.domains.length === 0) { errors.push(`Domain-scoped allow entry for ${entry.ip} must have non-empty domains array`); } } } } if (route.security.ipBlockList) { const blockList = Array.isArray(route.security.ipBlockList) ? route.security.ipBlockList : [route.security.ipBlockList]; for (const ip of blockList) { if (!this.isValidIPPattern(ip)) { errors.push(`Invalid IP pattern in block list: ${ip}`); } } } // Validate rate limits if (route.security.rateLimit) { if (route.security.rateLimit.maxRequests && route.security.rateLimit.maxRequests < 0) { errors.push('Rate limit maxRequests must be positive'); } if (route.security.rateLimit.window && route.security.rateLimit.window < 0) { errors.push('Rate limit window must be positive'); } } // Validate connection limits if (route.security.maxConnections && route.security.maxConnections < 0) { errors.push('Max connections must be positive'); } } // Validate priority if (route.priority !== undefined && (route.priority < 0 || route.priority > 10000)) { errors.push('Priority must be between 0 and 10000'); } return { valid: errors.length === 0, errors }; } /** * Validate multiple route configurations */ static validateRoutes(routes) { const errorMap = new Map(); let valid = true; // Check for duplicate route names const routeNames = new Set(); for (const route of routes) { if (route.name && routeNames.has(route.name)) { const existingErrors = errorMap.get(route.name) || []; existingErrors.push('Duplicate route name'); errorMap.set(route.name, existingErrors); valid = false; } if (route.name) { routeNames.add(route.name); } } // Validate each route for (const route of routes) { const result = this.validateRoute(route); if (!result.valid) { errorMap.set(route.name || 'unnamed', result.errors); valid = false; } } // Check for conflicting routes const conflicts = this.findRouteConflicts(routes); if (conflicts.length > 0) { for (const conflict of conflicts) { const existingErrors = errorMap.get(conflict.route) || []; existingErrors.push(conflict.message); errorMap.set(conflict.route, existingErrors); } valid = false; } return { valid, errors: errorMap }; } /** * Find potential conflicts between routes */ static findRouteConflicts(routes) { const conflicts = []; // Group routes by port const portMap = new Map(); for (const route of routes) { if (route.match?.ports) { const ports = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports]; // Expand port ranges to individual ports const expandedPorts = []; for (const port of ports) { if (typeof port === 'number') { expandedPorts.push(port); } else if (typeof port === 'object' && 'from' in port && 'to' in port) { for (let p = port.from; p <= port.to; p++) { expandedPorts.push(p); } } } for (const port of expandedPorts) { const routesOnPort = portMap.get(port) || []; routesOnPort.push(route); portMap.set(port, routesOnPort); } } } // Check for conflicting catch-all routes on the same port for (const [port, routesOnPort] of portMap) { const catchAllRoutes = routesOnPort.filter(r => this.isCatchAllDomainMatch(r.match) && !this.hasSourceScope(r.match)); if (catchAllRoutes.length > 1) { for (const route of catchAllRoutes) { conflicts.push({ route: route.name || 'unnamed', message: `Multiple catch-all routes on port ${port}` }); } } } // Inbound PROXY protocol is listener-scoped. A route-level declaration is // a shorthand for the full port/transport listener policy, so every route // sharing that listener must either all omit it or all use an identical one. const listenerPolicies = new Map(); for (const route of routes) { if (!route.match?.ports) { continue; } for (const port of this.expandPorts(route.match.ports)) { for (const transport of this.transportsForMatch(route.match.transport)) { const key = `${transport}:${port}`; const fingerprint = this.inboundProxyPolicyFingerprint(route.match.inboundProxyProtocol); const existing = listenerPolicies.get(key); if (!existing) { listenerPolicies.set(key, { route: route.name || 'unnamed', fingerprint, }); } else if (existing.fingerprint !== fingerprint) { conflicts.push({ route: route.name || 'unnamed', message: `Conflicting inbound PROXY protocol policy on ${transport} port ${port} (differs from ${existing.route})`, }); } } } } return conflicts; } static expandPorts(ports) { const entries = Array.isArray(ports) ? ports : [ports]; const expandedPorts = []; for (const port of entries) { if (typeof port === 'number') { expandedPorts.push(port); } else if (typeof port === 'object' && 'from' in port && 'to' in port) { for (let current = port.from; current <= port.to; current++) { expandedPorts.push(current); } } } return expandedPorts; } static transportsForMatch(transport) { if (transport === 'udp') { return ['udp']; } if (transport === 'all') { return ['tcp', 'udp']; } return ['tcp']; } static inboundProxyPolicyFingerprint(policy) { if (!policy) { return 'default'; } return JSON.stringify({ mode: policy.mode, trustedProxyIPs: [...(policy.trustedProxyIPs ?? [])].sort(), }); } static isCatchAllDomainMatch(match) { return !match.domains || (Array.isArray(match.domains) && match.domains.includes('*')) || match.domains === '*'; } static hasSourceScope(match) { return Array.isArray(match.clientIp) ? match.clientIp.length > 0 : Boolean(match.clientIp); } /** * Validate port number */ static isValidPort(port) { return Number.isInteger(port) && port >= 1 && port <= 65535; } /** * Validate domain pattern */ static isValidDomain(domain) { if (!domain || typeof domain !== 'string') return false; if (domain === '*') return true; if (domain === 'localhost') return true; // Allow both *.domain and *domain patterns // Also allow regular domains and subdomains const domainPatterns = [ // Standard domain with optional wildcard subdomain (*.example.com) /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/, // Wildcard prefix without dot (*example.com) /^\*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/, // IP address /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // IPv6 address /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/ ]; return domainPatterns.some(pattern => pattern.test(domain)); } /** * Validate path pattern */ static isValidPath(path) { if (!path || typeof path !== 'string') return false; if (!path.startsWith('/')) return false; // Check for invalid characters if (!/^[a-zA-Z0-9/_*:{}.-]+$/.test(path)) return false; // Validate parameter syntax const paramPattern = /\{[a-zA-Z_][a-zA-Z0-9_]*\}/g; const params = path.match(paramPattern) || []; for (const param of params) { if (param.length > 32) return false; } return true; } /** * Validate IP pattern */ static isValidIPPattern(ip) { if (!ip || typeof ip !== 'string') return false; if (ip === '*') return true; // Check for CIDR notation if (ip.includes('/')) { const [addr, prefix] = ip.split('/'); const prefixNum = parseInt(prefix, 10); if (addr.includes(':')) { // IPv6 CIDR return this.isValidIPv6(addr) && prefixNum >= 0 && prefixNum <= 128; } else { // IPv4 CIDR return this.isValidIPv4(addr) && prefixNum >= 0 && prefixNum <= 32; } } // Check for range if (ip.includes('-')) { const [start, end] = ip.split('-'); return (this.isValidIPv4(start) && this.isValidIPv4(end)) || (this.isValidIPv6(start) && this.isValidIPv6(end)); } // Check for wildcards in IPv4 if (ip.includes('*') && !ip.includes(':')) { const parts = ip.split('.'); // Allow 1-4 parts for wildcard patterns (e.g., '10.*', '192.168.*', '192.168.1.*') if (parts.length < 1 || parts.length > 4) return false; for (const part of parts) { if (part !== '*' && !/^\d{1,3}$/.test(part)) return false; if (part !== '*' && parseInt(part, 10) > 255) return false; } return true; } // Regular IP address return this.isValidIPv4(ip) || this.isValidIPv6(ip); } /** * Validate IPv4 address */ static isValidIPv4(ip) { const parts = ip.split('.'); if (parts.length !== 4) return false; for (const part of parts) { const num = parseInt(part, 10); if (isNaN(num) || num < 0 || num > 255) return false; } return true; } /** * Validate IPv6 address */ static isValidIPv6(ip) { // IPv6 validation including IPv6-mapped IPv4 addresses (::ffff:x.x.x.x) const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){0,6}|::1|::|::ffff:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i; return ipv6Pattern.test(ip); } /** * Log validation errors */ static logValidationErrors(errors) { for (const [routeName, routeErrors] of errors) { logger.log('error', `Route validation failed for ${routeName}:`, { route: routeName, errors: routeErrors, component: 'route-validator' }); for (const error of routeErrors) { logger.log('error', ` - ${error}`, { route: routeName, component: 'route-validator' }); } } } } // ============================================================================ // Functional API (for backwards compatibility with route-validators.ts) // ============================================================================ /** * Validates a port range or port number * @param port Port number, port range, or port function * @returns True if valid, false otherwise */ export function isValidPort(port) { if (typeof port === 'number') { return port > 0 && port < 65536; } else if (Array.isArray(port)) { return port.every(p => (typeof p === 'number' && p > 0 && p < 65536) || (typeof p === 'object' && 'from' in p && 'to' in p && p.from > 0 && p.from < 65536 && p.to > 0 && p.to < 65536)); } else if (typeof port === 'function') { return true; } else if (typeof port === 'object' && 'from' in port && 'to' in port) { return port.from > 0 && port.from < 65536 && port.to > 0 && port.to < 65536; } return false; } /** * Validates a domain string - supports wildcards, localhost, and IP addresses * @param domain Domain string to validate * @returns True if valid, false otherwise */ export function isValidDomain(domain) { if (!domain || typeof domain !== 'string') return false; if (domain === '*') return true; if (domain === 'localhost') return true; const domainPatterns = [ // Standard domain with optional wildcard subdomain (*.example.com) /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/, // Wildcard prefix without dot (*example.com) /^\*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/, // IP address /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // IPv6 address /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/ ]; return domainPatterns.some(pattern => pattern.test(domain)); } /** * Validates a route match configuration * @param match Route match configuration to validate * @returns { valid: boolean, errors: string[] } Validation result */ export function validateRouteMatch(match) { const errors = []; if (match.ports !== undefined) { if (!isValidPort(match.ports)) { errors.push('Invalid port number or port range in match.ports'); } } if (match.domains !== undefined) { if (typeof match.domains === 'string') { if (!isValidDomain(match.domains)) { errors.push(`Invalid domain format: ${match.domains}`); } } else if (Array.isArray(match.domains)) { for (const domain of match.domains) { if (!isValidDomain(domain)) { errors.push(`Invalid domain format: ${domain}`); } } } else { errors.push('Domains must be a string or an array of strings'); } } if (match.path !== undefined) { if (typeof match.path !== 'string' || !match.path.startsWith('/')) { errors.push('Path must be a string starting with /'); } } return { valid: errors.length === 0, errors }; } /** * Validates a route action configuration * @param action Route action configuration to validate * @returns { valid: boolean, errors: string[] } Validation result */ export function validateRouteAction(action) { const errors = []; if (!action.type) { errors.push('Action type is required'); } else if (!['forward', 'socket-handler'].includes(action.type)) { errors.push(`Invalid action type: ${action.type}`); } if (action.type === 'forward') { if (!action.targets || !Array.isArray(action.targets) || action.targets.length === 0) { errors.push('Targets array is required for forward action'); } else { action.targets.forEach((target, index) => { if (!target.host) { errors.push(`Target[${index}] host is required`); } else if (typeof target.host !== 'string' && !Array.isArray(target.host) && typeof target.host !== 'function') { errors.push(`Target[${index}] host must be a string, array of strings, or function`); } if (target.port === undefined) { errors.push(`Target[${index}] port is required`); } else if (typeof target.port !== 'number' && typeof target.port !== 'function' && target.port !== 'preserve') { errors.push(`Target[${index}] port must be a number, 'preserve', or a function`); } else if (typeof target.port === 'number' && !isValidPort(target.port)) { errors.push(`Target[${index}] port must be between 1 and 65535`); } if (target.match) { if (target.match.ports && !Array.isArray(target.match.ports)) { errors.push(`Target[${index}] match.ports must be an array`); } if (target.match.method && !Array.isArray(target.match.method)) { errors.push(`Target[${index}] match.method must be an array`); } } }); } if (action.tls) { if (!['passthrough', 'terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) { errors.push(`Invalid TLS mode: ${action.tls.mode}`); } if (['terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) { if (action.tls.certificate !== 'auto' && (!action.tls.certificate || !action.tls.certificate.key || !action.tls.certificate.cert)) { errors.push('Certificate must be "auto" or an object with key and cert properties'); } } } } // QUIC routes require TLS with termination if (action.udp?.quic && action.type === 'forward') { if (!action.tls) { errors.push('QUIC routes require TLS configuration — QUIC mandates TLS 1.3'); } else if (action.tls.mode === 'passthrough') { errors.push('QUIC routes cannot use TLS mode "passthrough"'); } } if (action.type === 'socket-handler') { if (!action.socketHandler && !action.datagramHandler) { errors.push('Socket handler or datagram handler function is required for socket-handler action'); } else if (action.socketHandler && typeof action.socketHandler !== 'function') { errors.push('Socket handler must be a function'); } else if (action.datagramHandler && typeof action.datagramHandler !== 'function') { errors.push('Datagram handler must be a function'); } } return { valid: errors.length === 0, errors }; } /** * Validates a complete route configuration * @param route Route configuration to validate * @returns { valid: boolean, errors: string[] } Validation result */ export function validateRouteConfig(route) { const errors = []; if (!route.match) { errors.push('Route match configuration is required'); } if (!route.action) { errors.push('Route action configuration is required'); } if (route.match) { const matchValidation = validateRouteMatch(route.match); if (!matchValidation.valid) { errors.push(...matchValidation.errors.map(err => `Match: ${err}`)); } } if (route.action) { const actionValidation = validateRouteAction(route.action); if (!actionValidation.valid) { errors.push(...actionValidation.errors.map(err => `Action: ${err}`)); } } return { valid: errors.length === 0, errors }; } /** * Validate an array of route configurations * @param routes Array of route configurations to validate * @returns { valid: boolean, errors: { index: number, errors: string[] }[] } Validation result */ export function validateRoutes(routes) { const results = []; routes.forEach((route, index) => { const validation = validateRouteConfig(route); if (!validation.valid) { results.push({ index, errors: validation.errors }); } }); return { valid: results.length === 0, errors: results }; } /** * Check if a route configuration has the required properties for a specific action type * @param route Route configuration to check * @param actionType Expected action type * @returns True if the route has the necessary properties, false otherwise */ export function hasRequiredPropertiesForAction(route, actionType) { if (!route.action || route.action.type !== actionType) { return false; } switch (actionType) { case 'forward': return !!route.action.targets && Array.isArray(route.action.targets) && route.action.targets.length > 0 && route.action.targets.every(t => t.host && t.port !== undefined); case 'socket-handler': return (!!route.action.socketHandler && typeof route.action.socketHandler === 'function') || (!!route.action.datagramHandler && typeof route.action.datagramHandler === 'function'); default: return false; } } /** * Throws an error if the route config is invalid, returns the config if valid * Useful for immediate validation when creating routes * @param route Route configuration to validate * @returns The validated route configuration * @throws Error if the route configuration is invalid */ export function assertValidRoute(route) { const validation = validateRouteConfig(route); if (!validation.valid) { throw new Error(`Invalid route configuration: ${validation.errors.join(', ')}`); } return route; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtdmFsaWRhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHMvcHJveGllcy9zbWFydC1wcm94eS91dGlscy9yb3V0ZS12YWxpZGF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBR3ZEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFDakIsTUFBTSxDQUFVLGVBQWUsR0FBRyxDQUFDLFdBQVcsRUFBRSxhQUFhLEVBQUUseUJBQXlCLENBQUMsQ0FBQztJQUMxRixNQUFNLENBQVUsa0JBQWtCLEdBQUcsQ0FBQyxTQUFTLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztJQUNuRSxNQUFNLENBQVUsZUFBZSxHQUFHLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ2hHLE1BQU0sQ0FBVSxTQUFTLEdBQUcsR0FBRyxDQUFDO0lBQ2hDLE1BQU0sQ0FBVSxXQUFXLEdBQUcsSUFBSSxDQUFDO0lBQ25DLE1BQU0sQ0FBVSxlQUFlLEdBQUcsSUFBSSxDQUFDO0lBRS9DOztPQUVHO0lBQ0ksTUFBTSxDQUFDLGFBQWEsQ0FBQyxLQUFtQjtRQUM3QyxNQUFNLE1BQU0sR0FBYSxFQUFFLENBQUM7UUFFNUIsNEJBQTRCO1FBQzVCLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxJQUFJLE9BQU8sS0FBSyxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUNsRCxNQUFNLENBQUMsSUFBSSxDQUFDLDhCQUE4QixDQUFDLENBQUM7UUFDOUMsQ0FBQztRQUVELDBCQUEwQjtRQUMxQixJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2pCLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztRQUNoRCxD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