From 7cb53b7e9beb2b5c9388bdd19a1a1aad4b983be5 Mon Sep 17 00:00:00 2001 From: Austin Wright Date: Thu, 25 Jun 2020 23:15:45 -0700 Subject: [PATCH] test: Verify request payload is uploaded consistently Node.js seems to change how it is uploaded based on the method, but HTTP doesn't make any distinction. --- test/known_issues/test-http-27880-bug.js | 30 ++++++++++++ test/known_issues/test-http-27880-chunked.js | 49 +++++++++++++++++++ .../test-http-27880-content-length.js | 48 ++++++++++++++++++ test/known_issues/test-http-27880-empty.js | 49 +++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 test/known_issues/test-http-27880-bug.js create mode 100644 test/known_issues/test-http-27880-chunked.js create mode 100644 test/known_issues/test-http-27880-content-length.js create mode 100644 test/known_issues/test-http-27880-empty.js diff --git a/test/known_issues/test-http-27880-bug.js b/test/known_issues/test-http-27880-bug.js new file mode 100644 index 00000000000000..03728846df69d6 --- /dev/null +++ b/test/known_issues/test-http-27880-bug.js @@ -0,0 +1,30 @@ +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const http = require('http'); + +const data = 'PUT / HTTP/1.1\r\n\r\n'; + +const server = http.createServer(common.mustCall(function(req, res) { + req.on('data', function(chunk) { + assert.strictEqual(chunk, Buffer.from(data)); + }); + res.setHeader('Content-Type', 'text/plain'); + for (let i = 0; i < req.rawHeaders.length; i += 2) { + if (req.rawHeaders[i].toLowerCase() === 'host') continue; + if (req.rawHeaders[i].toLowerCase() === 'connection') continue; + res.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`); + } + res.end(); +})).unref(); + +server.listen(0, common.mustCall(() => { + const port = server.address().port; + const req = http.request({ method: 'DELETE', port }, function(res) { + res.resume(); + }); + + req.write(data); + req.end(); +})); diff --git a/test/known_issues/test-http-27880-chunked.js b/test/known_issues/test-http-27880-chunked.js new file mode 100644 index 00000000000000..28d6d425ca1942 --- /dev/null +++ b/test/known_issues/test-http-27880-chunked.js @@ -0,0 +1,49 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// Test that ClientRequest#write with default options +// uses a chunked Transfer-Encoding +const upload = 'PUT / HTTP/1.1\r\n\r\n'; +const response = 'transfer-encoding: chunked\r\n'; + +// Test that the upload is properly received with the same headers, +// regardless of request method. +const methods = [ 'GET', 'HEAD', 'DELETE', 'POST', 'PATCH', 'PUT', 'OPTIONS' ]; + +const server = http.createServer(common.mustCall(function(req, res) { + req.on('data', function(chunk) { + assert.strictEqual(chunk.toString(), upload); + }); + res.setHeader('Content-Type', 'text/plain'); + res.write(`${req.method}\r\n`); + for (let i = 0; i < req.rawHeaders.length; i += 2) { + // Ignore a couple headers that may vary + if (req.rawHeaders[i].toLowerCase() === 'host') continue; + if (req.rawHeaders[i].toLowerCase() === 'connection') continue; + res.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`); + } + res.end(); +}), methods.length); + +server.listen(0, function tryNextRequest() { + const method = methods.pop(); + if (method === undefined) return; + const port = server.address().port; + const req = http.request({ method, port }, function(res) { + const chunks = []; + res.on('data', function(chunk) { + chunks.push(chunk); + }); + res.on('end', function() { + const received = Buffer.concat(chunks).toString(); + const expected = method.toLowerCase() + '\r\n' + response; + assert.strictEqual(received.toLowerCase(), expected); + tryNextRequest(); + }); + }); + + req.write(upload); + req.end(); +}).unref(); diff --git a/test/known_issues/test-http-27880-content-length.js b/test/known_issues/test-http-27880-content-length.js new file mode 100644 index 00000000000000..5062d090d5feae --- /dev/null +++ b/test/known_issues/test-http-27880-content-length.js @@ -0,0 +1,48 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// Test that ClientRequest#end with default options +// computes and sends a Content-Length header +const upload = 'PUT / HTTP/1.1\r\n\r\n'; +const response = 'content-length: 19\r\n'; + +// Test that the upload is properly received with the same headers, +// regardless of request method +const methods = [ 'GET', 'HEAD', 'DELETE', 'POST', 'PATCH', 'PUT', 'OPTIONS' ]; + +const server = http.createServer(common.mustCall(function(req, res) { + req.on('data', function(chunk) { + assert.strictEqual(chunk, Buffer.from(upload)); + }); + res.setHeader('Content-Type', 'text/plain'); + let payload = `${req.method}\r\n`; + for (let i = 0; i < req.rawHeaders.length; i += 2) { + // Ignore a couple headers that may vary + if (req.rawHeaders[i].toLowerCase() === 'host') continue; + if (req.rawHeaders[i].toLowerCase() === 'connection') continue; + payload += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`; + } + res.end(payload); +}), methods.length); + +server.listen(0, function tryNextRequest() { + const method = methods.pop(); + if (method === undefined) return; + const port = server.address().port; + const req = http.request({ method, port }, function(res) { + const chunks = []; + res.on('data', function(chunk) { + chunks.push(chunk); + }); + res.on('end', function() { + const received = Buffer.concat(chunks).toString(); + const expected = method.toLowerCase() + '\r\n' + response; + assert.strictEqual(received.toLowerCase(), expected); + tryNextRequest(); + }); + }); + + req.end(upload); +}).unref(); diff --git a/test/known_issues/test-http-27880-empty.js b/test/known_issues/test-http-27880-empty.js new file mode 100644 index 00000000000000..5e9396a322ea4b --- /dev/null +++ b/test/known_issues/test-http-27880-empty.js @@ -0,0 +1,49 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// Test that ClientRequest#end with default options +// and empty payload sends neither Content-Length nor Transfer-Encoding. +// Sending Content-Length: 0 would be acceptable, but is unnecessary. +const upload = 'PUT / HTTP/1.1\r\n\r\n'; +const response = ''; + +// Test that the upload is properly received with the same headers, +// regardless of request method. +const methods = [ 'GET', 'HEAD', 'DELETE', 'POST', 'PATCH', 'PUT', 'OPTIONS' ]; + +const server = http.createServer(common.mustCall(function(req, res) { + req.on('data', function(chunk) { + assert.strictEqual(chunk.toString(), upload); + }); + res.setHeader('Content-Type', 'text/plain'); + res.write(`${req.method}\r\n`); + for (let i = 0; i < req.rawHeaders.length; i += 2) { + // Ignore a couple headers that may vary + if (req.rawHeaders[i].toLowerCase() === 'host') continue; + if (req.rawHeaders[i].toLowerCase() === 'connection') continue; + res.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`); + } + res.end(); +}), methods.length); + +server.listen(0, function tryNextRequest() { + const method = methods.pop(); + if (method === undefined) return; + const port = server.address().port; + const req = http.request({ method, port }, function(res) { + const chunks = []; + res.on('data', function(chunk) { + chunks.push(chunk); + }); + res.on('end', function() { + const received = Buffer.concat(chunks).toString(); + const expected = method.toLowerCase() + '\r\n' + response; + assert.strictEqual(received.toLowerCase(), expected); + tryNextRequest(); + }); + }); + + req.end(); +}).unref();