forked from moll/node-mitm
-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
174 lines (142 loc) · 5.05 KB
/
index.js
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// @ts-check
import net from "node:net";
import tls from "node:tls";
import { ClientRequest, Agent } from "node:http";
import { EventEmitter } from "node:events";
import NODE_INTERNALS from "./lib/node-internals.js";
import MitmNetSocket from "./lib/net-socket.js";
import MitmTlsSocket from "./lib/tls-socket.js";
import MitmServer from "./lib/server.js";
import createRequestResponseHandlePair from "./lib/stream-handles.js";
import Stubs from "./lib/stubs.js";
export default class Mitm extends EventEmitter {
constructor() {
super();
this.stubs = new Stubs();
this.server = new MitmServer(this);
this.enable();
}
/**
* @returns {Mitm}
*/
enable() {
// Connect is called synchronously.
const netConnect = this.onNetConnect.bind(this, net.connect);
const tlsConnect = this.onTlsConnect.bind(this, tls.connect);
this.stubs.stub(net, "connect", netConnect);
this.stubs.stub(net, "createConnection", netConnect);
this.stubs.stub(Agent.prototype, "createConnection", netConnect);
this.stubs.stub(tls, "connect", tlsConnect);
// ClientRequest.prototype.onSocket is called synchronously from ClientRequest's constructor
// and is a convenient place to hook into new ClientRequest instances.
const originalOnSocket = ClientRequest.prototype.onSocket;
const self = this;
this.stubs.stub(ClientRequest.prototype, "onSocket", function (socket) {
originalOnSocket.call(this, socket);
self.clientRequestOnSocket(socket);
});
return this;
}
/**
* @returns {Mitm}
*/
disable() {
this.stubs.restore();
return this;
}
/**
* Create the fake `request` object and give the opportunity
* to bypass the interception.
*
* @param {typeof net.connect} originalNetConnect
* @param {...any} args
* @returns {MitmNetSocket}
*/
onNetConnect(originalNetConnect, ...args) {
const [options, callback] = NODE_INTERNALS.normalizeConnectArgs(args);
const request = this.connect(
originalNetConnect,
MitmNetSocket,
options,
callback
);
if (request.mitmResponseSocket == null) return request;
if (callback) request.once("connect", callback);
return request;
}
/**
* Create the fake `request` object and give the opportunity
* to bypass the interception.
*
* If the request is intercepted, we simulate a successful
* TLS handshake by emiting a "secureConnect" event asyncronously.
*
* @param {typeof tls.connect} originalTlsConnect
* @param {...any} args
* @returns {MitmTlsSocket}
*/
onTlsConnect(originalTlsConnect, ...args) {
const [options, callback] = NODE_INTERNALS.normalizeConnectArgs(args);
const request = this.connect(
originalTlsConnect,
MitmTlsSocket,
options,
callback
);
if (request.mitmResponseSocket == null) return request;
if (callback) request.once("secureConnect", callback);
setTimeout(request.emit.bind(request, "secureConnect"));
return request;
}
/**
* This is our hook into the `http.ClientRequest` constructor.
* Unless the interception is bypassed, we setup the socket handlers
* for the response using Node's internal connectionListener method.
*
* @see see https://github.com/nodejs/node/blob/b323cec78f713bc113be7f6030d787804a9af5a0/lib/_http_server.js#L440-L545
* @param {MitmNetSocket} requestSocket
* @returns
*/
clientRequestOnSocket(requestSocket) {
if (!requestSocket.mitmResponseSocket) return requestSocket;
NODE_INTERNALS.httpConnectionListener.call(
this.server,
requestSocket.mitmResponseSocket
);
return requestSocket;
}
/**
* This method is called when a socket is established, either through `net`,
* `tls`, or an `http.Agent` prototype. We create a fake `request` object
* and give the opportunity to bypass the interception in a `connect` hanler.
*
* If the request is intercepted, we call the original connect method
*/
connect(originalConnect, Socket, options, callback) {
const { requestHandle, responseHandle } = createRequestResponseHandlePair();
// request
const requestSocket = new Socket({
handle: requestHandle,
...options,
});
// give opportunity to bypass the intercept
this.emit("connect", requestSocket, options);
if (requestSocket.bypassed) {
return originalConnect.call(this, options, callback);
}
// response
const responseSocket = new Socket({ handle: responseHandle });
// We use `.mitmResponseSocket` as a means to check if a request is intercepted
// for net connects and to pass use it as a response when intercepting http(s) requests.
requestSocket.mitmResponseSocket = responseSocket;
this.emit("connection", responseSocket, options);
// Ensure connect is emitted asynchronously, otherwise it would be impossible
// to listen to it after calling net.connect or listening to it after the
// ClientRequest emits "socket".
setTimeout(() => {
requestSocket.emit("connect");
responseSocket.emit("connect");
});
return requestSocket;
}
}