Add option to disable file downloads and uploads 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. It also disables the "Show Local" button on the dialog 
for Save, Save-As and Save Workspace. The default value for this is `false`.

This patch also add support for a new CLI flag called `--disable-file-uploads`
which disables the drag to upload functionality and the "Upload..." option when you
right-click folders in Code. It also disables the "Show Local" button on the dialog 
for opening a file. The default value for this is `false`.

This patch also adds trace log statements for when a file is read and written to disk.

To test disabling downloads, 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. When saving a file or workspace, the "Show Local" button 
should **not** appear on the dialog that comes on screen.

To test disabling uploads, start code-server with `--disable-file-uploads`, open editor,
right-click on a folder (not a file) and you should **not** see the
"Upload..." option. If you drag a file into the file navigator, the file should **not** upload 
and appear in the file navigator. When opening a file, the "Show Local" button 
should **not** appear on the dialog that comes on screen.

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
@@ -303,6 +303,16 @@ export interface IWorkbenchConstructionO
 	 */
 	readonly userDataPath?: string
 
+	/**
+	 * Whether the "Download..." option is enabled for files.
+	 */
+	readonly isEnabledFileDownloads?: boolean
+
+	/**
+	 * Whether the "Upload..." button is enabled.
+	 */
+	readonly isEnabledFileUploads?: boolean
+
 	//#endregion
 
 	//#region Profile options
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
@@ -34,6 +34,16 @@ export interface IBrowserWorkbenchEnviro
 	readonly options?: IWorkbenchConstructionOptions;
 
 	/**
+	 * Enable downloading files via menu actions.
+	 */
+	readonly isEnabledFileDownloads?: boolean;
+
+	/**
+	 * Enable uploading files via menu actions.
+	 */
+	readonly isEnabledFileUploads?: boolean;
+
+	/**
 	 * Gets whether a resolver extension is expected for the environment.
 	 */
 	readonly expectsResolverExtension: boolean;
@@ -111,6 +121,20 @@ 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;
+	}
+
+	get isEnabledFileUploads(): boolean {
+		if (typeof this.options.isEnabledFileUploads === "undefined") {
+			throw new Error('isEnabledFileUploads was not provided to the browser');
+		}
+		return this.options.isEnabledFileUploads;
+	}
+
 	@memoize
 	get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.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
@@ -18,6 +18,8 @@ export const serverOptions: OptionDescri
 	/* ----- code-server ----- */
 	'disable-update-check': { type: 'boolean' },
 	'auth': { type: 'string' },
+	'disable-file-downloads': { type: 'boolean' },
+	'disable-file-uploads': { type: 'boolean' },
 
 	/* ----- server setup ----- */
 
@@ -104,6 +106,8 @@ export interface ServerParsedArgs {
 	/* ----- code-server ----- */
 	'disable-update-check'?: boolean;
 	'auth'?: string;
+	'disable-file-downloads'?: boolean;
+	'disable-file-uploads'?: 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
@@ -369,6 +369,8 @@ export class WebClientServer {
 			serverBasePath: basePath,
 			webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
 			userDataPath: this._environmentService.userDataPath,
+			isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
+			isEnabledFileUploads: !this._environmentService.args['disable-file-uploads'],
 			_wrapWebWorkerExtHostInIframe,
 			developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() },
 			settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
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,11 +7,11 @@ import { Event } from '../../base/common
 import { Disposable, DisposableStore, MutableDisposable } from '../../base/common/lifecycle.js';
 import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js';
 import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext, IsSimulationContext } from '../../platform/contextkey/common/contextkeys.js';
-import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext } from '../common/contextkeys.js';
+import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsEnabledFileDownloads, IsEnabledFileUploads } from '../common/contextkeys.js';
 import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow, isEditableElement } from '../../base/browser/dom.js';
 import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js';
 import { IConfigurationService } from '../../platform/configuration/common/configuration.js';
-import { IWorkbenchEnvironmentService } from '../services/environment/common/environmentService.js';
+import { IBrowserWorkbenchEnvironmentService } from '../services/environment/browser/environmentService.js';
 import { WorkbenchState, IWorkspaceContextService, isTemporaryWorkspace } from '../../platform/workspace/common/workspace.js';
 import { IWorkbenchLayoutService, Parts, positionToString } from '../services/layout/browser/layoutService.js';
 import { getRemoteName } from '../../platform/remote/common/remoteHosts.js';
