@@ -61,6 +61,12 @@ export interface Options {
6161 CONNECTION_AUTH_TOKEN : string ;
6262}
6363
64+ export interface Response {
65+ content ?: string | Buffer ;
66+ code ?: number ;
67+ headers : http . OutgoingHttpHeaders ;
68+ }
69+
6470export class HttpError extends Error {
6571 public constructor ( message : string , public readonly code : number ) {
6672 super ( message ) ;
@@ -87,16 +93,26 @@ export abstract class Server {
8793 }
8894
8995 const parsedUrl = url . parse ( request . url || "" , true ) ;
90- const requestPath = parsedUrl . pathname || "/" ;
9196
92- const [ content , headers ] = await this . handleRequest ( request , parsedUrl , requestPath ) ;
93- response . writeHead ( HttpCode . Ok , {
97+ const fullPath = decodeURIComponent ( parsedUrl . pathname || "/" ) ;
98+ const match = fullPath . match ( / ^ ( \/ ? [ ^ / ] * ) ( .* ) $ / ) ;
99+ const [ , base , requestPath ] = match
100+ ? match . map ( ( p ) => p !== "/" ? p . replace ( / \/ $ / , "" ) : p )
101+ : [ "" , "" , "" ] ;
102+
103+ const { content, headers, code } = await this . handleRequest (
104+ request , parsedUrl , base , requestPath ,
105+ ) ;
106+ response . writeHead ( code || HttpCode . Ok , {
94107 "Cache-Control" : "max-age=86400" ,
95108 // TODO: ETag?
96109 ...headers ,
97110 } ) ;
98111 response . end ( content ) ;
99112 } catch ( error ) {
113+ if ( error . code === "ENOENT" || error . code === "EISDIR" ) {
114+ error = new HttpError ( "Not found" , HttpCode . NotFound ) ;
115+ }
100116 response . writeHead ( typeof error . code === "number" ? error . code : 500 ) ;
101117 response . end ( error . message ) ;
102118 }
@@ -106,8 +122,9 @@ export abstract class Server {
106122 protected abstract handleRequest (
107123 request : http . IncomingMessage ,
108124 parsedUrl : url . UrlWithParsedQuery ,
125+ base : string ,
109126 requestPath : string ,
110- ) : Promise < [ string | Buffer , http . OutgoingHttpHeaders ] > ;
127+ ) : Promise < Response > ;
111128
112129 public listen ( ) : Promise < string > {
113130 if ( ! this . listenPromise ) {
@@ -146,7 +163,11 @@ export class MainServer extends Server {
146163
147164 private readonly services = new ServiceCollection ( ) ;
148165
149- public constructor ( port : number , private readonly webviewServer : WebviewServer , args : ParsedArgs ) {
166+ public constructor (
167+ port : number ,
168+ private readonly webviewServer : WebviewServer ,
169+ args : ParsedArgs ,
170+ ) {
150171 super ( port ) ;
151172
152173 this . server . on ( "upgrade" , async ( request , socket ) => {
@@ -163,7 +184,6 @@ export class MainServer extends Server {
163184 this . ipc . registerChannel ( "loglevel" , new LogLevelSetterChannel ( logService ) ) ;
164185
165186 const router = new StaticRouter ( ( context : any ) => {
166- console . log ( "static router" , context ) ;
167187 return context . clientId === "renderer" ;
168188 } ) ;
169189
@@ -194,72 +214,88 @@ export class MainServer extends Server {
194214 protected async handleRequest (
195215 request : http . IncomingMessage ,
196216 parsedUrl : url . UrlWithParsedQuery ,
217+ base : string ,
197218 requestPath : string ,
198- ) : Promise < [ string | Buffer , http . OutgoingHttpHeaders ] > {
199- if ( requestPath === "/" ) {
200- const htmlPath = path . join (
201- this . rootPath ,
202- 'out/vs/code/browser/workbench/workbench.html' ,
203- ) ;
204-
205- let html = await util . promisify ( fs . readFile ) ( htmlPath , "utf8" ) ;
206-
207- const remoteAuthority = request . headers . host as string ;
208- const transformer = getUriTransformer ( remoteAuthority ) ;
209-
210- const webviewEndpoint = await this . webviewServer . listen ( ) ;
211-
212- const cwd = process . env . VSCODE_CWD || process . cwd ( ) ;
213- const workspacePath = parsedUrl . query . workspace as string | undefined ;
214- const folderPath = ! workspacePath ? parsedUrl . query . folder as string | undefined || cwd : undefined ;
215-
216- const options : Options = {
217- WORKBENCH_WEB_CONGIGURATION : {
218- workspaceUri : workspacePath
219- ? transformer . transformOutgoing ( URI . file ( sanitizeFilePath ( workspacePath , cwd ) ) )
220- : undefined ,
221- folderUri : folderPath
222- ? transformer . transformOutgoing ( URI . file ( sanitizeFilePath ( folderPath , cwd ) ) )
223- : undefined ,
224- remoteAuthority,
225- webviewEndpoint,
226- } ,
227- REMOTE_USER_DATA_URI : transformer . transformOutgoing (
228- ( this . services . get ( IEnvironmentService ) as EnvironmentService ) . webUserDataHome ,
229- ) ,
230- PRODUCT_CONFIGURATION : product ,
231- CONNECTION_AUTH_TOKEN : "" ,
232- } ;
233-
234- Object . keys ( options ) . forEach ( ( key ) => {
235- html = html . replace ( `"{{${ key } }}"` , `'${ JSON . stringify ( options [ key ] ) } '` ) ;
236- } ) ;
219+ ) : Promise < Response > {
220+ switch ( base ) {
221+ case "/" :
222+ return this . getRoot ( request , parsedUrl ) ;
223+ case "/node_modules" :
224+ case "/out" :
225+ return this . getResource ( path . join ( this . rootPath , base , requestPath ) ) ;
226+ // TODO: this setup means you can't request anything from the root if it
227+ // starts with /node_modules or /out, although that's probably low risk.
228+ // There doesn't seem to be a really good way to solve this since some
229+ // resources are requested by the browser (like the extension icon) and
230+ // some by the file provider (like the extension README). Maybe add a
231+ // /resource prefix and a file provider that strips that prefix?
232+ default :
233+ return this . getResource ( path . join ( base , requestPath ) ) ;
234+ }
235+ }
237236
238- html = html . replace ( '{{WEBVIEW_ENDPOINT}}' , webviewEndpoint ) ;
237+ private async getRoot ( request : http . IncomingMessage , parsedUrl : url . UrlWithParsedQuery ) : Promise < Response > {
238+ const htmlPath = path . join (
239+ this . rootPath ,
240+ 'out/vs/code/browser/workbench/workbench.html' ,
241+ ) ;
239242
240- return [ html , {
243+ let content = await util . promisify ( fs . readFile ) ( htmlPath , "utf8" ) ;
244+
245+ const remoteAuthority = request . headers . host as string ;
246+ const transformer = getUriTransformer ( remoteAuthority ) ;
247+
248+ const webviewEndpoint = await this . webviewServer . listen ( ) ;
249+
250+ const cwd = process . env . VSCODE_CWD || process . cwd ( ) ;
251+ const workspacePath = parsedUrl . query . workspace as string | undefined ;
252+ const folderPath = ! workspacePath ? parsedUrl . query . folder as string | undefined || cwd : undefined ;
253+
254+ const options : Options = {
255+ WORKBENCH_WEB_CONGIGURATION : {
256+ workspaceUri : workspacePath
257+ ? transformer . transformOutgoing ( URI . file ( sanitizeFilePath ( workspacePath , cwd ) ) )
258+ : undefined ,
259+ folderUri : folderPath
260+ ? transformer . transformOutgoing ( URI . file ( sanitizeFilePath ( folderPath , cwd ) ) )
261+ : undefined ,
262+ remoteAuthority,
263+ webviewEndpoint,
264+ } ,
265+ REMOTE_USER_DATA_URI : transformer . transformOutgoing (
266+ ( this . services . get ( IEnvironmentService ) as EnvironmentService ) . webUserDataHome ,
267+ ) ,
268+ PRODUCT_CONFIGURATION : product ,
269+ CONNECTION_AUTH_TOKEN : "" ,
270+ } ;
271+
272+ Object . keys ( options ) . forEach ( ( key ) => {
273+ content = content . replace ( `"{{${ key } }}"` , `'${ JSON . stringify ( options [ key ] ) } '` ) ;
274+ } ) ;
275+
276+ content = content . replace ( '{{WEBVIEW_ENDPOINT}}' , webviewEndpoint ) ;
277+
278+ return {
279+ content,
280+ headers : {
241281 "Content-Type" : "text/html" ,
242- } ] ;
282+ } ,
243283 }
284+ }
244285
245- try {
246- const content = await util . promisify ( fs . readFile ) (
247- path . join ( this . rootPath , requestPath ) ,
248- ) ;
249- return [ content , {
250- "Content-Type" : getMediaMime ( requestPath ) || {
286+ private async getResource ( filePath : string ) : Promise < Response > {
287+ const content = await util . promisify ( fs . readFile ) ( filePath ) ;
288+ return {
289+ content ,
290+ headers : {
291+ "Content-Type" : getMediaMime ( filePath ) || {
251292 ".css" : "text/css" ,
252293 ".html" : "text/html" ,
253294 ".js" : "text/javascript" ,
254295 ".json" : "application/json" ,
255- } [ extname ( requestPath ) ] || "text/plain" ,
256- } ] ;
257- } catch ( error ) {
258- if ( error . code === "ENOENT" || error . code === "EISDIR" ) {
259- throw new HttpError ( "Not found" , HttpCode . NotFound ) ;
260- }
261- throw error ;
262- }
296+ } [ extname ( filePath ) ] || "text/plain" ,
297+ } ,
298+ } ;
263299 }
264300
265301 private createProtocol ( request : http . IncomingMessage , socket : net . Socket ) : Protocol {
@@ -356,7 +392,7 @@ export class MainServer extends Server {
356392}
357393
358394export class WebviewServer extends Server {
359- protected async handleRequest ( ) : Promise < [ string | Buffer , http . OutgoingHttpHeaders ] > {
395+ protected async handleRequest ( ) : Promise < Response > {
360396 throw new Error ( "not implemented" ) ;
361397 }
362398}
0 commit comments