Skip to content

feat: add option for disabling file downloads #5055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions patches/disable-downloads.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
Add option to disable file downloads via CLI

This patch adds support for a new CLI flag called `--disable-file-downloads`
which allows a user to remove the "Download..." option that shows up when you
right-click files in Code. The default value for this is `false`.

To test this, start code-server with `--disable-file-downloads`, open editor,
right-click on a file (not a folder) and you should **not** see the
"Download..." option.

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
@@ -210,6 +210,11 @@ export interface IWorkbenchConstructionO
*/
readonly userDataPath?: string

+ /**
+ * Whether the "Download..." option is enabled for files.
+ */
+ readonly isEnabledFileDownloads?: boolean
+
//#endregion


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
@@ -61,6 +61,13 @@ export class BrowserWorkbenchEnvironment
return this.options.userDataPath;
}

+ get isEnabledFileDownloads(): boolean {
+ if (typeof this.options.isEnabledFileDownloads === "undefined") {
+ throw new Error('isEnabledFileDownloads was not provided to the browser');
+ }
+ return this.options.isEnabledFileDownloads;
+ }
+
@memoize
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }

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
@@ -15,6 +15,7 @@ export const serverOptions: OptionDescri
'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' },
'locale': { type: 'string' },
+ 'disable-file-downloads': { type: 'boolean' },

/* ----- server setup ----- */

@@ -92,6 +93,7 @@ export interface ServerParsedArgs {
'disable-update-check'?: boolean;
'auth'?: string
'locale'?: string
+ 'disable-file-downloads'?: boolean;

/* ----- server setup ----- */

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
@@ -290,6 +290,7 @@ export class WebClientServer {
logLevel: this._logService.getLevel(),
},
userDataPath: this._environmentService.userDataPath,
+ isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
rootEndpoint: base,
Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/ev
import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys';
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext } from 'vs/workbench/common/contextkeys';
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -194,6 +194,9 @@ export class WorkbenchContextKeysHandler
this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService);
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));

+ // code-server
+ IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads)
+
this.registerListeners();
}

Index: code-server/lib/vscode/src/vs/workbench/services/environment/common/environmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/common/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/common/environmentService.ts
@@ -44,6 +44,9 @@ export interface IWorkbenchEnvironmentSe
readonly filesToOpenOrCreate?: IPath[] | undefined;
readonly filesToDiff?: IPath[] | undefined;

+ // --- code-server
+ readonly isEnabledFileDownloads: boolean;
+
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH:
// - PUT NON-WEB PROPERTIES INTO NATIVE WB ENV SERVICE
Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
@@ -21,7 +21,7 @@ import { CLOSE_SAVED_EDITORS_COMMAND_ID,
import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService';
import { Schemas } from 'vs/base/common/network';
-import { DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey } from 'vs/workbench/common/contextkeys';
+import { DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -475,13 +475,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
id: DOWNLOAD_COMMAND_ID,
title: DOWNLOAD_LABEL
},
- when: ContextKeyExpr.or(
- // native: for any remote resource
- ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)),
- // web: for any files
- ContextKeyExpr.and(IsWebContext, ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated()),
- // web: for any folders if file system API support is provided
- ContextKeyExpr.and(IsWebContext, HasWebFileSystemAccess)
+ when: ContextKeyExpr.and(
+ IsEnabledFileDownloads,
+ ContextKeyExpr.or(
+ // native: for any remote resource
+ ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)),
+ // web: for any files
+ ContextKeyExpr.and(IsWebContext, ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated()),
+ // web: for any folders if file system API support is provided
+ ContextKeyExpr.and(IsWebContext, HasWebFileSystemAccess)
+ )
)
}));

1 change: 1 addition & 0 deletions patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ local-storage.diff
service-worker.diff
connection-type.diff
sourcemaps.diff
disable-downloads.diff
10 changes: 10 additions & 0 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface UserProvidedCodeArgs {
category?: string
"github-auth"?: string
"disable-update-check"?: boolean
"disable-file-downloads"?: boolean
}

