Skip to content
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

Merged
merged 3 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/utils/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, () => {})
Copy link
Contributor Author

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)

next is not a function

}

const handleAddonUrl = function ({ addonUrl, req, res }) {
Expand Down Expand Up @@ -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
Copy link
Contributor Author

@danez danez Aug 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes a problem where static URLs are escaped to %2Findex.html. This was introduced in #2665 but even now after changing the to escapeUri the tests still work.

// 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 })
Expand Down Expand Up @@ -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
}

Expand Down
44 changes: 18 additions & 26 deletions tests/integration/0.command.dev.test.js
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
Expand Down Expand Up @@ -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()
Expand All @@ -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' })
Expand All @@ -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: {
Expand All @@ -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,
}),
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
})
})
})
Expand Down
Loading