Skip to content

Commit 4a08a73

Browse files
kanongilMarsup
authored andcommitted
Improve expect 100-continue handling
1 parent b720834 commit 4a08a73

File tree

2 files changed

+133
-9
lines changed

2 files changed

+133
-9
lines changed

lib/route.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -408,14 +408,15 @@ internals.payload = async function (request) {
408408
return;
409409
}
410410

411-
if (request._expectContinue) {
412-
request.raw.res.writeContinue();
413-
}
414-
415411
if (request.payload !== undefined) {
416412
return internals.drain(request);
417413
}
418414

415+
if (request._expectContinue) {
416+
request._expectContinue = false;
417+
request.raw.res.writeContinue();
418+
}
419+
419420
try {
420421
const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request.route.settings.payload);
421422

@@ -426,9 +427,7 @@ internals.payload = async function (request) {
426427
catch (err) {
427428
Bounce.rethrow(err, 'system');
428429

429-
if (request._isPayloadPending) {
430-
await internals.drain(request);
431-
}
430+
await internals.drain(request);
432431

433432
request.mime = err.mime;
434433
request.payload = null;
@@ -442,8 +441,15 @@ internals.drain = async function (request) {
442441

443442
// Flush out any pending request payload not consumed due to errors
444443

445-
await Streams.drain(request.raw.req);
446-
request._isPayloadPending = false;
444+
if (request._expectContinue) {
445+
request._isPayloadPending = false; // If we don't continue, client should not send a payload
446+
request._expectContinue = false;
447+
}
448+
449+
if (request._isPayloadPending) {
450+
await Streams.drain(request.raw.req);
451+
request._isPayloadPending = false;
452+
}
447453
};
448454

449455

test/payload.js

+118
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Net = require('net');
77
const Path = require('path');
88
const Zlib = require('zlib');
99

10+
const Boom = require('@hapi/boom');
1011
const Code = require('@hapi/code');
1112
const Hapi = require('..');
1213
const Hoek = require('@hapi/hoek');
@@ -309,6 +310,123 @@ describe('Payload', () => {
309310
await server.stop();
310311
});
311312

313+
it('does not continue on errors before payload processing', async () => {
314+
315+
const server = Hapi.server();
316+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
317+
server.ext('onPreAuth', (request, h) => {
318+
319+
throw new Boom.forbidden();
320+
});
321+
322+
await server.start();
323+
324+
const client = Net.connect(server.info.port);
325+
326+
await Events.once(client, 'connect');
327+
328+
client.write('POST / HTTP/1.1\r\nexpect: 100-continue\r\nhost: host\r\naccept-encoding: gzip\r\n' +
329+
'content-type: application/json\r\ncontent-length: 14\r\nConnection: close\r\n\r\n');
330+
331+
let continued = false;
332+
const lines = [];
333+
client.setEncoding('ascii');
334+
for await (const chunk of client) {
335+
336+
if (chunk.startsWith('HTTP/1.1 100 Continue')) {
337+
client.write('{"hello":true}');
338+
continued = true;
339+
}
340+
else {
341+
lines.push(...chunk.split('\r\n'));
342+
}
343+
}
344+
345+
const res = lines.shift();
346+
347+
expect(res).to.equal('HTTP/1.1 403 Forbidden');
348+
expect(continued).to.be.false();
349+
350+
await server.stop();
351+
});
352+
353+
it('handles expect 100-continue on undefined routes', async () => {
354+
355+
const server = Hapi.server();
356+
await server.start();
357+
358+
const client = Net.connect(server.info.port);
359+
360+
await Events.once(client, 'connect');
361+
362+
client.write('POST / HTTP/1.1\r\nexpect: 100-continue\r\nhost: host\r\naccept-encoding: gzip\r\n' +
363+
'content-type: application/json\r\ncontent-length: 14\r\nConnection: close\r\n\r\n');
364+
365+
let continued = false;
366+
const lines = [];
367+
client.setEncoding('ascii');
368+
for await (const chunk of client) {
369+
370+
if (chunk.startsWith('HTTP/1.1 100 Continue')) {
371+
client.write('{"hello":true}');
372+
continued = true;
373+
}
374+
else {
375+
lines.push(...chunk.split('\r\n'));
376+
}
377+
}
378+
379+
const res = lines.shift();
380+
381+
expect(res).to.equal('HTTP/1.1 404 Not Found');
382+
expect(continued).to.be.false();
383+
384+
await server.stop();
385+
});
386+
387+
it('does not continue on custom request.payload', async () => {
388+
389+
const server = Hapi.server();
390+
server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
391+
server.ext('onRequest', (request, h) => {
392+
393+
request.payload = { custom: true };
394+
return h.continue;
395+
});
396+
397+
await server.start();
398+
399+
const client = Net.connect(server.info.port);
400+
401+
await Events.once(client, 'connect');
402+
403+
client.write('POST / HTTP/1.1\r\nexpect: 100-continue\r\nhost: host\r\naccept-encoding: gzip\r\n' +
404+
'content-type: application/json\r\ncontent-length: 14\r\nConnection: close\r\n\r\n');
405+
406+
let continued = false;
407+
const lines = [];
408+
client.setEncoding('ascii');
409+
for await (const chunk of client) {
410+
411+
if (chunk.startsWith('HTTP/1.1 100 Continue')) {
412+
client.write('{"hello":true}');
413+
continued = true;
414+
}
415+
else {
416+
lines.push(...chunk.split('\r\n'));
417+
}
418+
}
419+
420+
const res = lines.shift();
421+
const payload = lines.pop();
422+
423+
expect(res).to.equal('HTTP/1.1 200 OK');
424+
expect(payload).to.equal('{"custom":true}');
425+
expect(continued).to.be.false();
426+
427+
await server.stop();
428+
});
429+
312430
it('peeks at unparsed data', async () => {
313431

314432
let data = null;

0 commit comments

Comments
 (0)