Skip to content

Commit

Permalink
Merge pull request from GHSA-c4qr-gmr9-v23w
Browse files Browse the repository at this point in the history
* added failing test

* fix first test

* Fixed 2/4

* three out of four

* four out of four
  • Loading branch information
mcollina authored Feb 19, 2021
1 parent 23d6cac commit 02d9b43
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 19 deletions.
55 changes: 37 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,8 @@ function proxyWebSockets (source, target) {
target.on('unexpected-response', () => close(1011, 'unexpected response'))
}

function createWebSocketUrl (options, request) {
const source = new URL(request.url, 'http://127.0.0.1')

const target = new URL(
options.rewritePrefix || options.prefix || source.pathname,
options.upstream
)

target.search = source.search

return target
}

function setupWebSocketProxy (fastify, options) {
function setupWebSocketProxy (fastify, options, rewritePrefix) {
const server = new WebSocket.Server({
path: options.prefix,
server: fastify.server,
...options.wsServerOptions
})
Expand All @@ -93,13 +79,46 @@ function setupWebSocketProxy (fastify, options) {
})

server.on('connection', (source, request) => {
const url = createWebSocketUrl(options, request)
if (fastify.prefix && !request.url.startsWith(fastify.prefix)) {
fastify.log.debug({ url: request.url }, 'not matching prefix')
source.close()
return
}

const url = createWebSocketUrl(request)

const target = new WebSocket(url, options.wsClientOptions)

fastify.log.debug({ url: url.href }, 'proxy websocket')
proxyWebSockets(source, target)
})

function createWebSocketUrl (request) {
const source = new URL(request.url, 'http://127.0.0.1')

const target = new URL(
source.pathname.replace(fastify.prefix, rewritePrefix),
options.upstream
)

target.search = source.search

return target
}
}

function generateRewritePrefix (prefix, opts) {
if (!prefix) {
return ''
}

let rewritePrefix = opts.rewritePrefix || new URL(opts.upstream).pathname

if (!prefix.endsWith('/') && rewritePrefix.endsWith('/')) {
rewritePrefix = rewritePrefix.slice(0, -1)
}

return rewritePrefix
}

async function httpProxy (fastify, opts) {
Expand All @@ -108,7 +127,7 @@ async function httpProxy (fastify, opts) {
}

const preHandler = opts.preHandler || opts.beforeHandler
const rewritePrefix = opts.rewritePrefix || ''
const rewritePrefix = generateRewritePrefix(fastify.prefix, opts)

const fromOpts = Object.assign({}, opts)
fromOpts.base = opts.upstream
Expand Down Expand Up @@ -164,7 +183,7 @@ async function httpProxy (fastify, opts) {
}

if (opts.websocket) {
setupWebSocketProxy(fastify, opts)
setupWebSocketProxy(fastify, opts, rewritePrefix)
}
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"express-http-proxy": "^1.6.2",
"fast-proxy": "^1.7.0",
"fastify": "^3.0.0",
"fastify-websocket": "^3.0.0",
"got": "^11.5.1",
"http-errors": "^1.8.0",
"http-proxy": "^1.17.0",
Expand Down
2 changes: 1 addition & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ async function run () {
async preHandler (request, reply) {
t.deepEqual(reply.context.config, {
foo: 'bar',
url: '/*',
url: '/',
method: [
'DELETE',
'GET',
Expand Down
145 changes: 145 additions & 0 deletions test/ws-prefix-rewrite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
'use strict'

const t = require('tap')
const { once } = require('events')

const Fastify = require('fastify')
const fastifyWebSocket = require('fastify-websocket')
const proxy = require('..')
const WebSocket = require('ws')
const got = require('got')

const level = 'warn'

async function proxyServer (t, backendURL, backendPath, proxyOptions, wrapperOptions) {
const frontend = Fastify({ logger: { level } })
const registerProxy = async fastify => {
fastify.register(proxy, {
upstream: backendURL + backendPath,
...proxyOptions
})
}

t.comment('starting proxy to ' + backendURL + backendPath)

if (wrapperOptions) {
await frontend.register(registerProxy, wrapperOptions)
} else {
await registerProxy(frontend)
}

return [frontend, await frontend.listen(0)]
}

async function processRequest (t, frontendURL, path, expected) {
const url = new URL(path, frontendURL)
t.comment('ws connecting to ' + url.toString())
const ws = new WebSocket(url)
let wsResult, gotResult

try {
await once(ws, 'open')
t.pass('socket connected')

const [buf] = await Promise.race([once(ws, 'message'), once(ws, 'close')])
if (buf instanceof Buffer) {
wsResult = buf.toString()
} else {
t.comment('websocket closed')
wsResult = 'error'
}
} catch (e) {
wsResult = 'error'
ws.terminate()
}

try {
const result = await got(url)
gotResult = result.body
} catch (e) {
gotResult = 'error'
}

t.is(wsResult, expected)
t.is(gotResult, expected)
}

async function handleProxy (info, { backendPath, proxyOptions, wrapperOptions }, expected, ...paths) {
t.test(info, async function (t) {
const backend = Fastify({ logger: { level } })
await backend.register(fastifyWebSocket)

backend.get('/*', {
handler: (req, reply) => {
reply.send(req.url)
},
wsHandler: (conn, req) => {
conn.write(req.url)
conn.end()
}
})

t.teardown(() => backend.close())

const backendURL = await backend.listen(0)

const [frontend, frontendURL] = await proxyServer(t, backendURL, backendPath, proxyOptions, wrapperOptions)

t.teardown(() => frontend.close())

for (const path of paths) {
await processRequest(t, frontendURL, path, expected(path))
}

t.end()
})
}

handleProxy(
'no prefix to `/`',
{
backendPath: '/',
proxyOptions: { websocket: true }
},
path => path,
'/',
'/pub',
'/pub/'
)

handleProxy(
'`/pub/` to `/`',
{
backendPath: '/',
proxyOptions: { websocket: true, prefix: '/pub/' }
},
path => path.startsWith('/pub/') ? path.replace('/pub/', '/') : 'error',
'/',
'/pub/',
'/pub/test'
)

handleProxy(
'`/pub/` to `/public/`',
{
backendPath: '/public/',
proxyOptions: { websocket: true, prefix: '/pub/' }
},
path => path.startsWith('/pub/') ? path.replace('/pub/', '/public/') : 'error',
'/',
'/pub/',
'/pub/test'
)

handleProxy(
'wrapped `/pub/` to `/public/`',
{
backendPath: '/public/',
proxyOptions: { websocket: true },
wrapperOptions: { prefix: '/pub/' }
},
path => path.startsWith('/pub/') ? path.replace('/pub/', '/public/') : 'error',
'/',
'/pub/',
'/pub/test'
)

0 comments on commit 02d9b43

Please sign in to comment.