Skip to content

Commit 02d9b43

Browse files
authored
Merge pull request from GHSA-c4qr-gmr9-v23w
* added failing test * fix first test * Fixed 2/4 * three out of four * four out of four
1 parent 23d6cac commit 02d9b43

File tree

4 files changed

+184
-19
lines changed

4 files changed

+184
-19
lines changed

index.js

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,8 @@ function proxyWebSockets (source, target) {
5353
target.on('unexpected-response', () => close(1011, 'unexpected response'))
5454
}
5555

56-
function createWebSocketUrl (options, request) {
57-
const source = new URL(request.url, 'http://127.0.0.1')
58-
59-
const target = new URL(
60-
options.rewritePrefix || options.prefix || source.pathname,
61-
options.upstream
62-
)
63-
64-
target.search = source.search
65-
66-
return target
67-
}
68-
69-
function setupWebSocketProxy (fastify, options) {
56+
function setupWebSocketProxy (fastify, options, rewritePrefix) {
7057
const server = new WebSocket.Server({
71-
path: options.prefix,
7258
server: fastify.server,
7359
...options.wsServerOptions
7460
})
@@ -93,13 +79,46 @@ function setupWebSocketProxy (fastify, options) {
9379
})
9480

9581
server.on('connection', (source, request) => {
96-
const url = createWebSocketUrl(options, request)
82+
if (fastify.prefix && !request.url.startsWith(fastify.prefix)) {
83+
fastify.log.debug({ url: request.url }, 'not matching prefix')
84+
source.close()
85+
return
86+
}
87+
88+
const url = createWebSocketUrl(request)
9789

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

10092
fastify.log.debug({ url: url.href }, 'proxy websocket')
10193
proxyWebSockets(source, target)
10294
})
95+
96+
function createWebSocketUrl (request) {
97+
const source = new URL(request.url, 'http://127.0.0.1')
98+
99+
const target = new URL(
100+
source.pathname.replace(fastify.prefix, rewritePrefix),
101+
options.upstream
102+
)
103+
104+
target.search = source.search
105+
106+
return target
107+
}
108+
}
109+
110+
function generateRewritePrefix (prefix, opts) {
111+
if (!prefix) {
112+
return ''
113+
}
114+
115+
let rewritePrefix = opts.rewritePrefix || new URL(opts.upstream).pathname
116+
117+
if (!prefix.endsWith('/') && rewritePrefix.endsWith('/')) {
118+
rewritePrefix = rewritePrefix.slice(0, -1)
119+
}
120+
121+
return rewritePrefix
103122
}
104123

105124
async function httpProxy (fastify, opts) {
@@ -108,7 +127,7 @@ async function httpProxy (fastify, opts) {
108127
}
109128

110129
const preHandler = opts.preHandler || opts.beforeHandler
111-
const rewritePrefix = opts.rewritePrefix || ''
130+
const rewritePrefix = generateRewritePrefix(fastify.prefix, opts)
112131

113132
const fromOpts = Object.assign({}, opts)
114133
fromOpts.base = opts.upstream
@@ -164,7 +183,7 @@ async function httpProxy (fastify, opts) {
164183
}
165184

166185
if (opts.websocket) {
167-
setupWebSocketProxy(fastify, opts)
186+
setupWebSocketProxy(fastify, opts, rewritePrefix)
168187
}
169188
}
170189

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"express-http-proxy": "^1.6.2",
3434
"fast-proxy": "^1.7.0",
3535
"fastify": "^3.0.0",
36+
"fastify-websocket": "^3.0.0",
3637
"got": "^11.5.1",
3738
"http-errors": "^1.8.0",
3839
"http-proxy": "^1.17.0",

