Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/mitm-socket/test/MitmSocket.test.ts
1030 views
1
import { Helpers } from '@secret-agent/testing';
2
import { createPromise } from '@secret-agent/commons/utils';
3
import * as http2 from 'http2';
4
import * as stream from 'stream';
5
import * as WebSocket from 'ws';
6
import { getTlsConnection, httpGetWithSocket } from '@secret-agent/testing/helpers';
7
import * as https from 'https';
8
import { IncomingMessage } from 'http';
9
import MitmSocket from '../index';
10
import MitmSocketSession from '../lib/MitmSocketSession';
11
12
afterAll(Helpers.afterAll);
13
afterEach(Helpers.afterEach);
14
15
let mitmSocketSession: MitmSocketSession;
16
beforeAll(() => {
17
mitmSocketSession = new MitmSocketSession('mitmSocket.test', {
18
clientHelloId: 'chrome-72',
19
rejectUnauthorized: false,
20
});
21
Helpers.onClose(() => mitmSocketSession.close(), true);
22
});
23
24
test('should be able to send a tls connection', async () => {
25
const htmlString = 'Secure as anything!';
26
const server = await Helpers.runHttpsServer((req, res) => {
27
return res.end(htmlString);
28
});
29
30
const tlsConnection = getTlsConnection(server.port);
31
await tlsConnection.connect(mitmSocketSession);
32
const httpResponse = await httpGetWithSocket(`${server.baseUrl}/any`, {}, tlsConnection.socket);
33
expect(httpResponse).toBe(htmlString);
34
});
35
36
test('should handle http2 requests', async () => {
37
const httpServer = await Helpers.runHttp2Server((request, response) => {
38
response.end('I am h2');
39
});
40
const tlsConnection = getTlsConnection(httpServer.port);
41
42
await tlsConnection.connect(mitmSocketSession);
43
expect(tlsConnection.alpn).toBe('h2');
44
45
const client = http2.connect('https://secretagent.dev', {
46
createConnection: () => tlsConnection.socket,
47
});
48
closeAfterTest(client);
49
50
const request = client.request({ ':path': '/' });
51
const httpResponse = await readResponse(request);
52
expect(httpResponse).toBe('I am h2');
53
client.destroy();
54
});
55
56
test('should be able to hit google using a Chrome Emulator', async () => {
57
const socketSession = new MitmSocketSession('mitmSocket.test', {
58
clientHelloId: 'chrome-79',
59
rejectUnauthorized: false,
60
});
61
Helpers.needsClosing.push(socketSession);
62
const tlsConnection = new MitmSocket('1', {
63
host: 'google.com',
64
port: '443',
65
servername: 'google.com',
66
isSsl: true,
67
});
68
Helpers.onClose(async () => tlsConnection.close());
69
70
await tlsConnection.connect(socketSession);
71
expect(tlsConnection.alpn).toBe('h2');
72
73
const client = http2.connect('https://www.google.com', {
74
createConnection: () => tlsConnection.socket,
75
});
76
closeAfterTest(client);
77
78
const request = client.request({ ':path': '/' });
79
const httpResponse = await readResponse(request);
80
expect(httpResponse).toBeTruthy();
81
expect(httpResponse).toMatch(/<\/body><\/html>$/);
82
});
83
84
test('should be able to hit gstatic using a Chrome Emulator', async () => {
85
const tlsConnection = new MitmSocket('optimove', {
86
host: 'www.gstatic.com',
87
port: '443',
88
servername: 'www.gstatic.com',
89
isSsl: true,
90
});
91
Helpers.onClose(async () => tlsConnection.close());
92
93
await tlsConnection.connect(mitmSocketSession);
94
expect(tlsConnection.alpn).toBe('h2');
95
96
const client = http2.connect('https://www.gstatic.com', {
97
createConnection: () => tlsConnection.socket,
98
});
99
closeAfterTest(client);
100
101
const request = client.request({
102
':path': '/firebasejs/4.9.1/firebase.js',
103
});
104
const httpResponse = await readResponse(request);
105
expect(httpResponse).toBeTruthy();
106
});
107
108
test('should be able to hit a server that disconnects', async () => {
109
const server = await Helpers.runHttpsServer(async (req, res) => {
110
res.socket.end(
111
`HTTP/1.1 301 Moved Permanently\r\nContent-Length: 0\r\nConnection: close\r\nLocation: https://www.location2.com\r\n\r\n`,
112
);
113
});
114
115
const tlsConnection = new MitmSocket('disconnect', {
116
host: `localhost`,
117
port: String(server.port),
118
servername: 'localhost',
119
keepAlive: true,
120
isSsl: true,
121
});
122
Helpers.onClose(async () => tlsConnection.close());
123
124
await tlsConnection.connect(mitmSocketSession);
125
expect(tlsConnection.alpn).toBe('http/1.1');
126
const request = https.request({
127
method: 'GET',
128
path: '/',
129
host: 'localhost',
130
port: server.port,
131
createConnection() {
132
return tlsConnection.socket;
133
},
134
});
135
136
const responsePromise = new Promise<IncomingMessage>(resolve => request.on('response', resolve));
137
request.end();
138
const response = await responsePromise;
139
expect(response.headers).toEqual({
140
'content-length': '0',
141
connection: 'close',
142
location: 'https://www.location2.com',
143
});
144
});
145
146
// only test this manually
147
// eslint-disable-next-line jest/no-disabled-tests
148
test.skip('should be able to get scripts from unpkg using Chrome emulator', async () => {
149
const socketSession = new MitmSocketSession('mitmSocket.test', {
150
clientHelloId: 'chrome-79',
151
rejectUnauthorized: false,
152
});
153
Helpers.needsClosing.push(socketSession);
154
const tlsConnection = new MitmSocket('3', {
155
host: 'unpkg.com',
156
port: '443',
157
servername: 'unpkg.com',
158
isSsl: true,
159
});
160
Helpers.onClose(async () => tlsConnection.close());
161
162
await tlsConnection.connect(socketSession);
163
expect(tlsConnection.alpn).toBe('h2');
164
165
const client = http2.connect('https://unpkg.com', {
166
createConnection: () => tlsConnection.socket,
167
});
168
closeAfterTest(client);
169
170
{
171
const request = client.request({ ':path': '/[email protected]/umd/react.production.min.js' });
172
const httpResponse = await readResponse(request);
173
expect(httpResponse).toBeTruthy();
174
expect(httpResponse).toMatch(/\(function\(/);
175
}
176
{
177
const request = client.request({
178
':path': '/[email protected]/umd/react-dom.production.min.js',
179
});
180
const httpResponse = await readResponse(request);
181
expect(httpResponse).toBeTruthy();
182
expect(httpResponse).toMatch(/\(function\(/);
183
}
184
});
185
186
test('should handle websockets', async () => {
187
const htmlString = 'Secure as anything!';
188
const server = await Helpers.runHttpsServer((req, res) => res.end(htmlString));
189
const messageCount = 500;
190
191
const wsServer = new WebSocket.Server({ server: server.server });
192
wsServer.on('connection', async (ws: WebSocket) => {
193
for (let i = 0; i < messageCount; i += 1) {
194
await new Promise(resolve =>
195
ws.send(i, () => {
196
setTimeout(resolve, 2);
197
}),
198
);
199
}
200
});
201
202
const tlsConnection = getTlsConnection(server.port, undefined, true);
203
tlsConnection.connectOpts.keepAlive = true;
204
await tlsConnection.connect(mitmSocketSession);
205
206
const wsClient = new WebSocket(`wss://localhost:${server.port}`, {
207
rejectUnauthorized: false,
208
createConnection: () => tlsConnection.socket,
209
});
210
211
Helpers.onClose(async () => wsClient.close());
212
213
const messages = [];
214
const messagePromise = createPromise();
215
wsClient.on('open', () => {
216
wsClient.on('message', msg => {
217
messages.push(msg);
218
if (messages.length === messageCount) {
219
messagePromise.resolve();
220
}
221
});
222
});
223
await messagePromise.promise;
224
expect(messages.length).toBe(messageCount);
225
}, 35e3);
226
227
test('should handle upstream disconnects', async () => {
228
const server = await Helpers.runHttpsServer((req, res) => {
229
res.connection.end();
230
});
231
232
const tlsConnection = getTlsConnection(server.port);
233
await tlsConnection.connect(mitmSocketSession);
234
235
await expect(
236
httpGetWithSocket(`${server.baseUrl}/any`, {}, tlsConnection.socket),
237
).rejects.toThrow();
238
});
239
240
function closeAfterTest(session: http2.ClientHttp2Session) {
241
Helpers.onClose(() => {
242
session.destroy();
243
});
244
}
245
246
async function readResponse(res: stream.Readable) {
247
return (await Helpers.readableToBuffer(res)).toString();
248
}
249
250