2022-03-22 21:07:14 +01:00
Add base path support
Some users will host code-server behind a path-rewriting reverse proxy, for
example domain.tld/my/base/path. This patch adds support for that since Code
assumes everything is on / by default.
To test this serve code-server behind a reverse proxy with a path like /code.
Index: code-server/lib/vscode/src/vs/base/common/network.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
+++ code-server/lib/vscode/src/vs/base/common/network.ts
2022-08-17 03:26:19 +02:00
@@ -162,7 +162,9 @@ class RemoteAuthoritiesImpl {
2022-03-22 21:07:14 +01:00
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
2022-06-21 23:51:46 +02:00
authority: `${host}:${port}`,
- path: this._remoteResourcesPath,
2022-03-22 21:07:14 +01:00
+ path: platform.isWeb
2022-06-21 23:51:46 +02:00
+ ? (window.location.pathname + "/" + this._remoteResourcesPath).replace(/\/\/+/g, "/")
+ : this._remoteResourcesPath,
2022-03-22 21:07:14 +01:00
query
});
}
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
@@ -11,8 +11,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Code">
- <link rel="apple-touch-icon" sizes="192x192" href="/_static/src/browser/media/pwa-icon-192.png" />
- <link rel="apple-touch-icon" sizes="512x512" href="/_static/src/browser/media/pwa-icon-512.png" />
+ <link rel="apple-touch-icon" sizes="192x192" href="{{BASE}}/_static/src/browser/media/pwa-icon-192.png" />
+ <link rel="apple-touch-icon" sizes="512x512" href="{{BASE}}/_static/src/browser/media/pwa-icon-512.png" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
2022-06-21 23:51:46 +02:00
@@ -27,9 +27,9 @@
2022-03-22 21:07:14 +01:00
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
<!-- Workbench Icon/Manifest/CSS -->
- <link rel="icon" href="/_static/src/browser/media/favicon-dark-support.svg" />
2022-06-21 23:51:46 +02:00
- <link rel="alternate icon" href="/_static/src/browser/media/favicon.ico" type="image/x-icon" />
2022-03-22 21:07:14 +01:00
- <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
+ <link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
2022-06-21 23:51:46 +02:00
+ <link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
2022-03-22 21:07:14 +01:00
+ <link rel="manifest" href="{{VS_BASE}}/manifest.json" crossorigin="use-credentials" />
</head>
<body aria-label="">
2022-06-21 23:51:46 +02:00
@@ -39,7 +39,7 @@
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js"></script>
2022-03-22 21:07:14 +01:00
<script>
2022-06-21 23:51:46 +02:00
- const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString();
+ const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location).toString();
2022-03-22 21:07:14 +01:00
Object.keys(self.webPackagePaths).map(function (key, index) {
2022-06-21 23:51:46 +02:00
self.webPackagePaths[key] = `${baseUrl}/remote/web/node_modules/${key}/${self.webPackagePaths[key]}`;
2022-03-22 21:07:14 +01:00
});
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
@@ -11,8 +11,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Code">
- <link rel="apple-touch-icon" sizes="192x192" href="/_static/src/browser/media/pwa-icon-192.png" />
- <link rel="apple-touch-icon" sizes="512x512" href="/_static/src/browser/media/pwa-icon-512.png" />
+ <link rel="apple-touch-icon" sizes="192x192" href="{{BASE}}/_static/src/browser/media/pwa-icon-192.png" />
+ <link rel="apple-touch-icon" sizes="512x512" href="{{BASE}}/_static/src/browser/media/pwa-icon-512.png" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
2022-06-21 23:51:46 +02:00
@@ -24,9 +24,9 @@
2022-03-22 21:07:14 +01:00
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
<!-- Workbench Icon/Manifest/CSS -->
- <link rel="icon" href="/_static/src/browser/media/favicon-dark-support.svg" />
2022-06-21 23:51:46 +02:00
- <link rel="alternate icon" href="/_static/src/browser/media/favicon.ico" type="image/x-icon" />
2022-03-22 21:07:14 +01:00
- <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
+ <link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
2022-06-21 23:51:46 +02:00
+ <link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
2022-03-22 21:07:14 +01:00
+ <link rel="manifest" href="{{VS_BASE}}/manifest.json" crossorigin="use-credentials" />
2022-06-21 23:51:46 +02:00
<link data-name="vs/workbench/workbench.web.main" rel="stylesheet" href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.main.css">
2022-03-22 21:07:14 +01:00
</head>
2022-07-14 18:25:48 +02:00
@@ -38,7 +38,7 @@
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js"></script>
<script>
- const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString();
+ const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location).toString();
Object.keys(self.webPackagePaths).map(function (key, index) {
self.webPackagePaths[key] = `${baseUrl}/node_modules/${key}/${self.webPackagePaths[key]}`;
});
2022-03-22 21:07:14 +01:00
Index: code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
+++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
2022-06-21 23:51:46 +02:00
@@ -274,6 +274,7 @@ export class BrowserSocketFactory implem
2022-03-22 21:07:14 +01:00
2022-06-21 23:51:46 +02:00
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
2022-03-22 21:07:14 +01:00
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
2022-06-21 23:51:46 +02:00
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
2022-03-22 21:07:14 +01:00
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
2022-06-21 23:51:46 +02:00
@@ -282,6 +283,3 @@ export class BrowserSocketFactory implem
2022-03-22 21:07:14 +01:00
});
}
}
-
-
-
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
2022-08-17 03:26:19 +02:00
@@ -267,12 +267,11 @@ export class WebClientServer {
2022-03-22 21:07:14 +01:00
return res.end();
}
2022-08-17 03:26:19 +02:00
- const getFirstHeader = (headerName: string) => {
- const val = req.headers[headerName];
- return Array.isArray(val) ? val[0] : val;
- };
-
- const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
+ // For now we are getting the remote authority from the client to avoid
+ // needing specific configuration for reverse proxies to work. Set this to
+ // something invalid to make sure we catch code that is using this value
+ // from the backend when it should not.
2022-03-22 21:07:14 +01:00
+ const remoteAuthority = 'remote';
2022-08-17 03:26:19 +02:00
if (!remoteAuthority) {
return serveError(req, res, 400, `Bad request.`);
}
@@ -298,6 +297,8 @@ export class WebClientServer {
2022-03-22 21:07:14 +01:00
scopes: [['user:email'], ['repo']]
} : undefined;
2022-06-21 23:51:46 +02:00
2022-03-22 21:07:14 +01:00
+ const base = relativeRoot(getOriginalUrl(req))
+ const vscodeBase = relativePath(getOriginalUrl(req))
2022-06-21 23:51:46 +02:00
const workbenchWebConfiguration = {
remoteAuthority,
2022-08-17 03:26:19 +02:00
@@ -309,6 +310,7 @@ export class WebClientServer {
2022-06-21 23:51:46 +02:00
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
productConfiguration: <Partial<IProductConfiguration>>{
codeServerVersion: this._productService.codeServerVersion,
+ rootEndpoint: base,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,
2022-08-17 03:26:19 +02:00
@@ -326,8 +328,10 @@ export class WebClientServer {
2022-06-21 23:51:46 +02:00
const values: { [key: string]: string } = {
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
- WORKBENCH_WEB_BASE_URL: this._staticRoute,
2022-08-17 03:26:19 +02:00
- WORKBENCH_NLS_BASE_URL: nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : '',
2022-06-21 23:51:46 +02:00
+ WORKBENCH_WEB_BASE_URL: vscodeBase + this._staticRoute,
2022-08-17 03:26:19 +02:00
+ WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
2022-06-21 23:51:46 +02:00
+ BASE: base,
+ VS_BASE: vscodeBase,
};
2022-08-17 03:26:19 +02:00
@@ -344,7 +348,7 @@ export class WebClientServer {
'default-src \'self\';',
'img-src \'self\' https: data: blob:;',
'media-src \'self\';',
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' http://${remoteAuthority};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=';`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
'child-src \'self\';',
`frame-src 'self' https://*.vscode-cdn.net data:;`,
'worker-src \'self\' data:;',
@@ -417,3 +421,70 @@ export class WebClientServer {
2022-03-22 21:07:14 +01:00
return res.end(data);
}
}
+
+/**
+ * Remove extra slashes in a URL.
+ *
+ * This is meant to fill the job of `path.join` so you can concatenate paths and
+ * then normalize out any extra slashes.
+ *
+ * If you are using `path.join` you do not need this but note that `path` is for
+ * file system paths, not URLs.
+ */
+export const normalizeUrlPath = (url: string, keepTrailing = false): string => {
+ return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
+}
+
+/**
+ * Get the relative path that will get us to the root of the page. For each
+ * slash we need to go up a directory. Will not have a trailing slash.
+ *
+ * For example:
+ *
+ * / => .
+ * /foo => .
+ * /foo/ => ./..
+ * /foo/bar => ./..
+ * /foo/bar/ => ./../..
+ *
+ * All paths must be relative in order to work behind a reverse proxy since we
+ * we do not know the base path. Anything that needs to be absolute (for
+ * example cookies) must get the base path from the frontend.
+ *
+ * All relative paths must be prefixed with the relative root to ensure they
+ * work no matter the depth at which they happen to appear.
+ *
+ * For Express `req.originalUrl` should be used as they remove the base from the
+ * standard `url` property making it impossible to get the true depth.
+ */
+export const relativeRoot = (originalUrl: string): string => {
+ const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length
+ return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : ""))
+}
+
+/**
+ * Get the relative path to the current resource.
+ *
+ * For example:
+ *
+ * / => .
+ * /foo => ./foo
+ * /foo/ => .
+ * /foo/bar => ./bar
+ * /foo/bar/ => .
+ */
+export const relativePath = (originalUrl: string): string => {
+ const parts = originalUrl.split("?", 1)[0].split("/")
+ return normalizeUrlPath("./" + parts[parts.length - 1])
+}
+
+/**
+ * code-server serves Code using Express. Express removes the base from the url
+ * and puts the original in `originalUrl` so we must use this to get the correct
+ * depth. Code is not aware it is behind Express so the types do not match. We
+ * may want to continue moving code into Code and eventually remove the Express
+ * wrapper or move the web server back into code-server.
+ */
+export const getOriginalUrl = (req: http.IncomingMessage): string => {
+ return (req as any).originalUrl || req.url
+}
Index: code-server/lib/vscode/src/vs/base/common/product.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -32,6 +32,7 @@ export type ExtensionVirtualWorkspaceSup
export interface IProductConfiguration {
readonly codeServerVersion?: string
+ readonly rootEndpoint?: string
readonly version: string;
readonly date?: string;
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
2022-06-21 23:51:46 +02:00
@@ -485,6 +485,7 @@ function doCreateUri(path: string, query
2022-03-22 21:07:14 +01:00
});
}
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
return URI.parse(window.location.href).with({ path, query });
}
2022-06-21 23:51:46 +02:00
@@ -496,7 +497,7 @@ function doCreateUri(path: string, query
2022-03-22 21:07:14 +01:00
if (!configElement || !configElementAttribute) {
throw new Error('Missing web configuration element');
}
2022-06-21 23:51:46 +02:00
- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute);
+ const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = { ...JSON.parse(configElementAttribute), remoteAuthority: location.host }
2022-03-22 21:07:14 +01:00
// Create workbench
create(document.body, {
2022-04-14 23:00:10 +02:00
Index: code-server/lib/vscode/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts
@@ -16,7 +16,6 @@ import { getServiceMachineId } from 'vs/
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { RemoteAuthorities } from 'vs/base/common/network';
2022-06-21 23:51:46 +02:00
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
2022-04-14 23:00:10 +02:00
export const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
2022-06-21 23:51:46 +02:00
@@ -75,7 +74,7 @@ export abstract class AbstractExtensionR
2022-04-14 23:00:10 +02:00
public getExtensionGalleryResourceURL(galleryExtension: { publisher: string; name: string; version: string }, path?: string): URI | undefined {
if (this._extensionGalleryResourceUrlTemplate) {
const uri = URI.parse(format2(this._extensionGalleryResourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' }));
- return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri;
+ return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri;
}
return undefined;
}