@@ -19,12 +19,13 @@ 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' ;
22
+ import { asText , IRequestService } from 'vs/platform/request/common/request' ;
23
23
import { IHeaders } from 'vs/base/parts/request/common/request' ;
24
24
import { CancellationToken } from 'vs/base/common/cancellation' ;
25
25
import { URI } from 'vs/base/common/uri' ;
26
26
import { streamToBuffer } from 'vs/base/common/buffer' ;
27
27
import { IProductConfiguration } from 'vs/base/common/product' ;
28
+ import { isString } from 'vs/base/common/types' ;
28
29
29
30
const textMimeType = {
30
31
'.html' : 'text/html' ,
@@ -139,34 +140,73 @@ export class WebClientServer {
139
140
return serveFile ( this . _logService , req , res , filePath , headers ) ;
140
141
}
141
142
143
+ private _getResourceURLTemplateAuthority ( uri : URI ) : string | undefined {
144
+ const index = uri . authority . indexOf ( '.' ) ;
145
+ return index !== - 1 ? uri . authority . substring ( index + 1 ) : undefined ;
146
+ }
147
+
142
148
/**
143
149
* Handle extension resources
144
150
*/
145
151
private async _handleWebExtensionResource ( req : http . IncomingMessage , res : http . ServerResponse , parsedUrl : url . UrlWithParsedQuery ) : Promise < void > {
152
+ if ( ! this . _productService . extensionsGallery ?. resourceUrlTemplate ) {
153
+ return serveError ( req , res , 500 , 'No extension gallery service configured.' ) ;
154
+ }
155
+
146
156
// Strip `/web-extension-resource/` from the path
147
157
const normalizedPathname = decodeURIComponent ( parsedUrl . pathname ! ) ; // support paths that are uri-encoded (e.g. spaces => %20)
148
158
const path = normalize ( normalizedPathname . substr ( '/web-extension-resource/' . length ) ) ;
149
-
150
- const url = URI . parse ( path ) . with ( {
159
+ const uri = URI . parse ( path ) . with ( {
151
160
scheme : this . _productService . extensionsGallery ?. resourceUrlTemplate ? URI . parse ( this . _productService . extensionsGallery . resourceUrlTemplate ) . scheme : 'https' ,
152
161
authority : path . substring ( 0 , path . indexOf ( '/' ) ) ,
153
162
path : path . substring ( path . indexOf ( '/' ) + 1 )
154
- } ) . toString ( true ) ;
163
+ } ) ;
164
+
165
+ if ( this . _getResourceURLTemplateAuthority ( URI . parse ( this . _productService . extensionsGallery . resourceUrlTemplate ) ) !== this . _getResourceURLTemplateAuthority ( uri ) ) {
166
+ return serveError ( req , res , 403 , 'Request Forbidden' ) ;
167
+ }
155
168
156
169
const headers : IHeaders = { } ;
157
- for ( const header of req . rawHeaders ) {
158
- if ( req . headers [ header ] ) {
159
- headers [ header ] = req . headers [ header ] ! [ 0 ] ;
170
+ const seRequestHeader = ( header : string ) => {
171
+ const value = req . headers [ header ] ;
172
+ if ( value && ( isString ( value ) || value [ 0 ] ) ) {
173
+ headers [ header ] = isString ( value ) ? value : value [ 0 ] ;
174
+ } else if ( header !== header . toLowerCase ( ) ) {
175
+ seRequestHeader ( header . toLowerCase ( ) ) ;
160
176
}
161
- }
177
+ } ;
178
+ seRequestHeader ( 'X-Client-Name' ) ;
179
+ seRequestHeader ( 'X-Client-Version' ) ;
180
+ seRequestHeader ( 'X-Machine-Id' ) ;
181
+ seRequestHeader ( 'X-Client-Commit' ) ;
162
182
163
183
const context = await this . _requestService . request ( {
164
184
type : 'GET' ,
165
- url,
185
+ url : uri . toString ( true ) ,
166
186
headers
167
187
} , CancellationToken . None ) ;
168
188
169
- res . writeHead ( context . res . statusCode || 500 , context . res . headers ) ;
189
+ const status = context . res . statusCode || 500 ;
190
+ if ( status !== 200 ) {
191
+ let text : string | null = null ;
192
+ try {
193
+ text = await asText ( context ) ;
194
+ } catch ( error ) { /* Ignore */ }
195
+ return serveError ( req , res , status , text || `Request failed with status ${ status } ` ) ;
196
+ }
197
+
198
+ const responseHeaders : Record < string , string > = Object . create ( null ) ;
199
+ const seResponseHeader = ( header : string ) => {
200
+ const value = context . res . headers [ header ] ;
201
+ if ( value ) {
202
+ responseHeaders [ header ] = value ;
203
+ } else if ( header !== header . toLowerCase ( ) ) {
204
+ seResponseHeader ( header . toLowerCase ( ) ) ;
205
+ }
206
+ } ;
207
+ seResponseHeader ( 'Cache-Control' ) ;
208
+ seResponseHeader ( 'Content-Type' ) ;
209
+ res . writeHead ( 200 , responseHeaders ) ;
170
210
const buffer = await streamToBuffer ( context . stream ) ;
171
211
return res . end ( buffer . buffer ) ;
172
212
}
0 commit comments