Skip to content

Commit 0152fb9

Browse files
committed
Fix redirects through subpath proxy
1 parent 4ed7b3a commit 0152fb9

File tree

3 files changed

+51
-38
lines changed

3 files changed

+51
-38
lines changed

src/browser/pages/home.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
1818
crossorigin="use-credentials"
1919
/>
20-
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.pnggg" />
20+
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
2121
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
2222
<meta id="coder-options" data-settings="{{OPTIONS}}" />
2323
</head>

src/node/app/proxy.ts

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import * as querystring from "querystring"
66
import { HttpCode, HttpError } from "../../common/http"
77
import { HttpProvider, HttpProviderOptions, HttpProxyProvider, HttpResponse, Route } from "../http"
88

9+
interface Request extends http.IncomingMessage {
10+
base?: string
11+
}
12+
913
/**
1014
* Proxy HTTP provider.
1115
*/
@@ -24,6 +28,12 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
2428
super(options)
2529
this.proxyDomains = proxyDomains.map((d) => d.replace(/^\*\./, "")).filter((d, i, arr) => arr.indexOf(d) === i)
2630
this.proxy.on("error", (error) => logger.warn(error.message))
31+
// Intercept the response to rewrite absolute redirects against the base path.
32+
this.proxy.on("proxyRes", (response, request: Request) => {
33+
if (response.headers.location && response.headers.location.startsWith("/") && request.base) {
34+
response.headers.location = request.base + response.headers.location
35+
}
36+
})
2737
}
2838

2939
public async handleRequest(
@@ -41,14 +51,15 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
4151
}
4252

