@@ -19,6 +19,12 @@ import { FileAccess, connectionTokenCookieName, connectionTokenQueryName } from
19
19
import { generateUuid } from 'vs/base/common/uuid' ;
20
20
import { IProductService } from 'vs/platform/product/common/productService' ;
21
21
import { ServerConnectionToken , ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken' ;
22
+ import { IRequestService } from 'vs/platform/request/common/request' ;
23
+ import { IHeaders } from 'vs/base/parts/request/common/request' ;
24
+ import { CancellationToken } from 'vs/base/common/cancellation' ;
25
+ import { URI } from 'vs/base/common/uri' ;
26
+ import { streamToBuffer } from 'vs/base/common/buffer' ;
27
+ import { IProductConfiguration } from 'vs/base/common/product' ;
22
28
23
29
const textMimeType = {
24
30
'.html' : 'text/html' ,
@@ -77,7 +83,8 @@ export class WebClientServer {
77
83
private readonly _connectionToken : ServerConnectionToken ,
78
84
@IServerEnvironmentService private readonly _environmentService : IServerEnvironmentService ,
79
85
@ILogService private readonly _logService : ILogService ,
80
- @IProductService private readonly _productService : IProductService
86
+ @IRequestService private readonly _requestService : IRequestService ,
87
+ @IProductService private readonly _productService : IProductService ,
81
88
) { }
82
89
83
90
async handle ( req : http . IncomingMessage , res : http . ServerResponse , parsedUrl : url . UrlWithParsedQuery ) : Promise < void > {
@@ -92,6 +99,10 @@ export class WebClientServer {
92
99
// always serve static requests, even without a token
93
100
return this . _handleStatic ( req , res , parsedUrl ) ;
94
101
}
102
+ if ( / ^ \/ e x t e n s i o n R e s o u r c e \/ / . test ( pathname ) ) {
103
+ // always serve static requests, even without a token
104
+ return this . _handleExtensionResource ( req , res , parsedUrl ) ;
105
+ }
95
106
if ( pathname === '/' ) {
96
107
// the token handling is done inside the handler
97
108
return this . _handleRoot ( req , res , parsedUrl ) ;
@@ -128,6 +139,38 @@ export class WebClientServer {
128
139
return serveFile ( this . _logService , req , res , filePath , headers ) ;
129
140
}
130
141
142
+ /**
143
+ * Handle HTTP requests for /static/*
144
+ */
145
+ private async _handleExtensionResource ( req : http . IncomingMessage , res : http . ServerResponse , parsedUrl : url . UrlWithParsedQuery ) : Promise < void > {
146
+ const normalizedPathname = decodeURIComponent ( parsedUrl . pathname ! ) ; // support paths that are uri-encoded (e.g. spaces => %20)
147
+ const path = normalize ( normalizedPathname . substr ( '/extensionResource/' . length ) ) ;
148
+ const url = URI . parse ( path ) . with ( { scheme : 'https' , authority : path . substring ( 0 , path . indexOf ( '/' ) ) , path : path . substring ( path . indexOf ( '/' ) + 1 ) } ) ;
149
+
150
+ const headers : IHeaders = { } ;
151
+ for ( const header of req . rawHeaders ) {
152
+ if ( req . headers [ header ] ) {
153
+ headers [ header ] = req . headers [ header ] ! [ 0 ] ;
154
+ }
155
+ }
156
+
157
+ const context = await this . _requestService . request ( {
158
+ type : 'GET' ,
159
+ url : url . toString ( true ) ,
160
+ headers
161
+ } , CancellationToken . None ) ;
162
+
163
+ if ( context . res . statusCode && context . res . statusCode !== 200 ) {
164
+ this . _logService . info ( `Request to '${ url . toString ( true ) } ' failed with status code ${ context . res . statusCode } ` ) ;
165
+ throw new Error ( `Server returned ${ context . res . statusCode } ` ) ;
166
+ }
167
+
168
+ const responseHeaders = context . res . headers ;
169
+ res . writeHead ( 200 , responseHeaders ) ;
170
+ const buffer = await streamToBuffer ( context . stream ) ;
171
+ return res . end ( buffer . buffer ) ;
172
+ }
173
+
131
174
/**
132
175
* Handle HTTP requests for /
133
176
*/
@@ -183,12 +226,16 @@ export class WebClientServer {
183
226
accessToken : this . _environmentService . args [ 'github-auth' ] ,
184
227
scopes : [ [ 'user:email' ] , [ 'repo' ] ]
185
228
} : undefined ;
229
+ const resourceUrlTemplate = this . _productService . extensionsGallery ? URI . parse ( this . _productService . extensionsGallery . resourceUrlTemplate ) : undefined ;
186
230
const data = ( await util . promisify ( fs . readFile ) ( filePath ) ) . toString ( )
187
231
. replace ( '{{WORKBENCH_WEB_CONFIGURATION}}' , escapeAttribute ( JSON . stringify ( {
188
232
remoteAuthority,
189
233
_wrapWebWorkerExtHostInIframe,
190
234
developmentOptions : { enableSmokeTestDriver : this . _environmentService . driverHandle === 'web' ? true : undefined } ,
191
235
settingsSyncOptions : ! this . _environmentService . isBuilt && this . _environmentService . args [ 'enable-sync' ] ? { enabled : true } : undefined ,
236
+ productConfiguration : < Partial < IProductConfiguration > > {
237
+ extensionsGallery : resourceUrlTemplate ? { ...this . _productService . extensionsGallery , 'resourceUrlTemplate' : resourceUrlTemplate . with ( { scheme : 'http' , authority : remoteAuthority , path : `extensionResource/${ resourceUrlTemplate . authority } ${ resourceUrlTemplate . path } ` } ) . toString ( true ) } : undefined
238
+ }
192
239
} ) ) )
193
240
. replace ( '{{WORKBENCH_AUTH_SESSION}}' , ( ) => authSessionInfo ? escapeAttribute ( JSON . stringify ( authSessionInfo ) ) : '' ) ;
194
241
0 commit comments