rezo 1.0.42 → 1.0.43
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/adapters/curl.cjs +131 -29
- package/dist/adapters/curl.js +131 -29
- package/dist/adapters/entries/curl.d.ts +65 -0
- package/dist/adapters/entries/fetch.d.ts +65 -0
- package/dist/adapters/entries/http.d.ts +65 -0
- package/dist/adapters/entries/http2.d.ts +65 -0
- package/dist/adapters/entries/react-native.d.ts +65 -0
- package/dist/adapters/entries/xhr.d.ts +65 -0
- package/dist/adapters/http2.cjs +209 -22
- package/dist/adapters/http2.js +209 -22
- package/dist/adapters/index.cjs +6 -6
- package/dist/cache/file-cacher.cjs +7 -1
- package/dist/cache/file-cacher.js +7 -1
- package/dist/cache/index.cjs +15 -13
- package/dist/cache/index.js +1 -0
- package/dist/cache/navigation-history.cjs +298 -0
- package/dist/cache/navigation-history.js +296 -0
- package/dist/cache/url-store.cjs +7 -1
- package/dist/cache/url-store.js +7 -1
- package/dist/core/rezo.cjs +7 -0
- package/dist/core/rezo.js +7 -0
- package/dist/crawler.d.ts +196 -11
- package/dist/entries/crawler.cjs +5 -5
- package/dist/index.cjs +27 -24
- package/dist/index.d.ts +73 -0
- package/dist/index.js +1 -0
- package/dist/internal/agents/base.cjs +113 -0
- package/dist/internal/agents/base.js +110 -0
- package/dist/internal/agents/http-proxy.cjs +89 -0
- package/dist/internal/agents/http-proxy.js +86 -0
- package/dist/internal/agents/https-proxy.cjs +176 -0
- package/dist/internal/agents/https-proxy.js +173 -0
- package/dist/internal/agents/index.cjs +10 -0
- package/dist/internal/agents/index.js +5 -0
- package/dist/internal/agents/socks-client.cjs +571 -0
- package/dist/internal/agents/socks-client.js +567 -0
- package/dist/internal/agents/socks-proxy.cjs +75 -0
- package/dist/internal/agents/socks-proxy.js +72 -0
- package/dist/platform/browser.d.ts +65 -0
- package/dist/platform/bun.d.ts +65 -0
- package/dist/platform/deno.d.ts +65 -0
- package/dist/platform/node.d.ts +65 -0
- package/dist/platform/react-native.d.ts +65 -0
- package/dist/platform/worker.d.ts +65 -0
- package/dist/plugin/crawler-options.cjs +1 -1
- package/dist/plugin/crawler-options.js +1 -1
- package/dist/plugin/crawler.cjs +192 -1
- package/dist/plugin/crawler.js +192 -1
- package/dist/plugin/index.cjs +36 -36
- package/dist/proxy/index.cjs +18 -16
- package/dist/proxy/index.js +17 -12
- package/dist/queue/index.cjs +8 -8
- package/dist/responses/buildError.cjs +11 -2
- package/dist/responses/buildError.js +11 -2
- package/dist/responses/universal/index.cjs +11 -11
- package/dist/utils/curl.cjs +317 -0
- package/dist/utils/curl.js +314 -0
- package/package.json +1 -1
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
|
|
4
|
+
export class SocksClientError extends Error {
|
|
5
|
+
options;
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SocksClientError";
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const SOCKS_VERSION = {
|
|
13
|
+
SOCKS4: 4,
|
|
14
|
+
SOCKS5: 5
|
|
15
|
+
};
|
|
16
|
+
const SOCKS_COMMAND = {
|
|
17
|
+
connect: 1,
|
|
18
|
+
bind: 2,
|
|
19
|
+
associate: 3
|
|
20
|
+
};
|
|
21
|
+
const SOCKS5_AUTH = {
|
|
22
|
+
NoAuth: 0,
|
|
23
|
+
UserPass: 2,
|
|
24
|
+
NoAcceptable: 255
|
|
25
|
+
};
|
|
26
|
+
const SOCKS5_HOST_TYPE = {
|
|
27
|
+
IPv4: 1,
|
|
28
|
+
Hostname: 3,
|
|
29
|
+
IPv6: 4
|
|
30
|
+
};
|
|
31
|
+
const SOCKS4_RESPONSE = {
|
|
32
|
+
Granted: 90,
|
|
33
|
+
Failed: 91,
|
|
34
|
+
FailedNoIdentd: 92,
|
|
35
|
+
FailedIdMismatch: 93
|
|
36
|
+
};
|
|
37
|
+
const SOCKS5_RESPONSE = {
|
|
38
|
+
Granted: 0,
|
|
39
|
+
GeneralFailure: 1,
|
|
40
|
+
ConnectionNotAllowed: 2,
|
|
41
|
+
NetworkUnreachable: 3,
|
|
42
|
+
HostUnreachable: 4,
|
|
43
|
+
ConnectionRefused: 5,
|
|
44
|
+
TTLExpired: 6,
|
|
45
|
+
CommandNotSupported: 7,
|
|
46
|
+
AddressTypeNotSupported: 8
|
|
47
|
+
};
|
|
48
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
49
|
+
var SocksState;
|
|
50
|
+
((SocksState) => {
|
|
51
|
+
SocksState[SocksState["Created"] = 0] = "Created";
|
|
52
|
+
SocksState[SocksState["Connecting"] = 1] = "Connecting";
|
|
53
|
+
SocksState[SocksState["Connected"] = 2] = "Connected";
|
|
54
|
+
SocksState[SocksState["SentInitialHandshake"] = 3] = "SentInitialHandshake";
|
|
55
|
+
SocksState[SocksState["SentAuthentication"] = 4] = "SentAuthentication";
|
|
56
|
+
SocksState[SocksState["SentFinalHandshake"] = 5] = "SentFinalHandshake";
|
|
57
|
+
SocksState[SocksState["ReceivedAuthenticationResponse"] = 6] = "ReceivedAuthenticationResponse";
|
|
58
|
+
SocksState[SocksState["ReceivedFinalResponse"] = 7] = "ReceivedFinalResponse";
|
|
59
|
+
SocksState[SocksState["Established"] = 8] = "Established";
|
|
60
|
+
SocksState[SocksState["Error"] = 9] = "Error";
|
|
61
|
+
SocksState[SocksState["BoundWaitingForConnection"] = 10] = "BoundWaitingForConnection";
|
|
62
|
+
})(SocksState ||= {});
|
|
63
|
+
function ipv4ToBuffer(ip) {
|
|
64
|
+
const parts = ip.split(".").map((p) => parseInt(p, 10));
|
|
65
|
+
return Buffer.from(parts);
|
|
66
|
+
}
|
|
67
|
+
function bufferToIpv4(buffer, offset) {
|
|
68
|
+
return `${buffer[offset]}.${buffer[offset + 1]}.${buffer[offset + 2]}.${buffer[offset + 3]}`;
|
|
69
|
+
}
|
|
70
|
+
function bufferToIpv6(buffer, offset) {
|
|
71
|
+
const parts = [];
|
|
72
|
+
for (let i = 0;i < 16; i += 2) {
|
|
73
|
+
const value = buffer.readUInt16BE(offset + i);
|
|
74
|
+
parts.push(value.toString(16));
|
|
75
|
+
}
|
|
76
|
+
return parts.join(":");
|
|
77
|
+
}
|
|
78
|
+
function ipv6ToBuffer(ip) {
|
|
79
|
+
const expandedIp = expandIPv6(ip);
|
|
80
|
+
const parts = expandedIp.split(":").map((p) => parseInt(p, 16));
|
|
81
|
+
const buffer = Buffer.alloc(16);
|
|
82
|
+
for (let i = 0;i < 8; i++) {
|
|
83
|
+
buffer.writeUInt16BE(parts[i], i * 2);
|
|
84
|
+
}
|
|
85
|
+
return buffer;
|
|
86
|
+
}
|
|
87
|
+
function expandIPv6(ip) {
|
|
88
|
+
if (ip.includes("::")) {
|
|
89
|
+
const [left, right = ""] = ip.split("::");
|
|
90
|
+
const leftParts = left ? left.split(":") : [];
|
|
91
|
+
const rightParts = right ? right.split(":") : [];
|
|
92
|
+
const missingParts = 8 - leftParts.length - rightParts.length;
|
|
93
|
+
const middle = Array(missingParts).fill("0");
|
|
94
|
+
return [...leftParts, ...middle, ...rightParts].join(":");
|
|
95
|
+
}
|
|
96
|
+
return ip;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class ReceiveBuffer {
|
|
100
|
+
buffer;
|
|
101
|
+
readOffset = 0;
|
|
102
|
+
writeOffset = 0;
|
|
103
|
+
static INITIAL_SIZE = 256;
|
|
104
|
+
constructor() {
|
|
105
|
+
this.buffer = Buffer.allocUnsafe(ReceiveBuffer.INITIAL_SIZE);
|
|
106
|
+
}
|
|
107
|
+
append(data) {
|
|
108
|
+
const requiredSize = this.writeOffset + data.length;
|
|
109
|
+
if (requiredSize > this.buffer.length) {
|
|
110
|
+
const availableData = this.writeOffset - this.readOffset;
|
|
111
|
+
if (this.readOffset > this.buffer.length / 2) {
|
|
112
|
+
this.buffer.copy(this.buffer, 0, this.readOffset, this.writeOffset);
|
|
113
|
+
this.writeOffset = availableData;
|
|
114
|
+
this.readOffset = 0;
|
|
115
|
+
}
|
|
116
|
+
if (this.writeOffset + data.length > this.buffer.length) {
|
|
117
|
+
const newSize = Math.max(this.buffer.length * 2, this.writeOffset + data.length);
|
|
118
|
+
const newBuffer = Buffer.allocUnsafe(newSize);
|
|
119
|
+
this.buffer.copy(newBuffer, 0, this.readOffset, this.writeOffset);
|
|
120
|
+
this.writeOffset -= this.readOffset;
|
|
121
|
+
this.readOffset = 0;
|
|
122
|
+
this.buffer = newBuffer;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
data.copy(this.buffer, this.writeOffset);
|
|
126
|
+
this.writeOffset += data.length;
|
|
127
|
+
}
|
|
128
|
+
get length() {
|
|
129
|
+
return this.writeOffset - this.readOffset;
|
|
130
|
+
}
|
|
131
|
+
peek(length) {
|
|
132
|
+
return this.buffer.subarray(this.readOffset, this.readOffset + length);
|
|
133
|
+
}
|
|
134
|
+
get(length) {
|
|
135
|
+
const result = this.buffer.subarray(this.readOffset, this.readOffset + length);
|
|
136
|
+
this.readOffset += length;
|
|
137
|
+
if (this.readOffset === this.writeOffset) {
|
|
138
|
+
this.readOffset = 0;
|
|
139
|
+
this.writeOffset = 0;
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class SocksClient extends EventEmitter {
|
|
146
|
+
options;
|
|
147
|
+
socket;
|
|
148
|
+
state = 0 /* Created */;
|
|
149
|
+
receiveBuffer;
|
|
150
|
+
nextRequiredPacketBufferSize = 0;
|
|
151
|
+
socks5ChosenAuthType = SOCKS5_AUTH.NoAuth;
|
|
152
|
+
onDataReceived;
|
|
153
|
+
onClose;
|
|
154
|
+
onError;
|
|
155
|
+
onConnect;
|
|
156
|
+
constructor(options) {
|
|
157
|
+
super();
|
|
158
|
+
this.options = { ...options };
|
|
159
|
+
this.validateOptions();
|
|
160
|
+
this.state = 0 /* Created */;
|
|
161
|
+
}
|
|
162
|
+
validateOptions() {
|
|
163
|
+
const { proxy, destination } = this.options;
|
|
164
|
+
if (!proxy || !proxy.host || !proxy.port) {
|
|
165
|
+
throw new SocksClientError("Invalid proxy configuration", this.options);
|
|
166
|
+
}
|
|
167
|
+
if (!destination || !destination.host || !destination.port) {
|
|
168
|
+
throw new SocksClientError("Invalid destination configuration", this.options);
|
|
169
|
+
}
|
|
170
|
+
if (proxy.type !== 4 && proxy.type !== 5) {
|
|
171
|
+
throw new SocksClientError("Invalid SOCKS version (must be 4 or 5)", this.options);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
static async createConnection(options) {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
try {
|
|
177
|
+
const client = new SocksClient(options);
|
|
178
|
+
client.connect(options.existing_socket);
|
|
179
|
+
client.once("established", (info) => {
|
|
180
|
+
client.removeAllListeners();
|
|
181
|
+
resolve(info);
|
|
182
|
+
});
|
|
183
|
+
client.once("error", (err) => {
|
|
184
|
+
client.removeAllListeners();
|
|
185
|
+
reject(err);
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
reject(err);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
setState(newState) {
|
|
193
|
+
if (this.state !== 9 /* Error */) {
|
|
194
|
+
this.state = newState;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
connect(existingSocket) {
|
|
198
|
+
this.onDataReceived = (data) => this.onDataReceivedHandler(data);
|
|
199
|
+
this.onClose = () => this.onCloseHandler();
|
|
200
|
+
this.onError = (err) => this.onErrorHandler(err);
|
|
201
|
+
this.onConnect = () => this.onConnectHandler();
|
|
202
|
+
const timer = setTimeout(() => this.onEstablishedTimeout(), this.options.timeout || DEFAULT_TIMEOUT);
|
|
203
|
+
if (timer.unref && typeof timer.unref === "function") {
|
|
204
|
+
timer.unref();
|
|
205
|
+
}
|
|
206
|
+
if (existingSocket) {
|
|
207
|
+
this.socket = existingSocket;
|
|
208
|
+
} else {
|
|
209
|
+
this.socket = new net.Socket;
|
|
210
|
+
}
|
|
211
|
+
this.socket.once("close", this.onClose);
|
|
212
|
+
this.socket.once("error", this.onError);
|
|
213
|
+
this.socket.once("connect", this.onConnect);
|
|
214
|
+
this.socket.on("data", this.onDataReceived);
|
|
215
|
+
this.setState(1 /* Connecting */);
|
|
216
|
+
this.receiveBuffer = new ReceiveBuffer;
|
|
217
|
+
if (existingSocket) {
|
|
218
|
+
this.socket.emit("connect");
|
|
219
|
+
} else {
|
|
220
|
+
const socketOptions = {
|
|
221
|
+
...this.options.socket_options,
|
|
222
|
+
host: this.options.proxy.host,
|
|
223
|
+
port: this.options.proxy.port
|
|
224
|
+
};
|
|
225
|
+
this.socket.connect(socketOptions);
|
|
226
|
+
}
|
|
227
|
+
this.prependOnceListener("established", (info) => {
|
|
228
|
+
setImmediate(() => {
|
|
229
|
+
if (this.receiveBuffer.length > 0) {
|
|
230
|
+
const excessData = this.receiveBuffer.get(this.receiveBuffer.length);
|
|
231
|
+
info.socket.emit("data", excessData);
|
|
232
|
+
}
|
|
233
|
+
info.socket.resume();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
onEstablishedTimeout() {
|
|
238
|
+
if (this.state !== 8 /* Established */ && this.state !== 10 /* BoundWaitingForConnection */) {
|
|
239
|
+
this.closeSocket("Proxy connection timed out");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
onConnectHandler() {
|
|
243
|
+
this.setState(2 /* Connected */);
|
|
244
|
+
if (this.options.proxy.type === 4) {
|
|
245
|
+
this.sendSocks4InitialHandshake();
|
|
246
|
+
} else {
|
|
247
|
+
this.sendSocks5InitialHandshake();
|
|
248
|
+
}
|
|
249
|
+
this.setState(3 /* SentInitialHandshake */);
|
|
250
|
+
}
|
|
251
|
+
onDataReceivedHandler(data) {
|
|
252
|
+
this.receiveBuffer.append(data);
|
|
253
|
+
this.processData();
|
|
254
|
+
}
|
|
255
|
+
processData() {
|
|
256
|
+
while (this.state !== 8 /* Established */ && this.state !== 9 /* Error */ && this.receiveBuffer.length >= this.nextRequiredPacketBufferSize) {
|
|
257
|
+
if (this.state === 3 /* SentInitialHandshake */) {
|
|
258
|
+
if (this.options.proxy.type === 4) {
|
|
259
|
+
this.handleSocks4FinalHandshakeResponse();
|
|
260
|
+
} else {
|
|
261
|
+
this.handleInitialSocks5HandshakeResponse();
|
|
262
|
+
}
|
|
263
|
+
} else if (this.state === 4 /* SentAuthentication */) {
|
|
264
|
+
this.handleInitialSocks5AuthenticationHandshakeResponse();
|
|
265
|
+
} else if (this.state === 5 /* SentFinalHandshake */) {
|
|
266
|
+
this.handleSocks5FinalHandshakeResponse();
|
|
267
|
+
} else if (this.state === 10 /* BoundWaitingForConnection */) {
|
|
268
|
+
if (this.options.proxy.type === 4) {
|
|
269
|
+
this.handleSocks4IncomingConnectionResponse();
|
|
270
|
+
} else {
|
|
271
|
+
this.handleSocks5IncomingConnectionResponse();
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
this.closeSocket("Internal error");
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
onCloseHandler() {
|
|
280
|
+
this.closeSocket("Socket closed");
|
|
281
|
+
}
|
|
282
|
+
onErrorHandler(err) {
|
|
283
|
+
this.closeSocket(err.message);
|
|
284
|
+
}
|
|
285
|
+
removeInternalSocketHandlers() {
|
|
286
|
+
this.socket.pause();
|
|
287
|
+
this.socket.removeListener("data", this.onDataReceived);
|
|
288
|
+
this.socket.removeListener("close", this.onClose);
|
|
289
|
+
this.socket.removeListener("error", this.onError);
|
|
290
|
+
this.socket.removeListener("connect", this.onConnect);
|
|
291
|
+
}
|
|
292
|
+
closeSocket(err) {
|
|
293
|
+
if (this.state !== 9 /* Error */) {
|
|
294
|
+
this.setState(9 /* Error */);
|
|
295
|
+
this.socket.destroy();
|
|
296
|
+
this.removeInternalSocketHandlers();
|
|
297
|
+
this.emit("error", new SocksClientError(err, this.options));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
sendSocks4InitialHandshake() {
|
|
301
|
+
const userId = this.options.proxy.userId || "";
|
|
302
|
+
const { destination } = this.options;
|
|
303
|
+
const command = SOCKS_COMMAND[this.options.command || "connect"];
|
|
304
|
+
const isIPv4 = net.isIPv4(destination.host);
|
|
305
|
+
const hostBuffer = isIPv4 ? ipv4ToBuffer(destination.host) : Buffer.from([0, 0, 0, 1]);
|
|
306
|
+
const userIdBuffer = Buffer.from(userId, "utf8");
|
|
307
|
+
const hostnameBuffer = !isIPv4 ? Buffer.from(destination.host, "utf8") : Buffer.alloc(0);
|
|
308
|
+
const bufferLength = 1 + 1 + 2 + 4 + userIdBuffer.length + 1 + (isIPv4 ? 0 : hostnameBuffer.length + 1);
|
|
309
|
+
const buffer = Buffer.alloc(bufferLength);
|
|
310
|
+
let offset = 0;
|
|
311
|
+
buffer.writeUInt8(SOCKS_VERSION.SOCKS4, offset++);
|
|
312
|
+
buffer.writeUInt8(command, offset++);
|
|
313
|
+
buffer.writeUInt16BE(destination.port, offset);
|
|
314
|
+
offset += 2;
|
|
315
|
+
hostBuffer.copy(buffer, offset);
|
|
316
|
+
offset += 4;
|
|
317
|
+
userIdBuffer.copy(buffer, offset);
|
|
318
|
+
offset += userIdBuffer.length;
|
|
319
|
+
buffer.writeUInt8(0, offset++);
|
|
320
|
+
if (!isIPv4) {
|
|
321
|
+
hostnameBuffer.copy(buffer, offset);
|
|
322
|
+
offset += hostnameBuffer.length;
|
|
323
|
+
buffer.writeUInt8(0, offset++);
|
|
324
|
+
}
|
|
325
|
+
this.nextRequiredPacketBufferSize = 8;
|
|
326
|
+
this.socket.write(buffer);
|
|
327
|
+
}
|
|
328
|
+
handleSocks4FinalHandshakeResponse() {
|
|
329
|
+
const data = this.receiveBuffer.get(8);
|
|
330
|
+
if (data[1] !== SOCKS4_RESPONSE.Granted) {
|
|
331
|
+
const responseCode = Object.entries(SOCKS4_RESPONSE).find(([, v]) => v === data[1])?.[0] || "Unknown";
|
|
332
|
+
this.closeSocket(`SOCKS4 proxy rejected connection - ${responseCode}`);
|
|
333
|
+
} else {
|
|
334
|
+
const command = this.options.command || "connect";
|
|
335
|
+
if (command === "bind") {
|
|
336
|
+
const port = data.readUInt16BE(2);
|
|
337
|
+
let host = bufferToIpv4(data, 4);
|
|
338
|
+
if (host === "0.0.0.0") {
|
|
339
|
+
host = this.options.proxy.host;
|
|
340
|
+
}
|
|
341
|
+
this.setState(10 /* BoundWaitingForConnection */);
|
|
342
|
+
this.emit("bound", { remoteHost: { host, port }, socket: this.socket });
|
|
343
|
+
} else {
|
|
344
|
+
this.setState(8 /* Established */);
|
|
345
|
+
this.removeInternalSocketHandlers();
|
|
346
|
+
this.emit("established", { socket: this.socket });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
handleSocks4IncomingConnectionResponse() {
|
|
351
|
+
const data = this.receiveBuffer.get(8);
|
|
352
|
+
if (data[1] !== SOCKS4_RESPONSE.Granted) {
|
|
353
|
+
const responseCode = Object.entries(SOCKS4_RESPONSE).find(([, v]) => v === data[1])?.[0] || "Unknown";
|
|
354
|
+
this.closeSocket(`SOCKS4 proxy rejected incoming connection - ${responseCode}`);
|
|
355
|
+
} else {
|
|
356
|
+
const port = data.readUInt16BE(2);
|
|
357
|
+
let host = bufferToIpv4(data, 4);
|
|
358
|
+
if (host === "0.0.0.0") {
|
|
359
|
+
host = this.options.proxy.host;
|
|
360
|
+
}
|
|
361
|
+
this.setState(8 /* Established */);
|
|
362
|
+
this.removeInternalSocketHandlers();
|
|
363
|
+
this.emit("established", { remoteHost: { host, port }, socket: this.socket });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
sendSocks5InitialHandshake() {
|
|
367
|
+
const supportedAuthMethods = [SOCKS5_AUTH.NoAuth];
|
|
368
|
+
if (this.options.proxy.userId || this.options.proxy.password) {
|
|
369
|
+
supportedAuthMethods.push(SOCKS5_AUTH.UserPass);
|
|
370
|
+
}
|
|
371
|
+
const buffer = Buffer.alloc(2 + supportedAuthMethods.length);
|
|
372
|
+
buffer.writeUInt8(SOCKS_VERSION.SOCKS5, 0);
|
|
373
|
+
buffer.writeUInt8(supportedAuthMethods.length, 1);
|
|
374
|
+
for (let i = 0;i < supportedAuthMethods.length; i++) {
|
|
375
|
+
buffer.writeUInt8(supportedAuthMethods[i], 2 + i);
|
|
376
|
+
}
|
|
377
|
+
this.nextRequiredPacketBufferSize = 2;
|
|
378
|
+
this.socket.write(buffer);
|
|
379
|
+
this.setState(3 /* SentInitialHandshake */);
|
|
380
|
+
}
|
|
381
|
+
handleInitialSocks5HandshakeResponse() {
|
|
382
|
+
const data = this.receiveBuffer.get(2);
|
|
383
|
+
if (data[0] !== SOCKS_VERSION.SOCKS5) {
|
|
384
|
+
this.closeSocket(`Invalid SOCKS5 initial handshake response - expected version 5, got ${data[0]}`);
|
|
385
|
+
} else if (data[1] === SOCKS5_AUTH.NoAcceptable) {
|
|
386
|
+
const hasCredentials = !!(this.options.proxy.userId || this.options.proxy.password);
|
|
387
|
+
this.closeSocket(hasCredentials ? "SOCKS5 proxy rejected authentication - check username/password" : "SOCKS5 proxy requires authentication but no credentials provided");
|
|
388
|
+
} else {
|
|
389
|
+
if (data[1] === SOCKS5_AUTH.NoAuth) {
|
|
390
|
+
this.socks5ChosenAuthType = SOCKS5_AUTH.NoAuth;
|
|
391
|
+
this.sendSocks5CommandRequest();
|
|
392
|
+
} else if (data[1] === SOCKS5_AUTH.UserPass) {
|
|
393
|
+
this.socks5ChosenAuthType = SOCKS5_AUTH.UserPass;
|
|
394
|
+
this.sendSocks5UserPassAuthentication();
|
|
395
|
+
} else {
|
|
396
|
+
const authName = data[1] === 1 ? "GSSAPI" : `method ${data[1]}`;
|
|
397
|
+
this.closeSocket(`SOCKS5 proxy requires unsupported authentication: ${authName}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
sendSocks5UserPassAuthentication() {
|
|
402
|
+
const userId = this.options.proxy.userId || "";
|
|
403
|
+
const password = this.options.proxy.password || "";
|
|
404
|
+
const userIdLength = Buffer.byteLength(userId);
|
|
405
|
+
const passwordLength = Buffer.byteLength(password);
|
|
406
|
+
const buffer = Buffer.alloc(3 + userIdLength + passwordLength);
|
|
407
|
+
buffer.writeUInt8(1, 0);
|
|
408
|
+
buffer.writeUInt8(userIdLength, 1);
|
|
409
|
+
buffer.write(userId, 2, userIdLength);
|
|
410
|
+
buffer.writeUInt8(passwordLength, 2 + userIdLength);
|
|
411
|
+
buffer.write(password, 3 + userIdLength, passwordLength);
|
|
412
|
+
this.nextRequiredPacketBufferSize = 2;
|
|
413
|
+
this.socket.write(buffer);
|
|
414
|
+
this.setState(4 /* SentAuthentication */);
|
|
415
|
+
}
|
|
416
|
+
handleInitialSocks5AuthenticationHandshakeResponse() {
|
|
417
|
+
const data = this.receiveBuffer.get(2);
|
|
418
|
+
if (data[1] !== 0) {
|
|
419
|
+
this.closeSocket("SOCKS5 authentication failed");
|
|
420
|
+
} else {
|
|
421
|
+
this.sendSocks5CommandRequest();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
sendSocks5CommandRequest() {
|
|
425
|
+
const { destination } = this.options;
|
|
426
|
+
const command = SOCKS_COMMAND[this.options.command || "connect"];
|
|
427
|
+
let addressType;
|
|
428
|
+
let addressBuffer;
|
|
429
|
+
if (net.isIPv4(destination.host)) {
|
|
430
|
+
addressType = SOCKS5_HOST_TYPE.IPv4;
|
|
431
|
+
addressBuffer = ipv4ToBuffer(destination.host);
|
|
432
|
+
} else if (net.isIPv6(destination.host)) {
|
|
433
|
+
addressType = SOCKS5_HOST_TYPE.IPv6;
|
|
434
|
+
addressBuffer = ipv6ToBuffer(destination.host);
|
|
435
|
+
} else {
|
|
436
|
+
addressType = SOCKS5_HOST_TYPE.Hostname;
|
|
437
|
+
const hostnameLength = Buffer.byteLength(destination.host);
|
|
438
|
+
addressBuffer = Buffer.alloc(1 + hostnameLength);
|
|
439
|
+
addressBuffer.writeUInt8(hostnameLength, 0);
|
|
440
|
+
addressBuffer.write(destination.host, 1, hostnameLength);
|
|
441
|
+
}
|
|
442
|
+
const buffer = Buffer.alloc(4 + addressBuffer.length + 2);
|
|
443
|
+
buffer.writeUInt8(SOCKS_VERSION.SOCKS5, 0);
|
|
444
|
+
buffer.writeUInt8(command, 1);
|
|
445
|
+
buffer.writeUInt8(0, 2);
|
|
446
|
+
buffer.writeUInt8(addressType, 3);
|
|
447
|
+
addressBuffer.copy(buffer, 4);
|
|
448
|
+
buffer.writeUInt16BE(destination.port, 4 + addressBuffer.length);
|
|
449
|
+
this.nextRequiredPacketBufferSize = 5;
|
|
450
|
+
this.socket.write(buffer);
|
|
451
|
+
this.setState(5 /* SentFinalHandshake */);
|
|
452
|
+
}
|
|
453
|
+
handleSocks5FinalHandshakeResponse() {
|
|
454
|
+
const header = this.receiveBuffer.peek(5);
|
|
455
|
+
if (header[0] !== SOCKS_VERSION.SOCKS5 || header[1] !== SOCKS5_RESPONSE.Granted) {
|
|
456
|
+
const responseCode = Object.entries(SOCKS5_RESPONSE).find(([, v]) => v === header[1])?.[0] || "Unknown";
|
|
457
|
+
this.closeSocket(`SOCKS5 proxy rejected connection - ${responseCode}`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const addressType = header[3];
|
|
461
|
+
let remoteHost;
|
|
462
|
+
let dataNeeded;
|
|
463
|
+
if (addressType === SOCKS5_HOST_TYPE.IPv4) {
|
|
464
|
+
dataNeeded = 10;
|
|
465
|
+
if (this.receiveBuffer.length < dataNeeded) {
|
|
466
|
+
this.nextRequiredPacketBufferSize = dataNeeded;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const data = this.receiveBuffer.get(dataNeeded);
|
|
470
|
+
let host = bufferToIpv4(data, 4);
|
|
471
|
+
const port = data.readUInt16BE(8);
|
|
472
|
+
if (host === "0.0.0.0") {
|
|
473
|
+
host = this.options.proxy.host;
|
|
474
|
+
}
|
|
475
|
+
remoteHost = { host, port };
|
|
476
|
+
} else if (addressType === SOCKS5_HOST_TYPE.Hostname) {
|
|
477
|
+
const hostLength = header[4];
|
|
478
|
+
dataNeeded = 7 + hostLength;
|
|
479
|
+
if (this.receiveBuffer.length < dataNeeded) {
|
|
480
|
+
this.nextRequiredPacketBufferSize = dataNeeded;
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const data = this.receiveBuffer.get(dataNeeded);
|
|
484
|
+
const host = data.slice(5, 5 + hostLength).toString("utf8");
|
|
485
|
+
const port = data.readUInt16BE(5 + hostLength);
|
|
486
|
+
remoteHost = { host, port };
|
|
487
|
+
} else if (addressType === SOCKS5_HOST_TYPE.IPv6) {
|
|
488
|
+
dataNeeded = 22;
|
|
489
|
+
if (this.receiveBuffer.length < dataNeeded) {
|
|
490
|
+
this.nextRequiredPacketBufferSize = dataNeeded;
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const data = this.receiveBuffer.get(dataNeeded);
|
|
494
|
+
const host = bufferToIpv6(data, 4);
|
|
495
|
+
const port = data.readUInt16BE(20);
|
|
496
|
+
remoteHost = { host, port };
|
|
497
|
+
}
|
|
498
|
+
this.setState(7 /* ReceivedFinalResponse */);
|
|
499
|
+
const command = this.options.command || "connect";
|
|
500
|
+
if (command === "connect") {
|
|
501
|
+
this.setState(8 /* Established */);
|
|
502
|
+
this.removeInternalSocketHandlers();
|
|
503
|
+
this.emit("established", { remoteHost, socket: this.socket });
|
|
504
|
+
} else if (command === "bind") {
|
|
505
|
+
this.setState(10 /* BoundWaitingForConnection */);
|
|
506
|
+
this.nextRequiredPacketBufferSize = 5;
|
|
507
|
+
this.emit("bound", { remoteHost, socket: this.socket });
|
|
508
|
+
} else if (command === "associate") {
|
|
509
|
+
this.setState(8 /* Established */);
|
|
510
|
+
this.removeInternalSocketHandlers();
|
|
511
|
+
this.emit("established", { remoteHost, socket: this.socket });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
handleSocks5IncomingConnectionResponse() {
|
|
515
|
+
const header = this.receiveBuffer.peek(5);
|
|
516
|
+
if (header[0] !== SOCKS_VERSION.SOCKS5 || header[1] !== SOCKS5_RESPONSE.Granted) {
|
|
517
|
+
const responseCode = Object.entries(SOCKS5_RESPONSE).find(([, v]) => v === header[1])?.[0] || "Unknown";
|
|
518
|
+
this.closeSocket(`SOCKS5 proxy rejected incoming connection - ${responseCode}`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const addressType = header[3];
|
|
522
|
+
let remoteHost;
|
|
523
|
+
let dataNeeded;
|
|
524
|
+
if (addressType === SOCKS5_HOST_TYPE.IPv4) {
|
|
525
|
+
dataNeeded = 10;
|
|
526
|
+
if (this.receiveBuffer.length < dataNeeded) {
|
|
527
|
+
this.nextRequiredPacketBufferSize = dataNeeded;
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const data = this.receiveBuffer.get(dataNeeded);
|
|
531
|
+
let host = bufferToIpv4(data, 4);
|
|
532
|
+
const port = data.readUInt16BE(8);
|
|
533
|
+
if (host === "0.0.0.0") {
|
|
534
|
+
host = this.options.proxy.host;
|
|
535
|
+
}
|
|
536
|
+
remoteHost = { host, port };
|
|
537
|
+
} else if (addressType === SOCKS5_HOST_TYPE.Hostname) {
|
|
538
|
+
const hostLength = header[4];
|
|
539
|
+
dataNeeded = 7 + hostLength;
|
|
540
|
+
if (this.receiveBuffer.length < dataNeeded) {
|
|
541
|
+
this.nextRequiredPacketBufferSize = dataNeeded;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const data = this.receiveBuffer.get(dataNeeded);
|
|
545
|
+
const host = data.slice(5, 5 + hostLength).toString("utf8");
|
|
546
|
+
const port = data.readUInt16BE(5 + hostLength);
|
|
547
|
+
remoteHost = { host, port };
|
|
548
|
+
} else if (addressType === SOCKS5_HOST_TYPE.IPv6) {
|
|
549
|
+
dataNeeded = 22;
|
|
550
|
+
if (this.receiveBuffer.length < dataNeeded) {
|
|
551
|
+
this.nextRequiredPacketBufferSize = dataNeeded;
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const data = this.receiveBuffer.get(dataNeeded);
|
|
555
|
+
const host = bufferToIpv6(data, 4);
|
|
556
|
+
const port = data.readUInt16BE(20);
|
|
557
|
+
remoteHost = { host, port };
|
|
558
|
+
}
|
|
559
|
+
this.setState(8 /* Established */);
|
|
560
|
+
this.removeInternalSocketHandlers();
|
|
561
|
+
this.emit("established", { remoteHost, socket: this.socket });
|
|
562
|
+
}
|
|
563
|
+
get socksClientOptions() {
|
|
564
|
+
return { ...this.options };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
export default SocksClient;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const net = require("node:net");
|
|
2
|
+
const tls = require("node:tls");
|
|
3
|
+
const { Agent } = require('./base.cjs');
|
|
4
|
+
const { SocksClient } = require('./socks-client.cjs');
|
|
5
|
+
function parseSocksURL(url) {
|
|
6
|
+
let type;
|
|
7
|
+
switch (url.protocol.replace(":", "")) {
|
|
8
|
+
case "socks4":
|
|
9
|
+
type = 4;
|
|
10
|
+
break;
|
|
11
|
+
case "socks4a":
|
|
12
|
+
type = 4;
|
|
13
|
+
break;
|
|
14
|
+
case "socks5":
|
|
15
|
+
case "socks":
|
|
16
|
+
case "socks5h":
|
|
17
|
+
default:
|
|
18
|
+
type = 5;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const host = url.hostname;
|
|
22
|
+
const port = url.port ? parseInt(url.port, 10) : 1080;
|
|
23
|
+
const userId = url.username ? decodeURIComponent(url.username) : undefined;
|
|
24
|
+
const password = url.password ? decodeURIComponent(url.password) : undefined;
|
|
25
|
+
return { host, port, type, userId, password };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class SocksProxyAgent extends Agent {
|
|
29
|
+
static protocols = [
|
|
30
|
+
"socks",
|
|
31
|
+
"socks4",
|
|
32
|
+
"socks4a",
|
|
33
|
+
"socks5",
|
|
34
|
+
"socks5h"
|
|
35
|
+
];
|
|
36
|
+
proxy;
|
|
37
|
+
tlsConnectionOptions;
|
|
38
|
+
timeout;
|
|
39
|
+
constructor(proxy, opts) {
|
|
40
|
+
super(opts);
|
|
41
|
+
const url = typeof proxy === "string" ? new URL(proxy) : proxy;
|
|
42
|
+
this.proxy = parseSocksURL(url);
|
|
43
|
+
this.timeout = opts?.timeout ?? null;
|
|
44
|
+
this.tlsConnectionOptions = opts ?? {};
|
|
45
|
+
}
|
|
46
|
+
async connect(_req, opts) {
|
|
47
|
+
const { host, port } = opts;
|
|
48
|
+
if (!host) {
|
|
49
|
+
throw new Error('No "host" provided');
|
|
50
|
+
}
|
|
51
|
+
const socksOpts = {
|
|
52
|
+
proxy: this.proxy,
|
|
53
|
+
destination: { host, port },
|
|
54
|
+
command: "connect"
|
|
55
|
+
};
|
|
56
|
+
if (this.timeout !== null) {
|
|
57
|
+
socksOpts.timeout = this.timeout;
|
|
58
|
+
}
|
|
59
|
+
const { socket } = await SocksClient.createConnection(socksOpts);
|
|
60
|
+
if (opts.secureEndpoint) {
|
|
61
|
+
const servername = opts.servername ?? host;
|
|
62
|
+
const tlsSocket = tls.connect({
|
|
63
|
+
...this.tlsConnectionOptions,
|
|
64
|
+
socket,
|
|
65
|
+
servername: !net.isIP(servername) ? servername : undefined
|
|
66
|
+
});
|
|
67
|
+
return tlsSocket;
|
|
68
|
+
}
|
|
69
|
+
return socket;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
exports.SocksProxyAgent = SocksProxyAgent;
|
|
74
|
+
exports.default = SocksProxyAgent;
|
|
75
|
+
module.exports = Object.assign(SocksProxyAgent, exports);
|