@push.rocks/smartproxy 19.4.2 → 19.5.2

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 (32) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/proxies/http-proxy/handlers/index.d.ts +1 -2
  3. package/dist_ts/proxies/http-proxy/handlers/index.js +3 -3
  4. package/dist_ts/proxies/smart-proxy/certificate-manager.js +30 -25
  5. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +9 -40
  6. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  7. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +2 -10
  8. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +69 -43
  9. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +2 -2
  10. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -3
  11. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +61 -20
  12. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +240 -45
  13. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -18
  14. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +4 -43
  15. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +14 -15
  16. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +10 -31
  17. package/package.json +7 -7
  18. package/readme.hints.md +38 -1
  19. package/readme.plan.md +314 -382
  20. package/readme.plan2.md +764 -0
  21. package/ts/00_commitinfo_data.ts +1 -1
  22. package/ts/proxies/http-proxy/handlers/index.ts +1 -2
  23. package/ts/proxies/smart-proxy/certificate-manager.ts +29 -23
  24. package/ts/proxies/smart-proxy/models/route-types.ts +12 -56
  25. package/ts/proxies/smart-proxy/route-connection-handler.ts +73 -60
  26. package/ts/proxies/smart-proxy/utils/index.ts +0 -2
  27. package/ts/proxies/smart-proxy/utils/route-helpers.ts +278 -61
  28. package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -56
  29. package/ts/proxies/smart-proxy/utils/route-utils.ts +12 -15
  30. package/ts/proxies/smart-proxy/utils/route-validators.ts +9 -31
  31. package/ts/proxies/http-proxy/handlers/redirect-handler.ts +0 -105
  32. package/ts/proxies/http-proxy/handlers/static-handler.ts +0 -261
@@ -11,7 +11,6 @@
11
11
  * - HTTPS passthrough routes (createHttpsPassthroughRoute)
12
12
  * - Complete HTTPS servers with redirects (createCompleteHttpsServer)
13
13
  * - Load balancer routes (createLoadBalancerRoute)
14
- * - Static file server routes (createStaticFileRoute)
15
14
  * - API routes (createApiRoute)
16
15
  * - WebSocket routes (createWebSocketRoute)
17
16
  * - Port mapping routes (createPortMappingRoute, createOffsetPortMappingRoute)
@@ -19,6 +18,7 @@
19
18
  * - NFTables routes (createNfTablesRoute, createNfTablesTerminateRoute)
20
19
  */
21
20
 
21
+ import * as plugins from '../../../plugins.js';
22
22
  import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget, TPortRange, IRouteContext } from '../models/route-types.js';
23
23
 
