@push.rocks/smartproxy 19.4.2 → 19.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/proxies/http-proxy/handlers/index.d.ts +1 -2
- package/dist_ts/proxies/http-proxy/handlers/index.js +3 -3
- package/dist_ts/proxies/http-proxy/models/types.js +2 -2
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +30 -25
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +2 -5
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +9 -41
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +5 -6
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -12
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +152 -47
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +2 -0
- package/dist_ts/proxies/smart-proxy/route-manager.js +7 -8
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +2 -2
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -3
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +61 -20
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +249 -53
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -18
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +4 -43
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +14 -15
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +10 -31
- package/package.json +7 -7
- package/readme.hints.md +168 -5
- package/readme.plan.md +314 -382
- package/readme.plan2.md +764 -0
- package/readme.problems.md +86 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/http-proxy/handlers/index.ts +1 -2
- package/ts/proxies/http-proxy/models/types.ts +1 -1
- package/ts/proxies/smart-proxy/certificate-manager.ts +29 -23
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +1 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +12 -59
- package/ts/proxies/smart-proxy/nftables-manager.ts +4 -5
- package/ts/proxies/smart-proxy/route-connection-handler.ts +170 -64
- package/ts/proxies/smart-proxy/route-manager.ts +7 -8
- package/ts/proxies/smart-proxy/utils/index.ts +0 -2
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +289 -70
- package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -56
- package/ts/proxies/smart-proxy/utils/route-utils.ts +12 -15
- package/ts/proxies/smart-proxy/utils/route-validators.ts +9 -31
- package/ts/proxies/http-proxy/handlers/redirect-handler.ts +0 -105
- package/ts/proxies/http-proxy/handlers/static-handler.ts +0 -261
|
@@ -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
|
-
}
|
|
@@ -1,261 +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 type { IRouteContext } from '../../../core/models/route-context.js';
|
|
7
|
-
import { HttpStatus, getStatusText } from '../models/http-types.js';
|
|
8
|
-
|
|
9
|
-
export interface IStaticHandlerContext {
|
|
10
|
-
connectionId: string;
|
|
11
|
-
connectionManager: any; // Avoid circular deps
|
|
12
|
-
settings: any;
|
|
13
|
-
logger?: ILogger;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Handles static routes including ACME challenges
|
|
18
|
-
*/
|
|
19
|
-
export class StaticHandler {
|
|
20
|
-
/**
|
|
21
|
-
* Handle static routes
|
|
22
|
-
*/
|
|
23
|
-
public static async handleStatic(
|
|
24
|
-
socket: plugins.net.Socket,
|
|
25
|
-
route: IRouteConfig,
|
|
26
|
-
context: IStaticHandlerContext,
|
|
27
|
-
record: IConnectionRecord,
|
|
28
|
-
initialChunk?: Buffer
|
|
29
|
-
): Promise<void> {
|
|
30
|
-
const { connectionId, connectionManager, settings } = context;
|
|
31
|
-
const logger = context.logger || createLogger(settings.logLevel || 'info');
|
|
32
|
-
|
|
33
|
-
if (!route.action.handler) {
|
|
34
|
-
logger.error(`[${connectionId}] Static route '${route.name}' has no handler`);
|
|
35
|
-
socket.end();
|
|
36
|
-
connectionManager.cleanupConnection(record, 'no_handler');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let buffer = Buffer.alloc(0);
|
|
41
|
-
let processingData = false;
|
|
42
|
-
|
|
43
|
-
const handleHttpData = async (chunk: Buffer) => {
|
|
44
|
-
// Accumulate the data
|
|
45
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
46
|
-
|
|
47
|
-
// Prevent concurrent processing of the same buffer
|
|
48
|
-
if (processingData) return;
|
|
49
|
-
processingData = true;
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
// Process data until we have a complete request or need more data
|
|
53
|
-
await processBuffer();
|
|
54
|
-
} finally {
|
|
55
|
-
processingData = false;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const processBuffer = async () => {
|
|
60
|
-
// Look for end of HTTP headers
|
|
61
|
-
const headerEndIndex = buffer.indexOf('\r\n\r\n');
|
|
62
|
-
if (headerEndIndex === -1) {
|
|
63
|
-
// Need more data
|
|
64
|
-
if (buffer.length > 8192) {
|
|
65
|
-
// Prevent excessive buffering
|
|
66
|
-
logger.error(`[${connectionId}] HTTP headers too large`);
|
|
67
|
-
socket.end();
|
|
68
|
-
connectionManager.cleanupConnection(record, 'headers_too_large');
|
|
69
|
-
}
|
|
70
|
-
return; // Wait for more data to arrive
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Parse the HTTP request
|
|
74
|
-
const headerBuffer = buffer.slice(0, headerEndIndex);
|
|
75
|
-
const headers = headerBuffer.toString();
|
|
76
|
-
const lines = headers.split('\r\n');
|
|
77
|
-
|
|
78
|
-
if (lines.length === 0) {
|
|
79
|
-
logger.error(`[${connectionId}] Invalid HTTP request`);
|
|
80
|
-
socket.end();
|
|
81
|
-
connectionManager.cleanupConnection(record, 'invalid_request');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Parse request line
|
|
86
|
-
const requestLine = lines[0];
|
|
87
|
-
const requestParts = requestLine.split(' ');
|
|
88
|
-
if (requestParts.length < 3) {
|
|
89
|
-
logger.error(`[${connectionId}] Invalid HTTP request line`);
|
|
90
|
-
socket.end();
|
|
91
|
-
connectionManager.cleanupConnection(record, 'invalid_request_line');
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const [method, path, httpVersion] = requestParts;
|
|
96
|
-
|
|
97
|
-
// Parse headers
|
|
98
|
-
const headersMap: Record<string, string> = {};
|
|
99
|
-
for (let i = 1; i < lines.length; i++) {
|
|
100
|
-
const colonIndex = lines[i].indexOf(':');
|
|
101
|
-
if (colonIndex > 0) {
|
|
102
|
-
const key = lines[i].slice(0, colonIndex).trim().toLowerCase();
|
|
103
|
-
const value = lines[i].slice(colonIndex + 1).trim();
|
|
104
|
-
headersMap[key] = value;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Check for Content-Length to handle request body
|
|
109
|
-
const requestBodyLength = parseInt(headersMap['content-length'] || '0', 10);
|
|
110
|
-
const bodyStartIndex = headerEndIndex + 4; // Skip the \r\n\r\n
|
|
111
|
-
|
|
112
|
-
// If there's a body, ensure we have the full body
|
|
113
|
-
if (requestBodyLength > 0) {
|
|
114
|
-
const totalExpectedLength = bodyStartIndex + requestBodyLength;
|
|
115
|
-
|
|
116
|
-
// If we don't have the complete body yet, wait for more data
|
|
117
|
-
if (buffer.length < totalExpectedLength) {
|
|
118
|
-
// Implement a reasonable body size limit to prevent memory issues
|
|
119
|
-
if (requestBodyLength > 1024 * 1024) {
|
|
120
|
-
// 1MB limit
|
|
121
|
-
logger.error(`[${connectionId}] Request body too large`);
|
|
122
|
-
socket.end();
|
|
123
|
-
connectionManager.cleanupConnection(record, 'body_too_large');
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
return; // Wait for more data
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Extract query string if present
|
|
131
|
-
let pathname = path;
|
|
132
|
-
let query: string | undefined;
|
|
133
|
-
const queryIndex = path.indexOf('?');
|
|
134
|
-
if (queryIndex !== -1) {
|
|
135
|
-
pathname = path.slice(0, queryIndex);
|
|
136
|
-
query = path.slice(queryIndex + 1);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
// Get request body if present
|
|
141
|
-
let requestBody: Buffer | undefined;
|
|
142
|
-
if (requestBodyLength > 0) {
|
|
143
|
-
requestBody = buffer.slice(bodyStartIndex, bodyStartIndex + requestBodyLength);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Pause socket to prevent data loss during async processing
|
|
147
|
-
socket.pause();
|
|
148
|
-
|
|
149
|
-
// Remove the data listener since we're handling the request
|
|
150
|
-
socket.removeListener('data', handleHttpData);
|
|
151
|
-
|
|
152
|
-
// Build route context with parsed HTTP information
|
|
153
|
-
const context: IRouteContext = {
|
|
154
|
-
port: record.localPort,
|
|
155
|
-
domain: record.lockedDomain || headersMap['host']?.split(':')[0],
|
|
156
|
-
clientIp: record.remoteIP,
|
|
157
|
-
serverIp: socket.localAddress!,
|
|
158
|
-
path: pathname,
|
|
159
|
-
query: query,
|
|
160
|
-
headers: headersMap,
|
|
161
|
-
isTls: record.isTLS,
|
|
162
|
-
tlsVersion: record.tlsVersion,
|
|
163
|
-
routeName: route.name,
|
|
164
|
-
routeId: route.id,
|
|
165
|
-
timestamp: Date.now(),
|
|
166
|
-
connectionId,
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Since IRouteContext doesn't have a body property,
|
|
170
|
-
// we need an alternative approach to handle the body
|
|
171
|
-
let response;
|
|
172
|
-
|
|
173
|
-
if (requestBody) {
|
|
174
|
-
if (settings.enableDetailedLogging) {
|
|
175
|
-
logger.info(
|
|
176
|
-
`[${connectionId}] Processing request with body (${requestBody.length} bytes)`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Pass the body as an additional parameter by extending the context object
|
|
181
|
-
// This is not type-safe, but it allows handlers that expect a body to work
|
|
182
|
-
const extendedContext = {
|
|
183
|
-
...context,
|
|
184
|
-
// Provide both raw buffer and string representation
|
|
185
|
-
requestBody: requestBody,
|
|
186
|
-
requestBodyText: requestBody.toString(),
|
|
187
|
-
method: method,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// Call the handler with the extended context
|
|
191
|
-
// The handler needs to know to look for the non-standard properties
|
|
192
|
-
response = await route.action.handler(extendedContext as any);
|
|
193
|
-
} else {
|
|
194
|
-
// Call the handler with the standard context
|
|
195
|
-
const extendedContext = {
|
|
196
|
-
...context,
|
|
197
|
-
method: method,
|
|
198
|
-
};
|
|
199
|
-
response = await route.action.handler(extendedContext as any);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Prepare the HTTP response
|
|
203
|
-
const responseHeaders = response.headers || {};
|
|
204
|
-
const contentLength = Buffer.byteLength(response.body || '');
|
|
205
|
-
responseHeaders['Content-Length'] = contentLength.toString();
|
|
206
|
-
|
|
207
|
-
if (!responseHeaders['Content-Type']) {
|
|
208
|
-
responseHeaders['Content-Type'] = 'text/plain';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Build the response
|
|
212
|
-
let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`;
|
|
213
|
-
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
214
|
-
httpResponse += `${key}: ${value}\r\n`;
|
|
215
|
-
}
|
|
216
|
-
httpResponse += '\r\n';
|
|
217
|
-
|
|
218
|
-
// Send response
|
|
219
|
-
socket.write(httpResponse);
|
|
220
|
-
if (response.body) {
|
|
221
|
-
socket.write(response.body);
|
|
222
|
-
}
|
|
223
|
-
socket.end();
|
|
224
|
-
|
|
225
|
-
connectionManager.cleanupConnection(record, 'completed');
|
|
226
|
-
} catch (error) {
|
|
227
|
-
logger.error(`[${connectionId}] Error in static handler: ${error}`);
|
|
228
|
-
|
|
229
|
-
// Send error response
|
|
230
|
-
const errorResponse =
|
|
231
|
-
'HTTP/1.1 500 Internal Server Error\r\n' +
|
|
232
|
-
'Content-Type: text/plain\r\n' +
|
|
233
|
-
'Content-Length: 21\r\n' +
|
|
234
|
-
'\r\n' +
|
|
235
|
-
'Internal Server Error';
|
|
236
|
-
socket.write(errorResponse);
|
|
237
|
-
socket.end();
|
|
238
|
-
|
|
239
|
-
connectionManager.cleanupConnection(record, 'handler_error');
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Process initial chunk if provided
|
|
244
|
-
if (initialChunk && initialChunk.length > 0) {
|
|
245
|
-
if (settings.enableDetailedLogging) {
|
|
246
|
-
logger.info(`[${connectionId}] Processing initial data chunk (${initialChunk.length} bytes)`);
|
|
247
|
-
}
|
|
248
|
-
// Process the initial chunk immediately
|
|
249
|
-
handleHttpData(initialChunk);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Listen for additional data
|
|
253
|
-
socket.on('data', handleHttpData);
|
|
254
|
-
|
|
255
|
-
// Ensure cleanup on socket close
|
|
256
|
-
socket.once('close', () => {
|
|
257
|
-
socket.removeListener('data', handleHttpData);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|