@@ -72,7 +72,7 @@ export class WorkbenchContextKeysHandler
 		@IContextKeyService private readonly contextKeyService: IContextKeyService,
 		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
 		@IConfigurationService private readonly configurationService: IConfigurationService,
-		@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
+		@IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,
 		@IProductService private readonly productService: IProductService,
 		@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
 		@IEditorService private readonly editorService: IEditorService,
@@ -208,6 +208,10 @@ export class WorkbenchContextKeysHandler
 		this.auxiliaryBarMaximizedContext = AuxiliaryBarMaximizedContext.bindTo(this.contextKeyService);
 		this.auxiliaryBarMaximizedContext.set(this.layoutService.isAuxiliaryBarMaximized());
 
+		// code-server
+		IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
+		IsEnabledFileUploads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileUploads ?? true)
+
 		this.registerListeners();
 	}
 
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
@@ -20,7 +20,7 @@ import { CLOSE_SAVED_EDITORS_COMMAND_ID,
 import { AutoSaveAfterShortDelayContext } from '../../../services/filesConfiguration/common/filesConfigurationService.js';
 import { WorkbenchListDoubleSelection } from '../../../../platform/list/browser/listService.js';
 import { Schemas } from '../../../../base/common/network.js';
-import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from '../../../common/contextkeys.js';
+import { IsEnabledFileDownloads, IsEnabledFileUploads, DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from '../../../common/contextkeys.js';
 import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js';
 import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
 import { ThemeIcon } from '../../../../base/common/themables.js';
@@ -571,13 +571,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)
+		)
 	)
 }));
 
@@ -589,6 +592,7 @@ MenuRegistry.appendMenuItem(MenuId.Explo
 		title: UPLOAD_LABEL,
 	},
 	when: ContextKeyExpr.and(
+		IsEnabledFileUploads,
 		// only in web
 		IsWebContext,
 		// only on folders
Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
@@ -39,6 +39,9 @@ export const EmbedderIdentifierContext =
 
 export const InAutomationContext = new RawContextKey<boolean>('inAutomation', false, localize('inAutomation', "Whether VS Code is running under automation/smoke test"));
 
+export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);
+export const IsEnabledFileUploads = new RawContextKey<boolean>('isEnabledFileUploads', true, true);
+
 //#endregion
 
 //#region < --- Window --- >
Index: code-server/lib/vscode/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts
+++ code-server/lib/vscode/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts
@@ -18,7 +18,7 @@ import { IModelService } from '../../../
 import { ILanguageService } from '../../../../editor/common/languages/language.js';
 import { getIconClasses } from '../../../../editor/common/services/getIconClasses.js';
 import { Schemas } from '../../../../base/common/network.js';
