Skip to content

Commit

Permalink
Merge pull request #9926 from tolgap/fix/9901-urlencoded-raw-body
Browse files Browse the repository at this point in the history
fix(express,fastify): raw body for urlencoded requests
  • Loading branch information
kamilmysliwiec authored Jul 20, 2022
2 parents 0b24aff + 50716eb commit 4eacd87
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 73 deletions.
75 changes: 52 additions & 23 deletions integration/nest-application/raw-body/e2e/express.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ExpressModule } from '../src/express.module';

describe('Raw body (Express Application)', () => {
let app: NestExpressApplication;
const body = '{ "amount":0.0 }';

beforeEach(async () => {
const moduleFixture = await Test.createTestingModule({
Expand All @@ -16,33 +15,63 @@ describe('Raw body (Express Application)', () => {
app = moduleFixture.createNestApplication<NestExpressApplication>({
rawBody: true,
});
});

it('should return exact post body', async () => {
await app.init();
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.send(body)
.expect(201);

expect(response.body).to.eql({
parsed: {
amount: 0,
},
raw: '{ "amount":0.0 }',
});
});

it('should work if post body is empty', async () => {
await app.init();
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.expect(201);
});

afterEach(async () => {
await app.close();
});

describe('application/json', () => {
const body = '{ "amount":0.0 }';

it('should return exact post body', async () => {
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.send(body)
.expect(201);

expect(response.body).to.eql({
parsed: {
amount: 0,
},
raw: body,
});
});

it('should work if post body is empty', async () => {
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.expect(201);
});
});

describe('application/x-www-form-urlencoded', () => {
const body = 'content=this is a post\'s content by "Nest"';

it('should return exact post body', async () => {
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send(body)
.expect(201);

expect(response.body).to.eql({
parsed: {
content: 'this is a post\'s content by "Nest"',
},
raw: body,
});
});

it('should work if post body is empty', async () => {
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.expect(201);
});
});
});
93 changes: 64 additions & 29 deletions integration/nest-application/raw-body/e2e/fastify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { FastifyModule } from '../src/fastify.module';

describe('Raw body (Fastify Application)', () => {
let app: NestFastifyApplication;
const body = '{ "amount":0.0 }';

beforeEach(async () => {
const moduleFixture = await Test.createTestingModule({
Expand All @@ -21,43 +20,79 @@ describe('Raw body (Fastify Application)', () => {
rawBody: true,
},
);
});

it('should return exact post body', async () => {
await app.init();
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/json' },
payload: body,
});

afterEach(async () => {
await app.close();
});

describe('application/json', () => {
const body = '{ "amount":0.0 }';

it('should return exact post body', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/json' },
payload: body,
});

expect(JSON.parse(response.body)).to.eql({
parsed: {
amount: 0,
},
raw: body,
});
});

expect(JSON.parse(response.body)).to.eql({
parsed: {
amount: 0,
},
raw: '{ "amount":0.0 }',
it('should fail if post body is empty', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
});

// Unlike Express, when you send a POST request without a body
// with Fastify, Fastify will throw an error because it isn't valid
// JSON. See fastify/fastify#297.
expect(response.statusCode).to.equal(400);
});
});

it('should fail if post body is empty', async () => {
await app.init();
const response = await app.inject({
method: 'POST',
url: '/',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
describe('application/x-www-form-urlencoded', () => {
const body = 'content=this is a post\'s content by "Nest"';

it('should return exact post body', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
payload: body,
});

expect(JSON.parse(response.body)).to.eql({
parsed: {
content: 'this is a post\'s content by "Nest"',
},
raw: body,
});
});

// Unlike Express, when you send a POST request without a body
// with Fastify, Fastify will throw an error because it isn't valid
// JSON. See fastify/fastify#297.
expect(response.statusCode).to.equal(400);
});
it('should work if post body is empty', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
});

afterEach(async () => {
await app.close();
expect(response.statusCode).to.equal(201);
});
});
});
20 changes: 8 additions & 12 deletions packages/platform-express/adapters/express-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ import { RouterMethodFactory } from '@nestjs/core/helpers/router-method-factory'
import {
json as bodyParserJson,
OptionsJson,
OptionsUrlencoded,
urlencoded as bodyParserUrlencoded,
} from 'body-parser';
import * as cors from 'cors';
import * as express from 'express';
import * as http from 'http';
import * as https from 'https';
import { ServeStaticOptions } from '../interfaces/serve-static-options.interface';
import { getBodyParserOptions } from './utils/get-body-parser-options.util';

