@@ -70,6 +70,7 @@ import { ExtensionScanner, ExtensionScannerInput, IExtensionReference } from 'vs
7070import { IGetEnvironmentDataArguments , IRemoteAgentEnvironmentDTO , IScanExtensionsArguments , IScanSingleExtensionArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel' ;
7171import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel' ;
7272import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService' ;
73+ import { parse } from 'querystring' ;
7374
7475export type IRawURITransformerFactory = ( remoteAuthority : string ) => IRawURITransformer ;
7576export const IRawURITransformerFactory = createDecorator < IRawURITransformerFactory > ( 'rawURITransformerFactory' ) ;
@@ -583,10 +584,36 @@ export async function main(options: IServerOptions): Promise<void> {
583584 return ;
584585 }
585586
586- //#region static
587587 if ( pathname === '/' ) {
588588 return serveFile ( logService , req , res , await authenticated ( req ) ? devMode ? options . mainDev || WEB_MAIN_DEV : options . main || WEB_MAIN : LOGIN ) ;
589589 }
590+ if ( pathname === '/login' ) {
591+ const password = ( await collectRequestData ( req ) ) . password ;
592+ const passwordMethod = getPasswordMethod ( parsedArgs . hashedPassword ) ;
593+ const { isPasswordValid, hashedPassword } = await handlePasswordValidation ( {
594+ passwordMethod,
595+ hashedPasswordFromArgs : parsedArgs . hashedPassword ,
596+ passwordFromRequestBody : password ,
597+ passwordFromArgs : parsedArgs . password ,
598+ } ) ;
599+
600+ if ( isPasswordValid ) {
601+ // The hash does not add any actual security but we do it for
602+ // obfuscation purposes (and as a side effect it handles escaping).
603+ res . writeHead ( 302 , {
604+ 'Location' : '/' ,
605+ 'Set-Cookie' : `key=${ hashedPassword } ` ,
606+ 'Content-Type' : 'text/plain'
607+ } ) ;
608+ return res . end ( '' ) ;
609+ } else {
610+ res . writeHead ( 302 , {
611+ 'Location' : '/' ,
612+ 'Content-Type' : 'text/plain'
613+ } ) ;
614+ return res . end ( '' ) ;
615+ }
616+ }
590617 if ( ! await ensureAuthenticated ( req , res ) ) {
591618 return ;
592619 }
@@ -600,6 +627,14 @@ export async function main(options: IServerOptions): Promise<void> {
600627 'display' : 'standalone'
601628 } ) ) ;
602629 }
630+ if ( pathname === '/vscode-remote-resource' ) {
631+ const filePath = parsedUrl . query [ 'path' ] ;
632+ const fsPath = typeof filePath === 'string' && URI . from ( { scheme : 'file' , path : filePath } ) . fsPath ;
633+ if ( ! fsPath ) {
634+ return serveError ( req , res , 400 , 'Bad Request.' ) ;
635+ }
636+ return serveFile ( logService , req , res , fsPath ) ;
637+ }
603638 if ( pathname ) {
604639 let relativeFilePath ;
605640 if ( / ^ \/ s t a t i c \/ / . test ( pathname ) ) {
@@ -609,18 +644,8 @@ export async function main(options: IServerOptions): Promise<void> {
609644 }
610645 return serveFile ( logService , req , res , path . join ( APP_ROOT , relativeFilePath ) ) ;
611646 }
612- //#region static end
613647
614- //#region headless
615- if ( pathname === '/vscode-remote-resource' ) {
616- const filePath = parsedUrl . query [ 'path' ] ;
617- const fsPath = typeof filePath === 'string' && URI . from ( { scheme : 'file' , path : filePath } ) . fsPath ;
618- if ( ! fsPath ) {
619- return serveError ( req , res , 400 , 'Bad Request.' ) ;
620- }
621- return serveFile ( logService , req , res , fsPath ) ;
622- }
623- //#region headless end
648+
624649
625650 // TODO uri callbacks ?
626651 logService . error ( `${ req . method } ${ req . url } not found` ) ;
@@ -922,6 +947,25 @@ export async function main(options: IServerOptions): Promise<void> {
922947 } ) ;
923948}
924949
950+ function collectRequestData ( request : http . IncomingMessage ) : Promise < Record < string , string > > {
951+ return new Promise ( resolve => {
952+ const FORM_URLENCODED = 'application/x-www-form-urlencoded' ;
953+ if ( request . headers [ 'content-type' ] === FORM_URLENCODED ) {
954+ let body = '' ;
955+ request . on ( 'data' , chunk => {
956+ body += chunk . toString ( ) ;
957+ } ) ;
958+ request . on ( 'end' , ( ) => {
959+ const item = parse ( body ) as Record < string , string > ;
960+ resolve ( item ) ;
961+ } ) ;
962+ }
963+ else {
964+ resolve ( { } ) ;
965+ }
966+ } ) ;
967+ }
968+
925969/** Ensures that the input is sanitized by checking
926970 * - it's a string
927971 * - greater than 0 characters
@@ -949,9 +993,10 @@ export const authenticated = async (req: http.IncomingMessage): Promise<boolean>
949993 return true ;
950994 }
951995 const passwordMethod = getPasswordMethod ( parsedArgs . hashedPassword ) ;
996+ const cookies = parseCookies ( req ) ;
952997 const isCookieValidArgs : IsCookieValidArgs = {
953998 passwordMethod,
954- cookieKey : sanitizeString ( parseCookies ( req ) . key ) ,
999+ cookieKey : sanitizeString ( cookies . key ) ,
9551000 passwordFromArgs : parsedArgs . password || '' ,
9561001 hashedPasswordFromArgs : parsedArgs . hashedPassword ,
9571002 } ;
@@ -967,7 +1012,10 @@ function parseCookies(request: http.IncomingMessage): Record<string, string> {
9671012 rc && rc . split ( ';' ) . forEach ( cookie => {
9681013 let parts = cookie . split ( '=' ) ;
9691014 if ( parts . length > 0 ) {
970- cookies [ parts . shift ( ) ! . trim ( ) ] = decodeURI ( parts . join ( '=' ) ) ;
1015+ const name = parts . shift ( ) ! . trim ( ) ;
1016+ let value = decodeURI ( parts . join ( '=' ) ) ;
1017+ value = value . substring ( 1 , value . length - 1 ) ;
1018+ cookies [ name ] = value ;
9711019 }
9721020 } ) ;
9731021
0 commit comments