Path: blob/main/mitm/handlers/HttpUpgradeHandler.ts
1030 views
import * as net from 'net';1import * as http from 'http';2import Log, { hasBeenLoggedSymbol } from '@secret-agent/commons/Logger';34import MitmRequestContext from '../lib/MitmRequestContext';5import BaseHttpHandler from './BaseHttpHandler';6import IMitmRequestContext from '../interfaces/IMitmRequestContext';7import ResourceState from '../interfaces/ResourceState';89const { log } = Log(module);1011export default class HttpUpgradeHandler extends BaseHttpHandler {12constructor(13request: Pick<IMitmRequestContext, 'requestSession' | 'isSSL' | 'clientToProxyRequest'>,14readonly clientSocket: net.Socket,15readonly clientHead: Buffer,16) {17super(request, true, null);18this.context.setState(ResourceState.ClientToProxyRequest);19this.context.eventSubscriber.on(20this.clientSocket,21'error',22this.onError.bind(this, 'ClientToProxy.UpgradeSocketError'),23);24}2526public async onUpgrade(): Promise<void> {27try {28const proxyToServerRequest = await this.createProxyToServerRequest();29if (!proxyToServerRequest) return;3031this.context.eventSubscriber.once(32proxyToServerRequest,33'upgrade',34this.onResponse.bind(this),35);36proxyToServerRequest.end();37} catch (err) {38this.onError('ClientToProxy.UpgradeHandlerError', err);39}40}4142protected onError(errorType: string, error: Error): void {43const socket = this.clientSocket;44const context = this.context;45const url = context.url.href;46const session = context.requestSession;47const sessionId = session.sessionId;48context.setState(ResourceState.Error);4950session.emit('http-error', { request: MitmRequestContext.toEmittedResource(context), error });5152if (!error[hasBeenLoggedSymbol]) {53log.info(`MitmWebSocketUpgrade.${errorType}`, {54sessionId,55error,56url,57});58}59socket.destroy(error);60this.cleanup();61}6263private async onResponse(64serverResponse: http.IncomingMessage,65serverSocket: net.Socket,66serverHead: Buffer,67): Promise<void> {68this.context.setState(ResourceState.ServerToProxyOnResponse);69serverSocket.pause();70MitmRequestContext.readHttp1Response(this.context, serverResponse);71this.context.serverToProxyResponse = serverResponse;7273const clientSocket = this.clientSocket;7475const { proxyToServerMitmSocket, requestSession } = this.context;7677this.context.eventSubscriber.on(clientSocket, 'end', () => proxyToServerMitmSocket.close());78this.context.eventSubscriber.on(serverSocket, 'end', () => proxyToServerMitmSocket.close());79this.context.eventSubscriber.on(proxyToServerMitmSocket, 'close', () => {80this.context.setState(ResourceState.End);81// don't try to write again82try {83clientSocket.destroy();84serverSocket.destroy();85this.cleanup();86} catch (err) {87// no-operation88}89});9091// copy response message (have to write to raw socket)92let responseMessage = `HTTP/${serverResponse.httpVersion} ${serverResponse.statusCode} ${serverResponse.statusMessage}\r\n`;93for (let i = 0; i < serverResponse.rawHeaders.length; i += 2) {94responseMessage += `${serverResponse.rawHeaders[i]}: ${serverResponse.rawHeaders[i + 1]}\r\n`;95}96await requestSession.willSendResponse(this.context);9798this.context.setState(ResourceState.WriteProxyToClientResponseBody);99clientSocket.write(`${responseMessage}\r\n`, error => {100if (error) this.onError('ProxyToClient.UpgradeWriteError', error);101});102103if (!serverSocket.readable || !serverSocket.writable) {104this.context.setState(ResourceState.PrematurelyClosed);105try {106return serverSocket.destroy();107} catch (error) {108// don't log if error109}110}111this.context.eventSubscriber.on(112serverSocket,113'error',114this.onError.bind(this, 'ServerToProxy.UpgradeSocketError'),115);116117if (serverResponse.statusCode === 101) {118clientSocket.setNoDelay(true);119clientSocket.setTimeout(0);120121serverSocket.setNoDelay(true);122serverSocket.setTimeout(0);123}124125serverSocket.pipe(clientSocket);126clientSocket.pipe(serverSocket);127128serverSocket.resume();129clientSocket.resume();130if (serverHead.length > 0) serverSocket.unshift(serverHead);131if (this.clientHead.length > 0) clientSocket.unshift(this.clientHead);132133const formattedResponse = MitmRequestContext.toEmittedResource(this.context);134this.context.requestSession.emit('response', formattedResponse);135// don't log close since this stays open...136}137138public static async onUpgrade(139request: Pick<IMitmRequestContext, 'requestSession' | 'isSSL' | 'clientToProxyRequest'> & {140socket: net.Socket;141head: Buffer;142},143): Promise<void> {144const handler = new HttpUpgradeHandler(request, request.socket, request.head);145await handler.onUpgrade();146}147}148149150