Skip to content

Commit c50dd06

Browse files
authored
fix(fixRequestBody): handle invalid request (#1091)
1 parent 76a9d8d commit c50dd06

File tree

2 files changed

+110
-8
lines changed

2 files changed

+110
-8
lines changed

src/handlers/fix-request-body.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@ import type * as http from 'http';
22
import type { Request } from '../types';
33
import * as querystring from 'querystring';
44

5+
type HandleBadRequestArgs = {
6+
proxyReq: http.ClientRequest;
7+
req: http.IncomingMessage;
8+
res: http.ServerResponse;
9+
};
10+
511
/**
612
* Fix proxied body if bodyParser is involved.
713
*/
8-
export function fixRequestBody(proxyReq: http.ClientRequest, req: http.IncomingMessage): void {
14+
export function fixRequestBody(
15+
proxyReq: http.ClientRequest,
16+
req: http.IncomingMessage,
17+
res: http.ServerResponse
18+
): void {
919
const requestBody = (req as Request).body;
1020

1121
if (!requestBody) {
@@ -18,6 +28,18 @@ export function fixRequestBody(proxyReq: http.ClientRequest, req: http.IncomingM
1828
return;
1929
}
2030

31+
// Handle bad request when unexpected "Connect: Upgrade" header is provided
32+
if (/upgrade/gi.test(proxyReq.getHeader('Connection') as string)) {
33+
handleBadRequest({ proxyReq, req, res });
34+
return;
35+
}
36+
37+
// Handle bad request when invalid request body is provided
38+
if (hasInvalidKeys(requestBody)) {
39+
handleBadRequest({ proxyReq, req, res });
40+
return;
41+
}
42+
2143
const writeBody = (bodyData: string) => {
2244
// deepcode ignore ContentLengthInCode: bodyParser fix
2345
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
@@ -30,3 +52,14 @@ export function fixRequestBody(proxyReq: http.ClientRequest, req: http.IncomingM
3052
writeBody(querystring.stringify(requestBody));
3153
}
3254
}
55+
56+
function hasInvalidKeys(obj) {
57+
return Object.keys(obj).some((key) => /[\n\r]/.test(key));
58+
}
59+
60+
function handleBadRequest({ proxyReq, req, res }: HandleBadRequestArgs) {
61+
res.writeHead(400);
62+
res.end('Bad Request');
63+
proxyReq.destroy();
64+
req.destroy();
65+
}

test/unit/fix-request-body.spec.ts

+76-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ClientRequest } from 'http';
1+
import { Socket } from 'net';
2+
import { ClientRequest, ServerResponse, IncomingMessage } from 'http';
23
import * as querystring from 'querystring';
34

45
import { fixRequestBody } from '../../src/handlers/fix-request-body';
@@ -11,14 +12,19 @@ const fakeProxyRequest = () => {
1112
return proxyRequest;
1213
};
1314

15+
const fakeProxyResponse = (): ServerResponse => {
16+
const res = new ServerResponse(new IncomingMessage(new Socket()));
17+
return res;
18+
};
19+
1420
describe('fixRequestBody', () => {
1521
it('should not write when body is undefined', () => {
1622
const proxyRequest = fakeProxyRequest();
1723

1824
jest.spyOn(proxyRequest, 'setHeader');
1925
jest.spyOn(proxyRequest, 'write');
2026

21-
fixRequestBody(proxyRequest, { body: undefined } as Request);
27+
fixRequestBody(proxyRequest, { body: undefined } as Request, fakeProxyResponse());
2228

2329
expect(proxyRequest.setHeader).not.toHaveBeenCalled();
2430
expect(proxyRequest.write).not.toHaveBeenCalled();
@@ -31,7 +37,7 @@ describe('fixRequestBody', () => {
3137
jest.spyOn(proxyRequest, 'setHeader');
3238
jest.spyOn(proxyRequest, 'write');
3339

34-
fixRequestBody(proxyRequest, { body: {} } as Request);
40+
fixRequestBody(proxyRequest, { body: {} } as Request, fakeProxyResponse());
3541

3642
expect(proxyRequest.setHeader).toHaveBeenCalled();
3743
expect(proxyRequest.write).toHaveBeenCalled();
@@ -44,7 +50,11 @@ describe('fixRequestBody', () => {
4450
jest.spyOn(proxyRequest, 'setHeader');
4551
jest.spyOn(proxyRequest, 'write');
4652

47-
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
53+
fixRequestBody(
54+
proxyRequest,
55+
{ body: { someField: 'some value' } } as Request,
56+
fakeProxyResponse()
57+
);
4858

4959
const expectedBody = JSON.stringify({ someField: 'some value' });
5060
expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
@@ -58,7 +68,11 @@ describe('fixRequestBody', () => {
5868
jest.spyOn(proxyRequest, 'setHeader');
5969
jest.spyOn(proxyRequest, 'write');
6070

61-
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
71+
fixRequestBody(
72+
proxyRequest,
73+
{ body: { someField: 'some value' } } as Request,
74+
fakeProxyResponse()
75+
);
6276

6377
const expectedBody = querystring.stringify({ someField: 'some value' });
6478
expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
@@ -72,7 +86,11 @@ describe('fixRequestBody', () => {
7286
jest.spyOn(proxyRequest, 'setHeader');
7387
jest.spyOn(proxyRequest, 'write');
7488

75-
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
89+
fixRequestBody(
90+
proxyRequest,
91+
{ body: { someField: 'some value' } } as Request,
92+
fakeProxyResponse()
93+
);
7694

7795
const expectedBody = querystring.stringify({ someField: 'some value' });
7896
expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
@@ -86,11 +104,62 @@ describe('fixRequestBody', () => {
86104
jest.spyOn(proxyRequest, 'setHeader');
87105
jest.spyOn(proxyRequest, 'write');
88106

89-
fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
107+
fixRequestBody(
108+
proxyRequest,
109+
{ body: { someField: 'some value' } } as Request,
110+
fakeProxyResponse()
111+
);
90112

91113
const expectedBody = JSON.stringify({ someField: 'some value' });
92114
expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
93115
expect(proxyRequest.write).toHaveBeenCalledTimes(1);
94116
expect(proxyRequest.write).toHaveBeenCalledWith(expectedBody);
95117
});
118+
119+
it('should return 400 and abort request on "Connection: Upgrade" header', () => {
120+
const proxyRequest = fakeProxyRequest();
121+
const request = { body: { someField: 'some value' } } as Request;
122+
123+
proxyRequest.destroy = jest.fn();
124+
request.destroy = jest.fn();
125+
126+
const proxyResponse = fakeProxyResponse();
127+
proxyRequest.setHeader('connection', 'upgrade');
128+
proxyRequest.setHeader('content-type', 'application/x-www-form-urlencoded');
129+
130+
jest.spyOn(proxyRequest, 'destroy');
131+
jest.spyOn(request, 'destroy');
132+
jest.spyOn(proxyResponse, 'writeHead');
133+
jest.spyOn(proxyResponse, 'end');
134+
135+
fixRequestBody(proxyRequest, request, proxyResponse);
136+
137+
expect(proxyResponse.writeHead).toHaveBeenCalledWith(400);
138+
expect(proxyResponse.end).toHaveBeenCalledTimes(1);
139+
expect(proxyRequest.destroy).toHaveBeenCalledTimes(1);
140+
expect(request.destroy).toHaveBeenCalledTimes(1);
141+
});
142+
143+
it('should return 400 and abort request on invalid request data', () => {
144+
const proxyRequest = fakeProxyRequest();
145+
const request = { body: { 'INVALID \n\r DATA': '' } } as Request;
146+
147+
proxyRequest.destroy = jest.fn();
148+
request.destroy = jest.fn();
149+
150+
const proxyResponse = fakeProxyResponse();
151+
proxyRequest.setHeader('content-type', 'application/x-www-form-urlencoded');
152+
153+
jest.spyOn(proxyRequest, 'destroy');
154+
jest.spyOn(request, 'destroy');
155+
jest.spyOn(proxyResponse, 'writeHead');
156+
jest.spyOn(proxyResponse, 'end');
157+
158+
fixRequestBody(proxyRequest, request, proxyResponse);
159+
160+
expect(proxyResponse.writeHead).toHaveBeenCalledWith(400);
161+
expect(proxyResponse.end).toHaveBeenCalledTimes(1);
162+
expect(proxyRequest.destroy).toHaveBeenCalledTimes(1);
163+
expect(request.destroy).toHaveBeenCalledTimes(1);
164+
});
96165
});

0 commit comments

Comments
 (0)