@push.rocks/smartproxy 22.6.0 → 23.1.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.
Files changed (196) hide show
  1. package/changelog.md +18 -0
  2. package/dist_rust/{rustproxy → rustproxy_linux_amd64} +0 -0
  3. package/dist_rust/rustproxy_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/index.d.ts +0 -1
  6. package/dist_ts/index.js +1 -3
  7. package/dist_ts/plugins.d.ts +2 -1
  8. package/dist_ts/plugins.js +3 -2
  9. package/dist_ts/proxies/index.d.ts +0 -1
  10. package/dist_ts/proxies/index.js +1 -3
  11. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  12. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +9 -21
  13. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +83 -212
  14. package/dist_ts/proxies/smart-proxy/smart-proxy.js +2 -3
  15. package/npmextra.json +3 -0
  16. package/package.json +13 -11
  17. package/readme.md +35 -31
  18. package/ts/00_commitinfo_data.ts +1 -1
  19. package/ts/index.ts +0 -3
  20. package/ts/plugins.ts +2 -0
  21. package/ts/proxies/index.ts +0 -3
  22. package/ts/proxies/smart-proxy/models/route-types.ts +0 -2
  23. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +102 -233
  24. package/ts/proxies/smart-proxy/smart-proxy.ts +1 -2
  25. package/dist_ts/common/eventUtils.d.ts +0 -14
  26. package/dist_ts/common/eventUtils.js +0 -20
  27. package/dist_ts/common/types.d.ts +0 -82
  28. package/dist_ts/common/types.js +0 -15
  29. package/dist_ts/core/utils/event-system.d.ts +0 -200
  30. package/dist_ts/core/utils/event-system.js +0 -224
  31. package/dist_ts/core/utils/event-utils.d.ts +0 -15
  32. package/dist_ts/core/utils/event-utils.js +0 -11
  33. package/dist_ts/core/utils/route-manager.d.ts +0 -88
  34. package/dist_ts/core/utils/route-manager.js +0 -342
  35. package/dist_ts/core/utils/route-utils.d.ts +0 -28
  36. package/dist_ts/core/utils/route-utils.js +0 -67
  37. package/dist_ts/detection/detectors/http-detector-v2.d.ts +0 -33
  38. package/dist_ts/detection/detectors/http-detector-v2.js +0 -87
  39. package/dist_ts/detection/detectors/tls-detector-v2.d.ts +0 -33
  40. package/dist_ts/detection/detectors/tls-detector-v2.js +0 -80
  41. package/dist_ts/detection/protocol-detector-v2.d.ts +0 -46
  42. package/dist_ts/detection/protocol-detector-v2.js +0 -116
  43. package/dist_ts/forwarding/config/forwarding-types.d.ts +0 -42
  44. package/dist_ts/forwarding/config/forwarding-types.js +0 -18
  45. package/dist_ts/forwarding/config/index.d.ts +0 -9
  46. package/dist_ts/forwarding/config/index.js +0 -10
  47. package/dist_ts/forwarding/factory/forwarding-factory.d.ts +0 -25
  48. package/dist_ts/forwarding/factory/forwarding-factory.js +0 -172
  49. package/dist_ts/forwarding/factory/index.d.ts +0 -4
  50. package/dist_ts/forwarding/factory/index.js +0 -5
  51. package/dist_ts/forwarding/handlers/base-handler.d.ts +0 -62
  52. package/dist_ts/forwarding/handlers/base-handler.js +0 -121
  53. package/dist_ts/forwarding/handlers/http-handler.d.ts +0 -30
  54. package/dist_ts/forwarding/handlers/http-handler.js +0 -143
  55. package/dist_ts/forwarding/handlers/https-passthrough-handler.d.ts +0 -29
  56. package/dist_ts/forwarding/handlers/https-passthrough-handler.js +0 -156
  57. package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.d.ts +0 -36
  58. package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +0 -276
  59. package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.d.ts +0 -35
  60. package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +0 -261
  61. package/dist_ts/forwarding/handlers/index.d.ts +0 -8
  62. package/dist_ts/forwarding/handlers/index.js +0 -9
  63. package/dist_ts/forwarding/index.d.ts +0 -13
  64. package/dist_ts/forwarding/index.js +0 -16
  65. package/dist_ts/http/index.d.ts +0 -5
  66. package/dist_ts/http/index.js +0 -8
  67. package/dist_ts/http/models/http-types.d.ts +0 -6
  68. package/dist_ts/http/models/http-types.js +0 -7
  69. package/dist_ts/http/router/index.d.ts +0 -8
  70. package/dist_ts/http/router/index.js +0 -7
  71. package/dist_ts/http/router/proxy-router.d.ts +0 -115
  72. package/dist_ts/http/router/proxy-router.js +0 -325
  73. package/dist_ts/http/router/route-router.d.ts +0 -108
  74. package/dist_ts/http/router/route-router.js +0 -393
  75. package/dist_ts/protocols/tls/constants.d.ts +0 -122
  76. package/dist_ts/protocols/tls/constants.js +0 -135
  77. package/dist_ts/protocols/tls/parser.d.ts +0 -53
  78. package/dist_ts/protocols/tls/parser.js +0 -294
  79. package/dist_ts/protocols/tls/types.d.ts +0 -65
  80. package/dist_ts/protocols/tls/types.js +0 -5
  81. package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +0 -95
  82. package/dist_ts/proxies/http-proxy/certificate-manager.js +0 -214
  83. package/dist_ts/proxies/http-proxy/connection-pool.d.ts +0 -47
  84. package/dist_ts/proxies/http-proxy/connection-pool.js +0 -195
  85. package/dist_ts/proxies/http-proxy/context-creator.d.ts +0 -34
  86. package/dist_ts/proxies/http-proxy/context-creator.js +0 -108
  87. package/dist_ts/proxies/http-proxy/default-certificates.d.ts +0 -54
  88. package/dist_ts/proxies/http-proxy/default-certificates.js +0 -127
  89. package/dist_ts/proxies/http-proxy/function-cache.d.ts +0 -95
  90. package/dist_ts/proxies/http-proxy/function-cache.js +0 -215
  91. package/dist_ts/proxies/http-proxy/handlers/index.d.ts +0 -4
  92. package/dist_ts/proxies/http-proxy/handlers/index.js +0 -6
  93. package/dist_ts/proxies/http-proxy/handlers/redirect-handler.d.ts +0 -18
  94. package/dist_ts/proxies/http-proxy/handlers/redirect-handler.js +0 -78
  95. package/dist_ts/proxies/http-proxy/handlers/static-handler.d.ts +0 -19
  96. package/dist_ts/proxies/http-proxy/handlers/static-handler.js +0 -211
  97. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -117
  98. package/dist_ts/proxies/http-proxy/http-proxy.js +0 -521
  99. package/dist_ts/proxies/http-proxy/http-request-handler.d.ts +0 -40
  100. package/dist_ts/proxies/http-proxy/http-request-handler.js +0 -257
  101. package/dist_ts/proxies/http-proxy/http2-request-handler.d.ts +0 -24
  102. package/dist_ts/proxies/http-proxy/http2-request-handler.js +0 -201
  103. package/dist_ts/proxies/http-proxy/index.d.ts +0 -14
  104. package/dist_ts/proxies/http-proxy/index.js +0 -16
  105. package/dist_ts/proxies/http-proxy/models/http-types.d.ts +0 -117
  106. package/dist_ts/proxies/http-proxy/models/http-types.js +0 -92
  107. package/dist_ts/proxies/http-proxy/models/index.d.ts +0 -5
  108. package/dist_ts/proxies/http-proxy/models/index.js +0 -6
  109. package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -75
  110. package/dist_ts/proxies/http-proxy/models/types.js +0 -35
  111. package/dist_ts/proxies/http-proxy/request-handler.d.ts +0 -97
  112. package/dist_ts/proxies/http-proxy/request-handler.js +0 -737
  113. package/dist_ts/proxies/http-proxy/security-manager.d.ts +0 -98
  114. package/dist_ts/proxies/http-proxy/security-manager.js +0 -341
  115. package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +0 -50
  116. package/dist_ts/proxies/http-proxy/websocket-handler.js +0 -505
  117. package/dist_ts/proxies/nftables-proxy/index.d.ts +0 -6
  118. package/dist_ts/proxies/nftables-proxy/index.js +0 -7
  119. package/dist_ts/proxies/nftables-proxy/models/errors.d.ts +0 -15
  120. package/dist_ts/proxies/nftables-proxy/models/errors.js +0 -28
  121. package/dist_ts/proxies/nftables-proxy/models/index.d.ts +0 -5
  122. package/dist_ts/proxies/nftables-proxy/models/index.js +0 -6
  123. package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +0 -75
  124. package/dist_ts/proxies/nftables-proxy/models/interfaces.js +0 -5
  125. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +0 -124
  126. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +0 -1374
  127. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +0 -9
  128. package/dist_ts/proxies/nftables-proxy/utils/index.js +0 -12
  129. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +0 -66
  130. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +0 -131
  131. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +0 -39
  132. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +0 -112
  133. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +0 -59
  134. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +0 -130
  135. package/dist_ts/proxies/smart-proxy/acme-state-manager.d.ts +0 -42
  136. package/dist_ts/proxies/smart-proxy/acme-state-manager.js +0 -101
  137. package/dist_ts/proxies/smart-proxy/cert-store.d.ts +0 -10
  138. package/dist_ts/proxies/smart-proxy/cert-store.js +0 -72
  139. package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +0 -164
  140. package/dist_ts/proxies/smart-proxy/certificate-manager.js +0 -745
  141. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +0 -128
  142. package/dist_ts/proxies/smart-proxy/connection-manager.js +0 -689
  143. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +0 -43
  144. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +0 -180
  145. package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +0 -98
  146. package/dist_ts/proxies/smart-proxy/metrics-collector.js +0 -355
  147. package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +0 -82
  148. package/dist_ts/proxies/smart-proxy/nftables-manager.js +0 -237
  149. package/dist_ts/proxies/smart-proxy/port-manager.d.ts +0 -117
  150. package/dist_ts/proxies/smart-proxy/port-manager.js +0 -318
  151. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +0 -60
  152. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +0 -1407
  153. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +0 -112
  154. package/dist_ts/proxies/smart-proxy/route-manager.js +0 -453
  155. package/dist_ts/proxies/smart-proxy/route-orchestrator.d.ts +0 -56
  156. package/dist_ts/proxies/smart-proxy/route-orchestrator.js +0 -204
  157. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +0 -23
  158. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +0 -104
  159. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +0 -74
  160. package/dist_ts/proxies/smart-proxy/security-manager.js +0 -227
  161. package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +0 -36
  162. package/dist_ts/proxies/smart-proxy/throughput-tracker.js +0 -115
  163. package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +0 -48
  164. package/dist_ts/proxies/smart-proxy/timeout-manager.js +0 -158
  165. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +0 -50
  166. package/dist_ts/proxies/smart-proxy/tls-manager.js +0 -110
  167. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -161
  168. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +0 -282
  169. package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +0 -73
  170. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +0 -259
  171. package/dist_ts/routing/router/proxy-router.d.ts +0 -115
  172. package/dist_ts/routing/router/proxy-router.js +0 -325
  173. package/dist_ts/routing/router/route-router.d.ts +0 -108
  174. package/dist_ts/routing/router/route-router.js +0 -393
  175. package/dist_ts/tls/alerts/index.d.ts +0 -4
  176. package/dist_ts/tls/alerts/index.js +0 -5
  177. package/dist_ts/tls/alerts/tls-alert.d.ts +0 -150
  178. package/dist_ts/tls/alerts/tls-alert.js +0 -226
  179. package/dist_ts/tls/sni/client-hello-parser.d.ts +0 -100
  180. package/dist_ts/tls/sni/client-hello-parser.js +0 -464
  181. package/dist_ts/tls/sni/sni-extraction.d.ts +0 -58
  182. package/dist_ts/tls/sni/sni-extraction.js +0 -275
  183. package/dist_ts/tls/utils/index.d.ts +0 -4
  184. package/dist_ts/tls/utils/index.js +0 -5
  185. package/dist_ts/tls/utils/tls-utils.d.ts +0 -49
  186. package/dist_ts/tls/utils/tls-utils.js +0 -75
  187. package/ts/proxies/nftables-proxy/index.ts +0 -6
  188. package/ts/proxies/nftables-proxy/models/errors.ts +0 -30
  189. package/ts/proxies/nftables-proxy/models/index.ts +0 -5
  190. package/ts/proxies/nftables-proxy/models/interfaces.ts +0 -94
  191. package/ts/proxies/nftables-proxy/nftables-proxy.ts +0 -1754
  192. package/ts/proxies/nftables-proxy/utils/index.ts +0 -38
  193. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +0 -162
  194. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +0 -125
  195. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +0 -156
  196. package/ts/proxies/smart-proxy/rust-binary-locator.ts +0 -112