-import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
+import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js';
 import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';
 import { IContextKeyService, IContextKey, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
 import { equalsIgnoreCase, format, startsWithIgnoreCase } from '../../../../base/common/strings.js';
@@ -144,7 +144,7 @@ export class SimpleFileDialog extends Di
 		@IFileDialogService private readonly fileDialogService: IFileDialogService,
 		@IModelService private readonly modelService: IModelService,
 		@ILanguageService private readonly languageService: ILanguageService,
-		@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
+		@IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService,
 		@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
 		@IPathService protected readonly pathService: IPathService,
 		@IKeybindingService private readonly keybindingService: IKeybindingService,
@@ -310,20 +310,22 @@ export class SimpleFileDialog extends Di
 			this.filePickBox.ignoreFocusOut = true;
 			this.filePickBox.ok = true;
 			this.filePickBox.okLabel = typeof this.options.openLabel === 'string' ? this.options.openLabel : this.options.openLabel?.withoutMnemonic;
-			if ((this.scheme !== Schemas.file) && this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) {
-				this.filePickBox.customButton = true;
-				this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local');
-				let action;
-				if (isSave) {
-					action = SaveLocalFileCommand;
-				} else {
-					action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderCommand : OpenLocalFileCommand) : OpenLocalFolderCommand;
-				}
-				const keybinding = this.keybindingService.lookupKeybinding(action.ID);
-				if (keybinding) {
-					const label = keybinding.getLabel();
-					if (label) {
-						this.filePickBox.customHover = format('{0} ({1})', action.LABEL, label);
+			if ((isSave && this.environmentService.isEnabledFileDownloads) || (!isSave && this.environmentService.isEnabledFileUploads)) {
+				if ((this.scheme !== Schemas.file) && this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) {
+					this.filePickBox.customButton = true;
+					this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local');
+					let action;
+					if (isSave) {
+						action = SaveLocalFileCommand;
+					} else {
+						action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderCommand : OpenLocalFileCommand) : OpenLocalFolderCommand;
+					}
+					const keybinding = this.keybindingService.lookupKeybinding(action.ID);
+					if (keybinding) {
+						const label = keybinding.getLabel();
+						if (label) {
+							this.filePickBox.customHover = format('{0} ({1})', action.LABEL, label);
+						}
 					}
 				}
 			}
Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -65,6 +65,7 @@ import { timeout } from '../../../../../
 import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js';
 import { mainWindow } from '../../../../../base/browser/window.js';
 import { IExplorerFileContribution, explorerFileContribRegistry } from '../explorerFileContrib.js';
+import { IBrowserWorkbenchEnvironmentService } from '../../../../services/environment/browser/environmentService.js';
 import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
 import { ISearchService, QueryType, getExcludes, ISearchConfiguration, ISearchComplete, IFileQuery } from '../../../../services/search/common/search.js';
 import { CancellationToken } from '../../../../../base/common/cancellation.js';
@@ -1601,7 +1602,8 @@ export class FileDragAndDrop implements
 		@IConfigurationService private configurationService: IConfigurationService,
 		@IInstantiationService private instantiationService: IInstantiationService,
 		@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
-		@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
+		@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
+		@IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService
 	) {
 		const updateDropEnablement = (e: IConfigurationChangeEvent | undefined) => {
 			if (!e || e.affectsConfiguration('explorer.enableDragAndDrop')) {
@@ -1826,15 +1828,17 @@ export class FileDragAndDrop implements
 
 			// External file DND (Import/Upload file)
 			if (data instanceof NativeDragAndDropData) {
-				// Use local file import when supported
-				if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(mainWindow))) {
-					const fileImport = this.instantiationService.createInstance(ExternalFileImport);
-					await fileImport.import(resolvedTarget, originalEvent, mainWindow);
-				}
-				// Otherwise fallback to browser based file upload
-				else {
-					const browserUpload = this.instantiationService.createInstance(BrowserFileUpload);
-					await browserUpload.upload(target, originalEvent);
+				if (this.environmentService.isEnabledFileUploads) {
+					// Use local file import when supported
+					if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(mainWindow))) {
+						const fileImport = this.instantiationService.createInstance(ExternalFileImport);
+						await fileImport.import(resolvedTarget, originalEvent, mainWindow);
+					}
+					// Otherwise fallback to browser based file upload
+					else {
+						const browserUpload = this.instantiationService.createInstance(BrowserFileUpload);
+						await browserUpload.upload(target, originalEvent);
+					}
 				}
 			}
 
Index: code-server/lib/vscode/src/vs/platform/files/node/diskFileSystemProviderServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/files/node/diskFileSystemProviderServer.ts
+++ code-server/lib/vscode/src/vs/platform/files/node/diskFileSystemProviderServer.ts
@@ -99,6 +99,7 @@ export abstract class AbstractDiskFileSy
 
 	private async readFile(uriTransformer: IURITransformer, _resource: UriComponents, opts?: IFileAtomicReadOptions): Promise<VSBuffer> {
 		const resource = this.transformIncoming(uriTransformer, _resource, true);
+		this.logService.trace(`File action: readFile ${resource.path}`);
 		const buffer = await this.provider.readFile(resource, opts);
 
 		return VSBuffer.wrap(buffer);
@@ -117,6 +118,7 @@ export abstract class AbstractDiskFileSy
 			}
 		});
 
+		this.logService.trace(`File action: readFileStream ${resource.path}`);
 		const fileStream = this.provider.readFileStream(resource, opts, cts.token);
 		listenStream(fileStream, {
 			onData: chunk => emitter.fire(VSBuffer.wrap(chunk)),
@@ -137,7 +139,7 @@ export abstract class AbstractDiskFileSy
 
 	private writeFile(uriTransformer: IURITransformer, _resource: UriComponents, content: VSBuffer, opts: IFileWriteOptions): Promise<void> {
 		const resource = this.transformIncoming(uriTransformer, _resource);
-
+		this.logService.trace(`File action: writeFile ${resource.path}`);
 		return this.provider.writeFile(resource, content.buffer, opts);
 	}
 
