-
Notifications
You must be signed in to change notification settings - Fork 374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: correctly allow redirects to external URLs #5005
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,7 +70,7 @@ const proxyToExternalUrl = function ({ dest, destURL, req, res }) { | |
pathRewrite: () => destURL, | ||
...(Buffer.isBuffer(req.originalBody) && { buffer: toReadableStream(req.originalBody) }), | ||
}) | ||
return handler(req, res, {}) | ||
return handler(req, res, () => {}) | ||
} | ||
|
||
const handleAddonUrl = function ({ addonUrl, req, res }) { | ||
|
@@ -185,7 +185,7 @@ const serveRedirect = async function ({ match, options, proxy, req, res }) { | |
|
||
const staticFile = await getStatic(decodeURIComponent(reqUrl.pathname), options.publicFolder) | ||
if (staticFile) { | ||
req.url = encodeURIComponent(staticFile) + reqUrl.search | ||
req.url = encodeURI(staticFile) + reqUrl.search | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes a problem where static URLs are escaped to |
||
// if there is an existing static file and it is not a forced redirect, return the file | ||
if (!match.force) { | ||
return proxy.web(req, res, { ...options, staticFile }) | ||
|
@@ -214,18 +214,25 @@ const serveRedirect = async function ({ match, options, proxy, req, res }) { | |
}) | ||
} | ||
|
||
const destURL = stripOrigin(dest) | ||
let destURL = stripOrigin(dest) | ||
|
||
if (isExternal(match)) { | ||
return proxyToExternalUrl({ req, res, dest, destURL }) | ||
if (isRedirect(match)) { | ||
// This is a redirect, so we set the complete external URL as destination | ||
destURL = `${dest}` | ||
} else { | ||
return proxyToExternalUrl({ req, res, dest, destURL }) | ||
} | ||
} | ||
|
||
if (isRedirect(match)) { | ||
console.log(`${NETLIFYDEVLOG} Redirecting ${req.url} to ${destURL}`) | ||
res.writeHead(match.status, { | ||
Location: destURL, | ||
'Cache-Control': 'no-cache', | ||
}) | ||
res.end(`Redirecting to ${destURL}`) | ||
|
||
return | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
// Handlers are meant to be async outside tests | ||
const { promises: fs } = require('fs') | ||
const http = require('http') | ||
const { join } = require('path') | ||
|
||
// eslint-disable-next-line ava/use-test | ||
|
@@ -166,7 +165,7 @@ test('should source redirects file from publish directory', async (t) => { | |
}) | ||
}) | ||
|
||
test('should redirect requests to an external server', async (t) => { | ||
test('should rewrite requests to an external server', async (t) => { | ||
await withSiteBuilder('site-redirects-file-to-external', async (builder) => { | ||
const externalServer = startExternalServer() | ||
const { port } = externalServer.address() | ||
|
@@ -186,6 +185,7 @@ test('should redirect requests to an external server', async (t) => { | |
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: 'param=value', | ||
followRedirect: false, | ||
}) | ||
.json() | ||
t.deepEqual(postResponse, { body: { param: 'value' }, method: 'POST', url: '/ping' }) | ||
|
@@ -206,15 +206,18 @@ test('should follow 301 redirect to an external server', async (t) => { | |
await builder.buildAsync() | ||
|
||
await withDevServer({ cwd: builder.directory }, async (server) => { | ||
const response = await got(`${server.url}/api/ping`).json() | ||
t.deepEqual(response, { body: {}, method: 'GET', url: '/ping' }) | ||
const response = await got(`${server.url}/api/ping`, { followRedirect: false }) | ||
t.is(response.headers.location, `http://localhost:${port}/ping`) | ||
|
||
const body = await got(`${server.url}/api/ping`).json() | ||
danez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
t.deepEqual(body, { body: {}, method: 'GET', url: '/ping' }) | ||
}) | ||
|
||
externalServer.close() | ||
}) | ||
}) | ||
|
||
test('should redirect POST request if content-type is missing', async (t) => { | ||
test('should rewrite POST request if content-type is missing and not crash dev server', async (t) => { | ||
await withSiteBuilder('site-with-post-no-content-type', async (builder) => { | ||
builder.withNetlifyToml({ | ||
config: { | ||
|
@@ -226,27 +229,16 @@ test('should redirect POST request if content-type is missing', async (t) => { | |
await builder.buildAsync() | ||
|
||
await withDevServer({ cwd: builder.directory }, async (server) => { | ||
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') | ||
const error = await t.throwsAsync( | ||
async () => | ||
await got.post(`${server.url}/api/echo`, { | ||
body: 'param=value', | ||
followRedirect: false, | ||
}), | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just replaced with got instead of custom HTTP request. |
||
|
||
// Method Not Allowed | ||
t.is(error.response.statusCode, 405) | ||
}) | ||
}) | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only noticed this because a request was not matching and http-proxy-rewrite was actually calling
next
(the third parameter)