@@ -1,737 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import '../../core/models/socket-augmentation.js';
3
- import { createLogger, } from './models/types.js';
4
- import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
5
- import { ConnectionPool } from './connection-pool.js';
6
- import { ContextCreator } from './context-creator.js';
7
- import { HttpRequestHandler } from './http-request-handler.js';
8
- import { Http2RequestHandler } from './http2-request-handler.js';
9
- import { toBaseContext } from '../../core/models/route-context.js';
10
- import { TemplateUtils } from '../../core/utils/template-utils.js';
11
- import { SecurityManager } from './security-manager.js';
12
- /**
13
- * Handles HTTP request processing and proxying
14
- */
15
- export class RequestHandler {
16
- constructor(options, connectionPool, routeManager, functionCache, // FunctionCache - using any to avoid circular dependency
17
- router // HttpRouter - using any to avoid circular dependency
18
- ) {
19
- this.options = options;
20
- this.connectionPool = connectionPool;
21
- this.routeManager = routeManager;
22
- this.functionCache = functionCache;
23
- this.router = router;
24
- this.defaultHeaders = {};
25
- this.metricsTracker = null;
26
- // HTTP/2 client sessions for backend proxying
27
- this.h2Sessions = new Map();
28
- // Context creator for route contexts
29
- this.contextCreator = new ContextCreator();
30
- // Rate limit cleanup interval
31
- this.rateLimitCleanupInterval = null;
32
- this.logger = createLogger(options.logLevel || 'info');
33
- this.securityManager = new SecurityManager(this.logger);
34
- // Schedule rate limit cleanup every minute
35
- this.rateLimitCleanupInterval = setInterval(() => {
36
- this.securityManager.cleanupExpiredRateLimits();
37
- }, 60000);
38
- // Make sure the interval doesn't keep the process alive
39
- if (this.rateLimitCleanupInterval.unref) {
40
- this.rateLimitCleanupInterval.unref();
41
- }
42
- }
43
- /**
44
- * Set the route manager instance
45
- */
46
- setRouteManager(routeManager) {
47
- this.routeManager = routeManager;
48
- }
49
- /**
50
- * Set the metrics tracker instance
51
- */
52
- setMetricsTracker(tracker) {
53
- this.metricsTracker = tracker;
54
- }
55
- /**
56
- * Set default headers to be included in all responses
57
- */
58
- setDefaultHeaders(headers) {
59
- this.defaultHeaders = {
60
- ...this.defaultHeaders,
61
- ...headers
62
- };
63
- this.logger.info('Updated default response headers');
64
- }
65
- /**
66
- * Get all default headers
67
- */
68
- getDefaultHeaders() {
69
- return { ...this.defaultHeaders };
70
- }
71
- /**
72
- * Select the appropriate target from the targets array based on sub-matching criteria
73
- */
74
- selectTarget(targets, context) {
75
- // Sort targets by priority (higher first)
76
- const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0));
77
- // Find the first matching target
78
- for (const target of sortedTargets) {
79
- if (!target.match) {
80
- // No match criteria means this is a default/fallback target
81
- return target;
82
- }
83
- // Check port match
84
- if (target.match.ports && !target.match.ports.includes(context.port)) {
85
- continue;
86
- }
87
- // Check path match (supports wildcards)
88
- if (target.match.path && context.path) {
89
- const pathPattern = target.match.path.replace(/\*/g, '.*');
90
- const pathRegex = new RegExp(`^${pathPattern}$`);
91
- if (!pathRegex.test(context.path)) {
92
- continue;
93
- }
94
- }
95
- // Check method match
96
- if (target.match.method && context.method && !target.match.method.includes(context.method)) {
97
- continue;
98
- }
99
- // Check headers match
100
- if (target.match.headers && context.headers) {
101
- let headersMatch = true;
102
- for (const [key, pattern] of Object.entries(target.match.headers)) {
103
- const headerValue = context.headers[key.toLowerCase()];
104
- if (!headerValue) {
105
- headersMatch = false;
106
- break;
107
- }
108
- if (pattern instanceof RegExp) {
109
- if (!pattern.test(headerValue)) {
110
- headersMatch = false;
111
- break;
112
- }
113
- }
114
- else if (headerValue !== pattern) {
115
- headersMatch = false;
116
- break;
117
- }
118
- }
119
- if (!headersMatch) {
120
- continue;
121
- }
122
- }
123
- // All criteria matched
124
- return target;
125
- }
126
- // No matching target found, return the first target without match criteria (default)
127
- return sortedTargets.find(t => !t.match) || null;
128
- }
129
- /**
130
- * Apply CORS headers to response if configured
131
- * Implements Phase 5.5: Context-aware CORS handling
132
- *
133
- * @param res The server response to apply headers to
134
- * @param req The incoming request
135
- * @param route Optional route config with CORS settings
136
- */
137
- applyCorsHeaders(res, req, route) {
138
- // Use route-specific CORS config if available, otherwise use global config
139
- let corsConfig = null;
140
- // Route CORS config takes precedence if enabled
141
- if (route?.headers?.cors?.enabled) {
142
- corsConfig = route.headers.cors;
143
- this.logger.debug(`Using route-specific CORS config for ${route.name || 'unnamed route'}`);
144
- }
145
- // Fall back to global CORS config if available
146
- else if (this.options.cors) {
147
- corsConfig = this.options.cors;
148
- this.logger.debug('Using global CORS config');
149
- }
150
- // If no CORS config available, skip
151
- if (!corsConfig) {
152
- return;
153
- }
154
- // Get origin from request
155
- const origin = req.headers.origin;
156
- // Apply Allow-Origin (with dynamic validation if needed)
157
- if (corsConfig.allowOrigin) {
158
- // Handle multiple origins in array format
159
- if (Array.isArray(corsConfig.allowOrigin)) {
160
- if (origin && corsConfig.allowOrigin.includes(origin)) {
161
- // Match found, set specific origin
162
- res.setHeader('Access-Control-Allow-Origin', origin);
163
- res.setHeader('Vary', 'Origin'); // Important for caching
164
- }
165
- else if (corsConfig.allowOrigin.includes('*')) {
166
- // Wildcard match
167
- res.setHeader('Access-Control-Allow-Origin', '*');
168
- }
169
- }
170
- // Handle single origin or wildcard
171
- else if (corsConfig.allowOrigin === '*') {
172
- res.setHeader('Access-Control-Allow-Origin', '*');
173
- }
174
- // Match single origin against request
175
- else if (origin && corsConfig.allowOrigin === origin) {
176
- res.setHeader('Access-Control-Allow-Origin', origin);
177
- res.setHeader('Vary', 'Origin');
178
- }
179
- // Use template variables if present
180
- else if (origin && corsConfig.allowOrigin.includes('{')) {
181
- const resolvedOrigin = TemplateUtils.resolveTemplateVariables(corsConfig.allowOrigin, { domain: req.headers.host });
182
- if (resolvedOrigin === origin || resolvedOrigin === '*') {
183
- res.setHeader('Access-Control-Allow-Origin', origin);
184
- res.setHeader('Vary', 'Origin');
185
- }
186
- }
187
- }
188
- // Apply other CORS headers
189
- if (corsConfig.allowMethods) {
190
- res.setHeader('Access-Control-Allow-Methods', corsConfig.allowMethods);
191
- }
192
- if (corsConfig.allowHeaders) {
193
- res.setHeader('Access-Control-Allow-Headers', corsConfig.allowHeaders);
194
- }
195
- if (corsConfig.allowCredentials) {
196
- res.setHeader('Access-Control-Allow-Credentials', 'true');
197
- }
198
- if (corsConfig.exposeHeaders) {
199
- res.setHeader('Access-Control-Expose-Headers', corsConfig.exposeHeaders);
200
- }
201
- if (corsConfig.maxAge) {
202
- res.setHeader('Access-Control-Max-Age', corsConfig.maxAge.toString());
203
- }
204
- // Handle CORS preflight requests if enabled (default: true)
205
- if (req.method === 'OPTIONS' && corsConfig.preflight !== false) {
206
- res.statusCode = 204; // No content
207
- res.end();
208
- return;
209
- }
210
- }
211
- // First implementation of applyRouteHeaderModifications moved to the second implementation below
212
- /**
213
- * Apply default headers to response
214
- */
215
- applyDefaultHeaders(res) {
216
- // Apply default headers
217
- for (const [key, value] of Object.entries(this.defaultHeaders)) {
218
- if (!res.hasHeader(key)) {
219
- res.setHeader(key, value);
220
- }
221
- }
222
- // Add server identifier if not already set
223
- if (!res.hasHeader('Server')) {
224
- res.setHeader('Server', 'NetworkProxy');
225
- }
226
- }
227
- /**
228
- * Apply URL rewriting based on route configuration
229
- * Implements Phase 5.2: URL rewriting using route context
230
- *
231
- * @param req The request with the URL to rewrite
232
- * @param route The route configuration containing rewrite rules
233
- * @param routeContext Context for template variable resolution
234
- * @returns True if URL was rewritten, false otherwise
235
- */
236
- applyUrlRewriting(req, route, routeContext) {
237
- // Check if route has URL rewriting configuration
238
- if (!route.action.advanced?.urlRewrite) {
239
- return false;
240
- }
241
- const rewriteConfig = route.action.advanced.urlRewrite;
242
- // Store original URL for logging
243
- const originalUrl = req.url;
244
- if (rewriteConfig.pattern && rewriteConfig.target) {
245
- try {
246
- // Create a RegExp from the pattern
247
- const regex = new RegExp(rewriteConfig.pattern, rewriteConfig.flags || '');
248
- // Apply rewriting with template variable resolution
249
- let target = rewriteConfig.target;
250
- // Replace template variables in target with values from context
251
- target = TemplateUtils.resolveTemplateVariables(target, routeContext);
252
- // If onlyRewritePath is set, split URL into path and query parts
253
- if (rewriteConfig.onlyRewritePath && req.url) {
254
- const [path, query] = req.url.split('?');
255
- const rewrittenPath = path.replace(regex, target);
256
- req.url = query ? `${rewrittenPath}?${query}` : rewrittenPath;
257
- }
258
- else {
259
- // Perform the replacement on the entire URL
260
- req.url = req.url?.replace(regex, target);
261
- }
262
- this.logger.debug(`URL rewritten: ${originalUrl} -> ${req.url}`);
263
- return true;
264
- }
265
- catch (err) {
266
- this.logger.error(`Error in URL rewriting: ${err}`);
267
- return false;
268
- }
269
- }
270
- return false;
271
- }
272
- /**
273
- * Apply header modifications from route configuration
274
- * Implements Phase 5.1: Route-based header manipulation
275
- */
276
- applyRouteHeaderModifications(route, req, res) {
277
- // Check if route has header modifications
278
- if (!route.headers) {
279
- return;
280
- }
281
- // Apply request header modifications (these will be sent to the backend)
282
- if (route.headers.request && req.headers) {
283
- for (const [key, value] of Object.entries(route.headers.request)) {
284
- // Skip if header already exists and we're not overriding
285
- if (req.headers[key.toLowerCase()] && !value.startsWith('!')) {
286
- continue;
287
- }
288
- // Handle special delete directive (!delete)
289
- if (value === '!delete') {
290
- delete req.headers[key.toLowerCase()];
291
- this.logger.debug(`Deleted request header: ${key}`);
292
- continue;
293
- }
294
- // Handle forced override (!value)
295
- let finalValue;
296
- if (value.startsWith('!') && value !== '!delete') {
297
- // Keep the ! but resolve any templates in the rest
298
- const templateValue = value.substring(1);
299
- finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, {});
300
- }
301
- else {
302
- // Resolve templates in the entire value
303
- finalValue = TemplateUtils.resolveTemplateVariables(value, {});
304
- }
305
- // Set the header
306
- req.headers[key.toLowerCase()] = finalValue;
307
- this.logger.debug(`Modified request header: ${key}=${finalValue}`);
308
- }
309
- }
310
- // Apply response header modifications (these will be stored for later use)
311
- if (route.headers.response) {
312
- for (const [key, value] of Object.entries(route.headers.response)) {
313
- // Skip if header already exists and we're not overriding
314
- if (res.hasHeader(key) && !value.startsWith('!')) {
315
- continue;
316
- }
317
- // Handle special delete directive (!delete)
318
- if (value === '!delete') {
319
- res.removeHeader(key);
320
- this.logger.debug(`Deleted response header: ${key}`);
321
- continue;
322
- }
323
- // Handle forced override (!value)
324
- let finalValue;
325
- if (value.startsWith('!') && value !== '!delete') {
326
- // Keep the ! but resolve any templates in the rest
327
- const templateValue = value.substring(1);
328
- finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, {});
329
- }
330
- else {
331
- // Resolve templates in the entire value
332
- finalValue = TemplateUtils.resolveTemplateVariables(value, {});
333
- }
334
- // Set the header
335
- res.setHeader(key, finalValue);
336
- this.logger.debug(`Modified response header: ${key}=${finalValue}`);
337
- }
338
- }
339
- }
340
- /**
341
- * Handle an HTTP request
342
- */
343
- async handleRequest(req, res) {
344
- // Record start time for logging
345
- const startTime = Date.now();
346
- // Get route before applying CORS (we might need its settings)
347
- // Try to find a matching route using RouteManager
348
- let matchingRoute = null;
349
- if (this.routeManager) {
350
- try {
351
- // Create a connection ID for this request
352
- const connectionId = `http-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
353
- // Create route context for function-based targets
354
- const routeContext = this.contextCreator.createHttpRouteContext(req, {
355
- connectionId,
356
- clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
357
- serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
358
- tlsVersion: req.socket.getTLSVersion?.() || undefined
359
- });
360
- const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
361
- matchingRoute = matchResult?.route || null;
362
- }
363
- catch (err) {
364
- this.logger.error('Error finding matching route', err);
365
- }
366
- }
367
- // Apply CORS headers with route-specific settings if available
368
- this.applyCorsHeaders(res, req, matchingRoute);
369
- // If this is an OPTIONS request, the response has already been ended in applyCorsHeaders
370
- // so we should return early to avoid trying to set more headers
371
- if (req.method === 'OPTIONS') {
372
- // Increment metrics for OPTIONS requests too
373
- if (this.metricsTracker) {
374
- this.metricsTracker.incrementRequestsServed();
375
- }
376
- return;
377
- }
378
- // Apply default headers
379
- this.applyDefaultHeaders(res);
380
- // We already have the connection ID and routeContext from CORS handling
381
- const connectionId = `http-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
382
- // Create route context for function-based targets (if we don't already have one)
383
- const routeContext = this.contextCreator.createHttpRouteContext(req, {
384
- connectionId,
385
- clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
386
- serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
387
- tlsVersion: req.socket.getTLSVersion?.() || undefined
388
- });
389
- // Check security restrictions if we have a matching route
390
- if (matchingRoute) {
391
- // Check IP filtering and rate limiting
392
- if (!this.securityManager.isAllowed(matchingRoute, routeContext)) {
393
- this.logger.warn(`Access denied for ${routeContext.clientIp} to ${matchingRoute.name || 'unnamed'}`);
394
- res.statusCode = 403;
395
- res.end('Forbidden: Access denied by security policy');
396
- if (this.metricsTracker)
397
- this.metricsTracker.incrementFailedRequests();
398
- return;
399
- }
400
- // Check basic auth
401
- if (matchingRoute.security?.basicAuth?.enabled) {
402
- const authHeader = req.headers.authorization;
403
- if (!authHeader || !authHeader.startsWith('Basic ')) {
404
- // No auth header provided - send 401 with WWW-Authenticate header
405
- res.statusCode = 401;
406
- const realm = matchingRoute.security.basicAuth.realm || 'Protected Area';
407
- res.setHeader('WWW-Authenticate', `Basic realm="${realm}", charset="UTF-8"`);
408
- res.end('Authentication Required');
409
- if (this.metricsTracker)
410
- this.metricsTracker.incrementFailedRequests();
411
- return;
412
- }
413
- // Verify credentials
414
- try {
415
- const credentials = Buffer.from(authHeader.substring(6), 'base64').toString('utf-8');
416
- const [username, password] = credentials.split(':');
417
- if (!this.securityManager.checkBasicAuth(matchingRoute, username, password)) {
418
- res.statusCode = 401;
419
- const realm = matchingRoute.security.basicAuth.realm || 'Protected Area';
420
- res.setHeader('WWW-Authenticate', `Basic realm="${realm}", charset="UTF-8"`);
421
- res.end('Invalid Credentials');
422
- if (this.metricsTracker)
423
- this.metricsTracker.incrementFailedRequests();
424
- return;
425
- }
426
- }
427
- catch (err) {
428
- this.logger.error(`Error verifying basic auth: ${err}`);
429
- res.statusCode = 401;
430
- res.end('Authentication Error');
431
- if (this.metricsTracker)
432
- this.metricsTracker.incrementFailedRequests();
433
- return;
434
- }
435
- }
436
- // Check JWT auth
437
- if (matchingRoute.security?.jwtAuth?.enabled) {
438
- const authHeader = req.headers.authorization;
439
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
440
- // No auth header provided - send 401
441
- res.statusCode = 401;
442
- res.end('Authentication Required: JWT token missing');
443
- if (this.metricsTracker)
444
- this.metricsTracker.incrementFailedRequests();
445
- return;
446
- }
447
- // Verify token
448
- const token = authHeader.substring(7);
449
- if (!this.securityManager.verifyJwtToken(matchingRoute, token)) {
450
- res.statusCode = 401;
451
- res.end('Invalid or Expired JWT');
452
- if (this.metricsTracker)
453
- this.metricsTracker.incrementFailedRequests();
454
- return;
455
- }
456
- }
457
- }
458
- // If we found a matching route with forward action, select appropriate target
459
- if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.targets && matchingRoute.action.targets.length > 0) {
460
- this.logger.debug(`Found matching route: ${matchingRoute.name || 'unnamed'}`);
461
- // Select the appropriate target from the targets array
462
- const selectedTarget = this.selectTarget(matchingRoute.action.targets, {
463
- port: routeContext.port,
464
- path: routeContext.path,
465
- headers: routeContext.headers,
466
- method: routeContext.method
467
- });
468
- if (!selectedTarget) {
469
- this.logger.error(`No matching target found for route ${matchingRoute.name}`);
470
- req.socket.end();
471
- return;
472
- }
473
- // Extract target information, resolving functions if needed
474
- let targetHost;
475
- let targetPort;
476
- try {
477
- // Check function cache for host and resolve or use cached value
478
- if (typeof selectedTarget.host === 'function') {
479
- // Generate a function ID for caching (use route name or ID if available)
480
- const functionId = `host-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
481
- // Check if we have a cached result
482
- if (this.functionCache) {
483
- const cachedHost = this.functionCache.getCachedHost(routeContext, functionId);
484
- if (cachedHost !== undefined) {
485
- targetHost = cachedHost;
486
- this.logger.debug(`Using cached host value for ${functionId}`);
487
- }
488
- else {
489
- // Resolve the function and cache the result
490
- const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
491
- targetHost = resolvedHost;
492
- // Cache the result
493
- this.functionCache.cacheHost(routeContext, functionId, resolvedHost);
494
- this.logger.debug(`Resolved and cached function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
495
- }
496
- }
497
- else {
498
- // No cache available, just resolve
499
- const resolvedHost = selectedTarget.host(routeContext);
500
- targetHost = resolvedHost;
501
- this.logger.debug(`Resolved function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
502
- }
503
- }
504
- else {
505
- targetHost = selectedTarget.host;
506
- }
507
- // Check function cache for port and resolve or use cached value
508
- if (typeof selectedTarget.port === 'function') {
509
- // Generate a function ID for caching
510
- const functionId = `port-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
511
- // Check if we have a cached result
512
- if (this.functionCache) {
513
- const cachedPort = this.functionCache.getCachedPort(routeContext, functionId);
514
- if (cachedPort !== undefined) {
515
- targetPort = cachedPort;
516
- this.logger.debug(`Using cached port value for ${functionId}`);
517
- }
518
- else {
519
- // Resolve the function and cache the result
520
- const resolvedPort = selectedTarget.port(toBaseContext(routeContext));
521
- targetPort = resolvedPort;
522
- // Cache the result
523
- this.functionCache.cachePort(routeContext, functionId, resolvedPort);
524
- this.logger.debug(`Resolved and cached function-based port to: ${resolvedPort}`);
525
- }
526
- }
527
- else {
528
- // No cache available, just resolve
529
- const resolvedPort = selectedTarget.port(routeContext);
530
- targetPort = resolvedPort;
531
- this.logger.debug(`Resolved function-based port to: ${resolvedPort}`);
532
- }
533
- }
534
- else {
535
- targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port;
536
- }
537
- // Select a single host if an array was provided
538
- const selectedHost = Array.isArray(targetHost)
539
- ? targetHost[Math.floor(Math.random() * targetHost.length)]
540
- : targetHost;
541
- // Create a destination for the connection pool
542
- const destination = {
543
- host: selectedHost,
544
- port: targetPort
545
- };
546
- // Apply URL rewriting if configured
547
- this.applyUrlRewriting(req, matchingRoute, routeContext);
548
- // Apply header modifications if configured
549
- this.applyRouteHeaderModifications(matchingRoute, req, res);
550
- // Continue with handling using the resolved destination
551
- HttpRequestHandler.handleHttpRequestWithDestination(req, res, destination, routeContext, startTime, this.logger, this.metricsTracker, matchingRoute // Pass the route config for additional processing
552
- );
553
- return;
554
- }
555
- catch (err) {
556
- this.logger.error(`Error evaluating function-based target: ${err}`);
557
- res.statusCode = 500;
558
- res.end('Internal Server Error: Failed to evaluate target functions');
559
- if (this.metricsTracker)
560
- this.metricsTracker.incrementFailedRequests();
561
- return;
562
- }
563
- }
564
- // If no route was found, return 404
565
- this.logger.warn(`No route configuration for host: ${req.headers.host}`);
566
- res.statusCode = 404;
567
- res.end('Not Found: No route configuration for this host');
568
- if (this.metricsTracker)
569
- this.metricsTracker.incrementFailedRequests();
570
- }
571
- /**
572
- * Handle HTTP/2 stream requests with function-based target support
573
- */
574
- async handleHttp2(stream, headers) {
575
- const startTime = Date.now();
576
- // Create a connection ID for this HTTP/2 stream
577
- const connectionId = `http2-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
578
- // Get client IP and server IP from the socket
579
- const socket = stream.session?.socket;
580
- const clientIp = socket?.remoteAddress?.replace('::ffff:', '') || '0.0.0.0';
581
- const serverIp = socket?.localAddress?.replace('::ffff:', '') || '0.0.0.0';
582
- // Create route context for function-based targets
583
- const routeContext = this.contextCreator.createHttp2RouteContext(stream, headers, {
584
- connectionId,
585
- clientIp,
586
- serverIp
587
- });
588
- // Try to find a matching route using RouteManager
589
- let matchingRoute = null;
590
- if (this.routeManager) {
591
- try {
592
- const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
593
- matchingRoute = matchResult?.route || null;
594
- }
595
- catch (err) {
596
- this.logger.error('Error finding matching route for HTTP/2 request', err);
597
- }
598
- }
599
- // If we found a matching route with forward action, select appropriate target
600
- if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.targets && matchingRoute.action.targets.length > 0) {
601
- this.logger.debug(`Found matching route for HTTP/2 request: ${matchingRoute.name || 'unnamed'}`);
602
- // Select the appropriate target from the targets array
603
- const selectedTarget = this.selectTarget(matchingRoute.action.targets, {
604
- port: routeContext.port,
605
- path: routeContext.path,
606
- headers: routeContext.headers,
607
- method: routeContext.method
608
- });
609
- if (!selectedTarget) {
610
- this.logger.error(`No matching target found for route ${matchingRoute.name}`);
611
- stream.respond({ ':status': 502 });
612
- stream.end();
613
- return;
614
- }
615
- // Extract target information, resolving functions if needed
616
- let targetHost;
617
- let targetPort;
618
- try {
619
- // Check function cache for host and resolve or use cached value
620
- if (typeof selectedTarget.host === 'function') {
621
- // Generate a function ID for caching (use route name or ID if available)
622
- const functionId = `host-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
623
- // Check if we have a cached result
624
- if (this.functionCache) {
625
- const cachedHost = this.functionCache.getCachedHost(routeContext, functionId);
626
- if (cachedHost !== undefined) {
627
- targetHost = cachedHost;
628
- this.logger.debug(`Using cached host value for HTTP/2: ${functionId}`);
629
- }
630
- else {
631
- // Resolve the function and cache the result
632
- const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
633
- targetHost = resolvedHost;
634
- // Cache the result
635
- this.functionCache.cacheHost(routeContext, functionId, resolvedHost);
636
- this.logger.debug(`Resolved and cached HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
637
- }
638
- }
639
- else {
640
- // No cache available, just resolve
641
- const resolvedHost = selectedTarget.host(routeContext);
642
- targetHost = resolvedHost;
643
- this.logger.debug(`Resolved HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
644
- }
645
- }
646
- else {
647
- targetHost = selectedTarget.host;
648
- }
649
- // Check function cache for port and resolve or use cached value
650
- if (typeof selectedTarget.port === 'function') {
651
- // Generate a function ID for caching
652
- const functionId = `port-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
653
- // Check if we have a cached result
654
- if (this.functionCache) {
655
- const cachedPort = this.functionCache.getCachedPort(routeContext, functionId);
656
- if (cachedPort !== undefined) {
657
- targetPort = cachedPort;
658
- this.logger.debug(`Using cached port value for HTTP/2: ${functionId}`);
659
- }
660
- else {
661
- // Resolve the function and cache the result
662
- const resolvedPort = selectedTarget.port(toBaseContext(routeContext));
663
- targetPort = resolvedPort;
664
- // Cache the result
665
- this.functionCache.cachePort(routeContext, functionId, resolvedPort);
666
- this.logger.debug(`Resolved and cached HTTP/2 function-based port to: ${resolvedPort}`);
667
- }
668
- }
669
- else {
670
- // No cache available, just resolve
671
- const resolvedPort = selectedTarget.port(routeContext);
672
- targetPort = resolvedPort;
673
- this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`);
674
- }
675
- }
676
- else {
677
- targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port;
678
- }
679
- // Select a single host if an array was provided
680
- const selectedHost = Array.isArray(targetHost)
681
- ? targetHost[Math.floor(Math.random() * targetHost.length)]
682
- : targetHost;
683
- // Create a destination for forwarding
684
- const destination = {
685
- host: selectedHost,
686
- port: targetPort
687
- };
688
- // Handle HTTP/2 stream based on backend protocol
689
- const backendProtocol = matchingRoute.action.options?.backendProtocol || this.options.backendProtocol;
690
- if (backendProtocol === 'http2') {
691
- // Forward to HTTP/2 backend
692
- return Http2RequestHandler.handleHttp2WithHttp2Destination(stream, headers, destination, routeContext, this.h2Sessions, this.logger, this.metricsTracker);
693
- }
694
- else {
695
- // Forward to HTTP/1.1 backend
696
- return Http2RequestHandler.handleHttp2WithHttp1Destination(stream, headers, destination, routeContext, this.logger, this.metricsTracker);
697
- }
698
- }
699
- catch (err) {
700
- this.logger.error(`Error evaluating function-based target for HTTP/2: ${err}`);
701
- stream.respond({ ':status': 500 });
702
- stream.end('Internal Server Error: Failed to evaluate target functions');
703
- if (this.metricsTracker)
704
- this.metricsTracker.incrementFailedRequests();
705
- return;
706
- }
707
- }
708
- // Fall back to legacy routing if no matching route found
709
- const method = headers[':method'] || 'GET';
710
- const path = headers[':path'] || '/';
711
- // No route was found
712
- stream.respond({ ':status': 404 });
713
- stream.end('Not Found: No route configuration for this request');
714
- if (this.metricsTracker)
715
- this.metricsTracker.incrementFailedRequests();
716
- }
717
- /**
718
- * Cleanup resources and stop intervals
719
- */
720
- destroy() {
721
- if (this.rateLimitCleanupInterval) {
722
- clearInterval(this.rateLimitCleanupInterval);
723
- this.rateLimitCleanupInterval = null;
724
- }
725
- // Close all HTTP/2 sessions
726
- for (const [key, session] of this.h2Sessions) {
727
- session.close();
728
- }
729
- this.h2Sessions.clear();
730
- // Clear function cache if it has a destroy method
731
- if (this.functionCache && typeof this.functionCache.destroy === 'function') {
732
- this.functionCache.destroy();
733
- }
734
- this.logger.debug('RequestHandler destroyed');
735
- }
736
- }
737
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWVzdC1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9odHRwLXByb3h5L3JlcXVlc3QtaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sMENBQTBDLENBQUM7QUFDbEQsT0FBTyxFQUdMLFlBQVksR0FDYixNQUFNLG1CQUFtQixDQUFDO0FBQzNCLE9BQU8sRUFBRSxrQkFBa0IsSUFBSSxZQUFZLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUN6RixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDdEQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQy9ELE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBR2pFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUNuRSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDbkUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBYXhEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFnQnpCLFlBQ1UsT0FBMEIsRUFDMUIsY0FBOEIsRUFDOUIsWUFBMkIsRUFDM0IsYUFBbUIsRUFBRSx5REFBeUQ7SUFDOUUsTUFBWSxDQUFDLHNEQUFzRDs7UUFKbkUsWUFBTyxHQUFQLE9BQU8sQ0FBbUI7UUFDMUIsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBQzlCLGlCQUFZLEdBQVosWUFBWSxDQUFlO1FBQzNCLGtCQUFhLEdBQWIsYUFBYSxDQUFNO1FBQ25CLFdBQU0sR0FBTixNQUFNLENBQU07UUFwQmQsbUJBQWMsR0FBOEIsRUFBRSxDQUFDO1FBRS9DLG1C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