Skip to content

Commit 4ef1e0d

Browse files
author
Aaron Hedges
authored
fix(node): ensure that unknown request bodies are handled without errors (#321)
1 parent f9c92d9 commit 4ef1e0d

File tree

5 files changed

+695
-37
lines changed

5 files changed

+695
-37
lines changed

packages/node/__tests__/index.test.ts

+57-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import config from '../src/config';
1010
import pkg from '../package.json';
1111
import { expressMiddleware } from '../src';
1212
import { ServerResponse } from 'http';
13+
import FormData from 'form-data';
14+
import multer from 'multer';
15+
16+
const upload = multer();
17+
1318

1419
const apiKey = 'mockReadMeApiKey';
1520
const incomingGroup = {
@@ -166,7 +171,7 @@ describe('#metrics', () => {
166171
.reply(200);
167172

168173
const app = express();
169-
app.use((req, res, next) => {
174+
app.use((req: any, res: any, next) => {
170175
req.a = 'a';
171176
res.b = 'b';
172177
res.c = 'c';
@@ -527,4 +532,55 @@ describe('#metrics', () => {
527532
mock.done();
528533
});
529534
});
535+
536+
describe('`req.body`', () => {
537+
let apiMock;
538+
539+
function createMock(checkLocation: 'text' | 'params', requestBody: unknown) {
540+
return nock(config.host, {
541+
reqheaders: {
542+
'Content-Type': 'application/json',
543+
'User-Agent': `${pkg.name}/${pkg.version}`,
544+
},
545+
})
546+
.post('/v1/request', ([body]) => {
547+
expect(body.request.log.entries[0].request.postData[checkLocation]).toBe(requestBody);
548+
return true;
549+
})
550+
.reply(200);
551+
}
552+
553+
beforeEach(() => {
554+
apiMock = getReadMeApiMock(1);
555+
});
556+
557+
afterEach(() => {
558+
apiMock.done();
559+
});
560+
561+
it('should accept multipart/form-data', async () => {
562+
563+
const form = new FormData();
564+
form.append('password', '123456');
565+
form.append('apiKey', 'abc');
566+
form.append('another', 'Hello world');
567+
568+
// If the request body for a multipart/form-data request comes in as an object (as it does with the express middleware) we expect it to be recorded json encoded
569+
const mock = createMock('text', JSON.stringify({password: '123456', apiKey: 'abc', another: 'Hello world'}));
570+
const app = express();
571+
app.use(upload.none());
572+
app.use(expressMiddleware(apiKey, () => incomingGroup));
573+
app.post('/test', (req, res) => {
574+
res.status(200).end();
575+
});
576+
577+
await request(app)
578+
.post('/test')
579+
.set(form.getHeaders())
580+
.send(form.getBuffer().toString())
581+
.expect(200);
582+
583+
mock.done();
584+
});
585+
});
530586
});

packages/node/__tests__/lib/process-request.test.ts

+40-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import request from 'supertest';
22
import * as http from 'http';
33
import processRequest from '../../src/lib/process-request';
44
import { LogOptions } from 'src/lib/construct-payload';
5+
import FormData from 'form-data';
56

6-
function createApp(reqOptions?: LogOptions, shouldPreParse: boolean = false) {
7+
function createApp(reqOptions?: LogOptions, shouldPreParse: boolean = false, bodyOverride?) {
78
const requestListener = function (req: http.IncomingMessage, res: http.ServerResponse) {
89
let body = "";
910

@@ -20,7 +21,7 @@ function createApp(reqOptions?: LogOptions, shouldPreParse: boolean = false) {
2021
body = JSON.parse(body);
2122
}
2223

23-
res.end(JSON.stringify(processRequest(req, body, reqOptions)));
24+
res.end(JSON.stringify(processRequest(req, bodyOverride ? bodyOverride : body, reqOptions)));
2425
});
2526
};
2627

@@ -96,6 +97,43 @@ it('should work with *+json', () => {
9697
});
9798
});
9899

100+
it('should work with multipart/form-data', () => {
101+
const app = createApp({ denylist: ['password']});
102+
103+
const form = new FormData();
104+
form.append('password', '123456');
105+
form.append('apiKey', 'abc');
106+
form.append('another', 'Hello world');
107+
108+
const formHeaders = form.getHeaders();
109+
110+
return request(app)
111+
.post('/')
112+
.set(formHeaders)
113+
.send(form.getBuffer().toString())
114+
.expect(({ body }) => {
115+
// If the request body for multipart form comes in as a string, we record it as is.
116+
expect(body.postData.text).toBe(form.getBuffer().toString());
117+
});
118+
});
119+
120+
it('should fail gracefully with circular json objects', () => {
121+
const obj = { foo: null };
122+
obj.foo = obj;
123+
124+
const app = createApp({ denylist: ['password']}, false, obj);
125+
126+
return request(app)
127+
.post('/')
128+
.set('content-type', 'text/plain')
129+
.send('this isn\'t used')
130+
.expect(({ body }) => {
131+
console.log(body);
132+
// If the request body for multipart form comes in as a string, we record it as is.
133+
expect(body.postData.text).toBe('[ReadMe is unable to handle circular JSON. Please contact support if you have any questions.]');
134+
});
135+
});
136+
99137
describe('options', () => {
100138
describe('denylist/allowlist', () => {
101139
it('should strip denylisted json properties', () => {

0 commit comments

Comments
 (0)