1+ import * as crypto from "crypto" ;
12import * as fs from "fs" ;
23import * as http from "http" ;
34import * as https from "https" ;
@@ -55,7 +56,7 @@ import { Connection, ManagementConnection, ExtensionHostConnection } from "vs/se
5556import { ExtensionEnvironmentChannel , FileProviderChannel , } from "vs/server/src/channel" ;
5657import { TelemetryClient } from "vs/server/src/insights" ;
5758import { Protocol } from "vs/server/src/protocol" ;
58- import { getMediaMime , getUriTransformer } from "vs/server/src/util" ;
59+ import { AuthType , getMediaMime , getUriTransformer } from "vs/server/src/util" ;
5960
6061export enum HttpCode {
6162 Ok = 200 ,
@@ -95,10 +96,6 @@ export class HttpError extends Error {
9596 }
9697}
9798
98- export enum AuthType {
99- Password = "password" ,
100- }
101-
10299export interface ServerOptions {
103100 readonly auth ?: AuthType ;
104101 readonly basePath ?: string ;
@@ -140,6 +137,7 @@ export abstract class Server {
140137 if ( ! this . listenPromise ) {
141138 this . listenPromise = new Promise ( ( resolve , reject ) => {
142139 this . server . on ( "error" , reject ) ;
140+ this . server . on ( "upgrade" , this . onUpgrade ) ;
143141 const onListen = ( ) => resolve ( this . address ( ) ) ;
144142 if ( this . options . socket ) {
145143 this . server . listen ( this . options . socket , onListen ) ;
@@ -167,14 +165,20 @@ export abstract class Server {
167165 return `${ this . protocol } ://${ endpoint } ` ;
168166 }
169167
168+ protected abstract handleWebSocket (
169+ socket : net . Socket ,
170+ parsedUrl : url . UrlWithParsedQuery
171+ ) : Promise < void > ;
172+
170173 protected abstract handleRequest (
171174 base : string ,
172175 requestPath : string ,
173176 parsedUrl : url . UrlWithParsedQuery ,
174177 request : http . IncomingMessage ,
175178 ) : Promise < Response > ;
176179
177- protected async getResource ( filePath : string ) : Promise < Response > {
180+ protected async getResource ( ...parts : string [ ] ) : Promise < Response > {
181+ const filePath = path . join ( ...parts ) ;
178182 return { content : await util . promisify ( fs . readFile ) ( filePath ) , filePath } ;
179183 }
180184
@@ -205,7 +209,7 @@ export abstract class Server {
205209 return { redirect : request . url } ;
206210 }
207211
208- const parsedUrl = request . url ? url . parse ( request . url , true ) : { } as url . UrlWithParsedQuery ;
212+ const parsedUrl = request . url ? url . parse ( request . url , true ) : { query : { } } ;
209213 const fullPath = decodeURIComponent ( parsedUrl . pathname || "/" ) ;
210214 const match = fullPath . match ( / ^ ( \/ ? [ ^ / ] * ) ( .* ) $ / ) ;
211215 let [ , base , requestPath ] = match
@@ -218,15 +222,13 @@ export abstract class Server {
218222 base = "/" ;
219223 }
220224 base = path . normalize ( base ) ;
221- if ( requestPath !== "" ) { // "" will become "." with normalize.
222- requestPath = path . normalize ( requestPath ) ;
223- }
225+ requestPath = path . normalize ( requestPath || "/index.html" ) ;
224226
225227 switch ( base ) {
226228 case "/" :
227229 this . ensureGet ( request ) ;
228230 if ( requestPath === "/favicon.ico" ) {
229- return this . getResource ( path . join ( this . rootPath , "/out/vs/server/src/favicon" , requestPath ) ) ;
231+ return this . getResource ( this . rootPath , "/out/vs/server/src/favicon" , requestPath ) ;
230232 } else if ( ! this . authenticate ( request ) ) {
231233 return { redirect : "/login" } ;
232234 }
@@ -238,18 +240,53 @@ export abstract class Server {
238240 return this . tryLogin ( request ) ;
239241 }
240242 this . ensureGet ( request ) ;
241- return this . getResource ( path . join ( this . rootPath , "/out/vs/server/src/login" , requestPath ) ) ;
243+ return this . getResource ( this . rootPath , "/out/vs/server/src/login" , requestPath ) ;
242244 default :
243245 this . ensureGet ( request ) ;
244246 if ( ! this . authenticate ( request ) ) {
245- throw new HttpError ( ` Unauthorized` , HttpCode . Unauthorized ) ;
247+ throw new HttpError ( " Unauthorized" , HttpCode . Unauthorized ) ;
246248 }
247249 break ;
248250 }
249251
250252 return this . handleRequest ( base , requestPath , parsedUrl , request ) ;
251253 }
252254
255+ private onUpgrade = async ( request : http . IncomingMessage , socket : net . Socket ) : Promise < void > => {
256+ try {
257+ await this . preHandleWebSocket ( request , socket ) ;
258+ } catch ( error ) {
259+ socket . destroy ( ) ;
260+ console . error ( error ) ;
261+ }
262+ }
263+
264+ private preHandleWebSocket ( request : http . IncomingMessage , socket : net . Socket ) : Promise < void > {
265+ socket . on ( "error" , ( ) => socket . destroy ( ) ) ;
266+ socket . on ( "end" , ( ) => socket . destroy ( ) ) ;
267+
268+ if ( ! this . authenticate ( request ) ) {
269+ throw new HttpError ( "Unauthorized" , HttpCode . Unauthorized ) ;
270+ } else if ( request . headers . upgrade !== "websocket" ) {
271+ throw new Error ( "HTTP/1.1 400 Bad Request" ) ;
272+ }
273+
274+ // This magic value is specified by the websocket spec.
275+ const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ;
276+ const reply = crypto . createHash ( "sha1" )
277+ . update ( < string > request . headers [ "sec-websocket-key" ] + magic )
278+ . digest ( "base64" ) ;
279+ socket . write ( [
280+ "HTTP/1.1 101 Switching Protocols" ,
281+ "Upgrade: websocket" ,
282+ "Connection: Upgrade" ,
283+ `Sec-WebSocket-Accept: ${ reply } ` ,
284+ ] . join ( "\r\n" ) + "\r\n\r\n" ) ;
285+
286+ const parsedUrl = request . url ? url . parse ( request . url , true ) : { query : { } } ;
287+ return this . handleWebSocket ( socket , parsedUrl ) ;
288+ }
289+
253290 private async tryLogin ( request : http . IncomingMessage ) : Promise < Response > {
254291 if ( this . authenticate ( request ) ) {
255292 this . ensureGet ( request ) ;
@@ -305,10 +342,7 @@ export abstract class Server {
305342 const onData = ( d : Buffer ) : void => {
306343 body += d ;
307344 if ( body . length > 1e6 ) {
308- onError ( new HttpError (
309- "Payload is too large" ,
310- HttpCode . LargePayload ,
311- ) ) ;
345+ onError ( new HttpError ( "Payload is too large" , HttpCode . LargePayload ) ) ;
312346 request . connection . destroy ( ) ;
313347 }
314348 } ;
@@ -359,16 +393,6 @@ export class MainServer extends Server {
359393
360394 public constructor ( options : ServerOptions , args : ParsedArgs ) {
361395 super ( options ) ;
362- this . server . on ( "upgrade" , async ( request , socket ) => {
363- const protocol = this . createProtocol ( request , socket ) ;
364- try {
365- await this . connect ( await protocol . handshake ( ) , protocol ) ;
366- } catch ( error ) {
367- protocol . sendMessage ( { type : "error" , reason : error . message } ) ;
368- protocol . dispose ( ) ;
369- protocol . getSocket ( ) . dispose ( ) ;
370- }
371- } ) ;
372396 this . servicesPromise = this . initializeServices ( args ) ;
373397 }
374398
@@ -382,6 +406,21 @@ export class MainServer extends Server {
382406 return address ;
383407 }
384408
409+ protected async handleWebSocket ( socket : net . Socket , parsedUrl : url . UrlWithParsedQuery ) : Promise < void > {
410+ const protocol = new Protocol ( socket , {
411+ reconnectionToken : < string > parsedUrl . query . reconnectionToken || "" ,
412+ reconnection : parsedUrl . query . reconnection === "true" ,
413+ skipWebSocketFrames : parsedUrl . query . skipWebSocketFrames === "true" ,
414+ } ) ;
415+ try {
416+ await this . connect ( await protocol . handshake ( ) , protocol ) ;
417+ } catch ( error ) {
418+ protocol . sendMessage ( { type : "error" , reason : error . message } ) ;
419+ protocol . dispose ( ) ;
420+ protocol . getSocket ( ) . dispose ( ) ;
421+ }
422+ }
423+
385424 protected async handleRequest (
386425 base : string ,
387426 requestPath : string ,
@@ -390,14 +429,15 @@ export class MainServer extends Server {
390429 ) : Promise < Response > {
391430 switch ( base ) {
392431 case "/" : return this . getRoot ( request , parsedUrl ) ;
393- case "/node_modules" :
394- case "/out" :
395- return this . getResource ( path . join ( this . rootPath , base , requestPath ) ) ;
396432 case "/resources" : return this . getResource ( requestPath ) ;
397433 case "/webview" :
398- const webviewPath = path . join ( this . rootPath , "out/vs/workbench/contrib/webview/browser/pre" ) ;
399- return this . getResource ( path . join ( webviewPath , requestPath || "/index.html" ) ) ;
400- default : throw new HttpError ( "Not found" , HttpCode . NotFound ) ;
434+ return this . getResource (
435+ this . rootPath ,
436+ "out/vs/workbench/contrib/webview/browser/pre" ,
437+ requestPath
438+ ) ;
439+ default :
440+ return this . getResource ( this . rootPath , base , requestPath ) ;
401441 }
402442 }
403443
@@ -440,18 +480,6 @@ export class MainServer extends Server {
440480 return { content, filePath } ;
441481 }
442482
443- private createProtocol ( request : http . IncomingMessage , socket : net . Socket ) : Protocol {
444- if ( request . headers . upgrade !== "websocket" ) {
445- throw new Error ( "HTTP/1.1 400 Bad Request" ) ;
446- }
447- const query = request . url ? url . parse ( request . url , true ) . query : { } ;
448- return new Protocol ( < string > request . headers [ "sec-websocket-key" ] , socket , {
449- reconnectionToken : < string > query . reconnectionToken || "" ,
450- reconnection : query . reconnection === "true" ,
451- skipWebSocketFrames : query . skipWebSocketFrames === "true" ,
452- } ) ;
453- }
454-
455483 private async connect ( message : ConnectionTypeRequest , protocol : Protocol ) : Promise < void > {
456484 switch ( message . desiredConnectionType ) {
457485 case ConnectionType . ExtensionHost :
0 commit comments