type VersionedRoute = <
TRequest extends Record<string, any> = any,
Expand Down Expand Up @@ -194,21 +196,15 @@ export class ExpressAdapter extends AbstractHttpAdapter {
}

public registerParserMiddleware(prefix?: string, rawBody?: boolean) {
let bodyParserJsonOptions: OptionsJson | undefined;
if (rawBody === true) {
bodyParserJsonOptions = {
verify: (req: RawBodyRequest<http.IncomingMessage>, _res, buffer) => {
if (Buffer.isBuffer(buffer)) {
req.rawBody = buffer;
}
return true;
},
};
}
const bodyParserJsonOptions = getBodyParserOptions<OptionsJson>(rawBody);
const bodyParserUrlencodedOptions = getBodyParserOptions<OptionsUrlencoded>(
rawBody,
{ extended: true },
);

const parserMiddleware = {
jsonParser: bodyParserJson(bodyParserJsonOptions),
urlencodedParser: bodyParserUrlencoded({ extended: true }),
urlencodedParser: bodyParserUrlencoded(bodyParserUrlencodedOptions),
};
Object.keys(parserMiddleware)
.filter(parser => !this.isMiddlewareApplied(parser))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { RawBodyRequest } from '@nestjs/common';
import type { Options } from 'body-parser';
import type { IncomingMessage, ServerResponse } from 'http';

const rawBodyParser = (
req: RawBodyRequest<IncomingMessage>,
_res: ServerResponse,
buffer: Buffer,
) => {
if (Buffer.isBuffer(buffer)) {
req.rawBody = buffer;
}
return true;
};

export function getBodyParserOptions<ParserOptions extends Options>(
rawBody: boolean,
options?: ParserOptions | undefined,
): ParserOptions {
let parserOptions: ParserOptions = options ?? ({} as ParserOptions);

if (rawBody === true) {
parserOptions = {
...parserOptions,
verify: rawBodyParser,
};
}

return parserOptions;
}
38 changes: 29 additions & 9 deletions packages/platform-fastify/adapters/fastify-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
InjectOptions,
Response as LightMyRequestResponse,
} from 'light-my-request';
// `querystring` is used internally in fastify for registering urlencoded body parser.
import { parse as querystringParse } from 'querystring';
import {
FastifyStaticOptions,
PointOfViewOptions,
Expand Down Expand Up @@ -454,13 +456,9 @@ export class FastifyAdapter<
if (this._isParserRegistered) {
return;
}
this.register(
import('@fastify/formbody') as Parameters<TInstance['register']>[0],
);

if (rawBody) {
this.registerContentParserWithRawBody();
}
this.registerUrlencodedContentParser(rawBody);
this.registerJsonContentParser(rawBody);

this._isParserRegistered = true;
}
Expand Down Expand Up @@ -509,16 +507,18 @@ export class FastifyAdapter<
return !('status' in response);
}

private registerContentParserWithRawBody() {
private registerJsonContentParser(rawBody?: boolean) {
const { bodyLimit } = this.getInstance().initialConfig;

this.getInstance().addContentTypeParser<Buffer>(
'application/json',
{ parseAs: 'buffer' },
{ parseAs: 'buffer', bodyLimit },
(
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
body: Buffer,
done,
) => {
if (Buffer.isBuffer(body)) {
if (rawBody === true && Buffer.isBuffer(body)) {
req.rawBody = body;
}

Expand All @@ -533,6 +533,26 @@ export class FastifyAdapter<
);
}

private registerUrlencodedContentParser(rawBody?: boolean) {
const { bodyLimit } = this.getInstance().initialConfig;

this.getInstance().addContentTypeParser<Buffer>(
'application/x-www-form-urlencoded',
{ parseAs: 'buffer', bodyLimit },
(
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
body: Buffer,
done,
) => {
if (rawBody === true && Buffer.isBuffer(body)) {
req.rawBody = body;
}

done(null, querystringParse(body.toString()));
},
);
}

private async registerMiddie() {
this.isMiddieRegistered = true;
await this.register(
Expand Down

0 comments on commit 4eacd87

Please sign in to comment.