24
24
  /**
@@ -118,11 +118,8 @@ export function createHttpToHttpsRedirect(
118
118
 
119
119
  // Create route action
120
120
  const action: IRouteAction = {
121
- type: 'redirect',
122
- redirect: {
123
- to: `https://{domain}:${httpsPort}{path}`,
124
- status: 301
125
- }
121
+ type: 'socket-handler',
122
+ socketHandler: SocketHandlers.httpRedirect(`https://{domain}:${httpsPort}{path}`, 301)
126
123
  };
127
124
 
128
125
  // Create the route config
@@ -266,60 +263,6 @@ export function createLoadBalancerRoute(
266
263
  };
267
264
  }
268
265
 
269
- /**
270
- * Create a static file server route
271
- * @param domains Domain(s) to match
272
- * @param rootDir Root directory path for static files
273
- * @param options Additional route options
274
- * @returns Route configuration object
275
- */
276
- export function createStaticFileRoute(
277
- domains: string | string[],
278
- rootDir: string,
279
- options: {
280
- indexFiles?: string[];
281
- serveOnHttps?: boolean;
282
- certificate?: 'auto' | { key: string; cert: string };
283
- httpPort?: number | number[];
284
- httpsPort?: number | number[];
285
- name?: string;
286
- [key: string]: any;
287
- } = {}
288
- ): IRouteConfig {
289
- // Create route match
290
- const match: IRouteMatch = {
291
- ports: options.serveOnHttps
292
- ? (options.httpsPort || 443)
293
- : (options.httpPort || 80),
294
- domains
295
- };
296
-
297
- // Create route action
298
- const action: IRouteAction = {
299
- type: 'static',
300
- static: {
301
- root: rootDir,
302
- index: options.indexFiles || ['index.html', 'index.htm']
303
- }
304
- };
305
-
306
- // Add TLS configuration if serving on HTTPS
307
- if (options.serveOnHttps) {
308
- action.tls = {
309
- mode: 'terminate',
310
- certificate: options.certificate || 'auto'
311
- };
312
- }
313
-
314
- // Create the route config
315
- return {
316
- match,
317
- action,
318
- name: options.name || `Static Files for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
319
- ...options
320
- };
321
- }
322
-
323
266
  /**
324
267
  * Create an API route configuration
325
268
  * @param domains Domain(s) to match
@@ -810,4 +753,278 @@ export function createCompleteNfTablesHttpsServer(
810
753
  );
811
754
 
812
755
  return [httpsRoute, httpRedirectRoute];
813
- }
756
+ }
757
+
758
+ /**
759
+ * Create a socket handler route configuration
760
+ * @param domains Domain(s) to match
761
+ * @param ports Port(s) to listen on
762
+ * @param handler Socket handler function
763
+ * @param options Additional route options
764
+ * @returns Route configuration object
765
+ */
766
+ export function createSocketHandlerRoute(
767
+ domains: string | string[],
768
+ ports: TPortRange,
769
+ handler: (socket: plugins.net.Socket) => void | Promise<void>,
770
+ options: {
771
+ name?: string;
772
+ priority?: number;
773
+ path?: string;
774
+ } = {}
775
+ ): IRouteConfig {
776
+ return {
777
+ name: options.name || 'socket-handler-route',
778
+ priority: options.priority !== undefined ? options.priority : 50,
779
+ match: {
780
+ domains,
781
+ ports,
782
+ ...(options.path && { path: options.path })
783
+ },
784
+ action: {
785
+ type: 'socket-handler',
786
+ socketHandler: handler
787
+ }
788
+ };
789
+ }
790
+
791
+ /**
792
+ * Pre-built socket handlers for common use cases
793
+ */
794
+ export const SocketHandlers = {
795
+ /**
796
+ * Simple echo server handler
797
+ */
798
+ echo: (socket: plugins.net.Socket, context: IRouteContext) => {
799
+ socket.write('ECHO SERVER READY\n');
800
+ socket.on('data', data => socket.write(data));
801
+ },
802
+
803
+ /**
804
+ * TCP proxy handler
805
+ */
806
+ proxy: (targetHost: string, targetPort: number) => (socket: plugins.net.Socket, context: IRouteContext) => {
807
+ const target = plugins.net.connect(targetPort, targetHost);
808
+ socket.pipe(target);
809
+ target.pipe(socket);
810
+ socket.on('close', () => target.destroy());
811
+ target.on('close', () => socket.destroy());
812
+ target.on('error', (err) => {
813
+ console.error('Proxy target error:', err);
814
+ socket.destroy();
815
+ });
816
+ },
817
+
818
+ /**
819
+ * Line-based protocol handler
820
+ */
821
+ lineProtocol: (handler: (line: string, socket: plugins.net.Socket) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
822
+ let buffer = '';
823
+ socket.on('data', (data) => {
824
+ buffer += data.toString();
825
+ const lines = buffer.split('\n');
826
+ buffer = lines.pop() || '';
827
+ lines.forEach(line => {
828
+ if (line.trim()) {
829
+ handler(line.trim(), socket);
830
+ }
831
+ });
832
+ });
833
+ },
834
+
835
+ /**
836
+ * Simple HTTP response handler (for testing)
837
+ */
838
+ httpResponse: (statusCode: number, body: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
839
+ const response = [
840
+ `HTTP/1.1 ${statusCode} ${statusCode === 200 ? 'OK' : 'Error'}`,
841
+ 'Content-Type: text/plain',
842
+ `Content-Length: ${body.length}`,
843
+ 'Connection: close',
844
+ '',
845
+ body
846
+ ].join('\r\n');
847
+
848
+ socket.write(response);
849
+ socket.end();
850
+ },
851
+
852
+ /**
853
+ * Block connection immediately
854
+ */
855
+ block: (message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
856
+ const finalMessage = message || `Connection blocked from ${context.clientIp}`;
857
+ if (finalMessage) {
858
+ socket.write(finalMessage);
859
+ }
860
+ socket.end();
861
+ },
862
+
863
+ /**
864
+ * HTTP block response
865
+ */
866
+ httpBlock: (statusCode: number = 403, message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
867
+ const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`;
868
+ const finalMessage = message || defaultMessage;
869
+
870
+ const response = [
871
+ `HTTP/1.1 ${statusCode} ${finalMessage}`,
872
+ 'Content-Type: text/plain',
873
+ `Content-Length: ${finalMessage.length}`,
874
+ 'Connection: close',
875
+ '',
876
+ finalMessage
877
+ ].join('\r\n');
878
+
879
+ socket.write(response);
880
+ socket.end();
881
+ },
882
+
883
+ /**
884
+ * HTTP redirect handler
885
+ */
886
+ httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => {
887
+ let buffer = '';
888
+
889
+ socket.once('data', (data) => {
890
+ buffer += data.toString();
891
+
892
+ const lines = buffer.split('\r\n');
893
+ const requestLine = lines[0];
894
+ const [method, path] = requestLine.split(' ');
895
+
896
+ const domain = context.domain || 'localhost';
897
+ const port = context.port;
898
+
899
+ let finalLocation = locationTemplate
900
+ .replace('{domain}', domain)
901
+ .replace('{port}', String(port))
902
+ .replace('{path}', path)
903
+ .replace('{clientIp}', context.clientIp);
904
+
905
+ const message = `Redirecting to ${finalLocation}`;
906
+ const response = [
907
+ `HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`,
908
+ `Location: ${finalLocation}`,
909
+ 'Content-Type: text/plain',
910
+ `Content-Length: ${message.length}`,
911
+ 'Connection: close',
912
+ '',
913
+ message
914
+ ].join('\r\n');
915
+
916
+ socket.write(response);
917
+ socket.end();
918
+ });
919
+ },
920
+
921
+ /**
922
+ * HTTP server handler for ACME challenges and other HTTP needs
923
+ */
924
+ httpServer: (handler: (req: { method: string; url: string; headers: Record<string, string>; body?: string }, res: { status: (code: number) => void; header: (name: string, value: string) => void; send: (data: string) => void; end: () => void }) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
925
+ let buffer = '';
926
+ let requestParsed = false;
927
+
928
+ socket.on('data', (data) => {
929
+ if (requestParsed) return; // Only handle the first request
930
+
931
+ buffer += data.toString();
932
+
933
+ // Check if we have a complete HTTP request
934
+ const headerEndIndex = buffer.indexOf('\r\n\r\n');
935
+ if (headerEndIndex === -1) return; // Need more data
936
+
937
+ requestParsed = true;
938
+
939
+ // Parse the HTTP request
940
+ const headerPart = buffer.substring(0, headerEndIndex);
941
+ const bodyPart = buffer.substring(headerEndIndex + 4);
942
+
943
+ const lines = headerPart.split('\r\n');
944
+ const [method, url] = lines[0].split(' ');
945
+
946
+ const headers: Record<string, string> = {};
947
+ for (let i = 1; i < lines.length; i++) {
948
+ const colonIndex = lines[i].indexOf(':');
949
+ if (colonIndex > 0) {
950
+ const name = lines[i].substring(0, colonIndex).trim().toLowerCase();
951
+ const value = lines[i].substring(colonIndex + 1).trim();
952
+ headers[name] = value;
953
+ }
954
+ }
955
+
956
+ // Create request object
957
+ const req = {
958
+ method: method || 'GET',
959
+ url: url || '/',
960
+ headers,
961
+ body: bodyPart
962
+ };
963
+
964
+ // Create response object
965
+ let statusCode = 200;
966
+ const responseHeaders: Record<string, string> = {};
967
+ let ended = false;
968
+
969
+ const res = {
970
+ status: (code: number) => {
971
+ statusCode = code;
972
+ },
973
+ header: (name: string, value: string) => {
974
+ responseHeaders[name] = value;
975
+ },
976
+ send: (data: string) => {
977
+ if (ended) return;
978
+ ended = true;
979
+
980
+ if (!responseHeaders['content-type']) {
981
+ responseHeaders['content-type'] = 'text/plain';
982
+ }
983
+ responseHeaders['content-length'] = String(data.length);
984
+ responseHeaders['connection'] = 'close';
985
+
986
+ const statusText = statusCode === 200 ? 'OK' :
987
+ statusCode === 404 ? 'Not Found' :
988
+ statusCode === 500 ? 'Internal Server Error' : 'Response';
989
+
990
+ let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
991
+ for (const [name, value] of Object.entries(responseHeaders)) {
992
+ response += `${name}: ${value}\r\n`;
993
+ }
994
+ response += '\r\n';
995
+ response += data;
996
+
997
+ socket.write(response);
998
+ socket.end();
999
+ },
1000
+ end: () => {
1001
+ if (ended) return;
1002
+ ended = true;
1003
+ socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
1004
+ socket.end();
1005
+ }
1006
+ };
1007
+
1008
+ try {
1009
+ handler(req, res);
1010
+ // Ensure response is sent even if handler doesn't call send()
1011
+ setTimeout(() => {
1012
+ if (!ended) {
1013
+ res.send('');
1014
+ }
1015
+ }, 1000);
1016
+ } catch (error) {
1017
+ if (!ended) {
1018
+ res.status(500);
1019
+ res.send('Internal Server Error');
1020
+ }
1021
+ }
1022
+ });
1023
+
1024
+ socket.on('error', () => {
1025
+ if (!requestParsed) {
1026
+ socket.end();
1027
+ }
1028
+ });
1029
+ }
1030
+ };
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget } from '../models/route-types.js';
9
9
  import { mergeRouteConfigs } from './route-utils.js';