/**
Expand Down Expand Up @@ -157,6 +158,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
"then notifies you once every week that a new release is available.",
},
"disable-file-downloads": {
type: "boolean",
description: "Disable file downloads from Code. Defaults to true.",
},
// --enable can be used to enable experimental features. These features
// provide no guarantees.
enable: { type: "string[]" },
Expand Down Expand Up @@ -445,6 +450,7 @@ export interface DefaultedArgs extends ConfigArgs {
usingEnvHashedPassword: boolean
"extensions-dir": string
"user-data-dir": string
"disable-file-downloads": boolean
/* Positional arguments. */
_: string[]
}
Expand Down Expand Up @@ -556,6 +562,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
args["proxy-domain"] = Array.from(proxyDomains)

if (!args["disable-file-downloads"]) {
args["disable-file-downloads"] = false
}

if (typeof args._ === "undefined") {
args._ = []
}
Expand Down
26 changes: 26 additions & 0 deletions test/e2e/downloads.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as path from "path"
import { promises as fs } from "fs"
import { clean } from "../utils/helpers"
import { describe, test, expect } from "./baseFixture"

describe("Downloads (enabled)", true, [], {}, async () => {
const testName = "downloads"
test.beforeAll(async () => {
await clean(testName)
})

test("should see the 'Download...' option", async ({ codeServerPage }) => {
// Setup
const workspaceDir = await codeServerPage.workspaceDir
const tmpFilePath = path.join(workspaceDir, "unique-file.txt")
await fs.writeFile(tmpFilePath, "hello world")

// Action
const fileInExplorer = await codeServerPage.page.waitForSelector("text=unique-file.txt")
await fileInExplorer.click({
button: "right",
})

expect(await codeServerPage.page.isVisible("text=Download...")).toBe(true)
})
})
26 changes: 26 additions & 0 deletions test/e2e/downloadsDisabled.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as path from "path"
import { promises as fs } from "fs"
import { clean } from "../utils/helpers"
import { describe, test, expect } from "./baseFixture"

describe("Downloads (disabled)", true, ["--disable-file-downloads"], {}, async () => {
const testName = "downloads"
test.beforeAll(async () => {
await clean(testName)
})

test("should not see the 'Download...' option", async ({ codeServerPage }) => {
// Setup
const workspaceDir = await codeServerPage.workspaceDir
const tmpFilePath = path.join(workspaceDir, "unique-file.txt")
await fs.writeFile(tmpFilePath, "hello world")

// Action
const fileInExplorer = await codeServerPage.page.waitForSelector("text=unique-file.txt")
await fileInExplorer.click({
button: "right",
})

expect(await codeServerPage.page.isVisible("text=Download...")).toBe(false)
})
})
2 changes: 1 addition & 1 deletion test/e2e/models/CodeServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class CodeServer {
})

proc.on("close", (code) => {
const error = new Error("code-server closed unexpectedly")
const error = new Error("code-server closed unexpectedly. Try running with LOG_LEVEL=debug to see more info.")
if (!this.closed) {
this.logger.error(error.message, field("code", code))
}
Expand Down
4 changes: 4 additions & 0 deletions test/unit/node/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const defaults = {
host: "localhost",
port: 8080,
"proxy-domain": [],
"disable-file-downloads": false,
usingEnvPassword: false,
usingEnvHashedPassword: false,
"extensions-dir": path.join(paths.data, "extensions"),
Expand Down Expand Up @@ -92,6 +93,8 @@ describe("parser", () => {

"--port=8081",

"--disable-file-downloads",

["--host", "0.0.0.0"],
"4",
"--",
Expand All @@ -108,6 +111,7 @@ describe("parser", () => {
cert: {
value: path.resolve("path/to/cert"),
},
"disable-file-downloads": true,
enable: ["feature1", "feature2"],
help: true,
host: "0.0.0.0",
Expand Down
1 change: 1 addition & 0 deletions test/unit/node/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe("plugin", () => {
"proxy-domain": [],
config: "~/.config/code-server/config.yaml",
verbose: false,
"disable-file-downloads": false,
usingEnvPassword: false,
usingEnvHashedPassword: false,
"extensions-dir": "",
Expand Down