From f67ab50b6ed60dec1a639a86745056b801312020 Mon Sep 17 00:00:00 2001 From: Yuvi Panda Date: Mon, 10 Aug 2020 15:16:17 +0530 Subject: [PATCH 1/2] Handle contentType not being set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `netlify dev` was failing for me consistently with the following error message: ``` ◈ Netlify Dev ◈ ◈ No app server detected and no "command" specified ◈ Using current working directory ◈ Unable to determine public folder to serve files from ◈ Setup a netlify.toml file with a [dev] section to specify your dev server settings. ◈ See docs at: https://cli.netlify.com/netlify-dev#project-detection ◈ Running static server from "mybinder.term" ◈ Server listening to 3999 ┌─────────────────────────────────────────────────┐ │ │ │ ◈ Server now ready on http://localhost:8888 │ │ │ └─────────────────────────────────────────────────┘ TypeError: Cannot read property 'endsWith' of undefined at serveRedirect (/usr/local/lib/node_modules/netlify-cli/src/commands/dev/index.js:344:16) TypeError: Cannot read property 'endsWith' of undefined at serveRedirect (/usr/local/lib/node_modules/netlify-cli/src/commands/dev/index.js:344:16) /usr/local/lib/node_modules/netlify-cli/node_modules/netlify-redirector/lib/redirects.js:116 throw ex; ^ abort({}) at Error at jsStackTrace (/usr/local/lib/node_modules/netlify-cli/node_modules/netlify-redirector/lib/redirects.js:1070:13) at stackTrace (/usr/local/lib/node_modules/netlify-cli/node_modules/netlify-redirector/lib/redirects.js:1087:12) at process.abort (/usr/local/lib/node_modules/netlify-cli/node_modules/netlify-redirector/lib/redirects.js:8502:44) at process.emit (events.js:314:20) at processPromiseRejections (internal/process/promises.js:245:33) at processTicksAndRejections (internal/process/task_queues.js:94:32) (Use `node --trace-uncaught ...` to show where the exception was thrown) ``` The code was setting `ct` to be an empty object if no content-type header was set, but then accessing `.type` on the object unconditionally. With this PR, we set `ct` to be the content-type itself, avoiding any attribute access that might cause undefined attribute access. --- src/commands/dev/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/dev/index.js b/src/commands/dev/index.js index 71bfe6b9e0f..ed1eb9b0bbc 100644 --- a/src/commands/dev/index.js +++ b/src/commands/dev/index.js @@ -214,11 +214,11 @@ async function startProxy(settings = {}, addonUrls, configPath, projectDir, func if (match) return serveRedirect(req, res, proxy, match, options) - const ct = req.headers['content-type'] ? contentType.parse(req) : {} + const ct = req.headers['content-type'] ? contentType.parse(req).type : '' if ( req.method === 'POST' && !isInternal(req.url) && - (ct.type.endsWith('/x-www-form-urlencoded') || ct.type === 'multipart/form-data') + (ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data') ) { return proxy.web(req, res, { target: functionsServer }) } @@ -338,12 +338,12 @@ async function serveRedirect(req, res, proxy, match, options) { return handler(req, res, {}) } - const ct = req.headers['content-type'] ? contentType.parse(req) : {} + const ct = req.headers['content-type'] ? contentType.parse(req).type : '' if ( req.method === 'POST' && !isInternal(req.url) && !isInternal(destURL) && - (ct.type.endsWith('/x-www-form-urlencoded') || ct.type === 'multipart/form-data') + (ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data') ) { return proxy.web(req, res, { target: options.functionsServer }) } From d3f24037c4eb7e297278d42232a8ff949ddfd7d3 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Wed, 12 Aug 2020 16:18:40 +0200 Subject: [PATCH 2/2] test: add redirect test with missing content-type --- tests/command.dev.test.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/command.dev.test.js b/tests/command.dev.test.js index 3b3f31f53bb..17273d78e69 100644 --- a/tests/command.dev.test.js +++ b/tests/command.dev.test.js @@ -764,3 +764,42 @@ test('should redirect requests to an external server', async t => { server.close() }) }) + +test('should redirect POST request if content-type is missing', async t => { + await withSiteBuilder('site-with-post-no-content-type', async builder => { + builder.withNetlifyToml({ + config: { + build: { functions: 'functions' }, + redirects: [{ from: '/api/*', to: '/other/:splat', status: 200 }], + }, + }) + + await builder.buildAsync() + + await withDevServer({ cwd: builder.directory }, async server => { + // we use http.request since fetch automatically sends a content-type header + const http = require('http') + const options = { + host: server.host, + port: server.port, + path: '/api/echo', + method: 'POST', + } + let data = '' + await new Promise(resolve => { + const callback = response => { + response.on('data', chunk => { + data += chunk + }) + response.on('end', resolve) + } + const req = http.request(options, callback) + req.write('param=value') + req.end() + }) + + // we're testing Netlify Dev didn't crash + t.is(data, 'Method Not Allowed') + }) + }) +})