test/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ async function run () {
179179
async preHandler (request, reply) {
180180
t.deepEqual(reply.context.config, {
181181
foo: 'bar',
182-
url: '/*',
182+
url: '/',
183183
method: [
184184
'DELETE',
185185
'GET',

test/ws-prefix-rewrite.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
'use strict'
2+
3+
const t = require('tap')
4+
const { once } = require('events')
5+
6+
const Fastify = require('fastify')
7+
const fastifyWebSocket = require('fastify-websocket')
8+
const proxy = require('..')
9+
const WebSocket = require('ws')
10+
const got = require('got')
11+
12+
const level = 'warn'
13+
14+
async function proxyServer (t, backendURL, backendPath, proxyOptions, wrapperOptions) {
15+
const frontend = Fastify({ logger: { level } })
16+
const registerProxy = async fastify => {
17+
fastify.register(proxy, {
18+
upstream: backendURL + backendPath,
19+
...proxyOptions
20+
})
21+
}
22+
23+
t.comment('starting proxy to ' + backendURL + backendPath)
24+
25+
if (wrapperOptions) {
26+
await frontend.register(registerProxy, wrapperOptions)
27+
} else {
28+
await registerProxy(frontend)
29+
}
30+
31+
return [frontend, await frontend.listen(0)]
32+
}
33+
34+
async function processRequest (t, frontendURL, path, expected) {
35+
const url = new URL(path, frontendURL)
36+
t.comment('ws connecting to ' + url.toString())
37+
const ws = new WebSocket(url)
38+
let wsResult, gotResult
39+
40+
try {
41+
await once(ws, 'open')
42+
t.pass('socket connected')
43+
44+
const [buf] = await Promise.race([once(ws, 'message'), once(ws, 'close')])
45+
if (buf instanceof Buffer) {
46+
wsResult = buf.toString()
47+
} else {
48+
t.comment('websocket closed')
49+
wsResult = 'error'
50+
}
51+
} catch (e) {
52+
wsResult = 'error'
53+
ws.terminate()
54+
}
55+
56+
try {
57+
const result = await got(url)
58+
gotResult = result.body
59+
} catch (e) {
60+
gotResult = 'error'
61+
}
62+
63+
t.is(wsResult, expected)
64+
t.is(gotResult, expected)
65+
}
66+
67+
async function handleProxy (info, { backendPath, proxyOptions, wrapperOptions }, expected, ...paths) {
68+
t.test(info, async function (t) {
69+
const backend = Fastify({ logger: { level } })
70+
await backend.register(fastifyWebSocket)
71+
72+
backend.get('/*', {
73+
handler: (req, reply) => {
74+
reply.send(req.url)
75+
},
76+
wsHandler: (conn, req) => {
77+
conn.write(req.url)
78+
conn.end()
79+
}
80+
})
81+
82+
t.teardown(() => backend.close())
83+
84+
const backendURL = await backend.listen(0)
85+
86+
const [frontend, frontendURL] = await proxyServer(t, backendURL, backendPath, proxyOptions, wrapperOptions)
87+
88+
t.teardown(() => frontend.close())
89+
90+
for (const path of paths) {
91+
await processRequest(t, frontendURL, path, expected(path))
92+
}
93+
94+
t.end()
95+
})
96+
}
97+
98+
handleProxy(
99+
'no prefix to `/`',
100+
{
101+
backendPath: '/',
102+
proxyOptions: { websocket: true }
103+
},
104+
path => path,
105+
'/',
106+
'/pub',
107+
'/pub/'
108+
)
109+
110+
handleProxy(
111+
'`/pub/` to `/`',
112+
{
113+
backendPath: '/',
114+
proxyOptions: { websocket: true, prefix: '/pub/' }
115+
},
116+
path => path.startsWith('/pub/') ? path.replace('/pub/', '/') : 'error',
117+
'/',
118+
'/pub/',
119+
'/pub/test'
120+
)
121+
122+
handleProxy(
123+
'`/pub/` to `/public/`',
124+
{
125+
backendPath: '/public/',
126+
proxyOptions: { websocket: true, prefix: '/pub/' }
127+
},
128+
path => path.startsWith('/pub/') ? path.replace('/pub/', '/public/') : 'error',
129+
'/',
130+
'/pub/',
131+
'/pub/test'
132+
)
133+
134+
handleProxy(
135+
'wrapped `/pub/` to `/public/`',
136+
{
137+
backendPath: '/public/',
138+
proxyOptions: { websocket: true },
139+
wrapperOptions: { prefix: '/pub/' }
140+
},
141+
path => path.startsWith('/pub/') ? path.replace('/pub/', '/public/') : 'error',
142+
'/',
143+
'/pub/',
144+
'/pub/test'
145+
)

0 commit comments

Comments
 (0)