Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/mitm/handlers/HttpUpgradeHandler.ts
1030 views
1
import * as net from 'net';
2
import * as http from 'http';
3
import Log, { hasBeenLoggedSymbol } from '@secret-agent/commons/Logger';
4
5
import MitmRequestContext from '../lib/MitmRequestContext';
6
import BaseHttpHandler from './BaseHttpHandler';
7
import IMitmRequestContext from '../interfaces/IMitmRequestContext';
8
import ResourceState from '../interfaces/ResourceState';
9
10
const { log } = Log(module);
11
12
export default class HttpUpgradeHandler extends BaseHttpHandler {
13
constructor(
14
request: Pick<IMitmRequestContext, 'requestSession' | 'isSSL' | 'clientToProxyRequest'>,
15
readonly clientSocket: net.Socket,
16
readonly clientHead: Buffer,
17
) {
18
super(request, true, null);
19
this.context.setState(ResourceState.ClientToProxyRequest);
20
this.context.eventSubscriber.on(
21
this.clientSocket,
22
'error',
23
this.onError.bind(this, 'ClientToProxy.UpgradeSocketError'),
24
);
25
}
26
27
public async onUpgrade(): Promise<void> {
28
try {
29
const proxyToServerRequest = await this.createProxyToServerRequest();
30
if (!proxyToServerRequest) return;
31
32
this.context.eventSubscriber.once(
33
proxyToServerRequest,
34
'upgrade',
35
this.onResponse.bind(this),
36
);
37
proxyToServerRequest.end();
38
} catch (err) {
39
this.onError('ClientToProxy.UpgradeHandlerError', err);
40
}
41
}
42
43
protected onError(errorType: string, error: Error): void {
44
const socket = this.clientSocket;
45
const context = this.context;
46
const url = context.url.href;
47
const session = context.requestSession;
48
const sessionId = session.sessionId;
49
context.setState(ResourceState.Error);
50
51
session.emit('http-error', { request: MitmRequestContext.toEmittedResource(context), error });
52
53
if (!error[hasBeenLoggedSymbol]) {
54
log.info(`MitmWebSocketUpgrade.${errorType}`, {
55
sessionId,
56
error,
57
url,
58
});
59
}
60
socket.destroy(error);
61
this.cleanup();
62
}
63
64
private async onResponse(
65
serverResponse: http.IncomingMessage,
66
serverSocket: net.Socket,
67
serverHead: Buffer,
68
): Promise<void> {
69
this.context.setState(ResourceState.ServerToProxyOnResponse);
70
serverSocket.pause();
71
MitmRequestContext.readHttp1Response(this.context, serverResponse);
72
this.context.serverToProxyResponse = serverResponse;
73
74
const clientSocket = this.clientSocket;
75
76
const { proxyToServerMitmSocket, requestSession } = this.context;
77
78
this.context.eventSubscriber.on(clientSocket, 'end', () => proxyToServerMitmSocket.close());
79
this.context.eventSubscriber.on(serverSocket, 'end', () => proxyToServerMitmSocket.close());
80
this.context.eventSubscriber.on(proxyToServerMitmSocket, 'close', () => {
81
this.context.setState(ResourceState.End);
82
// don't try to write again
83
try {
84
clientSocket.destroy();
85
serverSocket.destroy();
86
this.cleanup();
87
} catch (err) {
88
// no-operation
89
}
90
});
91
92
// copy response message (have to write to raw socket)
93
let responseMessage = `HTTP/${serverResponse.httpVersion} ${serverResponse.statusCode} ${serverResponse.statusMessage}\r\n`;
94
for (let i = 0; i < serverResponse.rawHeaders.length; i += 2) {
95
responseMessage += `${serverResponse.rawHeaders[i]}: ${serverResponse.rawHeaders[i + 1]}\r\n`;
96
}
97
await requestSession.willSendResponse(this.context);
98
99
this.context.setState(ResourceState.WriteProxyToClientResponseBody);
100
clientSocket.write(`${responseMessage}\r\n`, error => {
101
if (error) this.onError('ProxyToClient.UpgradeWriteError', error);
102
});
103
104
if (!serverSocket.readable || !serverSocket.writable) {
105
this.context.setState(ResourceState.PrematurelyClosed);
106
try {
107
return serverSocket.destroy();
108
} catch (error) {
109
// don't log if error
110
}
111
}
112
this.context.eventSubscriber.on(
113
serverSocket,
114
'error',
115
this.onError.bind(this, 'ServerToProxy.UpgradeSocketError'),
116
);
117
118
if (serverResponse.statusCode === 101) {
119
clientSocket.setNoDelay(true);
120
clientSocket.setTimeout(0);
121
122
serverSocket.setNoDelay(true);
123
serverSocket.setTimeout(0);
124
}
125
126
serverSocket.pipe(clientSocket);
127
clientSocket.pipe(serverSocket);
128
129
serverSocket.resume();
130
clientSocket.resume();
131
if (serverHead.length > 0) serverSocket.unshift(serverHead);
132
if (this.clientHead.length > 0) clientSocket.unshift(this.clientHead);
133
134
const formattedResponse = MitmRequestContext.toEmittedResource(this.context);
135
this.context.requestSession.emit('response', formattedResponse);
136
// don't log close since this stays open...
137
}
138
139
public static async onUpgrade(
140
request: Pick<IMitmRequestContext, 'requestSession' | 'isSSL' | 'clientToProxyRequest'> & {
141
socket: net.Socket;
142
head: Buffer;
143
},
144
): Promise<void> {
145
const handler = new HttpUpgradeHandler(request, request.socket, request.head);
146
await handler.onUpgrade();
147
}
148
}
149
150