10
+ import { SocketHandlers } from './route-helpers.js';
10
11
 
11
12
  /**
12
13
  * Create a basic HTTP route configuration
@@ -112,11 +113,11 @@ export function createHttpToHttpsRedirect(
112
113
  ports: 80
113
114
  },
114
115
  action: {
115
- type: 'redirect',
116
- redirect: {
117
- to: options.preservePath ? 'https://{domain}{path}' : 'https://{domain}',
118
- status: options.redirectCode || 301
119
- }
116
+ type: 'socket-handler',
117
+ socketHandler: SocketHandlers.httpRedirect(
118
+ options.preservePath ? 'https://{domain}{path}' : 'https://{domain}',
119
+ options.redirectCode || 301
120
+ )
120
121
  },
121
122
  name: options.name || `HTTP to HTTPS redirect: ${Array.isArray(domains) ? domains.join(', ') : domains}`
122
123
  };
@@ -214,57 +215,6 @@ export function createApiGatewayRoute(
214
215
  return mergeRouteConfigs(baseRoute, apiRoute);
215
216
  }
216
217
 
217
- /**
218
- * Create a static file server route pattern
219
- * @param domains Domain(s) to match
220
- * @param rootDirectory Root directory for static files
221
- * @param options Additional route options
222
- * @returns Static file server route configuration
223
- */
224
- export function createStaticFileServerRoute(
225
- domains: string | string[],
226
- rootDirectory: string,
227
- options: {
228
- useTls?: boolean;
229
- certificate?: 'auto' | { key: string; cert: string };
230
- indexFiles?: string[];
231
- cacheControl?: string;
232
- path?: string;
233
- [key: string]: any;
234
- } = {}
235
- ): IRouteConfig {
236
- // Create base route with static action
237
- const baseRoute: IRouteConfig = {
238
- match: {
239
- domains,
240
- ports: options.useTls ? 443 : 80,
241
- path: options.path || '/'
242
- },
243
- action: {
244
- type: 'static',
245
- static: {
246
- root: rootDirectory,
247
- index: options.indexFiles || ['index.html', 'index.htm'],
248
- headers: {
249
- 'Cache-Control': options.cacheControl || 'public, max-age=3600'
250
- }
251
- }
252
- },
253
- name: options.name || `Static Server: ${Array.isArray(domains) ? domains.join(', ') : domains}`,
254
- priority: options.priority || 50
255
- };
256
-
257
- // Add TLS configuration if requested
258
- if (options.useTls) {
259
- baseRoute.action.tls = {
260
- mode: 'terminate',
261
- certificate: options.certificate || 'auto'
262
- };
263
- }
264
-
265
- return baseRoute;
266
- }
267
-
268
218
  /**
269
219
  * Create a WebSocket route pattern
270
220
  * @param domains Domain(s) to match
@@ -53,7 +53,15 @@ export function mergeRouteConfigs(
53
53
  if (overrideRoute.action) {
54
54
  // If action types are different, replace the entire action
55
55
  if (overrideRoute.action.type && overrideRoute.action.type !== mergedRoute.action.type) {
56
- mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
56
+ // Handle socket handler specially since it's a function
57
+ if (overrideRoute.action.type === 'socket-handler' && overrideRoute.action.socketHandler) {
58
+ mergedRoute.action = {
59
+ type: 'socket-handler',
60
+ socketHandler: overrideRoute.action.socketHandler
61
+ };
62
+ } else {
63
+ mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
64
+ }
57
65
  } else {
58
66
  // Otherwise merge the action properties
59
67
  mergedRoute.action = { ...mergedRoute.action };
@@ -74,20 +82,9 @@ export function mergeRouteConfigs(
74
82
  };
75
83
  }
76
84
 
77
- // Merge redirect options
78
- if (overrideRoute.action.redirect) {
79
- mergedRoute.action.redirect = {
80
- ...mergedRoute.action.redirect,
81
- ...overrideRoute.action.redirect
82
- };
83
- }
84
-
85
- // Merge static options
86
- if (overrideRoute.action.static) {
87
- mergedRoute.action.static = {
88
- ...mergedRoute.action.static,
89
- ...overrideRoute.action.static
90
- };
85
+ // Handle socket handler update
86
+ if (overrideRoute.action.socketHandler) {
87
+ mergedRoute.action.socketHandler = overrideRoute.action.socketHandler;
91
88
  }
92
89
  }
93
90
  }
@@ -98,7 +98,7 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
98
98
  // Validate action type
99
99
  if (!action.type) {
100
100
  errors.push('Action type is required');
101
- } else if (!['forward', 'redirect', 'static', 'block'].includes(action.type)) {
101
+ } else if (!['forward', 'socket-handler'].includes(action.type)) {
102
102
  errors.push(`Invalid action type: ${action.type}`);
103
103
  }
104
104
 
@@ -143,30 +143,12 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
143
143
  }
144
144
  }
145
145
 
146
- // Validate redirect for 'redirect' action
147
- if (action.type === 'redirect') {
148
- if (!action.redirect) {
149
- errors.push('Redirect configuration is required for redirect action');
150
- } else {
151
- if (!action.redirect.to) {
152
- errors.push('Redirect target (to) is required');
153
- }
154
-
155
- if (action.redirect.status &&
156
- ![301, 302, 303, 307, 308].includes(action.redirect.status)) {
157
- errors.push('Invalid redirect status code');
158
- }
159
- }
160
- }
161
-
162
- // Validate static file config for 'static' action
163
- if (action.type === 'static') {
164
- if (!action.static) {
165
- errors.push('Static file configuration is required for static action');
166
- } else {
167
- if (!action.static.root) {
168
- errors.push('Static file root directory is required');
169
- }
146
+ // Validate socket handler for 'socket-handler' action
147
+ if (action.type === 'socket-handler') {
148
+ if (!action.socketHandler) {
149
+ errors.push('Socket handler function is required for socket-handler action');
150
+ } else if (typeof action.socketHandler !== 'function') {
151
+ errors.push('Socket handler must be a function');
170
152
  }
171
153
  }
172
154
 
@@ -261,12 +243,8 @@ export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType:
261
243
  switch (actionType) {
262
244
  case 'forward':
263
245
  return !!route.action.target && !!route.action.target.host && !!route.action.target.port;
264
- case 'redirect':
265
- return !!route.action.redirect && !!route.action.redirect.to;
266
- case 'static':
267
- return !!route.action.static && !!route.action.static.root;
268
- case 'block':
269
- return true; // Block action doesn't require additional properties
246
+ case 'socket-handler':
247
+ return !!route.action.socketHandler && typeof route.action.socketHandler === 'function';
270
248
  default:
271
249
  return false;
272
250
  }
@@ -1,105 +0,0 @@
1
- import * as plugins from '../../../plugins.js';
2
- import type { IRouteConfig } from '../../smart-proxy/models/route-types.js';
3
- import type { IConnectionRecord } from '../../smart-proxy/models/interfaces.js';
4
- import type { ILogger } from '../models/types.js';
5
- import { createLogger } from '../models/types.js';
6
- import { HttpStatus, getStatusText } from '../models/http-types.js';
7
-
8
- export interface IRedirectHandlerContext {
9
- connectionId: string;
10
- connectionManager: any; // Avoid circular deps
11
- settings: any;
12
- logger?: ILogger;
13
- }
14
-
15
- /**
16
- * Handles HTTP redirect routes
17
- */
18
- export class RedirectHandler {
19
- /**
20
- * Handle redirect routes
21
- */
22
- public static async handleRedirect(
23
- socket: plugins.net.Socket,
24
- route: IRouteConfig,
25
- context: IRedirectHandlerContext
26
- ): Promise<void> {
27
- const { connectionId, connectionManager, settings } = context;
28
- const logger = context.logger || createLogger(settings.logLevel || 'info');
29
- const action = route.action;
30
-
31
- // We should have a redirect configuration
32
- if (!action.redirect) {
33
- logger.error(`[${connectionId}] Redirect action missing redirect configuration`);
34
- socket.end();
35
- connectionManager.cleanupConnection({ id: connectionId }, 'missing_redirect');
36
- return;
37
- }
38
-
39
- // For TLS connections, we can't do redirects at the TCP level
40
- // This check should be done before calling this handler
41
-
42
- // Wait for the first HTTP request to perform the redirect
43
- const dataListeners: ((chunk: Buffer) => void)[] = [];
44
-
45
- const httpDataHandler = (chunk: Buffer) => {
46
- // Remove all data listeners to avoid duplicated processing
47
- for (const listener of dataListeners) {
48
- socket.removeListener('data', listener);
49
- }
50
-
51
- // Parse HTTP request to get path
52
- try {
53
- const headersEnd = chunk.indexOf('\r\n\r\n');
54
- if (headersEnd === -1) {
55
- // Not a complete HTTP request, need more data
56
- socket.once('data', httpDataHandler);
57
- dataListeners.push(httpDataHandler);
58
- return;
59
- }
60
-
61
- const httpHeaders = chunk.slice(0, headersEnd).toString();
62
- const requestLine = httpHeaders.split('\r\n')[0];
63
- const [method, path] = requestLine.split(' ');
64
-
65
- // Extract Host header
66
- const hostMatch = httpHeaders.match(/Host: (.+?)(\r\n|\r|\n|$)/i);
67
- const host = hostMatch ? hostMatch[1].trim() : '';
68
-
69
- // Process the redirect URL with template variables
70
- let redirectUrl = action.redirect.to;
71
- redirectUrl = redirectUrl.replace(/\{domain\}/g, host);
72
- redirectUrl = redirectUrl.replace(/\{path\}/g, path || '');
73
- redirectUrl = redirectUrl.replace(/\{port\}/g, socket.localPort?.toString() || '80');
74
-
75
- // Prepare the HTTP redirect response
76
- const redirectResponse = [
77
- `HTTP/1.1 ${action.redirect.status} Moved`,
78
- `Location: ${redirectUrl}`,
79
- 'Connection: close',
80
- 'Content-Length: 0',
81
- '',
82
- '',
83
- ].join('\r\n');
84
-
85
- if (settings.enableDetailedLogging) {
86
- logger.info(
87
- `[${connectionId}] Redirecting to ${redirectUrl} with status ${action.redirect.status}`
88
- );
89
- }
90
-
91
- // Send the redirect response
92
- socket.end(redirectResponse);
93
- connectionManager.initiateCleanupOnce({ id: connectionId }, 'redirect_complete');
94
- } catch (err) {
95
- logger.error(`[${connectionId}] Error processing HTTP redirect: ${err}`);
96
- socket.end();
97
- connectionManager.initiateCleanupOnce({ id: connectionId }, 'redirect_error');
98
- }
99
- };
100
-
101
- // Setup the HTTP data handler
102
- socket.once('data', httpDataHandler);
103
- dataListeners.push(httpDataHandler);
104
- }
105
- }