@@ -155,6 +155,13 @@ function countOccurrences(value: string, needle: string): number {
155155 return value . split ( needle ) . length - 1 ;
156156}
157157
158+ function expectSlashlessRootRedirectBeforeBase ( html : string , baseHref : string ) : void {
159+ const redirectIndex = html . indexOf ( "location.replace(location.origin+pathname" ) ;
160+ const baseIndex = html . indexOf ( `<base href="${ baseHref } "` ) ;
161+ expect ( redirectIndex ) . toBeGreaterThanOrEqual ( 0 ) ;
162+ expect ( baseIndex ) . toBeGreaterThan ( redirectIndex ) ;
163+ }
164+
158165async function createStaticTestServer (
159166 options : {
160167 files ?: Record < string , string > ;
@@ -231,7 +238,7 @@ describe("createOrpcServer", () => {
231238 expect ( uiRes . status ) . toBe ( 200 ) ;
232239 const uiText = await uiRes . text ( ) ;
233240 expect ( uiText ) . toContain ( "mux" ) ;
234- expect ( uiText ) . toContain ( '<base href="/"' ) ;
241+ expect ( uiText ) . toContain ( '<base href="./../.. /"' ) ;
235242
236243 const apiRes = await fetch ( `${ server . baseUrl } /api/not-a-real-route` ) ;
237244 expect ( apiRes . status ) . toBe ( 404 ) ;
@@ -248,7 +255,23 @@ describe("createOrpcServer", () => {
248255 const rootRes = await fetch ( `${ server . baseUrl } /` ) ;
249256 expect ( rootRes . status ) . toBe ( 200 ) ;
250257 const rootHtml = await rootRes . text ( ) ;
251- expect ( countOccurrences ( rootHtml , '<base href="/" />' ) ) . toBe ( 1 ) ;
258+ expect ( countOccurrences ( rootHtml , '<base href="./" />' ) ) . toBe ( 1 ) ;
259+ expectSlashlessRootRedirectBeforeBase ( rootHtml , "./" ) ;
260+
261+ const doubleSlashRes = await fetch ( `${ server . baseUrl } //attacker.example` ) ;
262+ expect ( doubleSlashRes . status ) . toBe ( 200 ) ;
263+ const doubleSlashHtml = await doubleSlashRes . text ( ) ;
264+ expect ( doubleSlashHtml ) . not . toContain ( "location.replace(location.origin+pathname" ) ;
265+
266+ const deepRouteRes = await fetch ( `${ server . baseUrl } /some/spa/route` ) ;
267+ expect ( deepRouteRes . status ) . toBe ( 200 ) ;
268+ const deepRouteHtml = await deepRouteRes . text ( ) ;
269+ expect ( deepRouteHtml ) . toContain ( '<base href="./../../" />' ) ;
270+ expect ( deepRouteHtml ) . not . toContain ( "location.replace(location.pathname" ) ;
271+
272+ const directoryRouteRes = await fetch ( `${ server . baseUrl } /some/spa/route/` ) ;
273+ expect ( directoryRouteRes . status ) . toBe ( 200 ) ;
274+ expect ( await directoryRouteRes . text ( ) ) . toContain ( '<base href="./../../../" />' ) ;
252275
253276 const forwardedPrefixRes = await fetch ( `${ server . baseUrl } /some/spa/route` , {
254277 headers : { "X-Forwarded-Prefix" : APP_PROXY_BASE_PATH } ,
@@ -297,6 +320,23 @@ describe("createOrpcServer", () => {
297320 expect ( prefixedAssetRes . status ) . toBe ( 200 ) ;
298321 expect ( await prefixedAssetRes . text ( ) ) . toBe ( await rootAssetRes . text ( ) ) ;
299322
323+ const prefixedRootRes = await fetch ( `${ server . baseUrl } ${ APP_PROXY_BASE_PATH } ` ) ;
324+ expect ( prefixedRootRes . status ) . toBe ( 200 ) ;
325+ const prefixedRootHtml = await prefixedRootRes . text ( ) ;
326+ expect ( prefixedRootHtml ) . toContain ( `<base href="${ APP_PROXY_BASE_PATH } /" />` ) ;
327+ expectSlashlessRootRedirectBeforeBase ( prefixedRootHtml , `${ APP_PROXY_BASE_PATH } /` ) ;
328+
329+ const coderUrlRes = await fetch (
330+ `${ server . baseUrl } /@admin/mux-workspace-095801.main/apps/mux/?token=redacted`
331+ ) ;
332+ expect ( coderUrlRes . status ) . toBe ( 200 ) ;
333+ const coderUrlHtml = await coderUrlRes . text ( ) ;
334+ expect ( coderUrlHtml ) . toContain ( '<base href="/@admin/mux-workspace-095801.main/apps/mux/" />' ) ;
335+ expectSlashlessRootRedirectBeforeBase (
336+ coderUrlHtml ,
337+ "/@admin/mux-workspace-095801.main/apps/mux/"
338+ ) ;
339+
300340 const prefixedSpaRes = await fetch ( `${ server . baseUrl } ${ APP_PROXY_BASE_PATH } /settings` ) ;
301341 expect ( prefixedSpaRes . status ) . toBe ( 200 ) ;
302342 expect ( await prefixedSpaRes . text ( ) ) . toContain ( `<base href="${ APP_PROXY_BASE_PATH } /" />` ) ;
@@ -323,7 +363,7 @@ describe("createOrpcServer", () => {
323363
324364 const falsePositiveRes = await fetch ( `${ server . baseUrl } /projects/apps/other` ) ;
325365 expect ( falsePositiveRes . status ) . toBe ( 200 ) ;
326- expect ( await falsePositiveRes . text ( ) ) . toContain ( '<base href="/" />' ) ;
366+ expect ( await falsePositiveRes . text ( ) ) . toContain ( '<base href="./../.. /" />' ) ;
327367 } finally {
328368 await close ( ) ;
329369 }
@@ -477,8 +517,10 @@ describe("createOrpcServer", () => {
477517 expect ( uiRes . status ) . toBe ( 200 ) ;
478518 const uiText = await uiRes . text ( ) ;
479519
520+ expect ( rootHtml ) . toContain ( '<base href="./"' ) ;
521+ expect ( uiText ) . toContain ( '<base href="./../../"' ) ;
522+
480523 for ( const html of [ rootHtml , uiText ] ) {
481- expect ( html ) . toContain ( '<base href="/"' ) ;
482524 expect ( html ) . toContain ( "window.__MUX_PROXY_URI_TEMPLATE__ =" ) ;
483525 expect ( html ) . toContain (
484526 'window.__MUX_PROXY_URI_TEMPLATE__ = "https://proxy-{{port}}.example.test/path\\u003c/script>";'
0 commit comments