@@ -153,7 +153,7 @@ export function buildRemoteControlUrl(
153153 relayOrigin = REMOTE_CONTROL_RELAY_ORIGIN ,
154154) : string {
155155 const url = new URL ( relayOrigin ) ;
156- const relayPath = url . pathname . replace ( / \/ + $ / , '' ) ;
156+ const relayPath = stripTrailingSlashes ( url . pathname ) ;
157157 const devicePath = `${ relayPath } /devices/${ encodeURIComponent ( deviceId ) } ` ;
158158 url . pathname =
159159 sessionId === undefined
@@ -231,23 +231,34 @@ export function filterForwardRequestHeaders(
231231 return result ;
232232}
233233
234+ function stripTrailingSlashes ( value : string ) : string {
235+ let end = value . length ;
236+ while ( end > 0 && value . codePointAt ( end - 1 ) === 47 ) end -= 1 ;
237+ return end === value . length ? value : value . slice ( 0 , end ) ;
238+ }
239+
240+ function findHeadTagEnd ( text : string ) : number {
241+ const start = / < h e a d (? = [ \s > ] ) / i. exec ( text ) ;
242+ if ( start === null ) return - 1 ;
243+ const close = text . indexOf ( '>' , start . index + 5 ) ;
244+ return close === - 1 ? - 1 : close + 1 ;
245+ }
246+
234247export function rewriteRemoteControlResponse (
235248 contentType : string ,
236249 body : Buffer ,
237250 publicPrefix : string ,
238251) : Buffer {
239- const normalizedPrefix = publicPrefix . replace ( / \/ + $ / , '' ) ;
252+ const normalizedPrefix = stripTrailingSlashes ( publicPrefix ) ;
240253 if ( contentType . toLowerCase ( ) . includes ( 'text/html' ) ) {
241254 const prefixLiteral = scriptStringLiteral ( normalizedPrefix ) ;
242255 const injected = `<script>(function(){var p=${ prefixLiteral } ;try{sessionStorage.setItem('pythinker-desktop-server-origin',location.origin+p)}catch(e){}var w=function(f){return function(s,t,u){if(typeof u==='string'&&u.charAt(0)==='/'&&u.indexOf(p)!==0)u=p+u;return f.apply(this,[s,t,u])}};history.pushState=w(history.pushState);history.replaceState=w(history.replaceState)})();</script>` ;
243256 let text = body . toString ( 'utf8' ) ;
244- const headMatch = / < h e a d (?: \s [ ^ > ] * ) ? > / i . exec ( text ) ;
257+ const headEnd = findHeadTagEnd ( text ) ;
245258 text =
246- headMatch === null
259+ headEnd === - 1
247260 ? injected + text
248- : text . slice ( 0 , headMatch . index + headMatch [ 0 ] . length ) +
249- injected +
250- text . slice ( headMatch . index + headMatch [ 0 ] . length ) ;
261+ : text . slice ( 0 , headEnd ) + injected + text . slice ( headEnd ) ;
251262 text = text . replaceAll ( / \b s r c = " \/ / g, `src="${ normalizedPrefix } /` ) ;
252263 text = text . replaceAll ( / \b h r e f = " \/ / g, `href="${ normalizedPrefix } /` ) ;
253264 return Buffer . from ( text ) ;
@@ -318,7 +329,7 @@ export async function startRemoteControl(
318329 const deviceName = hostname ( ) ;
319330 const url = buildRemoteControlUrl ( deviceId , undefined , relayOrigin ) ;
320331 const lock = await acquireRemoteControlLock ( options . homeDir , {
321- localOrigin : options . localOrigin . replace ( / \/ + $ / , '' ) ,
332+ localOrigin : stripTrailingSlashes ( options . localOrigin ) ,
322333 deviceId,
323334 url,
324335 } ) ;
@@ -387,7 +398,7 @@ class RemoteControlClient {
387398 readonly localServerToken : ( ) => string ;
388399 } ,
389400 ) {
390- this . localOrigin = options . localOrigin . replace ( / \/ + $ / , '' ) ;
401+ this . localOrigin = stripTrailingSlashes ( options . localOrigin ) ;
391402 this . localServerToken = options . localServerToken ;
392403 this . clientVersion = options . clientVersion ;
393404 this . relayOrigin = options . relayOrigin ;
@@ -758,7 +769,7 @@ class RemoteControlClient {
758769 }
759770
760771 private publicPrefix ( ) : string {
761- const relayPath = new URL ( this . relayOrigin ) . pathname . replace ( / \/ + $ / , '' ) ;
772+ const relayPath = stripTrailingSlashes ( new URL ( this . relayOrigin ) . pathname ) ;
762773 return `${ relayPath } /devices/${ encodeURIComponent ( this . deviceId ) } ` ;
763774 }
764775
@@ -1050,7 +1061,7 @@ function isValidCloseCode(code: number): boolean {
10501061function relayWebSocketUrl ( origin : string , path : string ) : string {
10511062 const url = new URL ( origin ) ;
10521063 url . protocol = url . protocol === 'https:' ? 'wss:' : 'ws:' ;
1053- const relayPath = url . pathname . replace ( / \/ + $ / , '' ) ;
1064+ const relayPath = stripTrailingSlashes ( url . pathname ) ;
10541065 const [ pathname , query ] = path . split ( '?' , 2 ) ;
10551066 url . pathname = `${ relayPath } ${ pathname } ` ;
10561067 url . search = query === undefined ? '' : query ;
0 commit comments