Files
code-server/patches/disable-file-cache.diff
T
2026-11-04 13:00:37 -09:00

189 lines
9.7 KiB
Diff

Add option to keep workspace files out of the browser cache
This patch adds support for a new CLI flag called `--disable-file-cache`. When
set, workspace files are not stored on the client disk through two caches:
- The webview resource loader sends no ETag for a resource outside the
webview's own extension, and the webview service worker only stores responses
that have an ETag, so these resources stay out of Cache Storage.
- `/vscode-remote-resource` responds with `Cache-Control: no-store` for files
outside the extension directories.
Extension resources keep their ETags so webviews still load extension code from
the cache. The default value for this is `false`.
To test, start code-server with `--disable-file-cache`, open a Markdown file
that embeds an image from the workspace, and open the preview. In the browser
dev tools, the `vscode-resource-cache-*` Cache Storage of the webview service
worker should have no entry for the image, and a request to
`/vscode-remote-resource?path=<image>` should respond with
`Cache-Control: no-store`.
Index: code-server/lib/vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts
+++ code-server/lib/vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts
@@ -177,15 +177,14 @@ class RemoteExtensionHostAgentServer ext
const requestOrigin = req.headers['origin'];
const responseHeaders = getRemoteResourceResponseHeaders(requestOrigin, origin => this._webEndpointOriginChecker.matches(origin));
- if (this._environmentService.isBuilt) {
- if (isEqualOrParent(filePath, this._environmentService.builtinExtensionsPath, !platform.isLinux)
- || isEqualOrParent(filePath, this._environmentService.extensionsPath, !platform.isLinux)
- ) {
- responseHeaders['Cache-Control'] = 'public, max-age=31536000';
- }
+ const isExtensionFile = isEqualOrParent(filePath, this._environmentService.builtinExtensionsPath, !platform.isLinux)
+ || isEqualOrParent(filePath, this._environmentService.extensionsPath, !platform.isLinux);
+ if (this._environmentService.isBuilt && isExtensionFile) {
+ responseHeaders['Cache-Control'] = 'public, max-age=31536000';
}
- return serveFile(filePath, CacheControl.ETAG, this._logService, req, res, responseHeaders);
+ const cacheControl = this._environmentService.args['disable-file-cache'] && !isExtensionFile ? CacheControl.NO_CACHING : CacheControl.ETAG;
+ return serveFile(filePath, cacheControl, this._logService, req, res, responseHeaders);
}
// workbench web UI
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -37,6 +37,7 @@ export const serverOptions: OptionDescri
'auth': { type: 'string' },
'disable-file-downloads': { type: 'boolean' },
'disable-file-uploads': { type: 'boolean' },
+ 'disable-file-cache': { type: 'boolean' },
'disable-getting-started-override': { type: 'boolean' },
'locale': { type: 'string' },
'link-protection-trusted-domains': { type: 'string[]' },
@@ -139,6 +140,7 @@ export interface ServerParsedArgs {
'auth'?: string;
'disable-file-downloads'?: boolean;
'disable-file-uploads'?: boolean;
+ 'disable-file-cache'?: boolean;
'disable-getting-started-override'?: boolean,
'locale'?: string
'link-protection-trusted-domains'?: string[],
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
@@ -442,6 +442,7 @@ export class WebClientServer {
userDataPath: this._environmentService.userDataPath,
isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
isEnabledFileUploads: !this._environmentService.args['disable-file-uploads'],
+ isEnabledFileCache: !this._environmentService.args['disable-file-cache'],
isEnabledCoderGettingStarted: !this._environmentService.args['disable-getting-started-override'],
_wrapWebWorkerExtHostInIframe,
developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() },
Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -330,6 +330,11 @@ export interface IWorkbenchConstructionO
readonly isEnabledFileUploads?: boolean
/**
+ * Whether the browser may cache workspace files.
+ */
+ readonly isEnabledFileCache?: boolean
+
+ /**
* Whether to use Coder's custom Getting Started text.
*/
readonly isEnabledCoderGettingStarted?: boolean
Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/webviewElement.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/webviewElement.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/webviewElement.ts
@@ -15,7 +15,7 @@ import { Lazy } from '../../../../base/c
import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { COI, Schemas } from '../../../../base/common/network.js';
import { observableValue } from '../../../../base/common/observable.js';
-import { isEqualAuthority } from '../../../../base/common/resources.js';
+import { isEqualAuthority, isEqualOrParent } from '../../../../base/common/resources.js';
import { listenStream } from '../../../../base/common/stream.js';
import { URI } from '../../../../base/common/uri.js';
import { generateUuid } from '../../../../base/common/uuid.js';
@@ -32,6 +32,7 @@ import { INotificationService } from '..
import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
import { ITunnelService } from '../../../../platform/tunnel/common/tunnel.js';
import { WebviewPortMappingManager } from '../../../../platform/webview/common/webviewPortMapping.js';
+import { IBrowserWorkbenchEnvironmentService } from '../../../services/environment/browser/environmentService.js';
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js';
import { decodeAuthority, webviewGenericCspSource, webviewMouseActivityEvent, webviewRootResourceAuthority } from '../common/webview.js';
@@ -868,6 +869,8 @@ export class WebviewElement extends Disp
const rangeHeader = range
? `bytes ${range.start}-${rangeEnd}/${result.size}`
: undefined;
+ // The service worker only stores responses that have an etag.
+ const etag = this._isFileCacheAllowed(uri) ? result.etag : undefined;
if (WebviewElement._supportsTransferableStreams.value) {
let controller: ReadableStreamDefaultController<Uint8Array<ArrayBuffer>> | undefined;
let closed = false;
@@ -919,7 +922,7 @@ export class WebviewElement extends Disp
status: range ? 206 : 200,
path: uri.path,
mime: result.mimeType,
- etag: result.etag,
+ etag,
mtime: result.mtime,
range: rangeHeader,
stream,
@@ -931,7 +934,7 @@ export class WebviewElement extends Disp
status: range ? 206 : 200,
path: uri.path,
mime: result.mimeType,
- etag: result.etag,
+ etag,
mtime: result.mtime,
range: rangeHeader,
});
@@ -988,6 +991,14 @@ export class WebviewElement extends Disp
});
}
+ private _isFileCacheAllowed(uri: URI): boolean {
+ if ((this._environmentService as IBrowserWorkbenchEnvironmentService).isEnabledFileCache) {
+ return true;
+ }
+ const extensionLocation = this.extension?.location;
+ return !!extensionLocation && isEqualOrParent(uri, extensionLocation);
+ }
+
private async localLocalhost(id: string, origin: string) {
const authority = this._environmentService.remoteAuthority;
const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined;
Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -49,6 +49,11 @@ export interface IBrowserWorkbenchEnviro
readonly isEnabledFileUploads?: boolean;
/**
+ * Allow the browser to cache workspace files.
+ */
+ readonly isEnabledFileCache?: boolean;
+
+ /**
* Enable Coder's custom getting started text.
*/
readonly isEnabledCoderGettingStarted?: boolean;
@@ -145,6 +150,13 @@ export class BrowserWorkbenchEnvironment
return this.options.isEnabledFileUploads;
}
+ get isEnabledFileCache(): boolean {
+ if (typeof this.options.isEnabledFileCache === "undefined") {
+ throw new Error('isEnabledFileCache was not provided to the browser');
+ }
+ return this.options.isEnabledFileCache;
+ }
+
get isEnabledCoderGettingStarted(): boolean {
if (typeof this.options.isEnabledCoderGettingStarted === "undefined") {
throw new Error('isEnabledCoderGettingStarted was not provided to the browser');