4353
// Ensure there is a trailing slash so relative paths work correctly.
44-
const base = route.base.replace(/^\//, "")
45-
if (isRoot && !route.originalPath.endsWith("/")) {
54+
const port = route.base.replace(/^\//, "")
55+
const base = `${this.options.base}/${port}`
56+
if (isRoot && !route.fullPath.endsWith("/")) {
4657
return {
47-
redirect: `/proxy/${base}/`,
58+
redirect: `${base}/`,
4859
}
4960
}
5061

51-
const payload = this.doProxy(route.requestPath, route.query, request, response, base)
62+
const payload = this.doProxy(route, request, response, port, base)
5263
if (payload) {
5364
return payload
5465
}
@@ -63,7 +74,9 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
6374
head: Buffer,
6475
): Promise<void> {
6576
this.ensureAuthenticated(request)
66-
this.doProxy(route.requestPath, route.query, request, socket, head, route.base.replace(/^\//, ""))
77+
const port = route.base.replace(/^\//, "")
78+
const base = `${this.options.base}/${port}`
79+
this.doProxy(route, request, { socket, head }, port, base)
6780
}
6881

6982
public getCookieDomain(host: string): string {
@@ -84,7 +97,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
8497
response: http.ServerResponse,
8598
): HttpResponse | undefined {
8699
const port = this.getPort(request)
87-
return port ? this.doProxy(route.fullPath, route.query, request, response, port) : undefined
100+
return port ? this.doProxy(route, request, response, port) : undefined
88101
}
89102

90103
public maybeProxyWebSocket(
@@ -94,7 +107,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
94107
head: Buffer,
95108
): HttpResponse | undefined {
96109
const port = this.getPort(request)
97-
return port ? this.doProxy(route.fullPath, route.query, request, socket, head, port) : undefined
110+
return port ? this.doProxy(route, request, { socket, head }, port) : undefined
98111
}
99112

100113
private getPort(request: http.IncomingMessage): string | undefined {
@@ -121,57 +134,55 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
121134
}
122135

123136
private doProxy(
124-
path: string,
125-
query: querystring.ParsedUrlQuery,
137+
route: Route,
126138
request: http.IncomingMessage,
127139
response: http.ServerResponse,
128140
portStr: string,
141+
base?: string,
129142
): HttpResponse
130143
private doProxy(
131-
path: string,
132-
query: querystring.ParsedUrlQuery,
144+
route: Route,
133145
request: http.IncomingMessage,
134-
socket: net.Socket,
135-
head: Buffer,
146+
response: { socket: net.Socket; head: Buffer },
136147
portStr: string,
148+
base?: string,
137149
): HttpResponse
138150
private doProxy(
139-
path: string,
140-
query: querystring.ParsedUrlQuery,
151+
route: Route,
141152
request: http.IncomingMessage,
142-
responseOrSocket: http.ServerResponse | net.Socket,
143-
headOrPortStr: Buffer | string,
144-
portStr?: string,
153+
response: http.ServerResponse | { socket: net.Socket; head: Buffer },
154+
portStr: string,
155+
base?: string,
145156
): HttpResponse {
146-
const _portStr = typeof headOrPortStr === "string" ? headOrPortStr : portStr
147-
if (!_portStr) {
148-
return {
149-
code: HttpCode.BadRequest,
150-
content: "Port must be provided",
151-
}
152-
}
153-
154-
const port = parseInt(_portStr, 10)
157+
const port = parseInt(portStr, 10)
155158
if (isNaN(port)) {
156159
return {
157160
code: HttpCode.BadRequest,
158-
content: `"${_portStr}" is not a valid number`,
161+
content: `"${portStr}" is not a valid number`,
159162
}
160163
}
161164

165+
// REVIEW: Absolute redirects need to be based on the subpath but I'm not
166+
// sure how best to get this information to the `proxyRes` event handler.
167+
// For now I'm sticking it on the request object which is passed through to
168+
// the event.
169+
;(request as Request).base = base
170+
171+
const hxxp = response instanceof http.ServerResponse
172+
const path = base ? route.fullPath.replace(base, "") : route.fullPath
162173
const options: proxy.ServerOptions = {
163-
autoRewrite: true,
164174
changeOrigin: true,
165175
ignorePath: true,
166-
target: `http://127.0.0.1:${port}${path}${
167-
Object.keys(query).length > 0 ? `?${querystring.stringify(query)}` : ""
176+
target: `${hxxp ? "http" : "ws"}://127.0.0.1:${port}${path}${
177+
Object.keys(route.query).length > 0 ? `?${querystring.stringify(route.query)}` : ""
168178
}`,
179+
ws: !hxxp,
169180
}
170181

171-
if (responseOrSocket instanceof net.Socket) {
172-
this.proxy.ws(request, responseOrSocket, headOrPortStr, options)
182+
if (response instanceof http.ServerResponse) {
183+
this.proxy.web(request, response, options)
173184
} else {
174-
this.proxy.web(request, responseOrSocket, options)
185+
this.proxy.ws(request, response.socket, response.head, options)
175186
}
176187

177188
return { handled: true }

src/node/http.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ export class HttpServer {
596596
`Path=${normalize(payload.cookie.path || "/", true)}`,
597597
domain ? `Domain=${(this.proxy && this.proxy.getCookieDomain(domain)) || domain}` : undefined,
598598
// "HttpOnly",
599-
"SameSite=strict",
599+
"SameSite=lax",
600600
]
601601
.filter((l) => !!l)
602602
.join(";"),
@@ -633,9 +633,11 @@ export class HttpServer {
633633
if (error.code === "ENOENT" || error.code === "EISDIR") {
634634
e = new HttpError("Not found", HttpCode.NotFound)
635635
}
636-
logger.debug("Request error", field("url", request.url))
637-
logger.debug(error.stack)
638636
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
637+
logger.debug("Request error", field("url", request.url), field("code", code))
638+
if (code >= HttpCode.ServerError) {
639+
logger.error(error.stack)
640+
}
639641
const payload = await route.provider.getErrorRoot(route, code, code, e.message)
640642
write({
641643
code,

0 commit comments

Comments
 (0)