-
Notifications
You must be signed in to change notification settings - Fork 145
/
anonymize_proxy.ts
136 lines (118 loc) · 4.28 KB
/
anonymize_proxy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import net from 'net';
import http from 'http';
import { Buffer } from 'buffer';
import { URL } from 'url';
import { Server, SOCKS_PROTOCOLS } from './server';
import { nodeify } from './utils/nodeify';
// Dictionary, key is value returned from anonymizeProxy(), value is Server instance.
const anonymizedProxyUrlToServer: Record<string, Server> = {};
export interface AnonymizeProxyOptions {
url: string;
port: number;
}
/**
* Parses and validates a HTTP proxy URL. If the proxy requires authentication, then the function
* starts an open local proxy server that forwards to the upstream proxy.
*/
export const anonymizeProxy = (
options: string | AnonymizeProxyOptions,
callback?: (error: Error | null) => void,
): Promise<string> => {
let proxyUrl: string;
let port = 0;
if (typeof options === 'string') {
proxyUrl = options;
} else {
proxyUrl = options.url;
port = options.port;
if (port < 0 || port > 65535) {
throw new Error(
'Invalid "port" option: only values equals or between 0-65535 are valid',
);
}
}
const parsedProxyUrl = new URL(proxyUrl);
if (!['http:', ...SOCKS_PROTOCOLS].includes(parsedProxyUrl.protocol)) {
// eslint-disable-next-line max-len
throw new Error(`Invalid "proxyUrl" provided: URL must have one of the following protocols: "http", ${SOCKS_PROTOCOLS.map((p) => `"${p.replace(':', '')}"`).join(', ')} (was "${parsedProxyUrl}")`);
}
// If upstream proxy requires no password, return it directly
if (!parsedProxyUrl.username && !parsedProxyUrl.password) {
return nodeify(Promise.resolve(proxyUrl), callback);
}
let server: Server & { port: number };
const startServer = () => {
return Promise.resolve().then(() => {
server = new Server({
// verbose: true,
port,
host: '127.0.0.1',
prepareRequestFunction: () => {
return {
requestAuthentication: false,
upstreamProxyUrl: proxyUrl,
};
},
}) as Server & { port: number };
return server.listen();
});
};
const promise = startServer().then(() => {
const url = `http://127.0.0.1:${server.port}`;
anonymizedProxyUrlToServer[url] = server;
return url;
});
return nodeify(promise, callback);
};
/**
* Closes anonymous proxy previously started by `anonymizeProxy()`.
* If proxy was not found or was already closed, the function has no effect
* and its result if `false`. Otherwise the result is `true`.
* @param closeConnections If true, pending proxy connections are forcibly closed.
*/
export const closeAnonymizedProxy = (
anonymizedProxyUrl: string,
closeConnections: boolean,
callback?: (error: Error | null, result?: boolean) => void,
): Promise<boolean> => {
if (typeof anonymizedProxyUrl !== 'string') {
throw new Error('The "anonymizedProxyUrl" parameter must be a string');
}
const server = anonymizedProxyUrlToServer[anonymizedProxyUrl];
if (!server) {
return nodeify(Promise.resolve(false), callback);
}
delete anonymizedProxyUrlToServer[anonymizedProxyUrl];
const promise = server.close(closeConnections).then(() => {
return true;
});
return nodeify(promise, callback);
};
type Callback = ({
response,
socket,
head,
}: {
response: http.IncomingMessage;
socket: net.Socket;
head: Buffer;
}) => void;
/**
* Add a callback on 'tunnelConnectResponded' Event in order to get headers from CONNECT tunnel to proxy
* Useful for some proxies that are using headers to send information like ProxyMesh
* @returns `true` if the callback is successfully configured, otherwise `false` (e.g. when an
* invalid proxy URL is given).
*/
export const listenConnectAnonymizedProxy = (
anonymizedProxyUrl: string,
tunnelConnectRespondedCallback: Callback,
): boolean => {
const server = anonymizedProxyUrlToServer[anonymizedProxyUrl];
if (!server) {
return false;
}
server.on('tunnelConnectResponded', ({ response, socket, head }) => {
tunnelConnectRespondedCallback({ response, socket, head });
});
return true;
};