Skip to content

Commit c3c1f63

Browse files
committed
Merge branch 'code-server-v2'
2 parents 1801d59 + 2a3c835 commit c3c1f63

File tree

3 files changed

+84
-31
lines changed

3 files changed

+84
-31
lines changed

src/vs/code/browser/workbench/workbench.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,25 @@ class WindowIndicator implements IWindowIndicator {
559559
// Finally create workbench
560560
create(document.body, {
561561
...config,
562+
/**
563+
* Override relative URLs in the product configuration against the window
564+
* location as necessary. Only paths that must be absolute need to be
565+
* rewritten (for example the webview endpoint); the rest can be left
566+
* relative (for example the update path).
567+
*
568+
* @author coder
569+
*/
570+
productConfiguration: {
571+
...config.productConfiguration,
572+
// The webview endpoint contains variables in the format {{var}} so decode
573+
// them as `new URI` will encode them.
574+
webviewContentExternalBaseUrlTemplate: decodeURIComponent(
575+
new URL(
576+
config.productConfiguration?.webviewContentExternalBaseUrlTemplate ?? "",
577+
window.location.toString(), // This works without toString() but TypeScript thinks otherwise.
578+
).toString(),
579+
),
580+
},
562581
developmentOptions: {
563582
logLevel: logLevel ? parseLogLevel(logLevel) : undefined,
564583
...config.developmentOptions

src/vs/server/webClientServer.ts

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -85,53 +85,62 @@ export class WebClientServer {
8585
private readonly _productService: IProductService
8686
) { }
8787

88-
/**
89-
* @coder Patched to handle an arbitrary path depth, such as in Coder Enterprise.
90-
*/
9188
async handle(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
9289
try {
9390
const pathname = parsedUrl.pathname!;
94-
const parsedPath = path.parse(pathname);
95-
const pathPrefix = getPathPrefix(pathname);
9691

97-
if (['manifest.json', 'webmanifest.json'].includes(parsedPath.base)) {
92+
/**
93+
* Add a custom manifest.
94+
*
95+
* @author coder
96+
*/
97+
if (pathname === '/manifest.json' || pathname === '/webmanifest.json') {
9898
return this._handleManifest(req, res, parsedUrl);
9999
}
100-
101-
if (['favicon.ico', 'code-192.png', 'code-512.png'].includes(parsedPath.base)) {
100+
if (pathname === '/favicon.ico' || pathname === '/manifest.json' || pathname === '/code-192.png' || pathname === '/code-512.png') {
102101
// always serve icons/manifest, even without a token
103-
return serveFile(this._logService, req, res, join(APP_ROOT, 'resources', 'server', parsedPath.base));
102+
return serveFile(this._logService, req, res, join(APP_ROOT, 'resources', 'server', pathname.substr(1)));
104103
}
105-
106-
if (parsedPath.base === this._environmentService.serviceWorkerFileName) {
107-
return serveFile(this._logService, req, res, this._environmentService.serviceWorkerPath, {
108-
'Service-Worker-Allowed': pathPrefix,
109-
});
104+
/**
105+
* Add an endpoint for self-hosting webviews. This must be unique per
106+
* webview as the code relies on unique service workers. In our case we
107+
* use /webview/{{uuid}}.
108+
*
109+
* @author coder
110+
*/
111+
if (/^\/webview\//.test(pathname)) {
112+
// always serve webview requests, even without a token
113+
return this._handleWebview(req, res, parsedUrl);
110114
}
111-
112-
if (parsedPath.dir.includes('/static/') && parsedPath.ext) {
113-
parsedUrl.pathname = pathname.substring(pathname.indexOf('/static/'));
115+
if (/^\/static\//.test(pathname)) {
114116
// always serve static requests, even without a token
115117
return this._handleStatic(req, res, parsedUrl);
116118
}
117-
118-
if (parsedPath.base === 'callback') {
119+
/**
120+
* Move the service worker to the root. This makes the scope the root
121+
* (otherwise we would need to include Service-Worker-Allowed).
122+
*
123+
* @author coder
124+
*/
125+
if (pathname === '/' + this._environmentService.serviceWorkerFileName) {
126+
return serveFile(this._logService, req, res, this._environmentService.serviceWorkerPath);
127+
}
128+
if (pathname === '/') {
129+
// the token handling is done inside the handler
130+
return this._handleRoot(req, res, parsedUrl);
131+
}
132+
if (pathname === '/callback') {
119133
// callback support
120134
return this._handleCallback(req, res, parsedUrl);
121135
}
122-
123-
if (parsedPath.base === 'fetch-callback') {
136+
if (pathname === '/fetch-callback') {
124137
// callback fetch support
125138
return this._handleFetchCallback(req, res, parsedUrl);
126139
}
127140

128-
if (pathname.endsWith('/')) {
129-
// the token handling is done inside the handler
130-
return this._handleRoot(req, res, parsedUrl);
131-
}
132-
133141
const error = new HTTPNotFoundError(`"${parsedUrl.pathname}" not found.`);
134142
req.emit('error', error);
143+
135144
return;
136145
} catch (error) {
137146
console.log('VS CODE ERRORED');
@@ -239,6 +248,28 @@ export class WebClientServer {
239248
return serveFile(this._logService, req, res, filePath, headers);
240249
}
241250

251+
/**
252+
* Handle HTTP requests for /webview/*
253+
*
254+
* A unique path is required for every webview service worker.
255+
*/
256+
private async _handleWebview(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
257+
const headers: Record<string, string> = Object.create(null);
258+
259+
// support paths that are uri-encoded (e.g. spaces => %20)
260+
const normalizedPathname = decodeURIComponent(parsedUrl.pathname!);
261+
262+
// Strip `/webview/{uuid}` from the path.
263+
const relativeFilePath = normalize(normalizedPathname.split('/').splice(3).join('/'));
264+
265+
const filePath = join(APP_ROOT, 'out/vs/workbench/contrib/webview/browser/pre', relativeFilePath);
266+
if (!isEqualOrParent(filePath, APP_ROOT, !isLinux)) {
267+
return this.serveError(req, res, 400, `Bad request.`, parsedUrl);
268+
}
269+
270+
return serveFile(this._logService, req, res, filePath, headers);
271+
}
272+
242273
/**
243274
* Handle HTTP requests for /
244275
*/
@@ -317,8 +348,9 @@ export class WebClientServer {
317348
logoutEndpointUrl: './logout',
318349
webEndpointUrl: this.createRequestUrl(req, parsedUrl, '/static').toString(),
319350
webEndpointUrlTemplate: this.createRequestUrl(req, parsedUrl, '/static').toString(),
351+
webviewContentExternalBaseUrlTemplate: './webview/{{uuid}}/',
320352

321-
updateUrl: this.createRequestUrl(req, parsedUrl, '/update/check').toString(),
353+
updateUrl: './update/check'
322354
},
323355
folderUri: (workspacePath && isFolder) ? transformer.transformOutgoing(URI.file(workspacePath)) : undefined,
324356
workspaceUri: (workspacePath && !isFolder) ? transformer.transformOutgoing(URI.file(workspacePath)) : undefined,
@@ -343,7 +375,7 @@ export class WebClientServer {
343375
// the sha is the same as in src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html
344376
`script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-cb2sg39EJV8ABaSNFfWu/ou8o1xVXYK7jp90oZ9vpcg=';`,
345377
'child-src \'self\';',
346-
`frame-src 'self' https://*.vscode-webview.net ${this._productService.webEndpointUrl || ''} data:;`,
378+
`frame-src 'self' ${this._productService.webEndpointUrl || ''} data:;`,
347379
'worker-src \'self\' data:;',
348380
'style-src \'self\' \'unsafe-inline\';',
349381
'connect-src \'self\' ws: wss: https:;',

src/vs/workbench/browser/web.main.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,15 @@ class BrowserMain extends Disposable {
103103
// Startup
104104
const instantiationService = workbench.startup();
105105

106-
/** @coder Initialize our own client-side additions. */
106+
/**
107+
* Initialize our own client-side additions.
108+
*
109+
* @author Coder
110+
*/
107111
if (!this.configuration.productConfiguration) {
108112
throw new Error('`productConfiguration` not present in workbench config');
109113
}
110-
111114
const codeServerClientAdditions = this._register(instantiationService.createInstance(CodeServerClientAdditions, this.configuration.productConfiguration));
112-
113115
await codeServerClientAdditions.startup();
114116

115117
// Window

0 commit comments

Comments
 (0)